1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
use std::marker::PhantomData;

use libc::pid_t;
use nix::sys::wait::{waitpid, WNOHANG, WUNTRACED, WCONTINUED};
use nix::errno::{EINTR, ECHILD};
use nix::Error;

use {ExitStatus, SigNum};

/// A non-blocking iteration over zombie processes
///
/// Use `reap_zombies()` to create one, and read docs there
pub struct ZombieIterator(PhantomData<u8>);


impl Iterator for ZombieIterator {
    type Item = (pid_t, ExitStatus);

    fn next(&mut self) -> Option<(pid_t, ExitStatus)> {
        use nix::sys::wait::WaitStatus::*;
        loop {
            match waitpid(-1, Some(WNOHANG)) {
                Ok(Exited(pid, status)) => {
                    return Some((pid, ExitStatus::Exited(status)));
                }
                Ok(Signaled(pid, sig, core)) => {
                    return Some((pid, ExitStatus::Signaled(sig, core)));
                }
                Ok(Stopped(_, _)) => continue,
                Ok(Continued(_)) => continue,
                Ok(StillAlive) => return None,
                Err(Error::Sys(EINTR)) => continue,
                Err(Error::Sys(ECHILD)) => return None,
                Err(Error::InvalidPath) => unreachable!(),
                Err(Error::Sys(x)) => {
                    panic!("Unexpected waitpid error: {:?}", x);
                }
            }
        }
    }
}


/// Creates iterator over zombie processes
///
/// On each iteration it calls `waitpid()` and returns child pid and exit
/// status if there is zombie process. The operation is non-blocking. The
/// iterator is exhausted when there are no zombie process at the moment,
///
/// Alternatively see a more comprehensive `child_events()` function.
///
/// # Example
///
/// So waiting for all processes to finish may look like this:
///
/// ```ignore
///     while alive.len() > 0 {
///         sigwait()
///         for (pid, status) in zombies() {
///             alive.remove(pid);
///         }
///     }
/// ```
///
/// # Important Notes
///
/// * If you are using this function you can't reliably use `Child::wait`
///   any more.
/// * If you got `SIGCHLD` you *must* exhaust this iterator until waiting for
///   next signal, or you will have zombie processes around
pub fn reap_zombies() -> ZombieIterator { ZombieIterator(PhantomData) }


/// The event returned from `child_events()` iterator
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ChildEvent {
    /// Child is dead, similar to what returned by `reap_zombies()`
    Death(pid_t, ExitStatus),
    /// Child is stopped on a signal SigNum
    Stop(pid_t, SigNum),
    /// Child is continued (SIGCONT sent)
    Continue(pid_t),
}


/// A non-blocking iteration over zombies and child stops
///
/// Use `child_events()` to create one, and read docs there
pub struct ChildEventsIterator(PhantomData<u8>);


impl Iterator for ChildEventsIterator {
    type Item = ChildEvent;

    fn next(&mut self) -> Option<ChildEvent> {
        use self::ChildEvent::*;
        use nix::sys::wait::WaitStatus::*;
        loop {
            match waitpid(-1, Some(WNOHANG | WUNTRACED | WCONTINUED)) {
                Ok(Exited(pid, status)) => {
                    return Some(Death(pid, ExitStatus::Exited(status)));
                }
                Ok(Signaled(pid, sig, core)) => {
                    return Some(Death(pid, ExitStatus::Signaled(sig, core)));
                }
                Ok(Stopped(pid, sig)) => return Some(Stop(pid, sig)),
                Ok(Continued(pid)) => return Some(Continue(pid)),
                Ok(StillAlive) => return None,
                Err(Error::Sys(EINTR)) => continue,
                Err(Error::Sys(ECHILD)) => return None,
                Err(Error::InvalidPath) => unreachable!(),
                Err(Error::Sys(x)) => {
                    panic!("Unexpected waitpid error: {:?}", x);
                }
            }
        }
    }
}


/// Creates iterator over child events
///
/// On each iteration it calls `waitpid()` and returns one of the
/// events described in `ChildEvent`.
///
/// The operation is non-blocking. The iterator is exhausted when there are no
/// zombie process at the moment.
///
/// Alternatively see a simpler `reap_zombies()` function.
///
/// # Example
///
/// So waiting for all processes to finish may look like this:
///
/// ```ignore
///     while alive.len() > 0 {
///         sigwait()
///         for event in zombies() {
///             match event {
///                 Death(pid, _) => alive.remove(pid),
///                 Stop(..) => {}
///                 Continue(..) => {}
///         }
///     }
/// ```
///
/// # Important Notes
///
/// * If you are using this function you can't reliably use `Child::wait`
///   any more.
/// * If you got `SIGCHLD` you *must* exhaust this iterator until waiting for
///   next signal, or you will have zombie processes around
pub fn child_events() -> ChildEventsIterator {
    ChildEventsIterator(PhantomData)
}