Skip to content

Commit 1c70e02

Browse files
authored
Merge pull request #466 from Bluemangoo/feature/skill
skill: add basic implementation
2 parents c4f18be + a0e61a9 commit 1c70e02

File tree

12 files changed

+304
-121
lines changed

12 files changed

+304
-121
lines changed

Cargo.lock

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ feat_common_core = [
4040
"pmap",
4141
"ps",
4242
"pwdx",
43+
"skill",
4344
"slabtop",
4445
"snice",
4546
"sysctl",
@@ -98,6 +99,7 @@ pkill = { optional = true, version = "0.0.1", package = "uu_pkill", path = "src/
9899
pmap = { optional = true, version = "0.0.1", package = "uu_pmap", path = "src/uu/pmap" }
99100
ps = { optional = true, version = "0.0.1", package = "uu_ps", path = "src/uu/ps" }
100101
pwdx = { optional = true, version = "0.0.1", package = "uu_pwdx", path = "src/uu/pwdx" }
102+
skill = { optional = true, version = "0.0.1", package = "uu_skill", path = "src/uu/skill" }
101103
slabtop = { optional = true, version = "0.0.1", package = "uu_slabtop", path = "src/uu/slabtop" }
102104
snice = { optional = true, version = "0.0.1", package = "uu_snice", path = "src/uu/snice" }
103105
sysctl = { optional = true, version = "0.0.1", package = "uu_sysctl", path = "src/uu/sysctl" }

src/uu/skill/Cargo.toml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
[package]
2+
name = "uu_skill"
3+
description = "skill - (uutils) send a signal or report process status"
4+
repository = "https://github.com/uutils/procps/tree/main/src/uu/skill"
5+
authors.workspace = true
6+
categories.workspace = true
7+
edition.workspace = true
8+
homepage.workspace = true
9+
keywords.workspace = true
10+
license.workspace = true
11+
version.workspace = true
12+
13+
[dependencies]
14+
uucore = { workspace = true, features = ["signals"] }
15+
clap = { workspace = true }
16+
nix = { workspace = true }
17+
18+
uu_snice = { path = "../snice" }
19+
20+
21+
[lib]
22+
path = "src/skill.rs"
23+
24+
[[bin]]
25+
name = "skill"
26+
path = "src/main.rs"

src/uu/skill/skill.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# skill
2+
3+
```
4+
skill [signal] [options] <expression>
5+
```
6+
7+
Report processes matching an expression and send a signal to them.

src/uu/skill/src/main.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
uucore::bin!(uu_skill);

src/uu/skill/src/skill.rs

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
// This file is part of the uutils procps package.
2+
//
3+
// For the full copyright and license information, please view the LICENSE
4+
// file that was distributed with this source code.
5+
6+
use clap::{arg, crate_version, value_parser, Arg, Command};
7+
#[cfg(unix)]
8+
use nix::{sys::signal, sys::signal::Signal, unistd::Pid};
9+
use uu_snice::{
10+
collect_pids, construct_verbose_result, print_signals, process_matcher, ActionResult,
11+
};
12+
use uucore::error::USimpleError;
13+
#[cfg(unix)]
14+
use uucore::signals::signal_by_name_or_value;
15+
use uucore::{error::UResult, format_usage, help_about, help_usage};
16+
17+
const ABOUT: &str = help_about!("skill.md");
18+
const USAGE: &str = help_usage!("skill.md");
19+
20+
#[uucore::main]
21+
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
22+
let matches = uu_app().try_get_matches_from(args)?;
23+
let settings = process_matcher::Settings::try_new(&matches)?;
24+
25+
// Case0: Print SIGNALS
26+
if let Some(display) = &settings.display {
27+
print_signals(display);
28+
return Ok(());
29+
}
30+
31+
// Case1: Send signal
32+
if let Some(targets) = settings.expressions {
33+
let pids = collect_pids(&targets);
34+
35+
#[cfg(unix)]
36+
let signal_str = matches.get_one::<String>("signal").cloned();
37+
38+
#[cfg(unix)]
39+
let signal = if let Some(sig) = signal_str {
40+
(signal_by_name_or_value(sig.strip_prefix('-').unwrap()).unwrap() as i32).try_into()?
41+
} else {
42+
Signal::SIGTERM
43+
};
44+
45+
#[cfg(unix)]
46+
let results = perform_action(&pids, &signal);
47+
#[cfg(not(unix))]
48+
let results: Vec<Option<ActionResult>> = Vec::new();
49+
50+
if results.iter().all(|it| it.is_none()) || results.is_empty() {
51+
return Err(USimpleError::new(1, "no process selection criteria"));
52+
}
53+
54+
if settings.verbose {
55+
let output = construct_verbose_result(&pids, &results).trim().to_owned();
56+
println!("{output}");
57+
}
58+
}
59+
60+
Ok(())
61+
}
62+
63+
#[cfg(unix)]
64+
fn perform_action(pids: &[u32], signal: &Signal) -> Vec<Option<ActionResult>> {
65+
pids.iter()
66+
.map(|pid| {
67+
{
68+
Some(match signal::kill(Pid::from_raw(*pid as i32), *signal) {
69+
Ok(_) => ActionResult::Success,
70+
71+
Err(_) => ActionResult::PermissionDenied,
72+
})
73+
}
74+
})
75+
.collect()
76+
}
77+
78+
#[allow(clippy::cognitive_complexity)]
79+
pub fn uu_app() -> Command {
80+
Command::new(uucore::util_name())
81+
.version(crate_version!())
82+
.about(ABOUT)
83+
.override_usage(format_usage(USAGE))
84+
.infer_long_args(true)
85+
.arg_required_else_help(true)
86+
.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+
])
106+
}

