Skip to content
Open
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
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -298,7 +298,7 @@ feat_os_unix_musl = [
"feat_require_unix_utmpx",
]
# "feat_os_windows" == set of utilities which can be built/run on modern windows platforms
feat_os_windows = ["feat_Tier1", "stdbuf", "timeout"]
feat_os_windows = ["feat_Tier1", "kill", "stdbuf", "timeout"]
## (secondary platforms) feature sets
# "feat_os_unix_gnueabihf" == set of utilities which can be built/run on the "arm-unknown-linux-gnueabihf" target (ARMv6 Linux [hardfloat])
feat_os_unix_gnueabihf = [
Expand Down
2 changes: 1 addition & 1 deletion src/uu/kill/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ doctest = false
[dependencies]
clap = { workspace = true }
thiserror = { workspace = true }
uucore = { workspace = true, features = ["signals"] }
uucore = { workspace = true, features = ["process", "signals"] }
fluent = { workspace = true }

[target.'cfg(unix)'.dependencies]
Expand Down
8 changes: 8 additions & 0 deletions src/uu/kill/locales/en-US.ftl
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
kill-about = Send signal to processes or list information about signals.
kill-usage = kill [OPTIONS]... PID...
kill-after-help-windows = Windows notes:
Signalled processes are force-terminated (Windows has no signal delivery);
their exit status is 128 plus the signal number. Process groups (PID <= 0)
and STOP are not supported. Permissions come from your current token: kill
never enables SeDebugPrivilege, so an elevated kill may report 'Permission
Comment thread
nikolalukovic marked this conversation as resolved.
denied' where 'taskkill /F' succeeds.

# Help messages
kill-help-list = Lists signals
Expand All @@ -13,3 +19,5 @@ kill-error-invalid-signal = { $signal }: invalid signal
kill-error-parse-argument = failed to parse argument { $argument }: { $error }
kill-error-sending-signal = sending signal to { $pid } failed
kill-error-write = write error: { $error }
kill-error-unsupported-signal = unsupported signal on Windows
kill-error-process-groups-unsupported = process groups are not supported on Windows
9 changes: 9 additions & 0 deletions src/uu/kill/locales/fr-FR.ftl
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
kill-about = Envoyer un signal aux processus ou lister les informations sur les signaux.
kill-usage = kill [OPTIONS]... PID...
kill-after-help-windows = Notes pour Windows :
Les processus signalés sont terminés de force (Windows ne délivre pas de
signaux) ; leur code de sortie est 128 plus le numéro du signal. Les groupes
de processus (PID <= 0) et STOP ne sont pas pris en charge. Les permissions
proviennent de votre jeton actuel : kill n'active jamais SeDebugPrivilege,
donc un kill élevé peut signaler « Permission denied » là où « taskkill /F »
réussit.

# Messages d'aide
kill-help-list = Liste les signaux
Expand All @@ -13,3 +20,5 @@ kill-error-invalid-signal = { $signal } : signal invalide
kill-error-parse-argument = échec de l'analyse de l'argument { $argument } : { $error }
kill-error-sending-signal = échec de l'envoi du signal au processus { $pid }
kill-error-write = erreur d'écriture : { $error }
kill-error-unsupported-signal = signal non pris en charge sur Windows
kill-error-process-groups-unsupported = les groupes de processus ne sont pas pris en charge sur Windows
64 changes: 8 additions & 56 deletions src/uu/kill/src/kill.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,6 @@
// spell-checker:ignore (ToDO) signalname pids killpg NOPESIG

use clap::{Arg, ArgAction, Command};
use rustix::process::{
Pid, Signal, kill_current_process_group, kill_process, kill_process_group,
test_kill_current_process_group, test_kill_process, test_kill_process_group,
};
use std::cmp::Ordering;
use std::io::{self, BufWriter, Write};
use thiserror::Error;
use uucore::display::Quotable;
Expand All @@ -23,6 +18,8 @@ use uucore::signals::{
};
use uucore::{format_usage, show};

mod platform;

// When the -l option is selected, the program displays the type of signal related to a certain
// value or string. In case of a value, the program should control the lower 8 bits, but there is
// a particular case in which if the value is in range [128, 159], it is translated to a signal
Expand Down Expand Up @@ -95,7 +92,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
}

