summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlex Gulyás <gulyas.alex@gmail.com>2015-09-03 21:20:53 +0200
committerCarl Lerche <me@carllerche.com>2015-09-10 15:35:35 -0700
commit36432e1a13f4340a437e6250f4c665742439a5aa (patch)
tree0d67f57c217a56c9fc5d0b6849f1fe410675e68d
parentf37e45628e44d86c16a6f02b38ea0a9d68e8b3d4 (diff)
downloadnix-36432e1a13f4340a437e6250f4c665742439a5aa.zip
Add signalfd support
-rw-r--r--Cargo.toml8
-rw-r--r--src/sys/mod.rs3
-rw-r--r--src/sys/signal.rs191
-rw-r--r--src/sys/signalfd.rs219
-rw-r--r--test/test_signalfd.rs30
5 files changed, 435 insertions, 16 deletions
diff --git a/Cargo.toml b/Cargo.toml
index 496485c1..8a5422fb 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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() {}