Handle SIGINT/SIGTERM in run_process_foreground for -r

Previously, Ctrl-C or `kill` during -r's bwrap run left slocker_lite
killed outright (default signal disposition), skipping
run_container()'s unmount/cleanup entirely and leaving the layer
imported and/or mounted as garbage.

run_process_foreground() now installs a handler around its waitpid()
that forwards the signal to the running child (kill(), async-signal-
safe) and keeps waiting (EINTR-retry loop) instead of dying. Once the
child actually exits, control returns normally so the existing
unmount/cleanup runs as usual.

Verified with a backgrounded `-r ... -- /bin/sleep 30`, sending both
SIGINT and SIGTERM to the outer slocker_lite PID: process tree exits
cleanly, unmount+delete-layer both run, containers-storage layers
returns to baseline.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-21 05:10:46 +00:00
parent b8f4680745
commit f88b4c08e7
2 changed files with 41 additions and 2 deletions
+6 -1
View File
@@ -38,7 +38,12 @@ Source layout (all under `src/`):
- `process.{h,cpp}` — argv-based subprocess helpers (fork/execvp, no shell): - `process.{h,cpp}` — argv-based subprocess helpers (fork/execvp, no shell):
`run_process()` captures stdout (used for `containers-storage` calls), `run_process()` captures stdout (used for `containers-storage` calls),
`run_process_foreground()` inherits all of stdio (used for the interactive `bwrap` `run_process_foreground()` inherits all of stdio (used for the interactive `bwrap`
run). Also `find_in_path()`, a shared `$PATH` lookup. run). Also `find_in_path()`, a shared `$PATH` lookup. `run_process_foreground()`
installs a SIGINT/SIGTERM handler around its `waitpid()` that forwards the signal
to the running child and keeps waiting instead of letting the default disposition
kill `slocker_lite` itself — without this, Ctrl-C (or `kill`) during `-r`'s `bwrap`
run would skip `run_container()`'s unmount/cleanup entirely, leaving the layer
imported and/or mounted.
Errors are logged via `spdlog::error`; every external command is also traced at debug Errors are logged via `spdlog::error`; every external command is also traced at debug
level in `run_process()`/`run_process_foreground()` (`src/process.cpp`) — visible via level in `run_process()`/`run_process_foreground()` (`src/process.cpp`) — visible via
+35 -1
View File
@@ -19,6 +19,8 @@
#include <sys/wait.h> #include <sys/wait.h>
#include <unistd.h> #include <unistd.h>
#include <cerrno>
#include <csignal>
#include <cstdlib> #include <cstdlib>
#include <cstring> #include <cstring>
@@ -27,6 +29,21 @@
namespace { namespace {
// Set by run_process_foreground() before it blocks in waitpid(), so the signal
// handler below has a pid to forward SIGINT/SIGTERM to (a handler can't take extra
// arguments, and this is the conventional way to pass it state).
volatile sig_atomic_t g_foreground_child_pid = 0;
// Forwards the signal to the running foreground child instead of letting the
// default disposition kill this process outright -- without this, Ctrl-C or a
// `kill` during `-r`'s bwrap run skips run_container()'s unmount/cleanup entirely,
// leaving the layer imported and/or mounted. kill() is async-signal-safe.
void forward_signal_to_foreground_child(int sig) {
if (g_foreground_child_pid > 0) {
kill(g_foreground_child_pid, sig);
}
}
std::vector<char*> to_c_argv(const std::vector<std::string>& argv) { std::vector<char*> to_c_argv(const std::vector<std::string>& argv) {
std::vector<char*> c_argv; std::vector<char*> c_argv;
c_argv.reserve(argv.size() + 1); c_argv.reserve(argv.size() + 1);
@@ -107,8 +124,25 @@ int run_process_foreground(const std::vector<std::string>& argv) {
_exit(127); _exit(127);
} }
g_foreground_child_pid = pid;
struct sigaction action = {};
action.sa_handler = forward_signal_to_foreground_child;
sigemptyset(&action.sa_mask);
struct sigaction old_sigint;
struct sigaction old_sigterm;
sigaction(SIGINT, &action, &old_sigint);
sigaction(SIGTERM, &action, &old_sigterm);
int status = 0; int status = 0;
waitpid(pid, &status, 0); pid_t wait_result;
do {
wait_result = waitpid(pid, &status, 0);
} while (wait_result == -1 && errno == EINTR);
sigaction(SIGINT, &old_sigint, nullptr);
sigaction(SIGTERM, &old_sigterm, nullptr);
g_foreground_child_pid = 0;
int exit_code = WIFEXITED(status) ? WEXITSTATUS(status) : -1; int exit_code = WIFEXITED(status) ? WEXITSTATUS(status) : -1;
if (exit_code != 0) { if (exit_code != 0) {