sys/unix/process: Reset signal behavior before exec
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).
This commit is contained in:
parent
56d904c4bb
commit
cae005162d
3 changed files with 124 additions and 2 deletions
|
|
@ -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;
|
||||
|
||||
|
|
@ -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,
|
||||
|
|
@ -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,
|
||||
|
|
@ -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;
|
||||
|
||||
|
|
@ -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 {
|
||||
|
|
@ -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 {
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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};
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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
src/test/run-pass/process-sigpipe.rs
Normal file
39
src/test/run-pass/process-sigpipe.rs
Normal file
|
|
@ -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
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue