lib_grim_reaper/
lib.rs

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
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
#![deny(clippy::pedantic)]

use std::{cmp::Ordering, ffi::CString};

use anyhow::Context;
use fork::{fork, Fork};
use signal_hook::iterator::Signals;

#[allow(clippy::too_many_lines)]
/// # Errors
///
/// Returns an `anyhow::Error` if spawning or executing the reaper fails.
pub fn exec_reaper(program: &CString, args: &[CString]) -> anyhow::Result<()> {
    let canary_pid = unsafe { libc::getpid() };

    match fork()
        .map_err(|_| std::io::Error::last_os_error())
        .context("Failed to fork the wrapper into the canary and handler.")?
    {
        Fork::Parent(handler_pid) => {
            let mut signals = Signals::new(
                (1..32).filter(|signal| !signal_hook::consts::FORBIDDEN.contains(signal)),
            )
            .context("Failed to install the signal handler in the canary.")?;

            let status = loop {
                let mut status = 0;

                match unsafe { libc::waitpid(handler_pid, &mut status, libc::WNOHANG) }.cmp(&0) {
                    Ordering::Less => {
                        return Err(std::io::Error::last_os_error())
                            .context("Failed to wait for the handler to terminate.")
                    },
                    Ordering::Equal => (),
                    Ordering::Greater => break libc::WEXITSTATUS(status),
                };

                for signal in signals.wait() {
                    log::debug!(
                        "The CANARY {} has received the signal {}.",
                        canary_pid,
                        signal
                    );

                    if signal != libc::SIGCHLD {
                        log::debug!(
                            "The CANARY {} will send the signal {} to the HANDLER {}.",
                            handler_pid,
                            signal,
                            handler_pid
                        );

                        // Error is ignored as the handler may have already terminated
                        unsafe { libc::kill(handler_pid, signal) };
                    }
                }
            };

            log::debug!(
                "The CANARY {} will exit with status {}.",
                canary_pid,
                status
            );

            std::process::exit(status);
        },
        Fork::Child => {
            let handler_pid = unsafe { libc::getpid() };

            unsafe {
                if libc::prctl(libc::PR_SET_PDEATHSIG, libc::SIGCHLD) == -1 {
                    return Err(std::io::Error::last_os_error())
                        .context("Failed to setup the death signal in the handler.");
                }
                if libc::setsid() == -1 {
                    return Err(std::io::Error::last_os_error())
                        .context("Failed to create a new process group for the handler.");
                }
                if libc::prctl(libc::PR_SET_CHILD_SUBREAPER, 1) == -1 {
                    return Err(std::io::Error::last_os_error())
                        .context("Failed to make the handler a sub-reaper for its descendants.");
                }
            }

            // Check if the canary quit before the handler, if so abort
            if unsafe { libc::getppid() } != canary_pid {
                log::warn!(
                    "The CANARY {} has been terminated before the HANDLER {} could start.",
                    canary_pid,
                    handler_pid
                );

                std::process::abort();
            }

            let mut signals = Signals::new(
                (1..32).filter(|signal| !signal_hook::consts::FORBIDDEN.contains(signal)),
            )
            .context("Failed to install the signal handler in the handler.")?;

            match fork()
                .map_err(|_| std::io::Error::last_os_error())
                .context("Failed to fork the wrapper into the handler and worker.")?
            {
                Fork::Parent(worker_pid) => {
                    let status = 'outer: loop {
                        let mut status = 0;

                        match unsafe { libc::waitpid(worker_pid, &mut status, libc::WNOHANG) }
                            .cmp(&0)
                        {
                            Ordering::Less => {
                                return Err(std::io::Error::last_os_error())
                                    .context("Failed to wait for the worker to terminate.")
                            },
                            Ordering::Equal => (),
                            Ordering::Greater => break status,
                        };

                        for signal in signals.wait() {
                            log::debug!(
                                "The HANDLER {} has received the signal {}.",
                                handler_pid,
                                signal
                            );

                            if signal != libc::SIGCHLD {
                                log::debug!(
                                    "The HANDLER {} will send the signal {} to the WORKER group \
                                     {}.",
                                    handler_pid,
                                    signal,
                                    worker_pid
                                );

                                unsafe {
                                    // Error is ignored as the worker may have already terminated
                                    libc::kill(-worker_pid, signal);
                                }
                            } else if unsafe { libc::getppid() } != canary_pid {
                                log::debug!(
                                    "The HANDLER {} has been informed that the CANARY {} has died.",
                                    handler_pid,
                                    canary_pid
                                );
                                log::debug!(
                                    "The HANDLER {} will terminate the WORKER group {}.",
                                    handler_pid,
                                    worker_pid
                                );

                                // Error is ignored as the worker may have already terminated
                                unsafe {
                                    libc::kill(-worker_pid, libc::SIGKILL);
                                }

                                log::debug!(
                                    "The HANDLER {} will wait for the WORKER leader {}.",
                                    handler_pid,
                                    worker_pid
                                );

                                let mut status = 0;
                                if unsafe { libc::waitpid(worker_pid, &mut status, 0) } > 0 {
                                    break 'outer status;
                                }

                                return Err(std::io::Error::last_os_error())
                                    .context("Failed to wait for the worker to terminate.");
                            }
                        }
                    };

                    log::debug!(
                        "The HANDLER {} will wait for the WORKER group {}.",
                        handler_pid,
                        worker_pid
                    );

                    // Error means that we haved waited for all children
                    while unsafe { libc::waitpid(-1, std::ptr::null_mut(), libc::WNOHANG) } >= 0 {}

                    log::debug!(
                        "The HANDLER {} will exit with status {}.",
                        handler_pid,
                        status
                    );

                    std::process::exit(status);
                },
                Fork::Child => {
                    let worker_pid = unsafe { libc::getpid() };

                    if unsafe { libc::setsid() } == -1 {
                        return Err(std::io::Error::last_os_error())
                            .context("Failed to create a new process group for the worker.");
                    }

                    log::debug!(
                        "The WORKER {} will execute the {:?} command.",
                        worker_pid,
                        args
                    );

                    let mut args: Vec<*const libc::c_char> = std::iter::once(program.as_ptr())
                        .chain(args.iter().map(|s| s.as_ptr()))
                        .collect();
                    args.push(std::ptr::null());

                    // `execvp` only returns on error
                    unsafe { libc::execvp(program.as_ptr(), args.as_ptr()) };

                    Err(std::io::Error::last_os_error())
                        .context("Failed to execute the command in the worker.")
                },
            }
        },
    }
}