Skip to content

Commit ba2da94

Browse files
Merge pull request #484 from Bluemangoo/feature/skill-snice-w-i-n
skill&snice: implement `no-action` `warnings` `interactive`
2 parents 8e136eb + f1972d1 commit ba2da94

File tree

5 files changed

+123
-41
lines changed

5 files changed

+123
-41
lines changed

src/uu/skill/src/skill.rs

Lines changed: 23 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
// For the full copyright and license information, please view the LICENSE
44
// file that was distributed with this source code.
55

6-
use clap::{arg, crate_version, value_parser, Arg, Command};
6+
use clap::{crate_version, Arg, Command};
77
#[cfg(unix)]
88
use nix::{sys::signal, sys::signal::Signal, unistd::Pid};
99
use uu_snice::{
@@ -29,6 +29,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
2929
}
3030

3131
// Case1: Send signal
32+
let take_action = !settings.no_action;
3233
if let Some(targets) = settings.expressions {
3334
let pids = collect_pids(&targets);
3435

@@ -43,33 +44,46 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
4344
};
4445

4546
#[cfg(unix)]
46-
let results = perform_action(&pids, &signal);
47+
let results = perform_action(&pids, &signal, take_action, settings.interactive);
4748
#[cfg(not(unix))]
4849
let results: Vec<Option<ActionResult>> = Vec::new();
4950

5051
if results.iter().all(|it| it.is_none()) || results.is_empty() {
5152
return Err(USimpleError::new(1, "no process selection criteria"));
5253
}
5354

54-
if settings.verbose {
55-
let output = construct_verbose_result(&pids, &results).trim().to_owned();
55+
let error_only = settings.warnings || !settings.verbose;
56+
if settings.verbose || settings.warnings {
57+
let output = construct_verbose_result(&pids, &results, error_only, take_action)
58+
.trim()
59+
.to_owned();
5660
println!("{output}");
61+
} else if !take_action {
62+
pids.iter().for_each(|pid| println!("{pid}"));
5763
}
5864
}
5965

6066
Ok(())
6167
}
6268

