diff --git a/Cargo.toml b/Cargo.toml index d1b317e..8fffdc8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,10 @@ readme = "README.md" rust-version = "1.69.0" [target.'cfg(unix)'.dependencies] -nix = { version = "0.30", default-features = false, features = ["fs", "signal"]} +nix = { version = "0.30", default-features = false, features = ["signal"]} + +[target.'cfg(target_vendor = "apple")'.dependencies] +dispatch = "0.2" [target.'cfg(windows)'.dependencies] windows-sys = { version = "0.59", features = ["Win32_Foundation", "Win32_System_Threading", "Win32_Security", "Win32_System_Console"] } diff --git a/src/lib.rs b/src/lib.rs index 7bdbd30..613e4ee 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -42,7 +42,7 @@ //! ``` //! //! # Handling SIGTERM and SIGHUP -//! Handling of `SIGTERM and SIGHUP` can be enabled with `termination` feature. If this is enabled, +//! Handling of `SIGTERM` and `SIGHUP` can be enabled with `termination` feature. If this is enabled, //! the handler specified by `set_handler()` will be executed for `SIGINT`, `SIGTERM` and `SIGHUP`. //! diff --git a/src/platform/unix/mod.rs b/src/platform/unix/mod.rs index 1948564..cdd32f6 100644 --- a/src/platform/unix/mod.rs +++ b/src/platform/unix/mod.rs @@ -8,63 +8,55 @@ // according to those terms. use crate::error::Error as CtrlcError; -use nix::unistd; -use std::os::fd::BorrowedFd; -use std::os::fd::IntoRawFd; -use std::os::unix::io::RawFd; -static mut PIPE: (RawFd, RawFd) = (-1, -1); +#[cfg(not(target_vendor = "apple"))] +#[allow(static_mut_refs)] // rust-version = "1.69.0" +mod implementation { + static mut SEMAPHORE: nix::libc::sem_t = unsafe { std::mem::zeroed() }; + const SEM_THREAD_SHARED: nix::libc::c_int = 0; -/// Platform specific error type -pub type Error = nix::Error; + pub unsafe fn sem_init() { + nix::libc::sem_init(&mut SEMAPHORE as *mut _, SEM_THREAD_SHARED, 0); + } -/// Platform specific signal type -pub type Signal = nix::sys::signal::Signal; + pub unsafe fn sem_post() { + // No errors apply. EOVERFLOW is hypothetically possible but it's equivalent to a success for our oneshot use-case. + let _ = nix::libc::sem_post(&mut SEMAPHORE as *mut _); + } -extern "C" fn os_handler(_: nix::libc::c_int) { - // Assuming this always succeeds. Can't really handle errors in any meaningful way. - unsafe { - let fd = BorrowedFd::borrow_raw(PIPE.1); - let _ = unistd::write(fd, &[0u8]); + pub unsafe fn sem_wait_forever() { + // The only realistic error is EINTR, which is restartable. + while nix::libc::sem_wait(&mut SEMAPHORE as *mut _) == -1 {} } } -// pipe2(2) is not available on macOS, iOS, AIX, Haiku, etc., so we need to use pipe(2) and fcntl(2) -#[inline] -#[cfg(any( - target_vendor = "apple", - target_os = "haiku", - target_os = "aix", - target_os = "nto", -))] -fn pipe2(flags: nix::fcntl::OFlag) -> nix::Result<(RawFd, RawFd)> { - use nix::fcntl::{fcntl, FcntlArg, FdFlag, OFlag}; - - let pipe = unistd::pipe()?; - - if flags.contains(OFlag::O_CLOEXEC) { - fcntl(&pipe.0, FcntlArg::F_SETFD(FdFlag::FD_CLOEXEC))?; - fcntl(&pipe.1, FcntlArg::F_SETFD(FdFlag::FD_CLOEXEC))?; +#[cfg(target_vendor = "apple")] +mod implementation { + static mut SEMAPHORE: dispatch::ffi::dispatch_semaphore_t = std::ptr::null_mut(); + + pub unsafe fn sem_init() { + SEMAPHORE = dispatch::ffi::dispatch_semaphore_create(0); } - if flags.contains(OFlag::O_NONBLOCK) { - fcntl(&pipe.0, FcntlArg::F_SETFL(OFlag::O_NONBLOCK))?; - fcntl(&pipe.1, FcntlArg::F_SETFL(OFlag::O_NONBLOCK))?; + pub unsafe fn sem_post() { + dispatch::ffi::dispatch_semaphore_signal(SEMAPHORE); } - Ok((pipe.0.into_raw_fd(), pipe.1.into_raw_fd())) + pub unsafe fn sem_wait_forever() { + dispatch::ffi::dispatch_semaphore_wait(SEMAPHORE, dispatch::ffi::DISPATCH_TIME_FOREVER); + } } -#[inline] -#[cfg(not(any( - target_vendor = "apple", - target_os = "haiku", - target_os = "aix", - target_os = "nto", -)))] -fn pipe2(flags: nix::fcntl::OFlag) -> nix::Result<(RawFd, RawFd)> { - let pipe = unistd::pipe2(flags)?; - Ok((pipe.0.into_raw_fd(), pipe.1.into_raw_fd())) +/// Platform specific error type +pub type Error = nix::Error; + +/// Platform specific signal type +pub type Signal = nix::sys::signal::Signal; + +extern "C" fn os_handler(_: nix::libc::c_int) { + unsafe { + implementation::sem_post(); + } } /// Register os signal handler. @@ -77,26 +69,9 @@ fn pipe2(flags: nix::fcntl::OFlag) -> nix::Result<(RawFd, RawFd)> { /// #[inline] pub unsafe fn init_os_handler(overwrite: bool) -> Result<(), Error> { - use nix::fcntl; use nix::sys::signal; - PIPE = pipe2(fcntl::OFlag::O_CLOEXEC)?; - - let close_pipe = |e: nix::Error| -> Error { - // Try to close the pipes. close() should not fail, - // but if it does, there isn't much we can do - let _ = unistd::close(PIPE.1); - let _ = unistd::close(PIPE.0); - e - }; - - // Make sure we never block on write in the os handler. - if let Err(e) = fcntl::fcntl( - BorrowedFd::borrow_raw(PIPE.1), - fcntl::FcntlArg::F_SETFL(fcntl::OFlag::O_NONBLOCK), - ) { - return Err(close_pipe(e)); - } + implementation::sem_init(); let handler = signal::SigHandler::Handler(os_handler); #[cfg(not(target_os = "nto"))] @@ -110,13 +85,10 @@ pub unsafe fn init_os_handler(overwrite: bool) -> Result<(), Error> { let new_action = signal::SigAction::new(handler, signal::SaFlags::empty(), signal::SigSet::empty()); - let sigint_old = match signal::sigaction(signal::Signal::SIGINT, &new_action) { - Ok(old) => old, - Err(e) => return Err(close_pipe(e)), - }; + let sigint_old = signal::sigaction(signal::Signal::SIGINT, &new_action)?; if !overwrite && sigint_old.handler() != signal::SigHandler::SigDfl { signal::sigaction(signal::Signal::SIGINT, &sigint_old).unwrap(); - return Err(close_pipe(nix::Error::EEXIST)); + return Err(nix::Error::EEXIST); } #[cfg(feature = "termination")] @@ -125,27 +97,27 @@ pub unsafe fn init_os_handler(overwrite: bool) -> Result<(), Error> { Ok(old) => old, Err(e) => { signal::sigaction(signal::Signal::SIGINT, &sigint_old).unwrap(); - return Err(close_pipe(e)); + return Err(e); } }; if !overwrite && sigterm_old.handler() != signal::SigHandler::SigDfl { signal::sigaction(signal::Signal::SIGINT, &sigint_old).unwrap(); signal::sigaction(signal::Signal::SIGTERM, &sigterm_old).unwrap(); - return Err(close_pipe(nix::Error::EEXIST)); + return Err(nix::Error::EEXIST); } let sighup_old = match signal::sigaction(signal::Signal::SIGHUP, &new_action) { Ok(old) => old, Err(e) => { signal::sigaction(signal::Signal::SIGINT, &sigint_old).unwrap(); signal::sigaction(signal::Signal::SIGTERM, &sigterm_old).unwrap(); - return Err(close_pipe(e)); + return Err(e); } }; if !overwrite && sighup_old.handler() != signal::SigHandler::SigDfl { signal::sigaction(signal::Signal::SIGINT, &sigint_old).unwrap(); signal::sigaction(signal::Signal::SIGTERM, &sigterm_old).unwrap(); signal::sigaction(signal::Signal::SIGHUP, &sighup_old).unwrap(); - return Err(close_pipe(nix::Error::EEXIST)); + return Err(nix::Error::EEXIST); } } @@ -157,24 +129,10 @@ pub unsafe fn init_os_handler(overwrite: bool) -> Result<(), Error> { /// Must be called after calling [`init_os_handler()`](fn.init_os_handler.html). /// /// # Errors -/// Will return an error if a system error occurred. +/// None. /// #[inline] pub unsafe fn block_ctrl_c() -> Result<(), CtrlcError> { - use std::io; - let mut buf = [0u8]; - - // TODO: Can we safely convert the pipe fd into a std::io::Read - // with std::os::unix::io::FromRawFd, this would handle EINTR - // and everything for us. - loop { - match unistd::read(BorrowedFd::borrow_raw(PIPE.0), &mut buf[..]) { - Ok(1) => break, - Ok(_) => return Err(CtrlcError::System(io::ErrorKind::UnexpectedEof.into())), - Err(nix::errno::Errno::EINTR) => {} - Err(e) => return Err(e.into()), - } - } - + implementation::sem_wait_forever(); Ok(()) }