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.")
},
}
},
}
}