Skip to content

Improve Wait Documentation #651

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 16 commits into from
183 changes: 147 additions & 36 deletions src/sys/wait.rs
Original file line number Diff line number Diff line change
@@ -1,38 +1,83 @@
use libc::{self, c_int};
//! This module contains the `wait()` and `waitpid()` functions.
//!
//! These are used to wait on and obtain status information from child processes, which provide
//! more granular control over child management than the primitives provided by Rust's standard
//! library, and are critical in the creation of shells and managing jobs on *nix platforms.
//!
//! Manual Page: http://pubs.opengroup.org/onlinepubs/007908799/xsh/wait.html
//!
//! # Examples
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please provide a brief description of what this code does, either in text before the example or using comments within the code.

//!
//! ```rust
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add ,no_run at the end since this code example will fail when run.

//! use nix::sys::wait::*;
//! use nix::unistd::Pid;
//!
//! let pid = Pid::from_raw(17563);
//! loop {
//! match waitpid(PidGroup::ProcessGroupID(pid), WUNTRACED) {
//! Ok(WaitStatus::Exited(pid, status)) => {
//! println!("Process '{}' exited with status '{}'", pid, status);
//! break
//! },
//! Ok(WaitStatus::Stopped(pid, signal)) => {
//! println!("Process '{}' stopped with signal '{:?}'", pid, signal);
//! },
//! Ok(WaitStatus::Continued(pid)) => {
//! println!("Process '{}' continued", pid);
//! },
//! Ok(_) => (),
//! Err(why) => {
//! println!("waitpid returned an error code: {}", why);
//! break
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a semi-colon after break.

//! }
//! }
//! }
//! ```

use libc::{self, c_int, pid_t};
use {Errno, Result};
use unistd::Pid;

use sys::signal::Signal;

mod ffi {
use libc::{pid_t, c_int};

extern {
pub fn waitpid(pid: pid_t, status: *mut c_int, options: c_int) -> pid_t;
}
}

#[cfg(not(any(target_os = "linux",
target_os = "android")))]
libc_bitflags!(
pub flags WaitPidFlag: c_int {
WNOHANG,
WUNTRACED,
}
);

#[cfg(any(target_os = "linux",
target_os = "android"))]
libc_bitflags!(
/// Defines optional flags for the `waitpid` function.
pub flags WaitPidFlag: c_int {
/// Do not suspend execution of the calling thread if the status is not immediately
/// available for one of the child processes specified by pid.
WNOHANG,
/// The status of any child processes specified by pid that are stopped, and whose status
/// has not yet been reported since they stopped, shall also be reported to the requesting
/// process
WUNTRACED,
/// Waits for children that have terminated.
#[cfg(any(target_os = "android",
target_os = "freebsd",
target_os = "linux",
target_os = "netbsd"))]
WEXITED,
/// Report the status of any continued child process specified by pid whose status has not
/// been reported since it continued from a job control stop.
WCONTINUED,
WNOWAIT, // Don't reap, just poll status.
__WNOTHREAD, // Don't wait on children of other threads in this group
__WALL, // Wait on all children, regardless of type
/// Leave the child in a waitable state; a later wait call can be used to again retrieve
/// the child status information.
#[cfg(any(target_os = "android",
target_os = "freebsd",
target_os = "linux",
target_os = "netbsd"))]
WNOWAIT,
/// Don't wait on children of other threads in this group
#[cfg(any(target_os = "android", target_os = "linux"))]
__WNOTHREAD,
/// Wait for all children, regardless of type (clone or non-clone)
#[cfg(any(target_os = "android", target_os = "linux"))]
__WALL,
/// Wait for "clone" children only. If omitted then wait for "non-clone" children only.
/// (A "clone" child is one which delivers no signal, or a signal other than `SIGCHLD` to
/// its parent upon termination.) This option is ignored if `__WALL` is also specified.
#[cfg(any(target_os = "android", target_os = "linux"))]
__WCLONE,

}
);

Expand Down Expand Up @@ -93,8 +138,7 @@ pub enum WaitStatus {
StillAlive
}

