From 9518c429aaba26dc48ffcdaa5d99f3a655bf1f1c Mon Sep 17 00:00:00 2001 From: Tuomas Tynkkynen Date: Tue, 24 Jun 2025 03:30:00 +0300 Subject: [PATCH] ps: Implement bunch of field selection flags --- src/uu/ps/src/mapping.rs | 59 +++++++++++++++++++++++++++++++ src/uu/ps/src/picker.rs | 2 +- src/uu/ps/src/ps.rs | 65 ++++++++++++++++++++++++++++++++-- tests/by-util/test_ps.rs | 76 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 199 insertions(+), 3 deletions(-) diff --git a/src/uu/ps/src/mapping.rs b/src/uu/ps/src/mapping.rs index 62237c6..e71e3fb 100644 --- a/src/uu/ps/src/mapping.rs +++ b/src/uu/ps/src/mapping.rs @@ -26,6 +26,65 @@ pub(crate) fn default_codes() -> Vec { ["pid", "tname", "time", "ucmd"].map(Into::into).to_vec() } +/// Returns the full format codes (for -f flag). +pub(crate) fn full_format_codes() -> Vec { + [ + "uid_hack", "pid", "ppid", "c", "stime", "tname", "time", "cmd", + ] + .map(Into::into) + .to_vec() +} + +/// Returns the extra full format codes (for -F flag). +pub(crate) fn extra_full_format_codes() -> Vec { + [ + "uid", "pid", "ppid", "c", "sz", "rss", "psr", "stime", "tname", "time", "ucmd", + ] + .map(Into::into) + .to_vec() +} + +/// Returns the job format codes (for -j flag). +pub(crate) fn job_format_codes() -> Vec { + ["pid", "pgid", "sid", "tname", "time", "ucmd"] + .map(Into::into) + .to_vec() +} + +/// Returns the default codes with PSR column (for -P flag). +pub(crate) fn default_with_psr_codes() -> Vec { + ["pid", "psr", "tname", "time", "ucmd"] + .map(Into::into) + .to_vec() +} + +/// Returns the signal format codes (for -s flag). +pub(crate) fn signal_format_codes() -> Vec { + [ + "uid", "pid", "pending", "blocked", "ignored", "caught", "stat", "tname", "time", "command", + ] + .map(Into::into) + .to_vec() +} + +/// Returns the user format codes (for -u flag). +pub(crate) fn user_format_codes() -> Vec { + [ + "user", "pid", "%cpu", "%mem", "vsz", "rss", "tname", "stat", "bsdstart", "time", "command", + ] + .map(Into::into) + .to_vec() +} + +/// Returns the virtual memory format codes (for -v flag). +pub(crate) fn vm_format_codes() -> Vec { + [ + "pid", "tname", "stat", "time", "maj_flt", "trs", "drs", "rss", "%mem", "command", + ] + .map(Into::into) + .to_vec() +} + /// Collect mapping from argument pub(crate) fn default_mapping() -> HashMap { let mut mapping = HashMap::new(); diff --git a/src/uu/ps/src/picker.rs b/src/uu/ps/src/picker.rs index 553bd27..cfedcc0 100644 --- a/src/uu/ps/src/picker.rs +++ b/src/uu/ps/src/picker.rs @@ -31,7 +31,7 @@ pub(crate) fn collect_pickers( "uid" | "euid" => pickers.push(helper(euid)), "ruid" => pickers.push(helper(ruid)), "suid" => pickers.push(helper(suid)), - "user" | "euser" => pickers.push(helper(euser)), + "uid_hack" | "user" | "euser" => pickers.push(helper(euser)), "ruser" => pickers.push(helper(ruser)), "suser" => pickers.push(helper(suser)), "pgid" => pickers.push(helper(pgid)), diff --git a/src/uu/ps/src/ps.rs b/src/uu/ps/src/ps.rs index 5e6185b..db05c51 100644 --- a/src/uu/ps/src/ps.rs +++ b/src/uu/ps/src/ps.rs @@ -11,7 +11,11 @@ mod sorting; use clap::crate_version; use clap::{Arg, ArgAction, ArgMatches, Command}; -use mapping::{collect_code_mapping, default_codes, default_mapping}; +use mapping::{ + collect_code_mapping, default_codes, default_mapping, default_with_psr_codes, + extra_full_format_codes, full_format_codes, job_format_codes, signal_format_codes, + user_format_codes, vm_format_codes, +}; use parser::{parser, OptionalKeyValue}; use prettytable::{format::consts::FORMAT_CLEAN, Row, Table}; use std::{cell::RefCell, rc::Rc}; @@ -47,7 +51,21 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { }; // Collect codes with order - let codes = if arg_formats.is_empty() { + let codes = if matches.get_flag("f") { + full_format_codes() + } else if matches.get_flag("F") { + extra_full_format_codes() + } else if matches.get_flag("j") { + job_format_codes() + } else if matches.get_flag("P") { + default_with_psr_codes() + } else if matches.get_flag("s") { + signal_format_codes() + } else if matches.get_flag("u") { + user_format_codes() + } else if matches.get_flag("v") { + vm_format_codes() + } else if arg_formats.is_empty() { default_codes() } else { arg_formats.iter().map(|it| it.key().to_owned()).collect() @@ -164,6 +182,49 @@ pub fn uu_app() -> Command { // .help("processes without controlling ttys") // .allow_hyphen_values(true), ]) + .arg( + Arg::new("f") + .short('f') + .action(ArgAction::SetTrue) + .help("full format listing"), + ) + .arg( + Arg::new("F") + .short('F') + .action(ArgAction::SetTrue) + .help("extra full format listing"), + ) + .arg( + Arg::new("j") + .short('j') + .action(ArgAction::SetTrue) + .help("job format"), + ) + .arg( + Arg::new("P") + .short('P') + .action(ArgAction::SetTrue) + .help("add psr column"), + ) + .arg( + Arg::new("s") + .short('s') + .action(ArgAction::SetTrue) + .help("signal format"), + ) + // TODO: this can also be used with argument to filter by uid + .arg( + Arg::new("u") + .short('u') + .action(ArgAction::SetTrue) + .help("user format"), + ) + .arg( + Arg::new("v") + .short('v') + .action(ArgAction::SetTrue) + .help("virtual memory format"), + ) .arg( Arg::new("format") .short('o') diff --git a/tests/by-util/test_ps.rs b/tests/by-util/test_ps.rs index 32f0798..8eab0cf 100644 --- a/tests/by-util/test_ps.rs +++ b/tests/by-util/test_ps.rs @@ -21,6 +21,82 @@ fn test_invalid_arg() { new_ucmd!().arg("--definitely-invalid").fails().code_is(1); } +/// Helper function to check that ps output has the correct headers in the correct order +#[cfg(target_os = "linux")] +fn check_header(flag: &str, expected_headers: &[&str]) { + let result = new_ucmd!().arg(flag).succeeds(); + let lines: Vec<&str> = result.stdout_str().lines().collect(); + let headers: Vec<&str> = lines[0].split_whitespace().collect(); + + assert_eq!(headers, expected_headers); +} + +#[test] +#[cfg(target_os = "linux")] +fn test_full_format_listing() { + check_header( + "-f", + &["UID", "PID", "PPID", "C", "STIME", "TTY", "TIME", "CMD"], + ); +} + +#[test] +#[cfg(target_os = "linux")] +fn test_extra_full_format() { + check_header( + "-F", + &[ + "UID", "PID", "PPID", "C", "SZ", "RSS", "PSR", "STIME", "TTY", "TIME", "CMD", + ], + ); +} + +#[test] +#[cfg(target_os = "linux")] +fn test_job_format() { + check_header("-j", &["PID", "PGID", "SID", "TTY", "TIME", "CMD"]); +} + +#[test] +#[cfg(target_os = "linux")] +fn test_psr_format() { + check_header("-P", &["PID", "PSR", "TTY", "TIME", "CMD"]); +} + +#[test] +#[cfg(target_os = "linux")] +fn test_signal_format() { + check_header( + "-s", + &[ + "UID", "PID", "PENDING", "BLOCKED", "IGNORED", "CAUGHT", "STAT", "TTY", "TIME", + "COMMAND", + ], + ); +} + +#[test] +#[cfg(target_os = "linux")] +fn test_user_format() { + check_header( + "-u", + &[ + "USER", "PID", "%CPU", "%MEM", "VSZ", "RSS", "TTY", "STAT", "START", "TIME", "COMMAND", + ], + ); +} + +#[test] +#[cfg(target_os = "linux")] +fn test_virtual_memory_format() { + check_header( + "-v", + &[ + "PID", "TTY", "STAT", "TIME", "MAJFL", "TRS", "DRS", "RSS", "%MEM", "COMMAND", + ], + ); +} + #[test] #[cfg(target_os = "linux")] fn test_code_mapping() {