Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ feat_common_core = [
"pmap",
"ps",
"pwdx",
"skill",
"slabtop",
"snice",
"sysctl",
Expand Down Expand Up @@ -98,6 +99,7 @@ pkill = { optional = true, version = "0.0.1", package = "uu_pkill", path = "src/
pmap = { optional = true, version = "0.0.1", package = "uu_pmap", path = "src/uu/pmap" }
ps = { optional = true, version = "0.0.1", package = "uu_ps", path = "src/uu/ps" }
pwdx = { optional = true, version = "0.0.1", package = "uu_pwdx", path = "src/uu/pwdx" }
skill = { optional = true, version = "0.0.1", package = "uu_skill", path = "src/uu/skill" }
slabtop = { optional = true, version = "0.0.1", package = "uu_slabtop", path = "src/uu/slabtop" }
snice = { optional = true, version = "0.0.1", package = "uu_snice", path = "src/uu/snice" }
sysctl = { optional = true, version = "0.0.1", package = "uu_sysctl", path = "src/uu/sysctl" }
Expand Down
26 changes: 26 additions & 0 deletions src/uu/skill/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
[package]
name = "uu_skill"
description = "skill - (uutils) send a signal or report process status"
repository = "https://github.com/uutils/procps/tree/main/src/uu/skill"
authors.workspace = true
categories.workspace = true
edition.workspace = true
homepage.workspace = true
keywords.workspace = true
license.workspace = true
version.workspace = true

[dependencies]
uucore = { workspace = true, features = ["signals"] }
clap = { workspace = true }
nix = { workspace = true }

uu_snice = { path = "../snice" }


[lib]
path = "src/skill.rs"

[[bin]]
name = "skill"
path = "src/main.rs"
7 changes: 7 additions & 0 deletions src/uu/skill/skill.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# skill

```
skill [signal] [options] <expression>
```

Report processes matching an expression and send a signal to them.
1 change: 1 addition & 0 deletions src/uu/skill/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
uucore::bin!(uu_skill);
106 changes: 106 additions & 0 deletions src/uu/skill/src/skill.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
// 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::{arg, crate_version, value_parser, Arg, Command};
#[cfg(unix)]
use nix::{sys::signal, sys::signal::Signal, unistd::Pid};
use uu_snice::{
collect_pids, construct_verbose_result, print_signals, process_matcher, ActionResult,
};
use uucore::error::USimpleError;
#[cfg(unix)]
use uucore::signals::signal_by_name_or_value;
use uucore::{error::UResult, format_usage, help_about, help_usage};

const ABOUT: &str = help_about!("skill.md");
const USAGE: &str = help_usage!("skill.md");

#[uucore::main]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let matches = uu_app().try_get_matches_from(args)?;
let settings = process_matcher::Settings::try_new(&matches)?;

// Case0: Print SIGNALS
if let Some(display) = &settings.display {
print_signals(display);
return Ok(());
}

// Case1: Send signal
if let Some(targets) = settings.expressions {
let pids = collect_pids(&targets);

#[cfg(unix)]
let signal_str = matches.get_one::<String>("signal").cloned();

#[cfg(unix)]
let signal = if let Some(sig) = signal_str {
(signal_by_name_or_value(sig.strip_prefix('-').unwrap()).unwrap() as i32).try_into()?
} else {
Signal::SIGTERM
};

#[cfg(unix)]
let results = perform_action(&pids, &signal);
#[cfg(not(unix))]
let results: Vec<Option<ActionResult>> = Vec::new();

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

if settings.verbose {
let output = construct_verbose_result(&pids, &results).trim().to_owned();
println!("{output}");
}
}

Ok(())
}

#[cfg(unix)]
fn perform_action(pids: &[u32], signal: &Signal) -> Vec<Option<ActionResult>> {
pids.iter()
.map(|pid| {
{
Some(match signal::kill(Pid::from_raw(*pid as i32), *signal) {
Ok(_) => ActionResult::Success,

Err(_) => ActionResult::PermissionDenied,
})
}
})
.collect()
}

#[allow(clippy::cognitive_complexity)]
pub fn uu_app() -> Command {
Command::new(uucore::util_name())
.version(crate_version!())
.about(ABOUT)
.override_usage(format_usage(USAGE))
.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 <command> ... "expression is a command name"),
arg!(-p --pid <pid> ... "expression is a process id number")
.value_parser(value_parser!(u32)),
arg!(-t --tty <tty> ... "expression is a terminal"),
arg!(-u --user <username> ... "expression is a username"),
// arg!(--ns <PID> "match the processes that belong to the same namespace as <pid>"),
// arg!(--nslist <ns> "list which namespaces will be considered for the --ns option.")
// .value_delimiter(',')
// .value_parser(["ipc", "mnt", "net", "pid", "user", "uts"]),
])
}
4 changes: 2 additions & 2 deletions src/uu/snice/src/action.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ pub(crate) fn users() -> &'static Users {
}

