Skip to content

Commit

Permalink
sys/unix/process: Reset signal behavior before exec
Browse files Browse the repository at this point in the history
Make sure that child processes don't get affected by libstd's desire to
ignore SIGPIPE, nor a third-party library's signal mask (which is needed
to use either a signal-handling thread correctly or to use signalfd /
kqueue correctly).
  • Loading branch information
geofft committed Jun 22, 2015
1 parent 56d904c commit cae0051
Show file tree
Hide file tree
Showing 3 changed files with 124 additions and 2 deletions.
13 changes: 11 additions & 2 deletions src/libstd/sys/unix/c.rs
Expand Up @@ -25,7 +25,7 @@
#![allow(non_camel_case_types)]

pub use self::signal_os::{sigaction, siginfo, sigset_t, sigaltstack};
pub use self::signal_os::{SA_ONSTACK, SA_SIGINFO, SIGBUS, SIGSTKSZ};
pub use self::signal_os::{SA_ONSTACK, SA_SIGINFO, SIGBUS, SIGSTKSZ, SIG_SETMASK};

use libc;

Expand Down Expand Up @@ -112,6 +112,7 @@ pub struct passwd {
pub type sighandler_t = *mut libc::c_void;

pub const SIG_DFL: sighandler_t = 0 as sighandler_t;
pub const SIG_ERR: sighandler_t = !0 as sighandler_t;

extern {
pub fn getsockopt(sockfd: libc::c_int,
Expand All @@ -135,6 +136,8 @@ extern {
oss: *mut sigaltstack) -> libc::c_int;

pub fn sigemptyset(set: *mut sigset_t) -> libc::c_int;
pub fn pthread_sigmask(how: libc::c_int, set: *const sigset_t,
oldset: *mut sigset_t) -> libc::c_int;

#[cfg(not(target_os = "ios"))]
pub fn getpwuid_r(uid: libc::uid_t,
Expand All @@ -155,7 +158,7 @@ extern {
#[cfg(any(target_os = "linux",
target_os = "android"))]
mod signal_os {
pub use self::arch::{SA_ONSTACK, SA_SIGINFO, SIGBUS,
pub use self::arch::{SA_ONSTACK, SA_SIGINFO, SIGBUS, SIG_SETMASK,
sigaction, sigaltstack};
use libc;

Expand Down Expand Up @@ -216,6 +219,8 @@ mod signal_os {

pub const SIGBUS: libc::c_int = 7;

pub const SIG_SETMASK: libc::c_int = 2;

#[cfg(target_os = "linux")]
#[repr(C)]
pub struct sigaction {
Expand Down Expand Up @@ -263,6 +268,8 @@ mod signal_os {

pub const SIGBUS: libc::c_int = 10;

pub const SIG_SETMASK: libc::c_int = 3;

#[cfg(all(target_os = "linux", not(target_env = "musl")))]
#[repr(C)]
pub struct sigaction {
Expand Down Expand Up @@ -321,6 +328,8 @@ mod signal_os {
#[cfg(not(any(target_os = "macos", target_os = "ios")))]
pub const SIGSTKSZ: libc::size_t = 40960;

pub const SIG_SETMASK: libc::c_int = 3;

#[cfg(any(target_os = "macos",
target_os = "ios"))]
pub type sigset_t = u32;
Expand Down
74 changes: 74 additions & 0 deletions src/libstd/sys/unix/process.rs
Expand Up @@ -17,6 +17,7 @@ use ffi::{OsString, OsStr, CString, CStr};
use fmt;
use io::{self, Error, ErrorKind};
use libc::{self, pid_t, c_void, c_int, gid_t, uid_t};
use mem;
use ptr;
use sys::fd::FileDesc;
use sys::fs::{File, OpenOptions};
Expand Down Expand Up @@ -313,6 +314,23 @@ impl Process {
if !envp.is_null() {
*sys::os::environ() = envp as *const _;
}

// Reset signal handling so the child process starts in a
// standardized state. libstd ignores SIGPIPE, and signal-handling
// libraries often set a mask. Child processes inherit ignored
// signals and the signal mask from their parent, but most
// UNIX programs do not reset these things on their own, so we
// need to clean things up now to avoid confusing the program
// we're about to run.
let mut set: c::sigset_t = mem::uninitialized();
if c::sigemptyset(&mut set) != 0 ||
c::pthread_sigmask(c::SIG_SETMASK, &set, ptr::null_mut()) != 0 ||
libc::funcs::posix01::signal::signal(
libc::SIGPIPE, mem::transmute(c::SIG_DFL)
) == mem::transmute(c::SIG_ERR) {
fail(&mut output);
}

let _ = libc::execvp(*argv, argv);
fail(&mut output)
}
Expand Down Expand Up @@ -418,3 +436,59 @@ fn translate_status(status: c_int) -> ExitStatus {
ExitStatus::Signal(imp::WTERMSIG(status))
}
}

#[cfg(test)]
mod tests {
use super::*;
use prelude::v1::*;

use ffi::OsStr;
use mem;
use ptr;
use libc;
use sys::{self, c, cvt, pipe};

extern {
fn sigaddset(set: *mut c::sigset_t, signum: libc::c_int) -> libc::c_int;
}

#[test]
fn test_process_mask() {
unsafe {
// Test to make sure that a signal mask does not get inherited.
let cmd = Command::new(OsStr::new("cat"));
let (stdin_read, stdin_write) = sys::pipe::anon_pipe().unwrap();
let (stdout_read, stdout_write) = sys::pipe::anon_pipe().unwrap();

let mut set: c::sigset_t = mem::uninitialized();
let mut old_set: c::sigset_t = mem::uninitialized();
cvt(c::sigemptyset(&mut set)).unwrap();
cvt(sigaddset(&mut set, libc::SIGINT)).unwrap();
cvt(c::pthread_sigmask(c::SIG_SETMASK, &set, &mut old_set)).unwrap();

let cat = Process::spawn(&cmd, Stdio::Raw(stdin_read.raw()),
Stdio::Raw(stdout_write.raw()),
Stdio::None).unwrap();
drop(stdin_read);
drop(stdout_write);

cvt(c::pthread_sigmask(c::SIG_SETMASK, &old_set, ptr::null_mut())).unwrap();

cvt(libc::funcs::posix88::signal::kill(cat.id() as libc::pid_t, libc::SIGINT)).unwrap();
// We need to wait until SIGINT is definitely delivered. The
// easiest way is to write something to cat, and try to read it
// back: if SIGINT is unmasked, it'll get delivered when cat is
// next scheduled.
let _ = stdin_write.write(b"Hello");
drop(stdin_write);

// Either EOF or failure (EPIPE) is okay.
let mut buf = [0; 5];
if let Ok(ret) = stdout_read.read(&mut buf) {
assert!(ret == 0);
}

cat.wait().unwrap();
}
}
}
39 changes: 39 additions & 0 deletions src/test/run-pass/process-sigpipe.rs
@@ -0,0 +1,39 @@
// Copyright 2015 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

// ignore-android since the dynamic linker sets a SIGPIPE handler (to do
// a crash report) so inheritance is moot on the entire platform

// libstd ignores SIGPIPE, and other libraries may set signal masks.
// Make sure that these behaviors don't get inherited to children
// spawned via std::process, since they're needed for traditional UNIX
// filter behavior. This test checks that `yes | head` terminates
// (instead of running forever), and that it does not print an error
// message about a broken pipe.

use std::process;
use std::thread;

#[cfg(unix)]
fn main() {
// Just in case `yes` doesn't check for EPIPE...
thread::spawn(|| {
thread::sleep_ms(5000);
process::exit(1);
});
let output = process::Command::new("sh").arg("-c").arg("yes | head").output().unwrap();
assert!(output.status.success());
assert!(output.stderr.len() == 0);
}

#[cfg(not(unix))]
fn main() {
// Not worried about signal masks on other platforms
}

0 comments on commit cae0051

Please sign in to comment.