6369
#[cfg(unix)]
64-
fn perform_action(pids: &[u32], signal: &Signal) -> Vec<Option<ActionResult>> {
70+
fn perform_action(
71+
pids: &[u32],
72+
signal: &Signal,
73+
take_action: bool,
74+
ask: bool,
75+
) -> Vec<Option<ActionResult>> {
76+
let sig = if take_action { Some(*signal) } else { None };
6577
pids.iter()
6678
.map(|pid| {
67-
{
68-
Some(match signal::kill(Pid::from_raw(*pid as i32), *signal) {
79+
if !ask || uu_snice::ask_user(*pid) {
80+
Some(match signal::kill(Pid::from_raw(*pid as i32), sig) {
6981
Ok(_) => ActionResult::Success,
70-
7182
Err(_) => ActionResult::PermissionDenied,
7283
})
84+
} else {
85+
// won't be used, but we need to return (not None)
86+
Some(ActionResult::Success)
7387
}
7488
})
7589
.collect()
@@ -84,23 +98,5 @@ pub fn uu_app() -> Command {
8498
.infer_long_args(true)
8599
.arg_required_else_help(true)
86100
.arg(Arg::new("signal"))
87-
.args([
88-
// arg!(-f --fast "fast mode (not implemented)"),
89-
// arg!(-i --interactive "interactive"),
90-
arg!(-l --list "list all signal names"),
91-
arg!(-L --table "list all signal names in a nice table"),
92-
// arg!(-n --"no-action" "do not actually kill processes; just print what would happen"),
93-
arg!(-v --verbose "explain what is being done"),
94-
// arg!(-w --warnings "enable warnings (not implemented)"),
95-
// Expressions
96-
arg!(-c --command <command> ... "expression is a command name"),
97-
arg!(-p --pid <pid> ... "expression is a process id number")
98-
.value_parser(value_parser!(u32)),
99-
arg!(-t --tty <tty> ... "expression is a terminal"),
100-
arg!(-u --user <username> ... "expression is a username"),
101-
// arg!(--ns <PID> "match the processes that belong to the same namespace as <pid>"),
102-
// arg!(--nslist <ns> "list which namespaces will be considered for the --ns option.")
103-
// .value_delimiter(',')
104-
// .value_parser(["ipc", "mnt", "net", "pid", "user", "uts"]),
105-
])
101+
.args(uu_snice::clap_args())
106102
}

src/uu/snice/src/action.rs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// For the full copyright and license information, please view the LICENSE
44
// file that was distributed with this source code.
55

6+
use crate::ask_user;
67
use crate::priority::Priority;
78
use std::{
89
fmt::{self, Display, Formatter},
@@ -92,7 +93,7 @@ impl SelectedTarget {
9293
}
9394

9495
#[allow(unused)]
95-
#[derive(Debug, Clone)]
96+
#[derive(Debug, Clone, Eq, PartialEq)]
9697
pub enum ActionResult {
9798
PermissionDenied,
9899
Success,
@@ -171,7 +172,15 @@ pub(crate) fn perform_action(
171172
pids: &[u32],
172173
prio: &Priority,
173174
take_action: bool,
175+
ask: bool,
174176
) -> Vec<Option<ActionResult>> {
175-
let f = |pid: &u32| set_priority(*pid, prio, take_action);
177+
let f = |pid: &u32| {
178+
if !ask || ask_user(*pid) {
179+
set_priority(*pid, prio, take_action)
180+
} else {
181+
// won't be used, but we need to return (not None)
182+
Some(ActionResult::Success)
183+
}
184+
};
176185
pids.iter().map(f).collect()
177186
}

src/uu/snice/src/process_matcher.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ pub struct Settings {
1414
pub display: Option<SignalDisplay>,
1515
pub expressions: Option<Vec<SelectedTarget>>,
1616
pub verbose: bool,
17+
pub warnings: bool,
18+
pub interactive: bool,
19+
pub no_action: bool,
1720
}
1821

1922
impl Settings {
@@ -30,6 +33,9 @@ impl Settings {
3033
display,
3134
expressions: Self::targets(matches),
3235
verbose: matches.get_flag("verbose"),
36+
warnings: matches.get_flag("warnings"),
37+
interactive: matches.get_flag("interactive"),
38+
no_action: matches.get_flag("no-action"),
3339
})
3440
}
3541

@@ -81,12 +87,12 @@ impl Settings {
8187
pub fn clap_args() -> Vec<Arg> {
8288
vec![
8389
// arg!(-f --fast "fast mode (not implemented)"),
84-
// arg!(-i --interactive "interactive"),
90+
arg!(-i --interactive "interactive").conflicts_with_all(["verbose", "no-action"]),
8591
arg!(-l --list "list all signal names"),
8692
arg!(-L --table "list all signal names in a nice table"),
8793
arg!(-n --"no-action" "do not actually kill processes; just print what would happen"),
8894
arg!(-v --verbose "explain what is being done"),
89-
// arg!(-w --warnings "enable warnings (not implemented)"),
95+
arg!(-w --warnings "enable warnings (not implemented)"),
9096
// Expressions
9197
arg!(-c --command <command> ... "expression is a command name"),
9298
arg!(-p --pid <pid> ... "expression is a process id number")

src/uu/snice/src/snice.rs

Lines changed: 75 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,15 @@
33
// For the full copyright and license information, please view the LICENSE
44
// file that was distributed with this source code.
55

6-
use std::{collections::HashSet, path::PathBuf, str::FromStr};
7-
86
use crate::priority::Priority;
97
pub use action::ActionResult;
108
use action::{perform_action, process_snapshot, users, SelectedTarget};
119
use clap::{crate_version, Arg, Command};
1210
use prettytable::{format::consts::FORMAT_CLEAN, row, Table};
11+
pub use process_matcher::clap_args;
1312
use process_matcher::*;
13+
use std::io::Write;
14+
use std::{collections::HashSet, path::PathBuf, str::FromStr};
1415
use sysinfo::Pid;
1516
use uu_pgrep::process::ProcessInformation;
1617
#[cfg(target_family = "unix")]
@@ -92,7 +93,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
9293
}
9394

9495
// Case1: Perform priority
95-
let take_action = !matches.get_flag("no-action");
96+
let take_action = !settings.no_action;
9697
if let Some(targets) = settings.expressions {
9798
let priority_str = matches.get_one::<String>("priority").cloned();
9899

@@ -104,14 +105,17 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
104105
};
105106

106107
let pids = collect_pids(&targets);
107-
let results = perform_action(&pids, &priority, take_action);
108+
let results = perform_action(&pids, &priority, take_action, settings.interactive);
108109

109110
if results.iter().all(|it| it.is_none()) || results.is_empty() {
110111
return Err(USimpleError::new(1, "no process selection criteria"));
111112
}
112113

113-
if settings.verbose {
114-
let output = construct_verbose_result(&pids, &results).trim().to_owned();
114+
let error_only = settings.warnings || !settings.verbose;
115+
if settings.verbose || settings.warnings {
116+
let output = construct_verbose_result(&pids, &results, error_only, take_action)
117+
.trim()
118+
.to_owned();
115119
println!("{output}");
116120
} else if !take_action {
117121
pids.iter().for_each(|pid| println!("{pid}"));
@@ -121,15 +125,66 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
121125
Ok(())
122126
}
123127

128+
pub fn ask_user(pid: u32) -> bool {
129+
let process = process_snapshot().process(Pid::from_u32(pid)).unwrap();
130+
131+
let tty = ProcessInformation::try_new(PathBuf::from_str(&format!("/proc/{pid}")).unwrap())
132+
.map(|v| v.tty().to_string())
133+
.unwrap_or(String::from("?"));
134+
135+
let user = process
136+
.user_id()
137+
.and_then(|uid| users().iter().find(|it| it.id() == uid))
138+
.map(|it| it.name())
139+
.unwrap_or("?")
140+
.to_owned();
141+
142+
let cmd = process
143+
.exe()
144+
.and_then(|it| it.iter().next_back())
145+
.unwrap_or("?".as_ref());
146+
let cmd = cmd.to_str().unwrap();
147+
148+
// no newline at the end
149+
print!("{tty:<8} {user:<8} {pid:<5} {cmd:<18} ? ");
150+
std::io::stdout().flush().unwrap();
151+
let mut input = String::new();
152+
if std::io::stdin().read_line(&mut input).is_err() {
153+
return false;
154+
}
155+
let input = input.trim();
156+
if input.eq_ignore_ascii_case("y") || input.eq_ignore_ascii_case("yes") {
157+
return true;
158+
}
159+
160+
false
161+
}
162+
124163
#[allow(unused)]
125-
pub fn construct_verbose_result(pids: &[u32], action_results: &[Option<ActionResult>]) -> String {
164+
pub fn construct_verbose_result(
165+
pids: &[u32],
166+
action_results: &[Option<ActionResult>],
167+
error_only: bool,
168+
take_action: bool,
169+
) -> String {
126170
let mut table = action_results
127171
.iter()
128172
.enumerate()
129173
.map(|(index, it)| (pids[index], it))
130174
.filter(|(_, it)| it.is_some())
175+
.filter(|v| {
176+
!error_only
177+
|| !take_action
178+
|| v.1
179+
.clone()
180+
.is_some_and(|v| v == ActionResult::PermissionDenied)
181+
})
131182
.map(|(pid, action)| (pid, action.clone().unwrap()))
132183
.map(|(pid, action)| {
184+
if !take_action && action == ActionResult::Success {
185+
return (pid, None);
186+
}
187+
133188
let process = process_snapshot().process(Pid::from_u32(pid)).unwrap();
134189

135190
let tty =
@@ -148,10 +203,20 @@ pub fn construct_verbose_result(pids: &[u32], action_results: &[Option<ActionRes
148203
.unwrap_or("?".as_ref());
149204
let cmd = cmd.to_str().unwrap();
150205

151-
(tty, user, pid, cmd, action)
206+
(pid, Some((tty, user, cmd, action)))
207+
})
208+
.filter(|(_, v)| match v {
209+
None => true,
210+
Some((tty, _, _, _)) => tty.is_ok(),
211+
})
212+
.map(|(pid, v)| match v {
213+
None => {
214+
row![pid]
215+
}
216+
Some((tty, user, cmd, action)) => {
217+
row![tty.unwrap().tty(), user, pid, cmd, action]
218+
}
152219
})
153-
.filter(|(tty, _, _, _, _)| tty.is_ok())
154-
.map(|(tty, user, pid, cmd, action)| row![tty.unwrap().tty(), user, pid, cmd, action])
155220
.collect::<Table>();
156221

157222
table.set_format(*FORMAT_CLEAN);

tests/by-util/test_snice.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,9 @@ fn test_no_args() {
1616
fn test_no_process_selected() {
1717
new_ucmd!().arg("-u=invalid_user").fails().code_is(1);
1818
}
19+
20+
#[test]
21+
fn test_interactive_conflict_args() {
22+
new_ucmd!().args(&["-i", "-v"]).fails().code_is(1);
23+
new_ucmd!().args(&["-i", "-n"]).fails().code_is(1);
24+
}

0 commit comments

Comments
 (0)