#[derive(Debug)]
pub(crate) enum SelectedTarget {
pub enum SelectedTarget {
Command(String),
Pid(u32),
Tty(Teletype),
Expand Down Expand Up @@ -93,7 +93,7 @@ impl SelectedTarget {

#[allow(unused)]
#[derive(Debug, Clone)]
pub(crate) enum ActionResult {
pub enum ActionResult {
PermissionDenied,
Success,
}
Expand Down
2 changes: 1 addition & 1 deletion src/uu/snice/src/priority.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ pub enum Error {
}

#[derive(Debug, PartialEq, Eq)]
pub(crate) enum Priority {
pub enum Priority {
// The default priority is +4. (snice +4 ...)
Increase(u32),
Decrease(u32),
Expand Down
101 changes: 101 additions & 0 deletions src/uu/snice/src/process_matcher.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
// 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 crate::action::SelectedTarget;
use crate::SignalDisplay;
use clap::{arg, value_parser, Arg, ArgMatches};
use uu_pgrep::process::Teletype;
use uucore::error::UResult;

#[derive(Debug)]
pub struct Settings {
pub display: Option<SignalDisplay>,
pub expressions: Option<Vec<SelectedTarget>>,
pub verbose: bool,
}

impl Settings {
pub fn try_new(matches: &ArgMatches) -> UResult<Self> {
let display = if matches.get_flag("table") {
Some(SignalDisplay::Table)
} else if matches.get_flag("list") {
Some(SignalDisplay::List)
} else {
None
};

Ok(Self {
display,
expressions: Self::targets(matches),
verbose: matches.get_flag("verbose"),
})
}

fn targets(matches: &ArgMatches) -> Option<Vec<SelectedTarget>> {
let cmd = matches
.get_many::<String>("command")
.unwrap_or_default()
.map(Into::into)
.map(SelectedTarget::Command)
.collect::<Vec<_>>();

let pid = matches
.get_many::<u32>("pid")
.unwrap_or_default()
.map(Clone::clone)
.map(SelectedTarget::Pid)
.collect::<Vec<_>>();

let tty = matches
.get_many::<String>("tty")
.unwrap_or_default()
.flat_map(|it| Teletype::try_from(it.as_str()))
.map(SelectedTarget::Tty)
.collect::<Vec<_>>();

let user = matches
.get_many::<String>("user")
.unwrap_or_default()
.map(Into::into)
.map(SelectedTarget::User)
.collect::<Vec<_>>();

let collected = cmd
.into_iter()
.chain(pid)
.chain(tty)
.chain(user)
.collect::<Vec<_>>();

if collected.is_empty() {
None
} else {
Some(collected)
}
}
}

#[allow(clippy::cognitive_complexity)]
pub fn clap_args() -> Vec<Arg> {
vec![
// 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 <command> ... "expression is a command name"),
arg!(-p --pid <pid> ... "expression is a process id number")
.value_parser(value_parser!(u32)),
arg!(-t --tty <tty> ... "expression is a terminal"),
arg!(-u --user <username> ... "expression is a username"),
// arg!(--ns <PID> "match the processes that belong to the same namespace as <pid>"),
// arg!(--nslist <ns> "list which namespaces will be considered for the --ns option.")
// .value_delimiter(',')
// .value_parser(["ipc", "mnt", "net", "pid", "user", "uts"]),
]
}
Loading
Loading