#[cfg(any(target_os = "linux",
target_os = "android"))]
#[cfg(any(target_os = "android", target_os = "linux"))]
mod status {
use sys::signal::Signal;
use libc::c_int;
Expand Down Expand Up @@ -147,8 +191,7 @@ mod status {
}
}

#[cfg(any(target_os = "macos",
target_os = "ios"))]
#[cfg(any(target_os = "macos", target_os = "ios"))]
mod status {
use sys::signal::{Signal,SIGCONT};

Expand Down Expand Up @@ -254,7 +297,7 @@ fn decode(pid : Pid, status: i32) -> WaitStatus {
} else if status_additional == 0 {
WaitStatus::Stopped(pid, status::stop_signal(status))
} else {
WaitStatus::PtraceEvent(pid, status::stop_signal(status), status::stop_additional(status))
WaitStatus::PtraceEvent(pid, status::stop_signal(status), status)
}
}
} else {
Expand All @@ -270,24 +313,92 @@ fn decode(pid : Pid, status: i32) -> WaitStatus {
}
}

pub fn waitpid<P: Into<Option<Pid>>>(pid: P, options: Option<WaitPidFlag>) -> Result<WaitStatus> {
/// Designates whether the supplied `Pid` value is a process ID, process group ID,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here with the documentation.

/// specifies any child of the current process's group ID, or any child of the current process.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum PidGroup {
/// Signifies that the `Pid` is supposed to represent a process ID.
ProcessID(Pid),
/// Signifies that the `Pid` is supposed to represent a process group ID.
ProcessGroupID(Pid),
/// Signifies that any child process that belongs to the process group of the current process
/// should be selected.
AnyGroupChild,
/// Signifies that any child process that belongs to the current process should be selected.
AnyChild,
}

impl From<i32> for PidGroup {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be removed as it's unused and we shouldn't support this for our users.

fn from(pid: i32) -> PidGroup {
if pid > 0 {
PidGroup::ProcessID(Pid::from_raw(pid))
} else if pid < -1 {
PidGroup::ProcessGroupID(Pid::from_raw(pid))
} else if pid == 0 {
PidGroup::AnyGroupChild
} else {
PidGroup::AnyChild
}
}
}

