From b6dc9c140facbba515d9e3197f8ca6216316dac2 Mon Sep 17 00:00:00 2001 From: Bluemangoo Date: Sun, 10 Aug 2025 11:25:20 +0800 Subject: [PATCH 1/3] skill: implement `no-action` --- src/uu/skill/src/skill.rs | 33 ++++++++--------------------- src/uu/snice/src/process_matcher.rs | 2 ++ src/uu/snice/src/snice.rs | 6 +++--- 3 files changed, 14 insertions(+), 27 deletions(-) diff --git a/src/uu/skill/src/skill.rs b/src/uu/skill/src/skill.rs index e1c47c6..602082a 100644 --- a/src/uu/skill/src/skill.rs +++ b/src/uu/skill/src/skill.rs @@ -3,7 +3,7 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -use clap::{arg, crate_version, value_parser, Arg, Command}; +use clap::{crate_version, Arg, Command}; #[cfg(unix)] use nix::{sys::signal, sys::signal::Signal, unistd::Pid}; use uu_snice::{ @@ -29,6 +29,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } // Case1: Send signal + let take_action = !settings.no_action; if let Some(targets) = settings.expressions { let pids = collect_pids(&targets); @@ -43,7 +44,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { }; #[cfg(unix)] - let results = perform_action(&pids, &signal); + let results = perform_action(&pids, &signal, take_action); #[cfg(not(unix))] let results: Vec> = Vec::new(); @@ -54,6 +55,8 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { if settings.verbose { let output = construct_verbose_result(&pids, &results).trim().to_owned(); println!("{output}"); + } else if !take_action { + pids.iter().for_each(|pid| println!("{pid}")); } } @@ -61,13 +64,13 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } #[cfg(unix)] -fn perform_action(pids: &[u32], signal: &Signal) -> Vec> { +fn perform_action(pids: &[u32], signal: &Signal, take_action: bool) -> Vec> { + let sig = if take_action { Some(*signal) } else { None }; pids.iter() .map(|pid| { { - Some(match signal::kill(Pid::from_raw(*pid as i32), *signal) { + Some(match signal::kill(Pid::from_raw(*pid as i32), sig) { Ok(_) => ActionResult::Success, - Err(_) => ActionResult::PermissionDenied, }) } @@ -84,23 +87,5 @@ pub fn uu_app() -> Command { .infer_long_args(true) .arg_required_else_help(true) .arg(Arg::new("signal")) - .args([ - // arg!(-f --fast "fast mode (not implemented)"), - // arg!(-i --interactive "interactive"), - arg!(-l --list "list all signal names"), - arg!(-L --table "list all signal names in a nice table"), - // arg!(-n --"no-action" "do not actually kill processes; just print what would happen"), - arg!(-v --verbose "explain what is being done"), - // arg!(-w --warnings "enable warnings (not implemented)"), - // Expressions - arg!(-c --command ... "expression is a command name"), - arg!(-p --pid ... "expression is a process id number") - .value_parser(value_parser!(u32)), - arg!(-t --tty ... "expression is a terminal"), - arg!(-u --user ... "expression is a username"), - // arg!(--ns "match the processes that belong to the same namespace as "), - // arg!(--nslist "list which namespaces will be considered for the --ns option.") - // .value_delimiter(',') - // .value_parser(["ipc", "mnt", "net", "pid", "user", "uts"]), - ]) + .args(uu_snice::clap_args()) } diff --git a/src/uu/snice/src/process_matcher.rs b/src/uu/snice/src/process_matcher.rs index 939d326..cc09b10 100644 --- a/src/uu/snice/src/process_matcher.rs +++ b/src/uu/snice/src/process_matcher.rs @@ -14,6 +14,7 @@ pub struct Settings { pub display: Option, pub expressions: Option>, pub verbose: bool, + pub no_action: bool, } impl Settings { @@ -30,6 +31,7 @@ impl Settings { display, expressions: Self::targets(matches), verbose: matches.get_flag("verbose"), + no_action: matches.get_flag("no-action"), }) } diff --git a/src/uu/snice/src/snice.rs b/src/uu/snice/src/snice.rs index 5bb0c06..74d5323 100644 --- a/src/uu/snice/src/snice.rs +++ b/src/uu/snice/src/snice.rs @@ -3,14 +3,14 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -use std::{collections::HashSet, path::PathBuf, str::FromStr}; - use crate::priority::Priority; pub use action::ActionResult; use action::{perform_action, process_snapshot, users, SelectedTarget}; use clap::{crate_version, Arg, Command}; use prettytable::{format::consts::FORMAT_CLEAN, row, Table}; +pub use process_matcher::clap_args; use process_matcher::*; +use std::{collections::HashSet, path::PathBuf, str::FromStr}; use sysinfo::Pid; use uu_pgrep::process::ProcessInformation; #[cfg(target_family = "unix")] @@ -92,7 +92,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } // Case1: Perform priority - let take_action = !matches.get_flag("no-action"); + let take_action = !settings.no_action; if let Some(targets) = settings.expressions { let priority_str = matches.get_one::("priority").cloned(); From 1c5ff4350788c26a5742d4426ccc3c2ebd0c5808 Mon Sep 17 00:00:00 2001 From: Bluemangoo Date: Sun, 10 Aug 2025 11:27:57 +0800 Subject: [PATCH 2/3] snice&skill: implement `warnings` --- src/uu/skill/src/skill.rs | 7 +++-- src/uu/snice/src/action.rs | 2 +- src/uu/snice/src/process_matcher.rs | 4 ++- src/uu/snice/src/snice.rs | 41 ++++++++++++++++++++++++----- 4 files changed, 44 insertions(+), 10 deletions(-) diff --git a/src/uu/skill/src/skill.rs b/src/uu/skill/src/skill.rs index 602082a..48660e8 100644 --- a/src/uu/skill/src/skill.rs +++ b/src/uu/skill/src/skill.rs @@ -52,8 +52,11 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { return Err(USimpleError::new(1, "no process selection criteria")); } - if settings.verbose { - let output = construct_verbose_result(&pids, &results).trim().to_owned(); + let error_only = settings.warnings || !settings.verbose; + if settings.verbose || settings.warnings { + let output = construct_verbose_result(&pids, &results, error_only, take_action) + .trim() + .to_owned(); println!("{output}"); } else if !take_action { pids.iter().for_each(|pid| println!("{pid}")); diff --git a/src/uu/snice/src/action.rs b/src/uu/snice/src/action.rs index dff7bd3..80cbd3a 100644 --- a/src/uu/snice/src/action.rs +++ b/src/uu/snice/src/action.rs @@ -92,7 +92,7 @@ impl SelectedTarget { } #[allow(unused)] -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Eq, PartialEq)] pub enum ActionResult { PermissionDenied, Success, diff --git a/src/uu/snice/src/process_matcher.rs b/src/uu/snice/src/process_matcher.rs index cc09b10..0768efd 100644 --- a/src/uu/snice/src/process_matcher.rs +++ b/src/uu/snice/src/process_matcher.rs @@ -14,6 +14,7 @@ pub struct Settings { pub display: Option, pub expressions: Option>, pub verbose: bool, + pub warnings: bool, pub no_action: bool, } @@ -31,6 +32,7 @@ impl Settings { display, expressions: Self::targets(matches), verbose: matches.get_flag("verbose"), + warnings: matches.get_flag("warnings"), no_action: matches.get_flag("no-action"), }) } @@ -88,7 +90,7 @@ pub fn clap_args() -> Vec { arg!(-L --table "list all signal names in a nice table"), arg!(-n --"no-action" "do not actually kill processes; just print what would happen"), arg!(-v --verbose "explain what is being done"), - // arg!(-w --warnings "enable warnings (not implemented)"), + arg!(-w --warnings "enable warnings (not implemented)"), // Expressions arg!(-c --command ... "expression is a command name"), arg!(-p --pid ... "expression is a process id number") diff --git a/src/uu/snice/src/snice.rs b/src/uu/snice/src/snice.rs index 74d5323..5754967 100644 --- a/src/uu/snice/src/snice.rs +++ b/src/uu/snice/src/snice.rs @@ -110,8 +110,11 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { return Err(USimpleError::new(1, "no process selection criteria")); } - if settings.verbose { - let output = construct_verbose_result(&pids, &results).trim().to_owned(); + let error_only = settings.warnings || !settings.verbose; + if settings.verbose || settings.warnings { + let output = construct_verbose_result(&pids, &results, error_only, take_action) + .trim() + .to_owned(); println!("{output}"); } else if !take_action { pids.iter().for_each(|pid| println!("{pid}")); @@ -122,14 +125,30 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } #[allow(unused)] -pub fn construct_verbose_result(pids: &[u32], action_results: &[Option]) -> String { +pub fn construct_verbose_result( + pids: &[u32], + action_results: &[Option], + error_only: bool, + take_action: bool, +) -> String { let mut table = action_results .iter() .enumerate() .map(|(index, it)| (pids[index], it)) .filter(|(_, it)| it.is_some()) + .filter(|v| { + !error_only + || !take_action + || v.1 + .clone() + .is_some_and(|v| v == ActionResult::PermissionDenied) + }) .map(|(pid, action)| (pid, action.clone().unwrap())) .map(|(pid, action)| { + if !take_action && action == ActionResult::Success { + return (pid, None); + } + let process = process_snapshot().process(Pid::from_u32(pid)).unwrap(); let tty = @@ -148,10 +167,20 @@ pub fn construct_verbose_result(pids: &[u32], action_results: &[Option true, + Some((tty, _, _, _)) => tty.is_ok(), + }) + .map(|(pid, v)| match v { + None => { + row![pid] + } + Some((tty, user, cmd, action)) => { + row![tty.unwrap().tty(), user, pid, cmd, action] + } }) - .filter(|(tty, _, _, _, _)| tty.is_ok()) - .map(|(tty, user, pid, cmd, action)| row![tty.unwrap().tty(), user, pid, cmd, action]) .collect::(); table.set_format(*FORMAT_CLEAN); From f1972d15555dc3100d404308d8465da9c83d2aa7 Mon Sep 17 00:00:00 2001 From: Bluemangoo Date: Sun, 10 Aug 2025 12:17:43 +0800 Subject: [PATCH 3/3] snice&skill: implement `interactive` --- src/uu/skill/src/skill.rs | 14 ++++++++--- src/uu/snice/src/action.rs | 11 ++++++++- src/uu/snice/src/process_matcher.rs | 4 ++- src/uu/snice/src/snice.rs | 38 ++++++++++++++++++++++++++++- tests/by-util/test_snice.rs | 6 +++++ 5 files changed, 67 insertions(+), 6 deletions(-) diff --git a/src/uu/skill/src/skill.rs b/src/uu/skill/src/skill.rs index 48660e8..ff851a8 100644 --- a/src/uu/skill/src/skill.rs +++ b/src/uu/skill/src/skill.rs @@ -44,7 +44,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { }; #[cfg(unix)] - let results = perform_action(&pids, &signal, take_action); + let results = perform_action(&pids, &signal, take_action, settings.interactive); #[cfg(not(unix))] let results: Vec> = Vec::new(); @@ -67,15 +67,23 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } #[cfg(unix)] -fn perform_action(pids: &[u32], signal: &Signal, take_action: bool) -> Vec> { +fn perform_action( + pids: &[u32], + signal: &Signal, + take_action: bool, + ask: bool, +) -> Vec> { let sig = if take_action { Some(*signal) } else { None }; pids.iter() .map(|pid| { - { + if !ask || uu_snice::ask_user(*pid) { Some(match signal::kill(Pid::from_raw(*pid as i32), sig) { Ok(_) => ActionResult::Success, Err(_) => ActionResult::PermissionDenied, }) + } else { + // won't be used, but we need to return (not None) + Some(ActionResult::Success) } }) .collect() diff --git a/src/uu/snice/src/action.rs b/src/uu/snice/src/action.rs index 80cbd3a..f5752e5 100644 --- a/src/uu/snice/src/action.rs +++ b/src/uu/snice/src/action.rs @@ -3,6 +3,7 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. +use crate::ask_user; use crate::priority::Priority; use std::{ fmt::{self, Display, Formatter}, @@ -171,7 +172,15 @@ pub(crate) fn perform_action( pids: &[u32], prio: &Priority, take_action: bool, + ask: bool, ) -> Vec> { - let f = |pid: &u32| set_priority(*pid, prio, take_action); + let f = |pid: &u32| { + if !ask || ask_user(*pid) { + set_priority(*pid, prio, take_action) + } else { + // won't be used, but we need to return (not None) + Some(ActionResult::Success) + } + }; pids.iter().map(f).collect() } diff --git a/src/uu/snice/src/process_matcher.rs b/src/uu/snice/src/process_matcher.rs index 0768efd..be2f9b8 100644 --- a/src/uu/snice/src/process_matcher.rs +++ b/src/uu/snice/src/process_matcher.rs @@ -15,6 +15,7 @@ pub struct Settings { pub expressions: Option>, pub verbose: bool, pub warnings: bool, + pub interactive: bool, pub no_action: bool, } @@ -33,6 +34,7 @@ impl Settings { expressions: Self::targets(matches), verbose: matches.get_flag("verbose"), warnings: matches.get_flag("warnings"), + interactive: matches.get_flag("interactive"), no_action: matches.get_flag("no-action"), }) } @@ -85,7 +87,7 @@ impl Settings { pub fn clap_args() -> Vec { vec![ // arg!(-f --fast "fast mode (not implemented)"), - // arg!(-i --interactive "interactive"), + arg!(-i --interactive "interactive").conflicts_with_all(["verbose", "no-action"]), arg!(-l --list "list all signal names"), arg!(-L --table "list all signal names in a nice table"), arg!(-n --"no-action" "do not actually kill processes; just print what would happen"), diff --git a/src/uu/snice/src/snice.rs b/src/uu/snice/src/snice.rs index 5754967..4583a18 100644 --- a/src/uu/snice/src/snice.rs +++ b/src/uu/snice/src/snice.rs @@ -10,6 +10,7 @@ use clap::{crate_version, Arg, Command}; use prettytable::{format::consts::FORMAT_CLEAN, row, Table}; pub use process_matcher::clap_args; use process_matcher::*; +use std::io::Write; use std::{collections::HashSet, path::PathBuf, str::FromStr}; use sysinfo::Pid; use uu_pgrep::process::ProcessInformation; @@ -104,7 +105,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { }; let pids = collect_pids(&targets); - let results = perform_action(&pids, &priority, take_action); + let results = perform_action(&pids, &priority, take_action, settings.interactive); if results.iter().all(|it| it.is_none()) || results.is_empty() { return Err(USimpleError::new(1, "no process selection criteria")); @@ -124,6 +125,41 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { Ok(()) } +pub fn ask_user(pid: u32) -> bool { + let process = process_snapshot().process(Pid::from_u32(pid)).unwrap(); + + let tty = ProcessInformation::try_new(PathBuf::from_str(&format!("/proc/{pid}")).unwrap()) + .map(|v| v.tty().to_string()) + .unwrap_or(String::from("?")); + + let user = process + .user_id() + .and_then(|uid| users().iter().find(|it| it.id() == uid)) + .map(|it| it.name()) + .unwrap_or("?") + .to_owned(); + + let cmd = process + .exe() + .and_then(|it| it.iter().next_back()) + .unwrap_or("?".as_ref()); + let cmd = cmd.to_str().unwrap(); + + // no newline at the end + print!("{tty:<8} {user:<8} {pid:<5} {cmd:<18} ? "); + std::io::stdout().flush().unwrap(); + let mut input = String::new(); + if std::io::stdin().read_line(&mut input).is_err() { + return false; + } + let input = input.trim(); + if input.eq_ignore_ascii_case("y") || input.eq_ignore_ascii_case("yes") { + return true; + } + + false +} + #[allow(unused)] pub fn construct_verbose_result( pids: &[u32], diff --git a/tests/by-util/test_snice.rs b/tests/by-util/test_snice.rs index 3c06c94..f3d0ba8 100644 --- a/tests/by-util/test_snice.rs +++ b/tests/by-util/test_snice.rs @@ -16,3 +16,9 @@ fn test_no_args() { fn test_no_process_selected() { new_ucmd!().arg("-u=invalid_user").fails().code_is(1); } + +#[test] +fn test_interactive_conflict_args() { + new_ucmd!().args(&["-i", "-v"]).fails().code_is(1); + new_ucmd!().args(&["-i", "-n"]).fails().code_is(1); +}