-
Notifications
You must be signed in to change notification settings - Fork 13.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support setting signal masks for child processes on Unix #100737
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,9 +9,11 @@ use crate::fmt; | |
use crate::io; | ||
use crate::path::Path; | ||
use crate::ptr; | ||
use crate::sync::OnceLock; | ||
use crate::sys::fd::FileDesc; | ||
use crate::sys::fs::File; | ||
use crate::sys::pipe::{self, AnonPipe}; | ||
use crate::sys::{cvt, cvt_nz}; | ||
use crate::sys_common::process::{CommandEnv, CommandEnvs}; | ||
use crate::sys_common::IntoInner; | ||
|
||
|
@@ -32,9 +34,9 @@ cfg_if::cfg_if! { | |
} | ||
} | ||
|
||
// Android with api less than 21 define sig* functions inline, so it is not | ||
// available for dynamic link. Implementing sigemptyset and sigaddset allow us | ||
// to support older Android version (independent of libc version). | ||
// Android with api less than 21 define sig* functions inline, so they are not | ||
// available for dynamic linking. Implementing these functions allows us | ||
// to support older Android versions (independent of libc version). | ||
// The following implementations are based on | ||
// https://github.com/aosp-mirror/platform_bionic/blob/ad8dcd6023294b646e5a8288c0ed431b0845da49/libc/include/android/legacy_signal_inlines.h | ||
cfg_if::cfg_if! { | ||
|
@@ -43,8 +45,58 @@ cfg_if::cfg_if! { | |
set.write_bytes(0u8, 1); | ||
return 0; | ||
} | ||
|
||
const LONG_BIT: usize = crate::mem::size_of::<libc::c_ulong>() * 8; | ||
|
||
#[allow(dead_code)] | ||
pub unsafe fn sigaddset(set: *mut libc::sigset_t, signum: libc::c_int) -> libc::c_int { | ||
let bit = (signum - 1) as usize; | ||
let raw = match to_bitmap_slice_mut(set, bit) { | ||
Ok(raw) => raw, | ||
Err(val) => return val, | ||
}; | ||
raw[bit / LONG_BIT] |= 1 << (bit % LONG_BIT); | ||
return 0; | ||
} | ||
|
||
#[allow(dead_code)] | ||
pub unsafe fn sigdelset(set: *mut libc::sigset_t, signum: libc::c_int) -> libc::c_int { | ||
let bit = (signum - 1) as usize; | ||
let raw = match to_bitmap_slice_mut(set, bit) { | ||
Ok(raw) => raw, | ||
Err(val) => return val, | ||
}; | ||
raw[bit / LONG_BIT] &= !(1 << (bit % LONG_BIT)); | ||
return 0; | ||
} | ||
|
||
#[allow(dead_code)] | ||
pub unsafe fn sigismember(set: *const libc::sigset_t, signum: libc::c_int) -> libc::c_int { | ||
// Can't use to_bitmap_slice_mut because it's *mut, not *const. | ||
use crate::{ | ||
mem::size_of, | ||
slice, | ||
}; | ||
use libc::{c_ulong, sigset_t}; | ||
|
||
let bit = (signum - 1) as usize; | ||
if set.is_null() || bit >= (8 * size_of::<sigset_t>()) { | ||
crate::sys::unix::os::set_errno(libc::EINVAL); | ||
return -1; | ||
} | ||
let raw: &[c_ulong] = slice::from_raw_parts( | ||
set as *const c_ulong, | ||
size_of::<sigset_t>() / size_of::<c_ulong>(), | ||
); | ||
|
||
return ((raw[bit / LONG_BIT] >> (bit % LONG_BIT)) & 1) as i32; | ||
} | ||
|
||
// SAFETY: returned slice lives as long as set. | ||
unsafe fn to_bitmap_slice_mut<'a>( | ||
set: *mut libc::sigset_t, | ||
bit: usize, | ||
) -> Result<&'a mut [libc::c_ulong], libc::c_int> { | ||
use crate::{ | ||
mem::{align_of, size_of}, | ||
slice, | ||
|
@@ -59,21 +111,19 @@ cfg_if::cfg_if! { | |
&& (size_of::<sigset_t>() % size_of::<c_ulong>()) == 0 | ||
); | ||
|
||
let bit = (signum - 1) as usize; | ||
if set.is_null() || bit >= (8 * size_of::<sigset_t>()) { | ||
crate::sys::unix::os::set_errno(libc::EINVAL); | ||
return -1; | ||
return Err(-1); | ||
} | ||
let raw = slice::from_raw_parts_mut( | ||
set as *mut c_ulong, | ||
size_of::<sigset_t>() / size_of::<c_ulong>(), | ||
); | ||
const LONG_BIT: usize = size_of::<c_ulong>() * 8; | ||
raw[bit / LONG_BIT] |= 1 << (bit % LONG_BIT); | ||
return 0; | ||
|
||
Ok(raw) | ||
} | ||
} else { | ||
pub use libc::{sigemptyset, sigaddset}; | ||
pub use libc::{sigemptyset, sigaddset, sigdelset, sigismember}; | ||
} | ||
} | ||
|
||
|
@@ -104,6 +154,7 @@ pub struct Command { | |
#[cfg(target_os = "linux")] | ||
create_pidfd: bool, | ||
pgroup: Option<pid_t>, | ||
signal_mask: OnceLock<SignalSet>, | ||
} | ||
|
||
// Create a new type for argv, so that we can make it `Send` and `Sync` | ||
|
@@ -148,6 +199,49 @@ pub enum Stdio { | |
Fd(FileDesc), | ||
} | ||
|
||
pub struct SignalSet { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shouldn't this be private? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It follows the pattern of the other items in the module which are all declared |
||
pub sigset: libc::sigset_t, | ||
} | ||
|
||
impl SignalSet { | ||
#[allow(dead_code)] | ||
pub fn new_from_current() -> io::Result<Self> { | ||
let mut set = crate::mem::MaybeUninit::<libc::sigset_t>::uninit(); | ||
// pthread_sigmask returns the errno rather than setting it directly. | ||
unsafe { | ||
cvt_nz(libc::pthread_sigmask(libc::SIG_BLOCK, ptr::null(), set.as_mut_ptr()))?; | ||
Ok(Self { sigset: set.assume_init() }) | ||
} | ||
} | ||
|
||
pub fn empty() -> io::Result<Self> { | ||
let mut set = crate::mem::MaybeUninit::<libc::sigset_t>::uninit(); | ||
unsafe { | ||
cvt(sigemptyset(set.as_mut_ptr()))?; | ||
Ok(Self { sigset: set.assume_init() }) | ||
} | ||
} | ||
|
||
pub fn insert(&mut self, signal: i32) -> io::Result<&mut Self> { | ||
unsafe { | ||
cvt(sigaddset(&mut self.sigset, signal))?; | ||
} | ||
Ok(self) | ||
} | ||
|
||
pub fn remove(&mut self, signal: i32) -> io::Result<&mut Self> { | ||
unsafe { | ||
cvt(sigdelset(&mut self.sigset, signal))?; | ||
} | ||
Ok(self) | ||
} | ||
|
||
pub fn contains(&self, signal: i32) -> io::Result<bool> { | ||
let contains = unsafe { cvt(sigismember(&self.sigset, signal))? }; | ||
Ok(contains != 0) | ||
} | ||
} | ||
|
||
impl Command { | ||
#[cfg(not(target_os = "linux"))] | ||
pub fn new(program: &OsStr) -> Command { | ||
|
@@ -168,6 +262,7 @@ impl Command { | |
stdout: None, | ||
stderr: None, | ||
pgroup: None, | ||
mask: None, | ||
} | ||
} | ||
|
||
|
@@ -191,6 +286,7 @@ impl Command { | |
stderr: None, | ||
create_pidfd: false, | ||
pgroup: None, | ||
signal_mask: OnceLock::new(), | ||
} | ||
} | ||
|
||
|
@@ -229,6 +325,10 @@ impl Command { | |
pub fn pgroup(&mut self, pgroup: pid_t) { | ||
self.pgroup = Some(pgroup); | ||
} | ||
pub fn signal_mask(&mut self) -> io::Result<&mut SignalSet> { | ||
self.signal_mask.get_or_try_init(SignalSet::empty)?; | ||
Ok(self.signal_mask.get_mut().unwrap()) | ||
} | ||
|
||
#[cfg(target_os = "linux")] | ||
pub fn create_pidfd(&mut self, val: bool) { | ||
|
@@ -296,6 +396,10 @@ impl Command { | |
pub fn get_pgroup(&self) -> Option<pid_t> { | ||
self.pgroup | ||
} | ||
#[allow(dead_code)] | ||
pub fn get_signal_mask(&self) -> io::Result<&SignalSet> { | ||
self.signal_mask.get_or_try_init(SignalSet::empty) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We'll want to remove the OnceLock from this if the other PR's FCP doesn't get accepted. |
||
} | ||
|
||
pub fn get_closures(&mut self) -> &mut Vec<Box<dyn FnMut() -> io::Result<()> + Send + Sync>> { | ||
&mut self.closures | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sigprocmask is probably the more canonical reference.
Also, while the manpage may say that the effect of blocking synchronous traps (ie, SEGV/BUS/FPE/ILL raised while executing an instruction) is undefined, I wouldn't read that as being the same as UB at the Rust/llvm level.
In practice I think it would either deliver the signal as SIG_DFL (ie, Linux), or endlessly re-trap on the same instruction (what x86 macOS seems to do) - either way the program has effectively terminated and won't enter any UB states as far as the program semantics are concerned.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Heh it's actually the same man page for both
pthread_sigmask
andsigprocmask
. I can change the reference in the docs to besigprocmask
though.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Huh - on Fedora they're separate, but pthread_sigmask has almost no content and refers to sigprocmask for discussion of the detailed semantics.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(I was referring to the man pages from the official POSIX spec here, as linked in the comment)