/// Waits for and returns events that are received, with additional options.
///
/// The `pid` value may indicate either a process group ID, or process ID. The options
/// parameter controls the behavior of the function.
///
/// # Usage Notes
///
/// - If the value of the PID is `PidGroup::ProcessID(Pid)`, it will wait on the child
/// with that has the specified process ID.
/// - If the value of the PID is `PidGroup::ProcessGroupID(Pid)`, it will wait on any child that
/// belongs to the specified process group ID.
/// - If the value of the PID is `PidGroup::AnyGroupChild`, it will wait on any child process
/// that has the same group ID as the current process.
/// - If the value of the PID is `PidGroup::AnyChild`, it will wait on any child process of the
/// current process.
///
/// # Errors
///
/// - **ECHILD**: The process does not exist or is not a child of the current process.
/// - This may also happen if a child process has the `SIGCHLD` signal masked or set to
/// `SIG_IGN`.
/// - **EINTR**: `WNOHANG` was not set and either an unblocked signal or a `SIGCHLD` was caught.
/// - **EINVAL**: The supplied options were invalid.
pub fn waitpid<O>(pid: PidGroup, options: O) -> Result<WaitStatus>
where O: Into<Option<WaitPidFlag>>
{
use self::WaitStatus::*;

let mut status: i32 = 0;
let mut status = 0;
let options = options.into().map_or(0, |o| o.bits());

let option_bits = match options {
Some(bits) => bits.bits(),
None => 0
let pid = match pid {
PidGroup::ProcessID(pid) if pid < Pid::from_raw(-1) => -pid_t::from(pid),
PidGroup::ProcessGroupID(pid) if pid > Pid::from_raw(0) => -pid_t::from(pid),
PidGroup::ProcessID(pid) | PidGroup::ProcessGroupID(pid) => pid_t::from(pid),
PidGroup::AnyGroupChild => 0,
PidGroup::AnyChild => -1,
};

let res = unsafe { ffi::waitpid(pid.into().unwrap_or(Pid::from_raw(-1)).into(), &mut status as *mut c_int, option_bits) };
let res = unsafe { libc::waitpid(pid.into(), &mut status as *mut c_int, options) };

Ok(match try!(Errno::result(res)) {
Errno::result(res).map(|res| match res {
0 => StillAlive,
res => decode(Pid::from_raw(res), status),
})
}

/// Waits for and returns events from any child of the current process.
///
/// While waiting on the child, this function will return on events that indicate that the status
/// of that child has changed. It is directly equivalent to `waitpid(PidGroup::AnyChild, None)`.
///
/// # Errors
///
/// - **ECHILD**: The process does not exist or is not a child of the current process.
/// - This may also happen if a child process has the `SIGCHLD` signal masked or set to
/// `SIG_IGN`.
pub fn wait() -> Result<WaitStatus> {
waitpid(None, None)
waitpid(PidGroup::AnyChild, None)
}
3 changes: 2 additions & 1 deletion src/unistd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ impl fmt::Display for Gid {
///
/// Newtype pattern around `pid_t` (which is just alias). It prevents bugs caused by accidentally
/// passing wrong value.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, PartialOrd)]
pub struct Pid(pid_t);

impl Pid {
Expand Down Expand Up @@ -236,6 +236,7 @@ pub fn setpgid(pid: Pid, pgid: Pid) -> Result<()> {
let res = unsafe { libc::setpgid(pid.into(), pgid.into()) };
Errno::result(res).map(drop)
}

#[inline]
pub fn getpgid(pid: Option<Pid>) -> Result<Pid> {
let res = unsafe { libc::getpgid(pid.unwrap_or(Pid(0)).into()) };
Expand Down
4 changes: 2 additions & 2 deletions test/sys/test_wait.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ fn test_wait_signal() {
Ok(Child) => pause().unwrap_or(()),
Ok(Parent { child }) => {
kill(child, Some(SIGKILL)).ok().expect("Error: Kill Failed");
assert_eq!(waitpid(child, None), Ok(WaitStatus::Signaled(child, SIGKILL, false)));
assert_eq!(waitpid(PidGroup::ProcessID(child), None), Ok(WaitStatus::Signaled(child, SIGKILL, false)));
},
// panic, fork should never fail unless there is a serious problem with the OS
Err(_) => panic!("Error: Fork Failed")
Expand All @@ -28,7 +28,7 @@ fn test_wait_exit() {
match fork() {
Ok(Child) => unsafe { exit(12); },
Ok(Parent { child }) => {
assert_eq!(waitpid(child, None), Ok(WaitStatus::Exited(child, 12)));
assert_eq!(waitpid(PidGroup::ProcessID(child), None), Ok(WaitStatus::Exited(child, 12)));
},
// panic, fork should never fail unless there is a serious problem with the OS
Err(_) => panic!("Error: Fork Failed")
Expand Down
4 changes: 2 additions & 2 deletions test/test_unistd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ fn test_fork_and_waitpid() {
// assert that child was created and pid > 0
let child_raw: ::libc::pid_t = child.into();
assert!(child_raw > 0);
let wait_status = waitpid(child, None);
let wait_status = waitpid(PidGroup::ProcessID(child), None);
match wait_status {
// assert that waitpid returned correct status and the pid is the one of the child
Ok(WaitStatus::Exited(pid_t, _)) => assert!(pid_t == child),
Expand Down Expand Up @@ -135,7 +135,7 @@ macro_rules! execve_test_factory(
},
Parent { child } => {
// Wait for the child to exit.
waitpid(child, None).unwrap();
waitpid(PidGroup::ProcessID(child), None).unwrap();
// Read 1024 bytes.
let mut buf = [0u8; 1024];
read(reader, &mut buf).unwrap();
Expand Down