Add -D/--daemonize to run -r/--run sessions in the background

Single-fork daemonize: the child calls setsid() itself rather than
re-enabling bwrap's own --new-session, which was previously removed
(and stays that way) because it only detaches the deeply-nested
sandboxed command, leaving bwrap/nsenter/slocker-lite itself still
attached to the original session -- not real daemonization. Calling
setsid() in slocker-lite's own forked child, before it execs into
nsenter/bwrap, detaches the whole chain at once, since exec() never
changes session membership -- confirmed via ps -o sid,pgid,tty against
a running daemonized session.

The child also ignores SIGHUP (confirmed to survive exec() into bwrap,
unlike a real handler, which exec() resets) and redirects stdin to
/dev/null and stdout/stderr to a log file under
$XDG_STATE_HOME/slocker-lite/logs/ (session_log_file_path(), new
sibling to the existing session_pid_file_path() in pid_file.{h,cpp}).
The original process blocks briefly on a pipe until the child reports
the real bwrap pid (or exits without doing so), then prints it and
exits -- keeping "pid" meaning the same thing everywhere in this
codebase (the same one --list-processes/-e/--exec already use), rather
than introducing a separate daemon-supervisor pid. run_bwrap() gained
an on_bwrap_pid_known callback (bwrap.{h,cpp}) for this, invoked
alongside the existing session-lock creation at the same instant.

The daemonized child is what runs run_container()'s entire existing
body afterward, including the unmount/cleanup that already ran once
bwrap exits -- no separate watcher/reaper process.

Testing caught a real bug before this was correct: the log file gets
renamed from its initial (daemon-pid-named) filename to the final
<container_name>-<bwrap-pid>.log once the real pid is known, but the
parent had already been told the pre-rename path and was never updated
-- fixed by re-reporting the path over the same pipe after the rename.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gv3s5jckJKzh6JkMoi2Akz
This commit is contained in:
2026-08-23 16:41:07 +00:00
parent e7dac86eee
commit f904d33a11
10 changed files with 407 additions and 19 deletions
+18
View File
@@ -77,6 +77,7 @@ slocker-lite -V|--version
| `-u, --umount <layer-id>` | Unmount a previously mounted layer (the ID printed by `--mount`/`--run`, or from `containers-storage layers`). |
| `-c, --cleanup <layer-id>` | Delete a layer and its ancestor chain from local storage (unmount it first). |
| `-n, --no-nsenter` | With `--run`, bind the mount directly instead of `nsenter`-ing into `fuse-overlayfs`'s namespace. Automatic when running as root; use this to force it off otherwise. |
| `-D, --daemonize` | With `--run`, fork into the background: detaches from the controlling terminal (`setsid()`), ignores `SIGHUP`, and redirects stdin from `/dev/null` and stdout/stderr to a log file under `$XDG_STATE_HOME/slocker-lite/logs/`. Prints the session's pid and log path, then returns — the same pid `--list-processes`/`-e/--exec` use. |
| `--user <user>` | With `--run`, run the command as this user (name or numeric uid) instead of the image's own declared user (or root, if it declares none). Resolved against the image's own `/etc/passwd`. Only takes effect when `--run` executes as root. |
| `--group <group>` | With `--user`, use this group (name or numeric gid) instead of the user's primary group. |
| `--hostname <name>` | With `--run`, set the sandbox's hostname. Only takes effect if the running kernel supports `--unshare-uts`; ignored with a warning otherwise. |
@@ -117,6 +118,9 @@ sudo ./buildDir/slocker-lite -r myimage.tar --user git
# Run with extra environment variables, from flags and/or a file
./buildDir/slocker-lite -r myimage.tar --env FOO=bar --env-file ./app.env
# Run in the background; prints its pid and log file, then returns
./buildDir/slocker-lite -r myimage.tar -D
# List every OCI image tar in a directory
./buildDir/slocker-lite -l ./images
@@ -208,6 +212,20 @@ rather than joining the outer `bwrap` process's own namespaces, so the joined
command sees the container's process tree and hostname too, not just its
filesystem.
`-D/--daemonize` forks and detaches into the background by calling `setsid()`
itself, rather than re-enabling `bwrap`'s own `--new-session` — that flag only
detaches the deeply-nested sandboxed command, leaving `bwrap`/`nsenter` still
attached to the original session. Calling `setsid()` in `slocker-lite`'s own
forked child, before it execs into `nsenter`/`bwrap`, detaches the whole chain
at once (`exec()` never changes session membership), and correctly scopes
`bwrap`'s own `--die-with-parent` to that child. The child ignores `SIGHUP` and
redirects output to a log file before doing anything else; the original,
still-foreground process waits only long enough to learn the real session pid
(the same one `--list-processes`/`-e/--exec` use) before printing it and
returning — the detached child is what runs the entire session afterward,
including the same unmount/cleanup that always ran once the sandboxed command
exits.
See `CLAUDE.md` for the full architecture writeup (file-by-file breakdown, the
reasoning behind each of the above, and known gaps).