src/uu/snice/src/action.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ pub(crate) fn users() -> &'static Users {
2424
}
2525

2626
#[derive(Debug)]
27-
pub(crate) enum SelectedTarget {
27+
pub enum SelectedTarget {
2828
Command(String),
2929
Pid(u32),
3030
Tty(Teletype),
@@ -93,7 +93,7 @@ impl SelectedTarget {
9393

9494
#[allow(unused)]
9595
#[derive(Debug, Clone)]
96-
pub(crate) enum ActionResult {
96+
pub enum ActionResult {
9797
PermissionDenied,
9898
Success,
9999
}

src/uu/snice/src/priority.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ pub enum Error {
1313
}
1414

1515
#[derive(Debug, PartialEq, Eq)]
16-
pub(crate) enum Priority {
16+
pub enum Priority {
1717
// The default priority is +4. (snice +4 ...)
1818
Increase(u32),
1919
Decrease(u32),
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
// This file is part of the uutils procps package.
2+
//
3+
// For the full copyright and license information, please view the LICENSE
4+
// file that was distributed with this source code.
5+
6+
use crate::action::SelectedTarget;
7+
use crate::SignalDisplay;
8+
use clap::{arg, value_parser, Arg, ArgMatches};
9+
use uu_pgrep::process::Teletype;
10+
use uucore::error::UResult;
11+
12+
#[derive(Debug)]
13+
pub struct Settings {
14+
pub display: Option<SignalDisplay>,
15+
pub expressions: Option<Vec<SelectedTarget>>,
16+
pub verbose: bool,
17+
}
18+
19+
impl Settings {
20+
pub fn try_new(matches: &ArgMatches) -> UResult<Self> {
21+
let display = if matches.get_flag("table") {
22+
Some(SignalDisplay::Table)
23+
} else if matches.get_flag("list") {
24+
Some(SignalDisplay::List)
25+
} else {
26+
None
27+
};
28+
29+
Ok(Self {
30+
display,
31+
expressions: Self::targets(matches),
32+
verbose: matches.get_flag("verbose"),
33+
})
34+
}
35+
36+
fn targets(matches: &ArgMatches) -> Option<Vec<SelectedTarget>> {
37+
let cmd = matches
38+
.get_many::<String>("command")
39+
.unwrap_or_default()
40+
.map(Into::into)
41+
.map(SelectedTarget::Command)
42+
.collect::<Vec<_>>();
43+
44+
let pid = matches
45+
.get_many::<u32>("pid")
46+
.unwrap_or_default()
47+
.map(Clone::clone)
48+
.map(SelectedTarget::Pid)
49+
.collect::<Vec<_>>();
50+
51+
let tty = matches
52+
.get_many::<String>("tty")
53+
.unwrap_or_default()
54+
.flat_map(|it| Teletype::try_from(it.as_str()))
55+
.map(SelectedTarget::Tty)
56+
.collect::<Vec<_>>();
57+
58+
let user = matches
59+
.get_many::<String>("user")
60+
.unwrap_or_default()
61+
.map(Into::into)
62+
.map(SelectedTarget::User)
63+
.collect::<Vec<_>>();
64+
65+
let collected = cmd
66+
.into_iter()
67+
.chain(pid)
68+
.chain(tty)
69+
.chain(user)
70+
.collect::<Vec<_>>();
71+
72+
if collected.is_empty() {
73+
None
74+
} else {
75+
Some(collected)
76+
}
77+
}
78+
}
79+
80+
#[allow(clippy::cognitive_complexity)]
81+
pub fn clap_args() -> Vec<Arg> {
82+
vec![
83+
// arg!(-f --fast "fast mode (not implemented)"),
84+
// arg!(-i --interactive "interactive"),
85+
arg!(-l --list "list all signal names"),
86+
arg!(-L --table "list all signal names in a nice table"),
87+
arg!(-n --"no-action" "do not actually kill processes; just print what would happen"),
88+
arg!(-v --verbose "explain what is being done"),
89+
// arg!(-w --warnings "enable warnings (not implemented)"),
90+
// Expressions
91+
arg!(-c --command <command> ... "expression is a command name"),
92+
arg!(-p --pid <pid> ... "expression is a process id number")
93+
.value_parser(value_parser!(u32)),
94+
arg!(-t --tty <tty> ... "expression is a terminal"),
95+
arg!(-u --user <username> ... "expression is a username"),
96+
// arg!(--ns <PID> "match the processes that belong to the same namespace as <pid>"),
97+
// arg!(--nslist <ns> "list which namespaces will be considered for the --ns option.")
98+
// .value_delimiter(',')
99+
// .value_parser(["ipc", "mnt", "net", "pid", "user", "uts"]),
100+
]
101+
}

0 commit comments

Comments
 (0)