mirror of
https://github.com/uutils/procps.git
synced 2026-05-06 06:06:43 -04:00
ps: Support more process selection flags
This commit is contained in:
@@ -47,8 +47,22 @@ pub struct ProcessSelectionSettings {
|
||||
/// - `-x` Lift "must have a tty" restriction.
|
||||
pub dont_require_tty: bool,
|
||||
|
||||
/// - `-C` Select by command name
|
||||
pub command_names: Option<HashSet<String>>,
|
||||
/// - `-p, --pid` Select specific process IDs
|
||||
pub pids: Option<HashSet<usize>>,
|
||||
/// - `--ppid` Select specific parent process IDs
|
||||
pub ppids: Option<HashSet<usize>>,
|
||||
/// - `--sid` Select specific session IDs
|
||||
pub sids: Option<HashSet<usize>>,
|
||||
/// - `-G, --Group` Select by real group ID or name
|
||||
pub real_groups: Option<HashSet<u32>>,
|
||||
/// - `-g, --group` Select by effective group ID or name
|
||||
pub eff_groups: Option<HashSet<u32>>,
|
||||
/// - `-U, --User` Select by real user ID or name
|
||||
pub real_users: Option<HashSet<u32>>,
|
||||
/// - `-u, --user` Select by effective user ID or name
|
||||
pub eff_users: Option<HashSet<u32>>,
|
||||
|
||||
/// - `-r` Restrict the selection to only running processes.
|
||||
pub only_running: bool,
|
||||
@@ -64,9 +78,30 @@ impl ProcessSelectionSettings {
|
||||
select_non_session_leaders_with_tty: matches.get_flag("a"),
|
||||
select_non_session_leaders: matches.get_flag("d"),
|
||||
dont_require_tty: matches.get_flag("x"),
|
||||
command_names: matches
|
||||
.get_many::<Vec<String>>("command")
|
||||
.map(|xs| xs.flatten().cloned().collect()),
|
||||
pids: matches
|
||||
.get_many::<Vec<usize>>("pid")
|
||||
.map(|xs| xs.flatten().copied().collect()),
|
||||
ppids: matches
|
||||
.get_many::<Vec<usize>>("ppid")
|
||||
.map(|xs| xs.flatten().copied().collect()),
|
||||
sids: matches
|
||||
.get_many::<Vec<usize>>("sid")
|
||||
.map(|xs| xs.flatten().copied().collect()),
|
||||
real_groups: matches
|
||||
.get_many::<Vec<u32>>("real-group")
|
||||
.map(|xs| xs.flatten().copied().collect()),
|
||||
eff_groups: matches
|
||||
.get_many::<Vec<u32>>("effective-group")
|
||||
.map(|xs| xs.flatten().copied().collect()),
|
||||
real_users: matches
|
||||
.get_many::<Vec<u32>>("real-user")
|
||||
.map(|xs| xs.flatten().copied().collect()),
|
||||
eff_users: matches
|
||||
.get_many::<Vec<u32>>("effective-user")
|
||||
.map(|xs| xs.flatten().copied().collect()),
|
||||
only_running: matches.get_flag("r"),
|
||||
negate_selection: matches.get_flag("deselect"),
|
||||
}
|
||||
@@ -86,8 +121,30 @@ impl ProcessSelectionSettings {
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
if let Some(ref pids) = self.pids {
|
||||
return Ok(pids.contains(&process.pid));
|
||||
// Flags in this group seem to cause rest of the flags to be ignored
|
||||
let mut matched: Option<bool> = None;
|
||||
fn update_match<T, U>(
|
||||
matched: &mut Option<bool>,
|
||||
set_opt: &Option<HashSet<T>>,
|
||||
value: U,
|
||||
) where
|
||||
T: std::cmp::Eq + std::hash::Hash + std::borrow::Borrow<U>,
|
||||
U: std::cmp::Eq + std::hash::Hash,
|
||||
{
|
||||
if let Some(ref set) = set_opt {
|
||||
*matched.get_or_insert_default() |= set.contains(&value);
|
||||
}
|
||||
}
|
||||
update_match(&mut matched, &self.command_names, process.name().unwrap());
|
||||
update_match(&mut matched, &self.pids, process.pid);
|
||||
update_match(&mut matched, &self.ppids, process.ppid().unwrap() as usize);
|
||||
update_match(&mut matched, &self.sids, process.sid().unwrap() as usize);
|
||||
update_match(&mut matched, &self.real_users, process.uid().unwrap());
|
||||
update_match(&mut matched, &self.eff_users, process.euid().unwrap());
|
||||
update_match(&mut matched, &self.real_groups, process.gid().unwrap());
|
||||
update_match(&mut matched, &self.eff_groups, process.egid().unwrap());
|
||||
if let Some(m) = matched {
|
||||
return Ok(m);
|
||||
}
|
||||
|
||||
if self.select_non_session_leaders_with_tty {
|
||||
|
||||
+101
-22
@@ -21,6 +21,8 @@ use parser::{parser, OptionalKeyValue};
|
||||
use prettytable::{format::consts::FORMAT_CLEAN, Row, Table};
|
||||
use process_selection::ProcessSelectionSettings;
|
||||
use std::cell::RefCell;
|
||||
#[cfg(unix)]
|
||||
use uucore::entries::{grp2gid, usr2uid};
|
||||
use uucore::{
|
||||
error::{UError, UResult, USimpleError},
|
||||
format_usage, help_about, help_usage,
|
||||
@@ -29,6 +31,22 @@ use uucore::{
|
||||
const ABOUT: &str = help_about!("ps.md");
|
||||
const USAGE: &str = help_usage!("ps.md");
|
||||
|
||||
#[cfg(not(unix))]
|
||||
pub fn usr2uid(_name: &str) -> std::io::Result<u32> {
|
||||
Err(std::io::Error::new(
|
||||
std::io::ErrorKind::InvalidInput,
|
||||
"unsupported on this platform",
|
||||
))
|
||||
}
|
||||
|
||||
#[cfg(not(unix))]
|
||||
pub fn grp2gid(_name: &str) -> std::io::Result<u32> {
|
||||
Err(std::io::Error::new(
|
||||
std::io::ErrorKind::InvalidInput,
|
||||
"unsupported on this platform",
|
||||
))
|
||||
}
|
||||
|
||||
#[uucore::main]
|
||||
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||
let matches = uu_app().try_get_matches_from(args)?;
|
||||
@@ -138,8 +156,12 @@ fn collect_format(
|
||||
Ok(collect)
|
||||
}
|
||||
|
||||
fn parse_numeric_list(s: &str) -> Result<Vec<usize>, String> {
|
||||
fn split_arg_list(s: &str) -> impl Iterator<Item = &str> {
|
||||
s.split(|c: char| c.is_whitespace() || c == ',')
|
||||
}
|
||||
|
||||
fn parse_numeric_list(s: &str) -> Result<Vec<usize>, String> {
|
||||
split_arg_list(s)
|
||||
.map(|word| {
|
||||
word.parse::<usize>()
|
||||
.map_err(|_| format!("invalid number: '{}'", word))
|
||||
@@ -147,6 +169,32 @@ fn parse_numeric_list(s: &str) -> Result<Vec<usize>, String> {
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn parse_uid_list(s: &str) -> Result<Vec<u32>, String> {
|
||||
split_arg_list(s)
|
||||
.map(|uid_or_username| {
|
||||
uid_or_username
|
||||
.parse::<u32>()
|
||||
.or_else(|_| usr2uid(uid_or_username))
|
||||
.map_err(|_| format!("invalid user name '{}'", uid_or_username))
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn parse_gid_list(s: &str) -> Result<Vec<u32>, String> {
|
||||
split_arg_list(s)
|
||||
.map(|gid_or_group_name| {
|
||||
gid_or_group_name
|
||||
.parse::<u32>()
|
||||
.or_else(|_| grp2gid(gid_or_group_name))
|
||||
.map_err(|_| format!("invalid group name '{}'", gid_or_group_name))
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn parse_command_list(s: &str) -> Result<Vec<String>, String> {
|
||||
Ok(split_arg_list(s).map(|part| part.to_string()).collect())
|
||||
}
|
||||
|
||||
#[allow(clippy::cognitive_complexity)]
|
||||
pub fn uu_app() -> Command {
|
||||
Command::new(uucore::util_name())
|
||||
@@ -271,6 +319,13 @@ pub fn uu_app() -> Command {
|
||||
.action(ArgAction::SetTrue)
|
||||
.help("do not print header at all"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("command")
|
||||
.short('C')
|
||||
.action(ArgAction::Append)
|
||||
.value_parser(parse_command_list)
|
||||
.help("select by command name"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("pid")
|
||||
.short('p')
|
||||
@@ -279,33 +334,57 @@ pub fn uu_app() -> Command {
|
||||
.value_parser(parse_numeric_list)
|
||||
.help("select by process ID"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("ppid")
|
||||
.long("ppid")
|
||||
.action(ArgAction::Append)
|
||||
.value_parser(parse_numeric_list)
|
||||
.help("select by parent process ID"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("sid")
|
||||
.long("sid")
|
||||
.action(ArgAction::Append)
|
||||
.value_parser(parse_numeric_list)
|
||||
.help("select by session ID"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("real-group")
|
||||
.short('G')
|
||||
.long("Group")
|
||||
.action(ArgAction::Append)
|
||||
.value_parser(parse_gid_list)
|
||||
.help("select by real group ID (RGID) or name"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("effective-group")
|
||||
.short('g')
|
||||
.long("group")
|
||||
.action(ArgAction::Append)
|
||||
.value_parser(parse_gid_list)
|
||||
.help("select by effective group ID (EGID) or name"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("real-user")
|
||||
.short('U')
|
||||
.long("User")
|
||||
.action(ArgAction::Append)
|
||||
.value_parser(parse_uid_list)
|
||||
.help("select by real user ID (RUID) or name"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("effective-user")
|
||||
.long("user")
|
||||
.action(ArgAction::Append)
|
||||
.value_parser(parse_uid_list)
|
||||
.help("select by effective user ID (EUID) or name"),
|
||||
)
|
||||
// .args([
|
||||
// Arg::new("command").short('c').help("command name"),
|
||||
// Arg::new("GID")
|
||||
// .short('G')
|
||||
// .long("Group")
|
||||
// .help("real group id or name"),
|
||||
// Arg::new("group")
|
||||
// .short('g')
|
||||
// .long("group")
|
||||
// .help("session or effective group name"),
|
||||
// Arg::new("pPID").long("ppid").help("parent process id"),
|
||||
// Arg::new("qPID")
|
||||
// .short('q')
|
||||
// .long("quick-pid")
|
||||
// .help("process id"),
|
||||
// Arg::new("session")
|
||||
// .short('s')
|
||||
// .long("sid")
|
||||
// .help("session id"),
|
||||
// Arg::new("t").short('t').long("tty").help("terminal"),
|
||||
// Arg::new("eUID")
|
||||
// .short('u')
|
||||
// .long("user")
|
||||
// .help("effective user id or name"),
|
||||
// Arg::new("rUID")
|
||||
// .short('U')
|
||||
// .long("User")
|
||||
// .help("real user id or name"),
|
||||
// ])
|
||||
}
|
||||
|
||||
+168
-3
@@ -7,6 +7,9 @@
|
||||
use regex::Regex;
|
||||
use uutests::new_ucmd;
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
use uucore::process::geteuid;
|
||||
|
||||
#[test]
|
||||
#[cfg(target_os = "linux")]
|
||||
fn test_select_all_processes() {
|
||||
@@ -194,7 +197,7 @@ fn test_deselect() {
|
||||
.args(&["--deselect", "-A", "--no-headers"])
|
||||
.fails()
|
||||
.code_is(1)
|
||||
.stdout_is("");
|
||||
.no_output();
|
||||
|
||||
// PID 1 should be present in inverse of default filter criteria
|
||||
new_ucmd!()
|
||||
@@ -203,6 +206,28 @@ fn test_deselect() {
|
||||
.stdout_matches(&Regex::new("\n *1 ").unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(target_os = "linux")]
|
||||
fn test_command_name_selection() {
|
||||
// Test that test runner process can be located with -C flag
|
||||
let our_pid = std::process::id();
|
||||
let our_comm = std::fs::read_to_string(format!("/proc/{}/comm", our_pid))
|
||||
.unwrap()
|
||||
.trim()
|
||||
.to_string();
|
||||
new_ucmd!()
|
||||
.args(&["-C", &our_comm, "--no-headers", "-o", "pid"])
|
||||
.succeeds()
|
||||
.stdout_contains(our_pid.to_string());
|
||||
|
||||
// Test nonexistent command
|
||||
new_ucmd!()
|
||||
.args(&["-C", "non_existent_command", "--no-headers"])
|
||||
.fails()
|
||||
.code_is(1)
|
||||
.no_output();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(target_os = "linux")]
|
||||
fn test_pid_selection() {
|
||||
@@ -224,12 +249,12 @@ fn test_pid_selection() {
|
||||
test(&[flag, "1", flag, &our_pid.to_string()]);
|
||||
}
|
||||
|
||||
// Test nonexistent PID (should show no output)
|
||||
// Test nonexistent PID
|
||||
new_ucmd!()
|
||||
.args(&["-p", "0", "--no-headers"])
|
||||
.fails()
|
||||
.code_is(1)
|
||||
.stdout_is("");
|
||||
.no_output();
|
||||
|
||||
// Test invalid PID
|
||||
new_ucmd!()
|
||||
@@ -237,3 +262,143 @@ fn test_pid_selection() {
|
||||
.fails()
|
||||
.stderr_contains("invalid number");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(target_os = "linux")]
|
||||
fn test_ppid_selection() {
|
||||
new_ucmd!()
|
||||
.args(&["--ppid", "1"])
|
||||
.succeeds()
|
||||
.stdout_does_not_match(&Regex::new(".*\n *1 +.*").unwrap());
|
||||
|
||||
// Test nonexistent PPID
|
||||
new_ucmd!()
|
||||
.args(&["--ppid", "999999", "--no-headers"])
|
||||
.fails()
|
||||
.code_is(1)
|
||||
.no_output();
|
||||
|
||||
// Test invalid PPID
|
||||
new_ucmd!()
|
||||
.args(&["--ppid", "invalid"])
|
||||
.fails()
|
||||
.stderr_contains("invalid number");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(target_os = "linux")]
|
||||
fn test_sid_selection() {
|
||||
new_ucmd!()
|
||||
.args(&["--sid", "1"])
|
||||
.succeeds()
|
||||
.stdout_matches(&Regex::new(".*\n *1 +.*").unwrap());
|
||||
|
||||
// Test nonexistent SID
|
||||
new_ucmd!()
|
||||
.args(&["--sid", "999999", "--no-headers"])
|
||||
.fails()
|
||||
.code_is(1)
|
||||
.no_output();
|
||||
|
||||
// Test invalid SID
|
||||
new_ucmd!()
|
||||
.args(&["--sid", "invalid"])
|
||||
.fails()
|
||||
.stderr_contains("invalid number");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(target_os = "linux")]
|
||||
fn test_effective_user_selection() {
|
||||
let regex = Regex::new("^( *0 +root *\n)+").unwrap();
|
||||
|
||||
for user_param in ["root", "0"] {
|
||||
new_ucmd!()
|
||||
.args(&["--user", user_param, "--no-headers", "-o", "euid,euser"])
|
||||
.succeeds()
|
||||
.stdout_matches(®ex);
|
||||
}
|
||||
|
||||
new_ucmd!()
|
||||
.args(&["--user", "nonexistent_user"])
|
||||
.fails()
|
||||
.stderr_contains("invalid user name");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(target_os = "linux")]
|
||||
fn test_real_user_selection() {
|
||||
let regex = Regex::new("^( *0 +root *\n)+").unwrap();
|
||||
|
||||
for user_param in ["root", "0"] {
|
||||
new_ucmd!()
|
||||
.args(&["--User", user_param, "--no-headers", "-o", "ruid,ruser"])
|
||||
.succeeds()
|
||||
.stdout_matches(®ex);
|
||||
}
|
||||
|
||||
new_ucmd!()
|
||||
.args(&["--User", "nonexistent_user"])
|
||||
.fails()
|
||||
.stderr_contains("invalid user name");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(target_os = "linux")]
|
||||
fn test_effective_group_selection() {
|
||||
let regex = Regex::new("^( *0 +root *\n)+").unwrap();
|
||||
|
||||
for group_param in ["root", "0"] {
|
||||
new_ucmd!()
|
||||
.args(&["--group", group_param, "--no-headers", "-o", "egid,egroup"])
|
||||
.succeeds()
|
||||
.stdout_matches(®ex);
|
||||
}
|
||||
|
||||
new_ucmd!()
|
||||
.args(&["--group", "nonexistent_group"])
|
||||
.fails()
|
||||
.stderr_contains("invalid group name");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(target_os = "linux")]
|
||||
fn test_real_group_selection() {
|
||||
let regex = Regex::new("^( *0 +root *\n)+").unwrap();
|
||||
|
||||
for group_param in ["root", "0"] {
|
||||
new_ucmd!()
|
||||
.args(&["--Group", group_param, "--no-headers", "-o", "rgid,rgroup"])
|
||||
.succeeds()
|
||||
.stdout_matches(®ex);
|
||||
}
|
||||
|
||||
new_ucmd!()
|
||||
.args(&["--Group", "nonexistent_group"])
|
||||
.fails()
|
||||
.stderr_contains("invalid group name");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(target_os = "linux")]
|
||||
fn test_combined_selection_criteria() {
|
||||
let pids: Vec<u32> = new_ucmd!()
|
||||
.args(&[
|
||||
"--pid",
|
||||
"1",
|
||||
"--user",
|
||||
&geteuid().to_string(),
|
||||
"--no-headers",
|
||||
"-o",
|
||||
"pid",
|
||||
])
|
||||
.succeeds()
|
||||
.stdout_str()
|
||||
.lines()
|
||||
.filter_map(|line| line.trim().parse::<u32>().ok())
|
||||
.collect();
|
||||
|
||||
// Should include PID 1 and processes owned by current user
|
||||
assert!(pids.contains(&1));
|
||||
assert!(pids.len() > 1);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user