diff --git a/Cargo.lock b/Cargo.lock index 0f364dfe..80b25a5d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1540,6 +1540,7 @@ dependencies = [ "uu_dmesg", "uu_fsfreeze", "uu_hexdump", + "uu_kill", "uu_last", "uu_lscpu", "uu_lsipc", @@ -1635,6 +1636,14 @@ dependencies = [ "uucore 0.2.2", ] +[[package]] +name = "uu_kill" +version = "0.0.1" +dependencies = [ + "clap", + "uucore 0.2.2", +] + [[package]] name = "uu_last" version = "0.0.1" diff --git a/Cargo.toml b/Cargo.toml index a35f33c5..11b45436 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,6 +33,7 @@ feat_common_core = [ "dmesg", "fsfreeze", "hexdump", + "kill", "last", "lscpu", "lsipc", @@ -105,6 +106,7 @@ chcpu = { optional = true, version = "0.0.1", package = "uu_chcpu", path = "src/ ctrlaltdel = { optional = true, version = "0.0.1", package = "uu_ctrlaltdel", path = "src/uu/ctrlaltdel" } dmesg = { optional = true, version = "0.0.1", package = "uu_dmesg", path = "src/uu/dmesg" } fsfreeze = { optional = true, version = "0.0.1", package = "uu_fsfreeze", path = "src/uu/fsfreeze" } +kill = { optional = true, version = "0.0.1", package = "uu_kill", path = "src/uu/kill" } hexdump = { optional = true, version = "0.0.1", package = "uu_hexdump", path = "src/uu/hexdump" } last = { optional = true, version = "0.0.1", package = "uu_last", path = "src/uu/last" } lscpu = { optional = true, version = "0.0.1", package = "uu_lscpu", path = "src/uu/lscpu" } diff --git a/src/uu/kill/Cargo.toml b/src/uu/kill/Cargo.toml new file mode 100644 index 00000000..2a927068 --- /dev/null +++ b/src/uu/kill/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "uu_kill" +version = "0.0.1" +edition = "2024" + +[lib] +path = "src/kill.rs" + +[[bin]] +name = "kill" +path = "src/main.rs" + +[dependencies] +uucore = { workspace = true, features = ["entries"] } +clap = { workspace = true } diff --git a/src/uu/kill/kill.md b/src/uu/kill/kill.md new file mode 100644 index 00000000..6ab558bd --- /dev/null +++ b/src/uu/kill/kill.md @@ -0,0 +1,7 @@ +# lsns + +``` +kill [OPTION]... +``` + +Send a signal to a job. diff --git a/src/uu/kill/src/errors.rs b/src/uu/kill/src/errors.rs new file mode 100644 index 00000000..faf4ae45 --- /dev/null +++ b/src/uu/kill/src/errors.rs @@ -0,0 +1,41 @@ +// This file is part of the uutils util-linux package. +// +// For the full copyright and license information, please view the LICENSE +// file that was distributed with this source code. + +use std::error::Error; + +use uucore::error::UError; + +#[derive(Debug)] +pub enum KillError { + #[cfg(not(target_os = "linux"))] + UnsupportedPlatform, + OperationNotPermitted(i32), + NoSuchProcess(i32), +} + +impl std::fmt::Display for KillError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + #[cfg(not(target_os = "linux"))] + Self::UnsupportedPlatform => write!(f, "kill is only supported on Linux for now"), + Self::OperationNotPermitted(pid) => { + write!(f, "bash: kill: ({pid}) - Operation not permitted") + } + Self::NoSuchProcess(pid) => write!(f, "bash: kill: ({pid}) - No such process"), + } + } +} + +impl UError for KillError { + fn code(&self) -> i32 { + 1 + } + + fn usage(&self) -> bool { + false + } +} + +impl Error for KillError {} diff --git a/src/uu/kill/src/kill.rs b/src/uu/kill/src/kill.rs new file mode 100644 index 00000000..4f469d32 --- /dev/null +++ b/src/uu/kill/src/kill.rs @@ -0,0 +1,69 @@ +// This file is part of the uutils util-linux package. +// +// For the full copyright and license information, please view the LICENSE +// file that was distributed with this source code. + +// Remove this if the tool is ported to Non-UNIX platforms. +#![cfg_attr(not(target_os = "linux"), allow(dead_code))] + +mod errors; + +use crate::errors::KillError; +#[cfg(target_os = "linux")] +use crate::errors::KillError::{NoSuchProcess, OperationNotPermitted}; +use clap::{Arg, ArgAction, Command, crate_version, value_parser}; +use uucore::libc; +use uucore::{error::UResult, format_usage, help_about, help_usage}; + +const ABOUT: &str = help_about!("kill.md"); +const USAGE: &str = help_usage!("kill.md"); + +#[cfg(not(target_os = "linux"))] +fn kill(_pid: i32, _signal: i32) -> Result<(), KillError> { + Err(KillError::UnsupportedPlatform) +} + +#[cfg(target_os = "linux")] +fn kill(pid: i32, signal: i32) -> Result<(), KillError> { + unsafe { libc::kill(pid, signal) }; + + let err = std::io::Error::last_os_error().raw_os_error(); + if let Some(err_no) = err { + match err_no { + libc::EPERM => return Err(OperationNotPermitted(pid)), + libc::ESRCH => return Err(NoSuchProcess(pid)), + _ => {} + } + } + + Ok(()) +} + +#[uucore::main] +pub fn uumain(args: impl uucore::Args) -> UResult<()> { + let matches = uu_app().try_get_matches_from(args)?; + + if let Some(pids) = matches.get_many::("pid") { + for pid in pids { + kill(*pid, libc::SIGTERM)?; + } + } + + Ok(()) +} + +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( + Arg::new("pid") + .help("PID of the process to kill") + .required(true) + .action(ArgAction::Append) + .value_name("PID") + .value_parser(value_parser!(i32)), + ) +} diff --git a/src/uu/kill/src/main.rs b/src/uu/kill/src/main.rs new file mode 100644 index 00000000..6f3fbac4 --- /dev/null +++ b/src/uu/kill/src/main.rs @@ -0,0 +1 @@ +uucore::bin!(uu_kill); diff --git a/tests/by-util/test_kill.rs b/tests/by-util/test_kill.rs new file mode 100644 index 00000000..a843aefc --- /dev/null +++ b/tests/by-util/test_kill.rs @@ -0,0 +1,37 @@ +// This file is part of the uutils util-linux package. +// +// For the full copyright and license information, please view the LICENSE +// file that was distributed with this source code. + +use uutests::new_ucmd; + +#[test] +fn test_invalid_arg() { + new_ucmd!().arg("--definitely-invalid").fails().code_is(1); +} + +#[test] +#[cfg(target_os = "linux")] +fn test_non_numerical_pid() { + let res = new_ucmd!().arg("xyz").run(); + + let stdout = res.stdout_str(); + let stderr = res.stderr_str(); + + assert!(stdout.trim().is_empty()); + assert!(stderr.contains("invalid value 'xyz'")); +} + +#[test] +#[cfg(target_os = "linux")] +fn test_pid_doesnt_exist() { + let non_existent_pid = "1234567890"; + let res = new_ucmd!().arg(non_existent_pid).run(); + + let stdout = res.stdout_str(); + let stderr = res.stderr_str(); + let error_msg = format!("bash: kill: ({non_existent_pid}) - No such process"); + + assert!(stdout.trim().is_empty()); + assert!(stderr.contains(error_msg.as_str())); +} diff --git a/tests/tests.rs b/tests/tests.rs index 09ffc245..8a53def2 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -15,6 +15,11 @@ fn init() { std::env::set_var("UUTESTS_BINARY_PATH", TESTS_BINARY); } } + +#[cfg(feature = "kill")] +#[path = "by-util/test_kill.rs"] +mod test_kill; + #[cfg(feature = "lscpu")] #[path = "by-util/test_lscpu.rs"] mod test_lscpu;