diff options
author | Alex Gulyás <gulyas.alex@gmail.com> | 2015-09-03 21:20:53 +0200 |
---|---|---|
committer | Carl Lerche <me@carllerche.com> | 2015-09-10 15:35:35 -0700 |
commit | 36432e1a13f4340a437e6250f4c665742439a5aa (patch) | |
tree | 0d67f57c217a56c9fc5d0b6849f1fe410675e68d | |
parent | f37e45628e44d86c16a6f02b38ea0a9d68e8b3d4 (diff) | |
download | nix-36432e1a13f4340a437e6250f4c665742439a5aa.zip |
Add signalfd support
-rw-r--r-- | Cargo.toml | 8 | ||||
-rw-r--r-- | src/sys/mod.rs | 3 | ||||
-rw-r--r-- | src/sys/signal.rs | 191 | ||||
-rw-r--r-- | src/sys/signalfd.rs | 219 | ||||
-rw-r--r-- | test/test_signalfd.rs | 30 |
5 files changed, 435 insertions, 16 deletions
@@ -14,7 +14,6 @@ exclude = [ ] [features] - eventfd = [] execvpe = [] @@ -30,6 +29,11 @@ path = "nix-test" version = "*" [[test]] - name = "test" path = "test/test.rs" + +[[test]] +name = "test-signalfd" +path = "test/test_signalfd.rs" +harness = false +test = true diff --git a/src/sys/mod.rs b/src/sys/mod.rs index 235449a0..eda95972 100644 --- a/src/sys/mod.rs +++ b/src/sys/mod.rs @@ -17,6 +17,9 @@ pub mod ioctl; pub mod signal; +#[cfg(any(target_os = "linux", target_os = "android"))] +pub mod signalfd; + pub mod socket; pub mod stat; diff --git a/src/sys/signal.rs b/src/sys/signal.rs index 5f51bd11..a01102f0 100644 --- a/src/sys/signal.rs +++ b/src/sys/signal.rs @@ -4,6 +4,7 @@ use libc; use errno::Errno; use std::mem; +use std::ptr; use {Error, Result}; pub use libc::consts::os::posix88::{ @@ -43,6 +44,7 @@ pub use self::signal::{ }; pub use self::signal::SockFlag; +pub use self::signal::{HowFlag, SIG_BLOCK, SIG_UNBLOCK, SIG_SETMASK}; pub use self::signal::sigset_t; // This doesn't always exist, but when it does, it's 7 @@ -68,6 +70,14 @@ pub mod signal { } ); + bitflags!{ + flags HowFlag: libc::c_int { + const SIG_BLOCK = 0, + const SIG_UNBLOCK = 1, + const SIG_SETMASK = 2, + } + } + pub const SIGTRAP: libc::c_int = 5; pub const SIGIOT: libc::c_int = 6; pub const SIGBUS: libc::c_int = 7; @@ -98,9 +108,9 @@ pub mod signal { #[repr(C)] #[derive(Clone, Copy)] pub struct siginfo { - si_signo: libc::c_int, - si_errno: libc::c_int, - si_code: libc::c_int, + pub si_signo: libc::c_int, + pub si_errno: libc::c_int, + pub si_code: libc::c_int, pub pid: libc::pid_t, pub uid: libc::uid_t, pub status: libc::c_int, @@ -147,6 +157,14 @@ pub mod signal { } ); + bitflags!{ + flags HowFlag: libc::c_int { + const SIG_BLOCK = 1, + const SIG_UNBLOCK = 2, + const SIG_SETMASK = 3, + } + } + pub const SIGTRAP: libc::c_int = 5; pub const SIGIOT: libc::c_int = 6; pub const SIGBUS: libc::c_int = 10; @@ -175,9 +193,9 @@ pub mod signal { // however. #[repr(C)] pub struct siginfo { - si_signo: libc::c_int, - si_code: libc::c_int, - si_errno: libc::c_int, + pub si_signo: libc::c_int, + pub si_code: libc::c_int, + pub si_errno: libc::c_int, pub pid: libc::pid_t, pub uid: libc::uid_t, pub status: libc::c_int, @@ -218,6 +236,14 @@ pub mod signal { } ); + bitflags!{ + flags HowFlag: libc::c_int { + const SIG_BLOCK = 1, + const SIG_UNBLOCK = 2, + const SIG_SETMASK = 3, + } + } + pub const SIGTRAP: libc::c_int = 5; pub const SIGIOT: libc::c_int = 6; pub const SIGBUS: libc::c_int = 10; @@ -307,20 +333,24 @@ pub mod signal { } mod ffi { - use libc; + use libc::{c_int, pid_t}; use super::signal::{sigaction, sigset_t}; #[allow(improper_ctypes)] extern { - pub fn sigaction(signum: libc::c_int, + pub fn sigaction(signum: c_int, act: *const sigaction, - oldact: *mut sigaction) -> libc::c_int; + oldact: *mut sigaction) -> c_int; - pub fn sigaddset(set: *mut sigset_t, signum: libc::c_int) -> libc::c_int; - pub fn sigdelset(set: *mut sigset_t, signum: libc::c_int) -> libc::c_int; - pub fn sigemptyset(set: *mut sigset_t) -> libc::c_int; + pub fn sigaddset(set: *mut sigset_t, signum: c_int) -> c_int; + pub fn sigdelset(set: *mut sigset_t, signum: c_int) -> c_int; + pub fn sigemptyset(set: *mut sigset_t) -> c_int; + pub fn sigfillset(set: *mut sigset_t) -> c_int; + pub fn sigismember(set: *const sigset_t, signum: c_int) -> c_int; - pub fn kill(pid: libc::pid_t, signum: libc::c_int) -> libc::c_int; + pub fn pthread_sigmask(how: c_int, set: *const sigset_t, oldset: *mut sigset_t) -> c_int; + + pub fn kill(pid: pid_t, signum: c_int) -> c_int; } } @@ -332,8 +362,15 @@ pub struct SigSet { pub type SigNum = libc::c_int; impl SigSet { + pub fn all() -> SigSet { + let mut sigset: sigset_t = unsafe { mem::uninitialized() }; + let _ = unsafe { ffi::sigfillset(&mut sigset as *mut sigset_t) }; + + SigSet { sigset: sigset } + } + pub fn empty() -> SigSet { - let mut sigset = unsafe { mem::uninitialized::<sigset_t>() }; + let mut sigset: sigset_t = unsafe { mem::uninitialized() }; let _ = unsafe { ffi::sigemptyset(&mut sigset as *mut sigset_t) }; SigSet { sigset: sigset } @@ -358,6 +395,51 @@ impl SigSet { Ok(()) } + + pub fn contains(&self, signum: SigNum) -> Result<bool> { + let res = unsafe { ffi::sigismember(&self.sigset as *const sigset_t, signum) }; + + match res { + 1 => Ok(true), + 0 => Ok(false), + _ => Err(Error::Sys(Errno::last())) + } + } + + /// Gets the currently blocked (masked) set of signals for the calling thread. + pub fn thread_get_mask() -> Result<SigSet> { + let mut oldmask: SigSet = unsafe { mem::uninitialized() }; + try!(pthread_sigmask(HowFlag::empty(), None, Some(&mut oldmask))); + Ok(oldmask) + } + + /// Sets the set of signals as the signal mask for the calling thread. + pub fn thread_set_mask(&self) -> Result<()> { + pthread_sigmask(SIG_SETMASK, Some(self), None) + } + + /// Adds the set of signals to the signal mask for the calling thread. + pub fn thread_block(&self) -> Result<()> { + pthread_sigmask(SIG_BLOCK, Some(self), None) + } + + /// Removes the set of signals from the signal mask for the calling thread. + pub fn thread_unblock(&self) -> Result<()> { + pthread_sigmask(SIG_UNBLOCK, Some(self), None) + } + + /// Sets the set of signals as the signal mask, and returns the old mask. + pub fn thread_swap_mask(&self, how: HowFlag) -> Result<SigSet> { + let mut oldmask: SigSet = unsafe { mem::uninitialized() }; + try!(pthread_sigmask(how, Some(self), Some(&mut oldmask))); + Ok(oldmask) + } +} + +impl AsRef<sigset_t> for SigSet { + fn as_ref(&self) -> &sigset_t { + &self.sigset + } } type sigaction_t = self::signal::sigaction; @@ -390,6 +472,44 @@ pub unsafe fn sigaction(signum: SigNum, sigaction: &SigAction) -> Result<SigActi Ok(SigAction { sigaction: oldact }) } +/// Manages the signal mask (set of blocked signals) for the calling thread. +/// +/// If the `set` parameter is `Some(..)`, then the signal mask will be updated with the signal set. +/// The `how` flag decides the type of update. If `set` is `None`, `how` will be ignored, +/// and no modification will take place. +/// +/// If the 'oldset' parameter is `Some(..)` then the current signal mask will be written into it. +/// +/// If both `set` and `oldset` is `Some(..)`, the current signal mask will be written into oldset, +/// and then it will be updated with `set`. +/// +/// If both `set` and `oldset` is None, this function is a no-op. +/// +/// For more information, visit the [pthread_sigmask](http://man7.org/linux/man-pages/man3/pthread_sigmask.3.html), +/// or [sigprocmask](http://man7.org/linux/man-pages/man2/sigprocmask.2.html) man pages. +pub fn pthread_sigmask(how: HowFlag, + set: Option<&SigSet>, + oldset: Option<&mut SigSet>) -> Result<()> { + if set.is_none() && oldset.is_none() { + return Ok(()) + } + + let res = unsafe { + // if set or oldset is None, pass in null pointers instead + ffi::pthread_sigmask(how.bits(), + set.map_or_else(|| ptr::null::<sigset_t>(), + |s| &s.sigset as *const sigset_t), + oldset.map_or_else(|| ptr::null_mut::<sigset_t>(), + |os| &mut os.sigset as *mut sigset_t)) + }; + + if res != 0 { + return Err(Error::Sys(Errno::last())); + } + + Ok(()) +} + pub fn kill(pid: libc::pid_t, signum: SigNum) -> Result<()> { let res = unsafe { ffi::kill(pid, signum) }; @@ -399,3 +519,46 @@ pub fn kill(pid: libc::pid_t, signum: SigNum) -> Result<()> { Ok(()) } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_contains() { + let mut mask = SigSet::empty(); + mask.add(signal::SIGUSR1).unwrap(); + + assert_eq!(mask.contains(signal::SIGUSR1), Ok(true)); + assert_eq!(mask.contains(signal::SIGUSR2), Ok(false)); + + let all = SigSet::all(); + assert_eq!(all.contains(signal::SIGUSR1), Ok(true)); + assert_eq!(all.contains(signal::SIGUSR2), Ok(true)); + } + + #[test] + fn test_thread_signal_block() { + let mut mask = SigSet::empty(); + mask.add(signal::SIGUSR1).unwrap(); + + assert!(mask.thread_block().is_ok()); + } + + #[test] + fn test_thread_signal_swap() { + let mut mask = SigSet::empty(); + mask.add(signal::SIGUSR1).unwrap(); + mask.thread_block().unwrap(); + + assert!(SigSet::thread_get_mask().unwrap().contains(SIGUSR1).unwrap()); + + let mask2 = SigSet::empty(); + mask.add(signal::SIGUSR2).unwrap(); + + let oldmask = mask2.thread_swap_mask(signal::SIG_SETMASK).unwrap(); + + assert!(oldmask.contains(signal::SIGUSR1).unwrap()); + assert!(!oldmask.contains(signal::SIGUSR2).unwrap()); + } +} diff --git a/src/sys/signalfd.rs b/src/sys/signalfd.rs new file mode 100644 index 00000000..66eaa04e --- /dev/null +++ b/src/sys/signalfd.rs @@ -0,0 +1,219 @@ +//! Interface for the `signalfd` syscall. +//! +//! # Signal discarding +//! When a signal can't be delivered to a process (or thread), it will become a pending signal. +//! Failure to deliver could happen if the signal is blocked by every thread in the process or if +//! the signal handler is still handling a previous signal. +//! +//! If a signal is sent to a process (or thread) that already has a pending signal of the same +//! type, it will be discarded. This means that if signals of the same type are received faster than +//! they are processed, some of those signals will be dropped. Because of this limitation, +//! `signalfd` in itself cannot be used for reliable communication between processes or threads. +//! +//! Once the signal is unblocked, or the signal handler is finished, and a signal is still pending +//! (ie. not consumed from a signalfd) it will be delivered to the signal handler. +//! +//! Please note that signal discarding is not specific to `signalfd`, but also happens with regular +//! signal handlers. +use libc::{c_int, pid_t, uid_t}; +use {Error, Result}; +use unistd; +use errno::Errno; +use sys::signal::signal::siginfo as signal_siginfo; +pub use sys::signal::{self, SigSet}; + +use std::os::unix::io::{RawFd, AsRawFd}; +use std::mem; + +mod ffi { + use libc::c_int; + use sys::signal::sigset_t; + + extern { + pub fn signalfd(fd: c_int, mask: *const sigset_t, flags: c_int) -> c_int; + } +} + +bitflags!{ + flags SfdFlags: c_int { + const SFD_NONBLOCK = 0o00004000, // O_NONBLOCK + const SFD_CLOEXEC = 0o02000000, // O_CLOEXEC + } +} + +pub const CREATE_NEW_FD: RawFd = -1; + +/// Creates a new file descriptor for reading signals. +/// +/// **Important:** please read the module level documentation about signal discarding before using +/// this function! +/// +/// The `mask` parameter specifies the set of signals that can be accepted via this file descriptor. +/// +/// A signal must be blocked on every thread in a process, otherwise it won't be visible from +/// signalfd (the default handler will be invoked instead). +/// +/// See [the signalfd man page for more information](http://man7.org/linux/man-pages/man2/signalfd.2.html) +pub fn signalfd(fd: RawFd, mask: &SigSet, flags: SfdFlags) -> Result<RawFd> { + unsafe { + match ffi::signalfd(fd as c_int, mask.as_ref(), flags.bits()) { + -1 => Err(Error::Sys(Errno::last())), + res => Ok(res as RawFd), + } + } +} + +/// A helper struct for creating, reading and closing a `signalfd` instance. +/// +/// **Important:** please read the module level documentation about signal discarding before using +/// this struct! +/// +/// # Examples +/// +/// ``` +/// use nix::sys::signalfd::*; +/// +/// let mut mask = SigSet::empty(); +/// mask.add(signal::SIGUSR1).unwrap(); +/// +/// // Block the signal, otherwise the default handler will be invoked instead. +/// mask.thread_block().unwrap(); +/// +/// // Signals are queued up on the file descriptor +/// let mut sfd = SignalFd::with_flags(&mask, SFD_NONBLOCK).unwrap(); +/// +/// match sfd.read_signal() { +/// // we caught a signal +/// Ok(Some(sig)) => (), +/// +/// // there were no signals waiting (only happens when the SFD_NONBLOCK flag is set, +/// // otherwise the read_signal call blocks) +/// Ok(None) => (), +/// +/// Err(err) => (), // some error happend +/// } +/// ``` +#[derive(Debug)] +pub struct SignalFd(RawFd); + +impl SignalFd { + pub fn new(mask: &SigSet) -> Result<SignalFd> { + Self::with_flags(mask, SfdFlags::empty()) + } + + pub fn with_flags(mask: &SigSet, flags: SfdFlags) -> Result<SignalFd> { + let fd = try!(signalfd(CREATE_NEW_FD, mask, flags)); + + Ok(SignalFd(fd)) + } + + pub fn set_mask(&mut self, mask: &SigSet) -> Result<()> { + signalfd(self.0, mask, SfdFlags::empty()).map(|_| ()) + } + + pub fn read_signal(&mut self) -> Result<Option<siginfo>> { + let mut buffer: [u8; SIGINFO_SIZE] = unsafe { mem::uninitialized() }; + + match unistd::read(self.0, &mut buffer) { + Ok(SIGINFO_SIZE) => Ok(Some(unsafe { mem::transmute_copy(&buffer) })), + Ok(_) => unreachable!("partial read on signalfd"), + Err(Error::Sys(Errno::EAGAIN)) => Ok(None), + Err(error) => Err(error) + } + } +} + +impl Drop for SignalFd { + fn drop(&mut self) { + let _ = unistd::close(self.0); + } +} + +impl AsRawFd for SignalFd { + fn as_raw_fd(&self) -> RawFd { + self.0 + } +} + +impl Iterator for SignalFd { + type Item = siginfo; + + fn next(&mut self) -> Option<Self::Item> { + match self.read_signal() { + Ok(Some(sig)) => Some(sig), + Ok(None) => None, + Err(..) => None, + } + } +} + +pub const SIGINFO_SIZE: usize = 128; +pub const SIGINFO_PADDING: usize = 48; + +#[derive(Debug, Clone, PartialEq)] +#[repr(C, packed)] +pub struct siginfo { + pub ssi_signo: u32, + pub ssi_errno: i32, + pub ssi_code: i32, + pub ssi_pid: u32, + pub ssi_uid: u32, + pub ssi_fd: i32, + pub ssi_tid: u32, + pub ssi_band: u32, + pub ssi_overrun: u32, + pub ssi_trapno: u32, + pub ssi_status: i32, + pub ssi_int: i32, + pub ssi_ptr: u64, + pub ssi_utime: u64, + pub ssi_stime: u64, + pub ssi_addr: u64, +} + +impl Into<signal_siginfo> for siginfo { + fn into(self) -> signal_siginfo { + signal_siginfo { + si_signo: self.ssi_signo as c_int, + si_errno: self.ssi_errno as c_int, + si_code: self.ssi_code as c_int, + pid: self.ssi_pid as pid_t, + uid: self.ssi_uid as uid_t, + status: self.ssi_status as c_int, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::mem; + + #[test] + fn check_siginfo_size() { + assert_eq!(mem::size_of::<siginfo>() + SIGINFO_PADDING, SIGINFO_SIZE); + } + + #[test] + fn create_signalfd() { + let mask = SigSet::empty(); + let fd = SignalFd::new(&mask); + assert!(fd.is_ok()); + } + + #[test] + fn create_signalfd_with_opts() { + let mask = SigSet::empty(); + let fd = SignalFd::with_flags(&mask, SFD_CLOEXEC | SFD_NONBLOCK); + assert!(fd.is_ok()); + } + + #[test] + fn read_empty_signalfd() { + let mask = SigSet::empty(); + let mut fd = SignalFd::with_flags(&mask, SFD_NONBLOCK).unwrap(); + + let res = fd.read_signal(); + assert_eq!(res, Ok(None)); + } +} diff --git a/test/test_signalfd.rs b/test/test_signalfd.rs new file mode 100644 index 00000000..bb8941fa --- /dev/null +++ b/test/test_signalfd.rs @@ -0,0 +1,30 @@ +extern crate nix; + +use nix::unistd; + +#[cfg(any(target_os = "linux", target_os = "android"))] +use nix::sys::signalfd::*; + +#[cfg(any(target_os = "linux", target_os = "android"))] +fn main() { + let mut mask = SigSet::empty(); + mask.add(signal::SIGUSR1).unwrap(); + mask.thread_block().unwrap(); + + let mut fd = SignalFd::new(&mask).unwrap(); + + let pid = unistd::getpid(); + signal::kill(pid, signal::SIGUSR1).unwrap(); + + let res = fd.read_signal(); + assert!(res.is_ok()); + + let opt = res.ok().unwrap(); + assert!(opt.is_some()); + + let info = opt.unwrap(); + assert_eq!(info.ssi_signo as i32, signal::SIGUSR1); +} + +#[cfg(not(any(target_os = "linux", target_os = "android")))] +fn main() {} |