mirror of
https://github.com/uutils/procps.git
synced 2026-06-10 16:14:00 -07:00
Merge pull request #559 from dezgeg/ps_refact
ps: Refactor process selection + support new flags
This commit is contained in:
@@ -209,7 +209,7 @@ fn try_get_pattern_from(matches: &ArgMatches) -> UResult<String> {
|
||||
pattern
|
||||
};
|
||||
|
||||
Ok(pattern.to_string())
|
||||
Ok(pattern.clone())
|
||||
}
|
||||
|
||||
fn any_matches<T: Eq + Hash>(optional_ids: &Option<HashSet<T>>, id: T) -> bool {
|
||||
|
||||
@@ -1,107 +0,0 @@
|
||||
// This file is part of the uutils procps package.
|
||||
//
|
||||
// For the full copyright and license information, please view the LICENSE
|
||||
// file that was distributed with this source code.
|
||||
|
||||
use clap::ArgMatches;
|
||||
#[cfg(target_family = "unix")]
|
||||
use nix::errno::Errno;
|
||||
use std::{cell::RefCell, rc::Rc};
|
||||
use uu_pgrep::process::{ProcessInformation, Teletype};
|
||||
|
||||
// TODO: Temporary add to this file, this function will add to uucore.
|
||||
#[cfg(not(target_os = "redox"))]
|
||||
#[cfg(target_family = "unix")]
|
||||
fn getsid(pid: i32) -> Option<i32> {
|
||||
unsafe {
|
||||
let result = libc::getsid(pid);
|
||||
if Errno::last() == Errno::UnknownErrno {
|
||||
Some(result)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Temporary add to this file, this function will add to uucore.
|
||||
#[cfg(target_family = "windows")]
|
||||
fn getsid(_pid: i32) -> Option<i32> {
|
||||
Some(0)
|
||||
}
|
||||
|
||||
// Default behavior: select processes with same terminal and same effective user ID
|
||||
pub(crate) fn basic_collector(
|
||||
proc_snapshot: &[Rc<RefCell<ProcessInformation>>],
|
||||
) -> Vec<Rc<RefCell<ProcessInformation>>> {
|
||||
let mut result = Vec::new();
|
||||
|
||||
let mut cur = ProcessInformation::current_process_info().unwrap();
|
||||
let tty = cur.tty();
|
||||
let euid = cur.euid().unwrap();
|
||||
|
||||
for proc_info in proc_snapshot {
|
||||
if proc_info.borrow().tty() == tty && proc_info.borrow_mut().euid().unwrap() == euid {
|
||||
result.push(proc_info.clone());
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
/// Filter for processes
|
||||
///
|
||||
/// - `-A` Select all processes. Identical to `-e`.
|
||||
pub(crate) fn process_collector(
|
||||
matches: &ArgMatches,
|
||||
proc_snapshot: &[Rc<RefCell<ProcessInformation>>],
|
||||
) -> Vec<Rc<RefCell<ProcessInformation>>> {
|
||||
let mut result = Vec::new();
|
||||
|
||||
// flag `-A`
|
||||
if matches.get_flag("A") {
|
||||
result.extend(proc_snapshot.iter().map(Rc::clone));
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
/// Filter for session
|
||||
///
|
||||
/// - `-d` Select all processes except session leaders.
|
||||
/// - `-a` Select all processes except both session leaders (see getsid(2)) and processes not associated with a terminal.
|
||||
pub(crate) fn session_collector(
|
||||
matches: &ArgMatches,
|
||||
proc_snapshot: &[Rc<RefCell<ProcessInformation>>],
|
||||
) -> Vec<Rc<RefCell<ProcessInformation>>> {
|
||||
let mut result = Vec::new();
|
||||
|
||||
let tty = |proc: &Rc<RefCell<ProcessInformation>>| proc.borrow_mut().tty();
|
||||
|
||||
// flag `-d`
|
||||
if matches.get_flag("d") {
|
||||
for proc_info in proc_snapshot {
|
||||
let pid = proc_info.borrow().pid;
|
||||
|
||||
if getsid(pid as i32) != Some(pid as i32) {
|
||||
result.push(proc_info.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// flag `-a`
|
||||
// Guessing it pid=sid, and associated terminal.
|
||||
if matches.get_flag("a") {
|
||||
for it in proc_snapshot {
|
||||
let pid = it.borrow().pid;
|
||||
|
||||
if let Some(sid) = getsid(pid as i32) {
|
||||
// Check is session leader
|
||||
if sid != (pid as i32) && tty(it) != Teletype::Unknown {
|
||||
result.push(it.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
@@ -15,7 +15,7 @@ pub(crate) fn collect_code_mapping(formats: &[OptionalKeyValue]) -> Vec<(String,
|
||||
let key = it.key().to_string();
|
||||
match it.value() {
|
||||
Some(value) => (key, value.clone()),
|
||||
None => (key.clone(), mapping.get(&key).unwrap().to_string()),
|
||||
None => (key.clone(), mapping.get(&key).unwrap().clone()),
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
@@ -104,6 +104,13 @@ pub(crate) fn vm_format_codes() -> Vec<String> {
|
||||
.to_vec()
|
||||
}
|
||||
|
||||
/// Returns the format codes used when BSD flags (e.g. -x) are used.
|
||||
pub(crate) fn bsd_format_codes() -> Vec<String> {
|
||||
["pid", "tname", "stat", "time", "command"]
|
||||
.map(Into::into)
|
||||
.to_vec()
|
||||
}
|
||||
|
||||
/// Returns the register format codes (for -X flag).
|
||||
pub(crate) fn register_format_codes() -> Vec<String> {
|
||||
[
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
// This file is part of the uutils procps package.
|
||||
//
|
||||
// For the full copyright and license information, please view the LICENSE
|
||||
// file that was distributed with this source code.
|
||||
|
||||
use clap::ArgMatches;
|
||||
use std::collections::HashSet;
|
||||
use uu_pgrep::process::{walk_process, ProcessInformation, RunState, Teletype};
|
||||
use uucore::error::UResult;
|
||||
|
||||
#[cfg(target_family = "unix")]
|
||||
use nix::errno::Errno;
|
||||
|
||||
// TODO: Temporary add to this file, this function will add to uucore.
|
||||
#[cfg(not(target_os = "redox"))]
|
||||
#[cfg(target_family = "unix")]
|
||||
fn getsid(pid: i32) -> Option<i32> {
|
||||
unsafe {
|
||||
let result = libc::getsid(pid);
|
||||
if Errno::last() == Errno::UnknownErrno {
|
||||
Some(result)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Temporary add to this file, this function will add to uucore.
|
||||
#[cfg(target_family = "windows")]
|
||||
fn getsid(_pid: i32) -> Option<i32> {
|
||||
Some(0)
|
||||
}
|
||||
|
||||
fn is_session_leader(process: &ProcessInformation) -> bool {
|
||||
let pid = process.pid as i32;
|
||||
getsid(pid) == Some(pid)
|
||||
}
|
||||
|
||||
pub struct ProcessSelectionSettings {
|
||||
/// - `-A` Select all processes. Identical to `-e`.
|
||||
pub select_all: bool,
|
||||
/// - `-a` Select all processes except both session leaders (see getsid(2)) and processes not associated with a terminal.
|
||||
pub select_non_session_leaders_with_tty: bool,
|
||||
/// - `-d` Select all processes except session leaders.
|
||||
pub select_non_session_leaders: bool,
|
||||
|
||||
/// - '-x' Lift "must have a tty" restriction.
|
||||
pub dont_require_tty: bool,
|
||||
|
||||
/// Select specific process IDs (-p, --pid)
|
||||
pub pids: Option<HashSet<usize>>,
|
||||
|
||||
/// - `-r` Restrict the selection to only running processes.
|
||||
pub only_running: bool,
|
||||
|
||||
/// - `--deselect` Negates the selection.
|
||||
pub negate_selection: bool,
|
||||
}
|
||||
|
||||
impl ProcessSelectionSettings {
|
||||
pub fn from_matches(matches: &ArgMatches) -> Self {
|
||||
Self {
|
||||
select_all: matches.get_flag("A"),
|
||||
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"),
|
||||
pids: matches
|
||||
.get_many::<Vec<usize>>("pid")
|
||||
.map(|xs| xs.flatten().copied().collect()),
|
||||
only_running: matches.get_flag("r"),
|
||||
negate_selection: matches.get_flag("deselect"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn select_processes(self) -> UResult<Vec<ProcessInformation>> {
|
||||
let mut current_process = ProcessInformation::current_process_info().unwrap();
|
||||
let current_tty = current_process.tty();
|
||||
let current_euid = current_process.euid().unwrap();
|
||||
|
||||
let matches_criteria = |process: &mut ProcessInformation| -> UResult<bool> {
|
||||
if self.only_running && !process.run_state().is_ok_and(|x| x == RunState::Running) {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
if let Some(ref pids) = self.pids {
|
||||
return Ok(pids.contains(&process.pid));
|
||||
}
|
||||
|
||||
if self.select_all {
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
if self.select_non_session_leaders_with_tty {
|
||||
return Ok(!is_session_leader(process) && process.tty() != Teletype::Unknown);
|
||||
}
|
||||
|
||||
if self.select_non_session_leaders {
|
||||
return Ok(!is_session_leader(process));
|
||||
}
|
||||
|
||||
// Default behavior: select processes with same effective user ID and same tty (except -x removes tty restriction)
|
||||
Ok(process.euid().unwrap() == current_euid
|
||||
&& (self.dont_require_tty || process.tty() == current_tty))
|
||||
};
|
||||
|
||||
let mut selected = vec![];
|
||||
for mut process in walk_process() {
|
||||
if matches_criteria(&mut process)? ^ self.negate_selection {
|
||||
selected.push(process);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(selected)
|
||||
}
|
||||
}
|
||||
+53
-38
@@ -3,24 +3,24 @@
|
||||
// For the full copyright and license information, please view the LICENSE
|
||||
// file that was distributed with this source code.
|
||||
|
||||
mod collector;
|
||||
mod mapping;
|
||||
mod parser;
|
||||
mod picker;
|
||||
mod process_selection;
|
||||
mod sorting;
|
||||
|
||||
use clap::crate_version;
|
||||
use clap::{Arg, ArgAction, ArgMatches, Command};
|
||||
use mapping::{
|
||||
collect_code_mapping, default_codes, default_mapping, default_with_psr_codes,
|
||||
bsd_format_codes, collect_code_mapping, default_codes, default_mapping, default_with_psr_codes,
|
||||
extra_full_format_codes, full_format_codes, job_format_codes, long_format_codes,
|
||||
long_y_format_codes, register_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};
|
||||
use uu_pgrep::process::walk_process;
|
||||
use process_selection::ProcessSelectionSettings;
|
||||
use std::cell::RefCell;
|
||||
use uucore::{
|
||||
error::{UError, UResult, USimpleError},
|
||||
format_usage, help_about, help_usage,
|
||||
@@ -33,21 +33,12 @@ const USAGE: &str = help_usage!("ps.md");
|
||||
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||
let matches = uu_app().try_get_matches_from(args)?;
|
||||
|
||||
let snapshot = walk_process()
|
||||
.map(|it| Rc::new(RefCell::new(it)))
|
||||
.collect::<Vec<_>>();
|
||||
let mut proc_infos = Vec::new();
|
||||
|
||||
if !matches.get_flag("A") && !matches.get_flag("a") && !matches.get_flag("d") {
|
||||
proc_infos.extend(collector::basic_collector(&snapshot));
|
||||
} else {
|
||||
proc_infos.extend(collector::process_collector(&matches, &snapshot));
|
||||
proc_infos.extend(collector::session_collector(&matches, &snapshot));
|
||||
let selection_settings = ProcessSelectionSettings::from_matches(&matches);
|
||||
let mut proc_infos = selection_settings.select_processes()?;
|
||||
if proc_infos.is_empty() {
|
||||
uucore::error::set_exit_code(1);
|
||||
}
|
||||
|
||||
proc_infos.sort_by(|a, b| a.borrow().pid.cmp(&b.borrow().pid));
|
||||
proc_infos.dedup_by(|a, b| a.borrow().pid == b.borrow().pid);
|
||||
|
||||
sorting::sort(&mut proc_infos, &matches);
|
||||
|
||||
let arg_formats = collect_format(&matches);
|
||||
@@ -74,6 +65,8 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||
user_format_codes()
|
||||
} else if matches.get_flag("v") {
|
||||
vm_format_codes()
|
||||
} else if matches.get_flag("x") {
|
||||
bsd_format_codes()
|
||||
} else if matches.get_flag("X") {
|
||||
register_format_codes()
|
||||
} else if arg_formats.is_empty() {
|
||||
@@ -90,7 +83,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||
for proc in proc_infos {
|
||||
let picked = pickers
|
||||
.iter()
|
||||
.map(|picker| picker(Rc::unwrap_or_clone(proc.clone())));
|
||||
.map(|picker| picker(RefCell::new(proc.clone())));
|
||||
rows.push(Row::from_iter(picked));
|
||||
}
|
||||
|
||||
@@ -100,21 +93,22 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||
default_codes();
|
||||
codes
|
||||
.into_iter()
|
||||
.map(|code| (code.clone(), default_mapping[&code].to_string()))
|
||||
.map(|code| (code.clone(), default_mapping[&code].clone()))
|
||||
.collect::<Vec<_>>()
|
||||
} else {
|
||||
collect_code_mapping(&arg_formats)
|
||||
};
|
||||
|
||||
let header = code_mapping
|
||||
.iter()
|
||||
.map(|(_, header)| header)
|
||||
.map(Into::into)
|
||||
.collect::<Vec<String>>();
|
||||
|
||||
// Apply header
|
||||
let mut table = Table::from_iter([Row::from_iter(header)]);
|
||||
let mut table = Table::new();
|
||||
table.set_format(*FORMAT_CLEAN);
|
||||
if !matches.get_flag("no-headers") {
|
||||
let header = code_mapping
|
||||
.iter()
|
||||
.map(|(_, header)| header)
|
||||
.map(Into::into)
|
||||
.collect::<Vec<String>>();
|
||||
table.add_row(Row::from_iter(header));
|
||||
}
|
||||
table.extend(rows);
|
||||
|
||||
print!("{table}");
|
||||
@@ -144,6 +138,15 @@ fn collect_format(
|
||||
Ok(collect)
|
||||
}
|
||||
|
||||
fn parse_numeric_list(s: &str) -> Result<Vec<usize>, String> {
|
||||
s.split(|c: char| c.is_whitespace() || c == ',')
|
||||
.map(|word| {
|
||||
word.parse::<usize>()
|
||||
.map_err(|_| format!("invalid number: '{}'", word))
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[allow(clippy::cognitive_complexity)]
|
||||
pub fn uu_app() -> Command {
|
||||
Command::new(uucore::util_name())
|
||||
@@ -177,21 +180,19 @@ pub fn uu_app() -> Command {
|
||||
.short('N')
|
||||
.help("negate selection")
|
||||
.action(ArgAction::SetTrue),
|
||||
// Arg::new("r")
|
||||
// .short('r')
|
||||
// .action(ArgAction::SetTrue)
|
||||
// .help("only running processes")
|
||||
// .allow_hyphen_values(true),
|
||||
Arg::new("r")
|
||||
.short('r')
|
||||
.action(ArgAction::SetTrue)
|
||||
.help("only running processes"),
|
||||
// Arg::new("T")
|
||||
// .short('T')
|
||||
// .action(ArgAction::SetTrue)
|
||||
// .help("all processes on this terminal")
|
||||
// .allow_hyphen_values(true),
|
||||
// Arg::new("x")
|
||||
// .short('x')
|
||||
// .action(ArgAction::SetTrue)
|
||||
// .help("processes without controlling ttys")
|
||||
// .allow_hyphen_values(true),
|
||||
Arg::new("x")
|
||||
.short('x')
|
||||
.action(ArgAction::SetTrue)
|
||||
.help("processes without controlling ttys"),
|
||||
])
|
||||
.arg(
|
||||
Arg::new("f")
|
||||
@@ -263,6 +264,21 @@ pub fn uu_app() -> Command {
|
||||
.value_parser(parser)
|
||||
.help("user-defined format"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("no-headers")
|
||||
.long("no-headers")
|
||||
.visible_alias("no-heading")
|
||||
.action(ArgAction::SetTrue)
|
||||
.help("do not print header at all"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("pid")
|
||||
.short('p')
|
||||
.long("pid")
|
||||
.action(ArgAction::Append)
|
||||
.value_parser(parse_numeric_list)
|
||||
.help("select by process ID"),
|
||||
)
|
||||
// .args([
|
||||
// Arg::new("command").short('c').help("command name"),
|
||||
// Arg::new("GID")
|
||||
@@ -273,7 +289,6 @@ pub fn uu_app() -> Command {
|
||||
// .short('g')
|
||||
// .long("group")
|
||||
// .help("session or effective group name"),
|
||||
// Arg::new("PID").short('p').long("pid").help("process id"),
|
||||
// Arg::new("pPID").long("ppid").help("parent process id"),
|
||||
// Arg::new("qPID")
|
||||
// .short('q')
|
||||
|
||||
@@ -4,15 +4,14 @@
|
||||
// file that was distributed with this source code.
|
||||
|
||||
use clap::ArgMatches;
|
||||
use std::{cell::RefCell, rc::Rc};
|
||||
use uu_pgrep::process::ProcessInformation;
|
||||
|
||||
// TODO: Implementing sorting flags.
|
||||
pub(crate) fn sort(input: &mut [Rc<RefCell<ProcessInformation>>], _matches: &ArgMatches) {
|
||||
pub(crate) fn sort(input: &mut [ProcessInformation], _matches: &ArgMatches) {
|
||||
sort_by_pid(input);
|
||||
}
|
||||
|
||||
/// Sort by pid. (Default)
|
||||
fn sort_by_pid(input: &mut [Rc<RefCell<ProcessInformation>>]) {
|
||||
input.sort_by(|a, b| a.borrow().pid.cmp(&b.borrow().pid));
|
||||
fn sort_by_pid(input: &mut [ProcessInformation]) {
|
||||
input.sort_by(|a, b| a.pid.cmp(&b.pid));
|
||||
}
|
||||
|
||||
@@ -293,7 +293,7 @@ pub(crate) fn parse_data(line: &str) -> Option<(String, Vec<u64>)> {
|
||||
|
||||
split.first().map(|name| {
|
||||
(
|
||||
name.to_string(),
|
||||
name.clone(),
|
||||
split
|
||||
.clone()
|
||||
.into_iter()
|
||||
|
||||
@@ -324,7 +324,7 @@ fn get_process_info(
|
||||
let blocked = proc_data.stat.get("procs_blocked").unwrap();
|
||||
let len = if matches.get_flag("wide") { 4 } else { 2 };
|
||||
|
||||
vec![(len, runnable.to_string()), (len, blocked.to_string())]
|
||||
vec![(len, runnable.clone()), (len, blocked.clone())]
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
// For the full copyright and license information, please view the LICENSE
|
||||
// file that was distributed with this source code.
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
use regex::Regex;
|
||||
use uutests::new_ucmd;
|
||||
|
||||
#[test]
|
||||
@@ -129,6 +131,12 @@ fn test_register_format() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(target_os = "linux")]
|
||||
fn test_x_format() {
|
||||
check_header("-x", &["PID", "TTY", "STAT", "TIME", "COMMAND"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(target_os = "linux")]
|
||||
fn test_code_mapping() {
|
||||
@@ -165,3 +173,67 @@ fn test_code_mapping() {
|
||||
.stdout_contains("CMD1")
|
||||
.stdout_contains("CMD2");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(target_os = "linux")]
|
||||
fn test_no_headers_flags() {
|
||||
let regex = Regex::new("^ *PID +").unwrap();
|
||||
for flag in &["--no-headers", "--no-heading"] {
|
||||
new_ucmd!()
|
||||
.arg(flag)
|
||||
.succeeds()
|
||||
.stdout_does_not_match(®ex);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(target_os = "linux")]
|
||||
fn test_deselect() {
|
||||
// Inverse of all processes should be empty
|
||||
new_ucmd!()
|
||||
.args(&["--deselect", "-A", "--no-headers"])
|
||||
.fails()
|
||||
.code_is(1)
|
||||
.stdout_is("");
|
||||
|
||||
// PID 1 should be present in inverse of default filter criteria
|
||||
new_ucmd!()
|
||||
.args(&["--deselect"])
|
||||
.succeeds()
|
||||
.stdout_matches(&Regex::new("\n *1 ").unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(target_os = "linux")]
|
||||
fn test_pid_selection() {
|
||||
let our_pid = std::process::id();
|
||||
// Test that only pid 1 and pid of the test runner is present
|
||||
let test = |pid_args: &[&str]| {
|
||||
let match_regex = Regex::new(&format!("^ *1 *\n *{our_pid} *\n$")).unwrap();
|
||||
let mut args = vec!["--no-headers", "-o", "pid"];
|
||||
args.extend_from_slice(pid_args);
|
||||
new_ucmd!()
|
||||
.args(&args)
|
||||
.succeeds()
|
||||
.stdout_matches(&match_regex);
|
||||
};
|
||||
|
||||
for flag in ["-p", "--pid"] {
|
||||
test(&[flag, &format!("1 {our_pid}")]);
|
||||
test(&[flag, &format!("1,{our_pid}")]);
|
||||
test(&[flag, "1", flag, &our_pid.to_string()]);
|
||||
}
|
||||
|
||||
// Test nonexistent PID (should show no output)
|
||||
new_ucmd!()
|
||||
.args(&["-p", "0", "--no-headers"])
|
||||
.fails()
|
||||
.code_is(1)
|
||||
.stdout_is("");
|
||||
|
||||
// Test invalid PID
|
||||
new_ucmd!()
|
||||
.args(&["-p", "invalid"])
|
||||
.fails()
|
||||
.stderr_contains("invalid number");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user