From 227333991aa2e90b07a7b02492f5d38fa9143d18 Mon Sep 17 00:00:00 2001 From: Krysztal112233 Date: Thu, 30 May 2024 17:44:25 +0800 Subject: [PATCH] pgrep: Impl -o -n --- Cargo.lock | 1 + Cargo.toml | 1 + src/uu/pgrep/Cargo.toml | 1 + src/uu/pgrep/src/pgrep.rs | 80 ++++++++++++++++++++++++++++++++----- src/uu/pgrep/src/pid.rs | 84 +++++++++++++++++++++++++++++++++------ 5 files changed, 145 insertions(+), 22 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 807c1a4..107a0c3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -862,6 +862,7 @@ name = "uu_pgrep" version = "0.0.1" dependencies = [ "clap", + "regex", "uucore", "walkdir", ] diff --git a/Cargo.toml b/Cargo.toml index db3acf6..3f08487 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -55,6 +55,7 @@ uucore = { workspace = true } phf = { workspace = true } textwrap = { workspace = true } sysinfo = { workspace = true } +regex = { workspace = true } # pwdx = { optional = true, version = "0.0.1", package = "uu_pwdx", path = "src/uu/pwdx" } diff --git a/src/uu/pgrep/Cargo.toml b/src/uu/pgrep/Cargo.toml index ea215f9..ccbf33f 100644 --- a/src/uu/pgrep/Cargo.toml +++ b/src/uu/pgrep/Cargo.toml @@ -16,6 +16,7 @@ categories = ["command-line-utilities"] uucore = { workspace = true } clap = { workspace = true } walkdir = { workspace = true } +regex = { workspace = true } [lib] path = "src/pgrep.rs" diff --git a/src/uu/pgrep/src/pgrep.rs b/src/uu/pgrep/src/pgrep.rs index c95878f..02a72ae 100644 --- a/src/uu/pgrep/src/pgrep.rs +++ b/src/uu/pgrep/src/pgrep.rs @@ -5,7 +5,9 @@ pub mod pid; -use clap::{arg, crate_version, Arg, ArgAction, Command}; +use std::{borrow::BorrowMut, cmp::Ordering}; + +use clap::{arg, crate_version, Arg, ArgAction, ArgGroup, Command}; use pid::walk_pid; use uucore::{error::UResult, format_usage, help_about, help_usage}; @@ -30,19 +32,68 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let should_ignore_case = matches.get_flag("ignore-case"); let should_inverse = matches.get_flag("inverse"); - let flag_list_name = matches.get_flag("list-name"); - let flag_list_full = matches.get_flag("list-full"); + let flag_newest = matches.get_flag("newest"); + let flag_oldest = matches.get_flag("oldest"); - let flag_count = matches.get_flag("count"); - // let flag_full = matches.get_flag("full"); + if flag_newest != flag_oldest { + let mut result = walk_pid().collect::>(); + + result.sort_by(|a, b| { + if let (Ok(b), Ok(a)) = ( + b.to_owned().borrow_mut().start_time(), + a.to_owned().borrow_mut().start_time(), + ) { + b.cmp(&a) + } else { + Ordering::Equal + } + }); + + let sort = |start_time: u64| { + let mut result = result + .iter() + .filter(move |it| { + if let Ok(s_time) = (*it).to_owned().borrow_mut().start_time() { + s_time == start_time + } else { + false + } + }) + .collect::>(); + result.sort_by(|a, b| { + if let (Ok(b), Ok(a)) = ( + (*b).to_owned().borrow_mut().start_time(), + (*a).to_owned().borrow_mut().start_time(), + ) { + b.cmp(&a) + } else { + Ordering::Equal + } + }); + + result + }; + + let mut entry = if flag_newest { + result.first() + } else { + result.last() + } + .expect("empty pid list") + .to_owned(); + + println!("{}", sort(entry.start_time()?).first().unwrap().pid); + return Ok(()); + } if should_ignore_case { programs = programs.into_iter().map(|it| it.to_lowercase()).collect(); } - let result: Vec<_> = walk_pid() + let result = walk_pid() .filter(|it| { - let Some(name) = it.status.get("Name") else { + let binding = it.to_owned().borrow_mut().status(); + let Some(name) = binding.get("Name") else { return false; }; @@ -55,8 +106,12 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { programs.iter().any(|it| name.contains(it)) ^ should_inverse }) - .collect(); + .collect::>(); + let flag_list_name = matches.get_flag("list-name"); + let flag_list_full = matches.get_flag("list-full"); + + let flag_count = matches.get_flag("count"); if flag_count { println!("{}", result.len()); } else { @@ -67,7 +122,13 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { if flag_list_full { format!("{} {}", it.pid, it.cmdline) } else if flag_list_name { - let name = it.status.get("Name").cloned().unwrap_or_default(); + let name = it + .to_owned() + .borrow_mut() + .status() + .get("Name") + .cloned() + .unwrap_or_default(); format!("{} {}", it.pid, name) } else { format!("{}", it.pid) @@ -85,6 +146,7 @@ pub fn uu_app() -> Command { .version(crate_version!()) .about(ABOUT) .override_usage(format_usage(USAGE)) + .group(ArgGroup::new("oldest_newest").args(["oldest", "newest"])) .args([ arg!(-d --delimiter "specify output delimiter") .default_value("\n") diff --git a/src/uu/pgrep/src/pid.rs b/src/uu/pgrep/src/pid.rs index 842b30f..36715de 100644 --- a/src/uu/pgrep/src/pid.rs +++ b/src/uu/pgrep/src/pid.rs @@ -3,14 +3,76 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -use std::{collections::HashMap, fs, io, path::PathBuf}; +use regex::Regex; +use std::{collections::HashMap, fs, io, path::PathBuf, rc::Rc}; use walkdir::{DirEntry, WalkDir}; -#[derive(Debug)] +#[derive(Debug, Clone, Default)] pub struct PidEntry { pub pid: usize, pub cmdline: String, - pub status: HashMap, + + inner_status: String, + inner_stat: String, + + cached_status: Option>>, + cached_stat: Option>>, +} + +impl PidEntry { + pub fn status(&mut self) -> Rc> { + if let Some(c) = &self.cached_status { + return Rc::clone(c); + } + + let result = self + .inner_status + .lines() + .filter_map(|it| it.split_once(':')) + .map(|it| (it.0.to_string(), it.1.trim_start().to_string())) + .collect::>(); + + let result = Rc::new(result); + self.cached_status = Some(Rc::clone(&result)); + Rc::clone(&result) + } + + fn stat(&mut self) -> Result>, io::Error> { + if let Some(c) = &self.cached_stat { + return Ok(Rc::clone(c)); + } + + let result: Vec<_> = { + // Regex that have been validated + let regex = Regex::new(r"\(([^()]*)\)").unwrap(); + let result = regex + .replace_all(&self.inner_stat, |caps: ®ex::Captures| { + let inner = &caps[1]; + format!("({})", inner.replace(' ', "$$")) + }) + .into_owned(); + + result + .split_whitespace() + .map(|it| it.replace("$$", " ")) + .collect() + }; + + let result = Rc::new(result); + self.cached_stat = Some(Rc::clone(&result)); + Ok(Rc::clone(&result)) + } + + pub fn start_time(&mut self) -> Result { + // Kernel doc: https://docs.kernel.org/filesystems/proc.html#process-specific-subdirectories + // Table 1-4 + Ok(self + .stat()? + .get(21) + .ok_or(io::ErrorKind::InvalidData)? + .parse::() + .map_err(|_| io::ErrorKind::InvalidData)?) + } } impl TryFrom for PidEntry { @@ -37,20 +99,16 @@ impl TryFrom for PidEntry { .replace('\0', " ") .trim_end() .into(); - let status = { - let content = - fs::read_to_string(dir_append(value.clone().into_path(), "status".into()))?; - content - .lines() - .filter_map(|it| it.split_once(':')) - .map(|it| (it.0.to_string(), it.1.trim_start().to_string())) - .collect::>() - }; Ok(Self { pid, cmdline, - status, + inner_status: fs::read_to_string(dir_append( + value.clone().into_path(), + "status".into(), + ))?, + inner_stat: fs::read_to_string(dir_append(value.clone().into_path(), "stat".into()))?, + ..Default::default() }) } }