bunpen: forward signals to the child
note that pid namespaces will silently not deliver signals to PID 1 for which no handler is installed... i'll have to either install an intermediary PID 1 which forwards to the real process, or peek into /proc/PID/status to check if the signal is deliverable before/after sending it (but that's racy, and eww parsing)
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
// vim: set shiftwidth=2 :
|
||||
use errors;
|
||||
use errors::ext;
|
||||
use fmt;
|
||||
use fs;
|
||||
@@ -11,6 +12,7 @@ use rt;
|
||||
use rt::ext;
|
||||
use strings;
|
||||
use unix;
|
||||
use unix::signal;
|
||||
|
||||
export fn namespace_restrict(what: *resources) void = {
|
||||
// record the uid and gid of the initial namespace, so that we can re-map them
|
||||
@@ -99,14 +101,98 @@ type ns_ctx = struct {
|
||||
fn fork_and_propagate() (void | os::exec::error) = {
|
||||
match (os::exec::fork()?) {
|
||||
case let child_pid: os::exec::process =>
|
||||
let status = os::exec::wait(&child_pid)?;
|
||||
forward_signals(child_pid);
|
||||
log::println("[namespace/fork] signals configured");
|
||||
let status = wait_child(child_pid)?;
|
||||
let rc = rt::wexitstatus(status.status);
|
||||
// TODO: if the child exited due to a signal (e.g. SIGTERM), we (confusingly?) exit 0.
|
||||
// seems correct behavior may be to send the same termination signal to ourselves.
|
||||
log::printfln("[namespace/fork] child exited with {}; forwarding as {}", status.status, rc);
|
||||
os::exit(rc); // propagate exit code
|
||||
case => log::println("[namespace/fork] continuing as child");
|
||||
};
|
||||
};
|
||||
|
||||
fn wait_child(child_pid: os::exec::process) (os::exec::status | os::exec::error) = {
|
||||
for (true) {
|
||||
match (os::exec::wait(&child_pid)) {
|
||||
case let e: os::exec::error => match (e) {
|
||||
case errors::interrupted =>
|
||||
// i guess before the days of `poll`, `wait` had to wait on either the
|
||||
// child OR a signal sent to this pid; so we need to retry if the
|
||||
// reason we woke isn't because the child died...
|
||||
log::printfln("[namespace/fork] wait(child) interrupted (signal?)... will retry");
|
||||
case => return e;
|
||||
};
|
||||
case let status: os::exec::status => return status;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
const SIGNALS_TO_FORWARD = [
|
||||
unix::signal::sig::HUP,
|
||||
unix::signal::sig::INT,
|
||||
unix::signal::sig::QUIT,
|
||||
// unix::signal::sig::ILL,
|
||||
// unix::signal::sig::TRAP
|
||||
unix::signal::sig::ABRT,
|
||||
// unix::signal::sig::BUS,
|
||||
// unix::signal::sig::FPE,
|
||||
// unix::signal::sig::KILL (can't be caught or ignored)
|
||||
unix::signal::sig::USR1,
|
||||
// unix::signal::sig::SEGV,
|
||||
unix::signal::sig::USR2,
|
||||
// unix::signal::sig::PIPE,
|
||||
// unix::signal::sig::ALRM,
|
||||
unix::signal::sig::TERM,
|
||||
// unix::signal::sig::CHLD
|
||||
unix::signal::sig::CONT,
|
||||
// unix::signal::sig::STOP (can't be caught or ignored)
|
||||
unix::signal::sig::TSTP,
|
||||
unix::signal::sig::TTIN, // ??
|
||||
unix::signal::sig::TTOU, // ??
|
||||
unix::signal::sig::URG,
|
||||
unix::signal::sig::XCPU,
|
||||
unix::signal::sig::XFSZ,
|
||||
// unix::signal::sig::VTALRM,
|
||||
// unix::signal::sig::PROF,
|
||||
unix::signal::sig::WINCH,
|
||||
unix::signal::sig::IO,
|
||||
unix::signal::sig::POLL,
|
||||
unix::signal::sig::PWR,
|
||||
// unix::signal::sig::SYS,
|
||||
];
|
||||
|
||||
// there's no apparent way to pass state into a signal handler,
|
||||
// except via process globals
|
||||
let g_child: os::exec::process = 0;
|
||||
|
||||
// configure signal handlers so that any signal we receive is forwarded to the child.
|
||||
// this is important for systems which use signals for communication,
|
||||
// e.g. `kill -SIGUSR2 $(pidof wvkbd)` will toggle an on-screen-keyboard, but only if delivered.
|
||||
fn forward_signals(child: os::exec::process) void = {
|
||||
g_child = child;
|
||||
for (let sig .. SIGNALS_TO_FORWARD) {
|
||||
unix::signal::handle(sig, &forward_signal_handler);
|
||||
};
|
||||
// posix real-time extensions defines an additional set of general-purpose
|
||||
// signals for users. things like `wvkbd` use these (SIGRTMIN)
|
||||
for (let sig = 32; sig <= 64; sig +=1) {
|
||||
unix::signal::handle(sig: unix::signal::sig, &forward_signal_handler);
|
||||
};
|
||||
};
|
||||
|
||||
fn forward_signal_handler(sig: unix::signal::sig, info: *unix::signal::siginfo, ucontext: *opaque) void = {
|
||||
log::printfln("[namespace] forward signal {} to child {}", sig: int, g_child: int);
|
||||
// TODO: maybe we shouldn't swallow all errors; if the child somehow died, and
|
||||
// the signal is undeliverable, then abort?
|
||||
errors::ext::swallow(
|
||||
"[namespace] forward child signal {}",
|
||||
os::exec::sig(g_child, sig),
|
||||
sig: int,
|
||||
);
|
||||
};
|
||||
|
||||
// reconfigures all the mounts so that after this call the only paths accessible
|
||||
// are those reachable from the provided `paths`.
|
||||
// N.B.: this function does NOT preserve the current working directory.
|
||||
|
Reference in New Issue
Block a user