pub fn uu_app() -> Command {
Command::new("kill")
let cmd = Command::new("kill")
.version(uucore::crate_version!())
.help_template(uucore::localized_help_template("kill"))
.about(translate!("kill-about"))
Expand Down Expand Up @@ -131,7 +128,10 @@ pub fn uu_app() -> Command {
Arg::new(options::PIDS_OR_SIGNALS)
.hide(true)
.action(ArgAction::Append),
)
);
#[cfg(windows)]
let cmd = cmd.after_help(translate!("kill-after-help-windows"));
cmd
}

fn handle_obsolete(args: &mut Vec<String>) -> UResult<Option<usize>> {
Expand Down Expand Up @@ -244,18 +244,6 @@ fn list(signals: &Vec<String>) -> UResult<()> {
Ok(())
}

// rustix's `Signal` rejects libc-reserved realtime signals, so fall back to a
// raw `libc::kill` for any value its safe constructor doesn't recognize.
fn raw_kill(pid: i32, sig: usize) -> io::Result<()> {
let sig = i32::try_from(sig).map_err(|_| io::Error::from_raw_os_error(libc::EINVAL))?;
// SAFETY: plain FFI call; `kill` has no memory-safety preconditions.
if unsafe { libc::kill(pid as libc::pid_t, sig) } == 0 {
Ok(())
} else {
Err(io::Error::last_os_error())
}
}

fn parse_signal_value(signal_name: &str) -> UResult<usize> {
let optional_signal_value = signal_by_name_or_value(signal_name);
match optional_signal_value {
Expand All @@ -281,44 +269,8 @@ fn parse_pids(pids: &[String]) -> UResult<Vec<i32>> {
}

fn kill(sig: usize, pids: &[i32]) {
// Standard named signals use rustix's typed API; anything its safe
// constructor doesn't recognize (realtime/reserved) falls back to libc.
let named = (sig != 0)
.then(|| i32::try_from(sig).ok().and_then(Signal::from_named_raw))
.flatten();
for &pid in pids {
let result = match pid.cmp(&0) {
Ordering::Equal => match named {
_ if sig == 0 => test_kill_current_process_group().map_err(io::Error::from),
Some(s) => kill_current_process_group(s).map_err(io::Error::from),
None => raw_kill(0, sig),
},
Ordering::Greater => {
let pid = Pid::from_raw(pid).expect("pid > 0 guaranteed by Ordering::Greater");
match named {
_ if sig == 0 => test_kill_process(pid).map_err(io::Error::from),
Some(s) => kill_process(pid, s).map_err(io::Error::from),
None => raw_kill(pid.as_raw_nonzero().get(), sig),
}
}
Ordering::Less => {
let Some(abs_pid) = pid.checked_neg() else {
show!(USimpleError::new(
1,
translate!("kill-error-sending-signal", "pid" => pid),
));
continue;
};
let pid =
Pid::from_raw(abs_pid).expect("abs_pid > 0 since pid < 0 and pid != i32::MIN");
match named {
_ if sig == 0 => test_kill_process_group(pid).map_err(io::Error::from),
Some(s) => kill_process_group(pid, s).map_err(io::Error::from),
None => raw_kill(-pid.as_raw_nonzero().get(), sig),
}
}
};
if let Err(e) = result {
if let Err(e) = platform::send_signal(pid, sig) {
show!(e.map_err_context(|| translate!("kill-error-sending-signal", "pid" => pid)));
}
}
Expand Down
18 changes: 18 additions & 0 deletions src/uu/kill/src/platform/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// This file is part of the uutils coreutils package.
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.

//! Platform-specific piece of `kill`: delivering one signal to one pid via
//! the [`send_signal`] facade, provided by both submodules with an identical
//! signature. The shared control flow stays in `kill.rs`.

#[cfg(unix)]
mod unix;
#[cfg(unix)]
pub(crate) use unix::*;

#[cfg(windows)]
mod windows;
#[cfg(windows)]
pub(crate) use windows::*;
64 changes: 64 additions & 0 deletions src/uu/kill/src/platform/unix.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// This file is part of the uutils coreutils package.
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.

// spell-checker:ignore pids ESRCH

use std::cmp::Ordering;
use std::io;

use rustix::process::{
Pid, Signal, kill_current_process_group, kill_process, kill_process_group,
test_kill_current_process_group, test_kill_process, test_kill_process_group,
};

// rustix's `Signal` rejects libc-reserved realtime signals, so fall back to a
// raw `libc::kill` for any value its safe constructor doesn't recognize.
fn raw_kill(pid: i32, sig: usize) -> io::Result<()> {
let sig = i32::try_from(sig).map_err(|_| io::Error::from_raw_os_error(libc::EINVAL))?;
// SAFETY: plain FFI call; `kill` has no memory-safety preconditions.
if unsafe { libc::kill(pid as libc::pid_t, sig) } == 0 {
Ok(())
} else {
Err(io::Error::last_os_error())
}
}

/// Deliver `sig` to `pid` with kill(2) semantics: positive pids target one
/// process, 0 the current process group, negative pids the group `-pid`.
pub(crate) fn send_signal(pid: i32, sig: usize) -> io::Result<()> {
// Standard named signals use rustix's typed API; anything its safe
// constructor doesn't recognize (realtime/reserved) falls back to libc.
let named = (sig != 0)
.then(|| i32::try_from(sig).ok().and_then(Signal::from_named_raw))
.flatten();
match pid.cmp(&0) {
Ordering::Equal => match named {
_ if sig == 0 => test_kill_current_process_group().map_err(io::Error::from),
Some(s) => kill_current_process_group(s).map_err(io::Error::from),
None => raw_kill(0, sig),
},
Ordering::Greater => {
let pid = Pid::from_raw(pid).expect("pid > 0 guaranteed by Ordering::Greater");
match named {
_ if sig == 0 => test_kill_process(pid).map_err(io::Error::from),
Some(s) => kill_process(pid, s).map_err(io::Error::from),
None => raw_kill(pid.as_raw_nonzero().get(), sig),
}
}
Ordering::Less => {
// i32::MIN cannot be negated, so no such process group can exist.
let Some(abs_pid) = pid.checked_neg() else {
return Err(io::Error::from_raw_os_error(libc::ESRCH));
};
let pid =
Pid::from_raw(abs_pid).expect("abs_pid > 0 since pid < 0 and pid != i32::MIN");
match named {
_ if sig == 0 => test_kill_process_group(pid).map_err(io::Error::from),
Some(s) => kill_process_group(pid, s).map_err(io::Error::from),
None => raw_kill(-pid.as_raw_nonzero().get(), sig),
}
}
}
}
31 changes: 31 additions & 0 deletions src/uu/kill/src/platform/windows.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// This file is part of the uutils coreutils package.
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.

//! Windows implementation of `kill`'s platform facade, built on the signal
//! emulation in [`uucore::process`]. STOP (no process-suspend API) and
//! process groups (`pid <= 0`) have no Windows emulation and are rejected.

use std::io;

use uucore::process::send_signal_to_pid;
use uucore::translate;

const SIGNAL_STOP: usize = 19;

fn unsupported(message: String) -> io::Error {
io::Error::new(io::ErrorKind::Unsupported, message)
}

pub(crate) fn send_signal(pid: i32, sig: usize) -> io::Result<()> {
if sig == SIGNAL_STOP {
return Err(unsupported(translate!("kill-error-unsupported-signal")));
}
match u32::try_from(pid) {
Ok(pid) if pid != 0 => send_signal_to_pid(pid, sig),
_ => Err(unsupported(translate!(
"kill-error-process-groups-unsupported"
))),
Comment on lines +27 to +29

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not use TerminateJobObject here?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how do we get the handle?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's no direct public Win32 or NT API for that. So, we got to get creative, and creative is unfortunately a bit involved. If you're using AI to code this, I assume it won't trouble you too much. Otherwise, let's consider this a TODO. The idea would be to iterate through all processes using NT APIs and filter down to the current process and get its Job ID. Then get all handles with SystemExtendedHandleInformation and get the matching Job handle and kill it. Something like that - it's probably quite a bit of code. (More than I would've assumed intuitively. 🙁)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lets give it a shot

}
}
1 change: 1 addition & 0 deletions src/uucore/locales/en-US.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ help-flag-version = Print version information
error-io = I/O error
error-permission-denied = Permission denied
error-file-not-found = No such file or directory
error-no-such-process = No such process
error-invalid-argument = Invalid argument
error-is-a-directory = { $file }: Is a directory

Expand Down
1 change: 1 addition & 0 deletions src/uucore/locales/fr-FR.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ help-flag-version = Afficher les informations de version
error-io = Erreur E/S
error-permission-denied = Permission refusée
error-file-not-found = Aucun fichier ou répertoire de ce type
error-no-such-process = Aucun processus de ce type
error-invalid-argument = Argument invalide
error-is-a-directory = { $file }: Est un répertoire
Expand Down
Loading
Loading