#![allow(unsafe_code)]
pub(crate) fn reset_signal_dispositions() {
let signals = signal_range();
for sig in signals {
if sig == libc::SIGKILL || sig == libc::SIGSTOP || sig == libc::SIGPIPE {
continue;
}
let mut sa: libc::sigaction = unsafe { std::mem::zeroed() };
sa.sa_sigaction = libc::SIG_DFL;
unsafe { libc::sigemptyset(&mut sa.sa_mask) };
sa.sa_flags = 0;
let ret = unsafe { libc::sigaction(sig, &sa, std::ptr::null_mut()) };
if ret != 0 {
let err = std::io::Error::last_os_error();
if err.raw_os_error() != Some(libc::EINVAL) {
panic!("sigaction({sig}) failed: {err}");
}
}
}
}
fn signal_range() -> Vec<i32> {
#[cfg(target_os = "linux")]
{
let rtmin = libc::SIGRTMIN();
let rtmax = libc::SIGRTMAX();
(1..32).chain(rtmin..=rtmax).collect()
}
#[cfg(not(target_os = "linux"))]
{
(1..=64).collect()
}
}
pub(crate) fn raw_close(fd: i32) {
unsafe { libc::close(fd) };
}
pub(crate) fn raw_initgroups(
user: &std::ffi::CStr,
group: libc::gid_t,
) -> Result<(), nix::errno::Errno> {
let ret = unsafe { libc::initgroups(user.as_ptr(), group as _) };
if ret < 0 {
Err(nix::errno::Errno::last())
} else {
Ok(())
}
}
pub(crate) fn raw_exit(code: i32) -> ! {
unsafe { libc::_exit(code) }
}
static CLEANUP_PIDFILE: std::sync::atomic::AtomicPtr<libc::c_char> =
std::sync::atomic::AtomicPtr::new(std::ptr::null_mut());
extern "C" fn pidfile_cleanup_handler(signum: i32) {
use std::sync::atomic::Ordering;
let ptr = CLEANUP_PIDFILE.load(Ordering::Acquire);
if !ptr.is_null() {
unsafe { libc::unlink(ptr) };
}
unsafe { libc::raise(signum) };
}
pub(crate) fn install_pidfile_cleanup_signals(
pidfile: &std::ffi::CStr,
signals: &[i32],
) -> std::io::Result<()> {
use std::sync::atomic::Ordering;
let leaked: *mut libc::c_char = pidfile.to_owned().into_raw();
let prior_path = CLEANUP_PIDFILE.swap(leaked, Ordering::AcqRel);
let mut replaced: Vec<(i32, libc::sigaction)> = Vec::with_capacity(signals.len());
for &sig in signals {
let mut sa: libc::sigaction = unsafe { std::mem::zeroed() };
let handler = pidfile_cleanup_handler as extern "C" fn(i32);
sa.sa_sigaction = handler as usize;
unsafe { libc::sigemptyset(&mut sa.sa_mask) };
sa.sa_flags = libc::SA_RESETHAND;
let mut old: libc::sigaction = unsafe { std::mem::zeroed() };
let ret = unsafe { libc::sigaction(sig, &sa, &mut old) };
if ret != 0 {
let err = std::io::Error::last_os_error();
CLEANUP_PIDFILE.store(prior_path, Ordering::Release);
for (s, prev) in replaced.iter().rev() {
unsafe { libc::sigaction(*s, prev, std::ptr::null_mut()) };
}
return Err(std::io::Error::new(
err.kind(),
format!("signal {sig}: {err}"),
));
}
replaced.push((sig, old));
}
Ok(())
}
#[cfg(target_os = "macos")]
pub(crate) fn thread_count() -> std::io::Result<usize> {
let mut info: libc::proc_taskinfo = unsafe { std::mem::zeroed() };
let size = std::mem::size_of::<libc::proc_taskinfo>() as libc::c_int;
let written = unsafe {
libc::proc_pidinfo(
libc::getpid(),
libc::PROC_PIDTASKINFO,
0,
(&mut info as *mut libc::proc_taskinfo).cast(),
size,
)
};
if written < size {
return Err(std::io::Error::last_os_error());
}
Ok(info.pti_threadnum.max(0) as usize)
}
#[cfg(target_os = "freebsd")]
pub(crate) fn thread_count() -> std::io::Result<usize> {
let mut kp: libc::kinfo_proc = unsafe { std::mem::zeroed() };
let mut size = std::mem::size_of::<libc::kinfo_proc>();
let mut mib = [
libc::CTL_KERN,
libc::KERN_PROC,
libc::KERN_PROC_PID,
unsafe { libc::getpid() },
];
let rc = unsafe {
libc::sysctl(
mib.as_mut_ptr(),
mib.len() as libc::c_uint,
(&mut kp as *mut libc::kinfo_proc).cast(),
&mut size,
std::ptr::null_mut(),
0,
)
};
if rc != 0 {
return Err(std::io::Error::last_os_error());
}
Ok(kp.ki_numthreads.max(0) as usize)
}
#[cfg(target_os = "netbsd")]
pub(crate) fn thread_count() -> std::io::Result<usize> {
let mut kp: libc::kinfo_proc2 = unsafe { std::mem::zeroed() };
let mut size = std::mem::size_of::<libc::kinfo_proc2>();
let mut mib = [
libc::CTL_KERN,
libc::KERN_PROC2,
libc::KERN_PROC_PID,
unsafe { libc::getpid() },
size as libc::c_int,
1,
];
let rc = unsafe {
libc::sysctl(
mib.as_mut_ptr(),
mib.len() as libc::c_uint,
(&mut kp as *mut libc::kinfo_proc2).cast(),
&mut size,
std::ptr::null_mut(),
0,
)
};
if rc != 0 {
return Err(std::io::Error::last_os_error());
}
Ok(kp.p_nlwps as usize) }
#[cfg(target_os = "openbsd")]
pub(crate) fn thread_count() -> std::io::Result<usize> {
let elem = std::mem::size_of::<libc::kinfo_proc>();
let mut size: libc::size_t = 0;
let mut mib = [
libc::CTL_KERN,
libc::KERN_PROC,
libc::KERN_PROC_PID | libc::KERN_PROC_SHOW_THREADS,
unsafe { libc::getpid() },
elem as libc::c_int,
0,
];
let rc = unsafe {
libc::sysctl(
mib.as_mut_ptr(),
mib.len() as libc::c_uint,
std::ptr::null_mut(),
&mut size,
std::ptr::null_mut(),
0,
)
};
if rc != 0 {
return Err(std::io::Error::last_os_error());
}
if size == 0 {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
"sysctl(KERN_PROC_SHOW_THREADS) returned no thread records",
));
}
let mut buf = vec![0u8; size];
mib[5] = (size / elem) as libc::c_int;
let rc = unsafe {
libc::sysctl(
mib.as_mut_ptr(),
mib.len() as libc::c_uint,
buf.as_mut_ptr().cast(),
&mut size,
std::ptr::null_mut(),
0,
)
};
if rc != 0 {
return Err(std::io::Error::last_os_error());
}
Ok(size / elem)
}
pub(crate) fn at_fdcwd() -> std::os::fd::BorrowedFd<'static> {
unsafe { std::os::fd::BorrowedFd::borrow_raw(libc::AT_FDCWD) }
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_support::{is_subprocess, run_in_subprocess};
use std::sync::atomic::Ordering;
fn current_disposition(sig: i32) -> usize {
let mut old: libc::sigaction = unsafe { std::mem::zeroed() };
let ret = unsafe { libc::sigaction(sig, std::ptr::null(), &mut old) };
assert_eq!(ret, 0, "sigaction query for signal {sig} failed");
old.sa_sigaction
}
#[test]
fn failed_install_rolls_back() {
run_in_subprocess("unsafe_ops::tests::failed_install_rolls_back_subprocess");
}
#[test]
#[ignore]
fn failed_install_rolls_back_subprocess() {
if !is_subprocess() {
return;
}
install_pidfile_cleanup_signals(c"/tmp/rollback-prior.pid", &[libc::SIGUSR2])
.expect("SIGUSR2 install should succeed");
let prior_ptr = CLEANUP_PIDFILE.load(Ordering::Acquire);
let prior_usr1 = current_disposition(libc::SIGUSR1);
let err = install_pidfile_cleanup_signals(
c"/tmp/rollback-new.pid",
&[libc::SIGUSR1, libc::SIGKILL],
)
.expect_err("SIGKILL cannot be caught");
assert!(
err.to_string()
.contains(&format!("signal {}", libc::SIGKILL)),
"error should name the failing signal, got: {err}"
);
assert_eq!(
current_disposition(libc::SIGUSR1),
prior_usr1,
"SIGUSR1 disposition should be rolled back after the failed install"
);
assert_eq!(
CLEANUP_PIDFILE.load(Ordering::Acquire),
prior_ptr,
"pidfile pointer should revert to the prior install's path"
);
assert_eq!(
current_disposition(libc::SIGUSR2),
pidfile_cleanup_handler as extern "C" fn(i32) as usize,
"earlier successful install should be untouched"
);
}
}