From f1972d15555dc3100d404308d8465da9c83d2aa7 Mon Sep 17 00:00:00 2001 From: Bluemangoo Date: Sun, 10 Aug 2025 12:17:43 +0800 Subject: [PATCH] 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); +}