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
98 changes: 49 additions & 49 deletions src/uu/env/src/env.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.

// spell-checker:ignore (ToDO) chdir progname subcommand subcommands unsets setenv putenv spawnp SIGSEGV SIGBUS sigaction Sigmask sigprocmask elidable
// spell-checker:ignore (ToDO) chdir progname subcommand subcommands unsets setenv putenv spawnp SIGSEGV SIGBUS sigaction Sigmask sigprocmask elidable sigset sigemptyset sigaddset sighandler

pub mod native_int_str;
pub mod split_iterator;
Expand All @@ -20,11 +20,6 @@ use native_int_str::{
#[cfg(unix)]
use nix::libc;
#[cfg(unix)]
use nix::sys::signal::{
SigHandler::{SigDfl, SigIgn},
SigSet, SigmaskHow, Signal, signal, sigprocmask,
};
#[cfg(unix)]
use nix::unistd::execvp;
use std::borrow::Cow;
#[cfg(unix)]
Expand Down Expand Up @@ -294,17 +289,21 @@ fn build_signal_request(
Ok(request)
}

/// Validate a signal number and return it as a raw `c_int`.
///
/// Returns `None` for numbers that are not usable signals (e.g. the
/// glibc-reserved 32/33 on Linux). Real-time signals (`SIGRTMIN..=SIGRTMAX`)
/// are accepted directly since nix's `Signal` enum cannot represent them.
#[cfg(unix)]
fn signal_from_value(sig_value: usize) -> UResult<Signal> {
Signal::try_from(sig_value as i32).map_err(|_| {
USimpleError::new(
125,
translate!(
"env-error-invalid-signal",
"signal" => sig_value.to_string().quote()
),
)
})
fn signal_from_value(sig_value: usize) -> Option<i32> {
let sig = i32::try_from(sig_value).ok()?;
#[cfg(any(target_os = "linux", target_os = "android"))]
if sig >= libc::SIGRTMIN() && sig <= libc::SIGRTMAX() {
return Some(sig);
}
// For standard signals, let nix reject platform gaps (matches GNU, which
// silently skips undefined signals).
nix::sys::signal::Signal::try_from(sig).ok().map(|_| sig)
}

fn load_config_file(opts: &mut Options) -> UResult<()> {
Expand Down Expand Up @@ -731,13 +730,13 @@ impl EnvAppData {
&opts.default_signal,
&mut signal_action_log,
SignalActionKind::Default,
reset_signal,
|sig| set_signal_disposition(sig, libc::SIG_DFL),
)?;
apply_signal_action(
&opts.ignore_signal,
&mut signal_action_log,
SignalActionKind::Ignore,
ignore_signal,
|sig| set_signal_disposition(sig, libc::SIG_IGN),
)?;
apply_signal_action(
&opts.block_signal,
Expand Down Expand Up @@ -1072,12 +1071,12 @@ fn apply_signal_action<F>(
signal_fn: F,
) -> UResult<()>
where
F: Fn(Signal) -> UResult<()>,
F: Fn(i32) -> UResult<()>,
{
request.for_each_signal(|sig_value, explicit| {
// On some platforms ALL_SIGNALS may contain values that are not valid in libc.
// Skip those invalid ones and continue (GNU env also ignores undefined signals).
let Ok(sig) = signal_from_value(sig_value) else {
let Some(sig) = signal_from_value(sig_value) else {
return Ok(());
};
signal_fn(sig)?;
Expand All @@ -1095,46 +1094,47 @@ where
})
}

/// Set `sig`'s disposition to `SIG_IGN`/`SIG_DFL`.
///
/// We use `libc::sigaction` directly rather than nix so that real-time signals
/// (whose numbers are not in nix's `Signal` enum) are handled too. rustix
/// deliberately does not wrap sigaction (see its not_implemented::libc_internals),
/// leaving signal disposition to libc, so libc is the intended tool here.
#[cfg(unix)]
fn ignore_signal(sig: Signal) -> UResult<()> {
// SAFETY: This is safe because we write the handler for each signal only once, and therefore "the current handler is the default", as the documentation requires it.
let result = unsafe { signal(sig, SigIgn) };
if let Err(err) = result {
return Err(USimpleError::new(
125,
translate!("env-error-failed-set-signal-action", "signal" => (sig as i32), "error" => err.desc()),
));
fn set_signal_disposition(sig: i32, handler: libc::sighandler_t) -> UResult<()> {
// SAFETY: a zeroed sigaction with an empty mask and SIG_IGN/SIG_DFL is a valid
// disposition; we only set the handler for each signal once.
let mut action: libc::sigaction = unsafe { std::mem::zeroed() };
action.sa_sigaction = handler;
unsafe { libc::sigemptyset(&raw mut action.sa_mask) };
if unsafe { libc::sigaction(sig, &raw const action, std::ptr::null_mut()) } == -1 {
return Err(signal_action_error(sig));
}
Ok(())
}

#[cfg(unix)]
fn reset_signal(sig: Signal) -> UResult<()> {
let result = unsafe { signal(sig, SigDfl) };
if let Err(err) = result {
return Err(USimpleError::new(
125,
translate!("env-error-failed-set-signal-action", "signal" => (sig as i32), "error" => err.desc()),
));
fn block_signal(sig: i32) -> UResult<()> {
// SAFETY: build a set containing only `sig` and add it to the process mask.
let mut set: libc::sigset_t = unsafe { std::mem::zeroed() };
unsafe { libc::sigemptyset(&raw mut set) };
unsafe { libc::sigaddset(&raw mut set, sig) };
if unsafe { libc::sigprocmask(libc::SIG_BLOCK, &raw const set, std::ptr::null_mut()) } == -1 {
return Err(signal_action_error(sig));
}
Ok(())
}

#[cfg(unix)]
fn block_signal(sig: Signal) -> UResult<()> {
let mut set = SigSet::empty();
set.add(sig);
if let Err(err) = sigprocmask(SigmaskHow::SIG_BLOCK, Some(&set), None) {
return Err(USimpleError::new(
125,
translate!(
"env-error-failed-set-signal-action",
"signal" => (sig as i32),
"error" => err.desc()
),
));
}
Ok(())
fn signal_action_error(sig: i32) -> Box<dyn UError> {
USimpleError::new(
125,
translate!(
"env-error-failed-set-signal-action",
"signal" => sig,
"error" => io::Error::last_os_error().to_string()
),
)
}

#[cfg(unix)]
Expand Down
10 changes: 8 additions & 2 deletions src/uucore/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,13 @@ jiff = { workspace = true, optional = true, features = [
"tzdb-concatenated",
] }
rustc-hash = { workspace = true }
rustix = { workspace = true, features = ["fs", "net", "pipe", "process"] }
rustix = { workspace = true, features = [
"event",
"fs",
"net",
"pipe",
"process",
] }
time = { workspace = true, optional = true, features = [
"formatting",
"local-offset",
Expand Down Expand Up @@ -183,7 +189,7 @@ safe-copy = []
safe-traversal = ["libc"]
selinux = ["dep:selinux"]
smack = ["xattr"]
signals = []
signals = ["libc"]
sum = [
"digest",
"hex",
Expand Down
105 changes: 64 additions & 41 deletions src/uucore/src/lib/features/process.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@

// spell-checker:ignore (vars) cvar exitstatus cmdline kworker getsid getpid
// spell-checker:ignore (sys/unix) WIFSIGNALED ESRCH
// spell-checker:ignore pgrep pwait snice getpgrp
// spell-checker:ignore pgrep pwait snice getpgrp SRCH

use libc::{gid_t, pid_t, uid_t};
#[cfg(not(target_os = "redox"))]
use nix::errno::Errno;
use nix::sys::signal::{self as nix_signal, SigHandler, Signal};
use nix::unistd::Pid;
use rustix::process::{
Pid, Signal, kill_current_process_group, kill_process, test_kill_current_process_group,
test_kill_process,
};
use std::io;
use std::process::Child;
use std::process::ExitStatus;
Expand All @@ -22,23 +22,22 @@ use std::time::{Duration, Instant};

/// `geteuid()` returns the effective user ID of the calling process.
pub fn geteuid() -> uid_t {
nix::unistd::geteuid().as_raw()
rustix::process::geteuid().as_raw()
}

/// `getpgrp()` returns the process group ID of the calling process.
/// It is a trivial wrapper over nix::unistd::getpgrp.
pub fn getpgrp() -> pid_t {
nix::unistd::getpgrp().as_raw()
rustix::process::getpgrp().as_raw_pid()
}

/// `getegid()` returns the effective group ID of the calling process.
pub fn getegid() -> gid_t {
nix::unistd::getegid().as_raw()
rustix::process::getegid().as_raw()
}

/// `getgid()` returns the real group ID of the calling process.
pub fn getgid() -> gid_t {
nix::unistd::getgid().as_raw()
rustix::process::getgid().as_raw()
}

/// `getuid()` returns the real user ID of the calling process.
Expand All @@ -48,7 +47,7 @@ pub fn getuid() -> uid_t {

/// `getpid()` returns the pid of the calling process.
pub fn getpid() -> pid_t {
nix::unistd::getpid().as_raw()
rustix::process::getpid().as_raw_pid()
}

/// `getsid()` returns the session ID of the process with process ID pid.
Expand All @@ -57,22 +56,21 @@ pub fn getpid() -> pid_t {
///
/// # Error
///
/// - [Errno::EPERM] A process with process ID pid exists, but it is not in the same session as the calling process, and the implementation considers this an error.
/// - [Errno::ESRCH] No process with process ID pid was found.
/// - `EPERM` A process with process ID pid exists, but it is not in the same session as the calling process, and the implementation considers this an error.
/// - `ESRCH` No process with process ID pid was found.
///
///
/// # Platform
///
/// This function only support standard POSIX implementation platform,
/// so some system such as redox doesn't supported.
#[cfg(not(target_os = "redox"))]
pub fn getsid(pid: i32) -> Result<pid_t, Errno> {
let pid = if pid == 0 {
None
} else {
Some(Pid::from_raw(pid))
pub fn getsid(pid: i32) -> Result<pid_t, rustix::io::Errno> {
let pid = match pid {
0 => None,
_ => Some(Pid::from_raw(pid).ok_or(rustix::io::Errno::SRCH)?),
};
nix::unistd::getsid(pid).map(Pid::as_raw)
rustix::process::getsid(pid).map(Pid::as_raw_pid)
}

/// Missing methods for Child objects
Expand All @@ -95,17 +93,34 @@ pub trait ChildExt {
) -> io::Result<Option<ExitStatus>>;
}

/// Build a rustix [`Signal`] from a raw number, including real-time signals
/// (`SIGRTMIN..=SIGRTMAX`). Those are not "named" signals, so
/// [`Signal::from_named_raw`] rejects them; build them from the raw value.
fn signal_from_value(signal: usize) -> io::Result<Signal> {
let raw = i32::try_from(signal).ok().filter(|&s| s > 0);
raw.and_then(|raw| {
if let Some(sig) = Signal::from_named_raw(raw) {
return Some(sig);
}
#[cfg(any(target_os = "linux", target_os = "android"))]
if (libc::SIGRTMIN()..=libc::SIGRTMAX()).contains(&raw) {
// SAFETY: `raw` is within the real-time signal range.
return Some(unsafe { Signal::from_raw_unchecked(raw) });
}
None
})
.ok_or_else(|| io::Error::from_raw_os_error(libc::EINVAL))
}

impl ChildExt for Child {
fn send_signal(&mut self, signal: usize) -> io::Result<()> {
let pid = Pid::from_raw(self.id() as pid_t);
let result = if signal == 0 {
nix_signal::kill(pid, None)
} else {
let signal = Signal::try_from(signal as i32)
.map_err(|_| io::Error::from_raw_os_error(libc::EINVAL))?;
nix_signal::kill(pid, Some(signal))
};
result.map_err(|e| io::Error::from_raw_os_error(e as i32))
let pid = Pid::from_raw(self.id() as pid_t)
.ok_or_else(|| io::Error::from_raw_os_error(libc::EINVAL))?;
// signal == 0 only probes whether the pid is still alive.
if signal == 0 {
return test_kill_process(pid).map_err(io::Error::from);
}
kill_process(pid, signal_from_value(signal)?).map_err(io::Error::from)
}

fn send_signal_group(&mut self, signal: usize) -> io::Result<()> {
Expand All @@ -115,23 +130,31 @@ impl ChildExt for Child {
// in the group. If the child has created its own process group (via setpgid),
// it won't receive this group signal, but will have received the direct signal.

// Signal 0 is special - it just checks if process exists, doesn't send anything.
// Signal 0 is special - it just checks if the group exists, doesn't send anything.
// No need to manipulate signal handlers for it.
if signal == 0 {
return nix_signal::kill(Pid::from_raw(0), None)
.map_err(|e| io::Error::from_raw_os_error(e as i32));
return test_kill_current_process_group().map_err(io::Error::from);
}

let signal = Signal::try_from(signal as i32)
.map_err(|_| io::Error::from_raw_os_error(libc::EINVAL))?;

// Ignore the signal temporarily so we don't receive it ourselves.
let old_handler = unsafe { nix_signal::signal(signal, SigHandler::SigIgn) }
.map_err(|e| io::Error::from_raw_os_error(e as i32))?;
let result = nix_signal::kill(Pid::from_raw(0), Some(signal));
// Restore the old handler
let _ = unsafe { nix_signal::signal(signal, old_handler) };
result.map_err(|e| io::Error::from_raw_os_error(e as i32))
let sig = signal_from_value(signal)?;
let sig_raw = sig.as_raw();

// Ignore the signal temporarily so we don't receive it ourselves. rustix
// deliberately does not wrap sigaction (see its not_implemented::libc_internals);
// its only equivalent is the experimental `runtime` module, which is UB in a
// process that links libc. Signal disposition is left to libc, so use it here.
// SAFETY: a zeroed sigaction with SIG_IGN is a valid disposition; we restore the
// previous one right after sending to our own process group.
let mut ignore: libc::sigaction = unsafe { std::mem::zeroed() };
ignore.sa_sigaction = libc::SIG_IGN;
let mut old: libc::sigaction = unsafe { std::mem::zeroed() };
if unsafe { libc::sigaction(sig_raw, &raw const ignore, &raw mut old) } == -1 {
return Err(io::Error::last_os_error());
}
let res = kill_current_process_group(sig);
// Restore the previous disposition.
unsafe { libc::sigaction(sig_raw, &raw const old, std::ptr::null_mut()) };
res.map_err(io::Error::from)
}

fn wait_or_timeout(
Expand Down
Loading
Loading