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:
2024-09-06 23:16:10 +00:00
parent 9814cb5ad7
commit ed7c5ef89a

View File

@@ -1,4 +1,5 @@
// vim: set shiftwidth=2 : // vim: set shiftwidth=2 :
use errors;
use errors::ext; use errors::ext;
use fmt; use fmt;
use fs; use fs;
@@ -11,6 +12,7 @@ use rt;
use rt::ext; use rt::ext;
use strings; use strings;
use unix; use unix;
use unix::signal;
export fn namespace_restrict(what: *resources) void = { export fn namespace_restrict(what: *resources) void = {
// record the uid and gid of the initial namespace, so that we can re-map them // 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) = { fn fork_and_propagate() (void | os::exec::error) = {
match (os::exec::fork()?) { match (os::exec::fork()?) {
case let child_pid: os::exec::process => 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); 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); log::printfln("[namespace/fork] child exited with {}; forwarding as {}", status.status, rc);
os::exit(rc); // propagate exit code os::exit(rc); // propagate exit code
case => log::println("[namespace/fork] continuing as child"); 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 // reconfigures all the mounts so that after this call the only paths accessible
// are those reachable from the provided `paths`. // are those reachable from the provided `paths`.
// N.B.: this function does NOT preserve the current working directory. // N.B.: this function does NOT preserve the current working directory.