mirror of
https://github.com/uutils/procps.git
synced 2026-06-10 16:14:00 -07:00
Merge pull request #466 from Bluemangoo/feature/skill
skill: add basic implementation
This commit is contained in:
Generated
+11
@@ -1068,6 +1068,7 @@ dependencies = [
|
||||
"uu_pmap",
|
||||
"uu_ps",
|
||||
"uu_pwdx",
|
||||
"uu_skill",
|
||||
"uu_slabtop",
|
||||
"uu_snice",
|
||||
"uu_sysctl",
|
||||
@@ -1740,6 +1741,16 @@ dependencies = [
|
||||
"uucore",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "uu_skill"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"nix",
|
||||
"uu_snice",
|
||||
"uucore",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "uu_slabtop"
|
||||
version = "0.0.1"
|
||||
|
||||
@@ -40,6 +40,7 @@ feat_common_core = [
|
||||
"pmap",
|
||||
"ps",
|
||||
"pwdx",
|
||||
"skill",
|
||||
"slabtop",
|
||||
"snice",
|
||||
"sysctl",
|
||||
@@ -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" }
|
||||
|
||||
@@ -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"
|
||||
@@ -0,0 +1,7 @@
|
||||
# skill
|
||||
|
||||
```
|
||||
skill [signal] [options] <expression>
|
||||
```
|
||||
|
||||
Report processes matching an expression and send a signal to them.
|
||||
@@ -0,0 +1 @@
|
||||
uucore::bin!(uu_skill);
|
||||
@@ -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"]),
|
||||
])
|
||||
}
|
||||
@@ -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),
|
||||
@@ -93,7 +93,7 @@ impl SelectedTarget {
|
||||
|
||||
#[allow(unused)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) enum ActionResult {
|
||||
pub enum ActionResult {
|
||||
PermissionDenied,
|
||||
Success,
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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"]),
|
||||
]
|
||||
}
|
||||
+30
-118
@@ -5,12 +5,14 @@
|
||||
|
||||
use std::{collections::HashSet, path::PathBuf, str::FromStr};
|
||||
|
||||
use action::{perform_action, process_snapshot, users, ActionResult, SelectedTarget};
|
||||
use clap::{arg, crate_version, value_parser, Arg, ArgMatches, Command};
|
||||
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};
|
||||
use priority::Priority;
|
||||
use process_matcher::*;
|
||||
use sysinfo::Pid;
|
||||
use uu_pgrep::process::{ProcessInformation, Teletype};
|
||||
use uu_pgrep::process::ProcessInformation;
|
||||
#[cfg(target_family = "unix")]
|
||||
use uucore::signals::ALL_SIGNALS;
|
||||
use uucore::{
|
||||
@@ -23,9 +25,10 @@ const USAGE: &str = help_usage!("snice.md");
|
||||
|
||||
mod action;
|
||||
mod priority;
|
||||
pub mod process_matcher;
|
||||
|
||||
#[derive(Debug)]
|
||||
enum SignalDisplay {
|
||||
pub enum SignalDisplay {
|
||||
List,
|
||||
Table,
|
||||
}
|
||||
@@ -66,85 +69,13 @@ impl SignalDisplay {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Settings {
|
||||
display: Option<SignalDisplay>,
|
||||
expressions: Option<Vec<SelectedTarget>>,
|
||||
priority: Priority,
|
||||
verbose: bool,
|
||||
}
|
||||
#[allow(unused)] // unused argument under non-unix targets
|
||||
pub fn print_signals(display: &SignalDisplay) {
|
||||
#[cfg(target_family = "unix")]
|
||||
{
|
||||
let result = display.display(&ALL_SIGNALS);
|
||||
|
||||
impl Settings {
|
||||
fn try_new(matches: &ArgMatches) -> UResult<Self> {
|
||||
let priority = matches
|
||||
.try_get_one::<String>("priority")
|
||||
.unwrap_or(Some(&String::new()))
|
||||
.cloned();
|
||||
|
||||
let expression = match priority {
|
||||
Some(expr) => {
|
||||
Priority::try_from(expr).map_err(|err| USimpleError::new(1, err.to_string()))?
|
||||
}
|
||||
None => Priority::default(),
|
||||
};
|
||||
|
||||
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),
|
||||
priority: expression,
|
||||
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)
|
||||
}
|
||||
println!("{result}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -155,28 +86,25 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||
let settings = Settings::try_new(&matches)?;
|
||||
|
||||
// Case0: Print SIGNALS
|
||||
#[cfg(target_family = "unix")]
|
||||
{
|
||||
if let Some(display) = settings.display {
|
||||
let result = display.display(&ALL_SIGNALS);
|
||||
|
||||
println!("{result}");
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_family = "unix"))]
|
||||
{
|
||||
if let Some(_display) = settings.display {
|
||||
return Ok(());
|
||||
}
|
||||
if let Some(display) = &settings.display {
|
||||
print_signals(display);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Case1: Perform priority
|
||||
let take_action = !matches.get_flag("no-action");
|
||||
if let Some(targets) = settings.expressions {
|
||||
let priority_str = matches.get_one::<String>("priority").cloned();
|
||||
|
||||
let priority = match priority_str {
|
||||
Some(expr) => {
|
||||
Priority::try_from(expr).map_err(|err| USimpleError::new(1, err.to_string()))?
|
||||
}
|
||||
None => Priority::default(),
|
||||
};
|
||||
|
||||
let pids = collect_pids(&targets);
|
||||
let results = perform_action(&pids, &settings.priority, take_action);
|
||||
let results = perform_action(&pids, &priority, take_action);
|
||||
|
||||
if results.iter().all(|it| it.is_none()) || results.is_empty() {
|
||||
return Err(USimpleError::new(1, "no process selection criteria"));
|
||||
@@ -194,7 +122,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
fn construct_verbose_result(pids: &[u32], action_results: &[Option<ActionResult>]) -> String {
|
||||
pub fn construct_verbose_result(pids: &[u32], action_results: &[Option<ActionResult>]) -> String {
|
||||
let mut table = action_results
|
||||
.iter()
|
||||
.enumerate()
|
||||
@@ -232,7 +160,7 @@ fn construct_verbose_result(pids: &[u32], action_results: &[Option<ActionResult>
|
||||
}
|
||||
|
||||
/// Map and sort `SelectedTarget` to pids.
|
||||
fn collect_pids(targets: &[SelectedTarget]) -> Vec<u32> {
|
||||
pub fn collect_pids(targets: &[SelectedTarget]) -> Vec<u32> {
|
||||
let collected = targets
|
||||
.iter()
|
||||
.flat_map(SelectedTarget::to_pids)
|
||||
@@ -243,7 +171,6 @@ fn collect_pids(targets: &[SelectedTarget]) -> Vec<u32> {
|
||||
collected
|
||||
}
|
||||
|
||||
#[allow(clippy::cognitive_complexity)]
|
||||
pub fn uu_app() -> Command {
|
||||
Command::new(uucore::util_name())
|
||||
.version(crate_version!())
|
||||
@@ -252,22 +179,7 @@ pub fn uu_app() -> Command {
|
||||
.infer_long_args(true)
|
||||
.arg_required_else_help(true)
|
||||
.arg(Arg::new("priority"))
|
||||
.args([
|
||||
// Options
|
||||
// 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"),
|
||||
])
|
||||
.args(clap_args())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
// 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 uutests::new_ucmd;
|
||||
use uutests::util::TestScenario;
|
||||
use uutests::util_name;
|
||||
|
||||
#[test]
|
||||
fn test_no_args() {
|
||||
new_ucmd!().fails().code_is(1);
|
||||
}
|
||||
@@ -63,6 +63,10 @@ mod test_top;
|
||||
#[path = "by-util/test_vmstat.rs"]
|
||||
mod test_vmstat;
|
||||
|
||||
#[cfg(feature = "skill")]
|
||||
#[path = "by-util/test_skill.rs"]
|
||||
mod test_skill;
|
||||
|
||||
#[cfg(feature = "snice")]
|
||||
#[path = "by-util/test_snice.rs"]
|
||||
mod test_snice;
|
||||
|
||||
Reference in New Issue
Block a user