Add -r/--run and -c/--cleanup, fix nsenter under root

-r/--run mounts an image, runs a command under bwrap in the
foreground (default /bin/sh, overridable via -- <command> [args...]),
then unmounts and cleans up when it exits. -c/--cleanup deletes a
layer and its ancestor chain from local storage (containers-storage
delete-layer, walking parents via `layer --json`), since -u only ever
unmounted.

bwrap needs to see the merged mount from inside the private namespace
containers-storage mount creates when running rootless; run_bwrap()
locates the live fuse-overlayfs process and runs bwrap via nsenter
into its namespaces. When running as root no such namespace exists
(containers-storage doesn't need to reexec for privilege), so nsenter
fails with EINVAL; detect geteuid() == 0 and skip it automatically
there. -n/--no-nsenter forces it off manually for any other case.

process.cpp gains run_process_foreground() (inherited stdio, for the
interactive bwrap run) and the relocated find_in_path(), now shared
with bwrap.cpp's nsenter lookup.

Also: meson test only ran -m, leaking a layer on every run; it now
runs tests/run_test.py, which drives mount -> umount -> cleanup and
fails if any step does.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-17 07:44:24 +00:00
parent 839551a648
commit c0bef0d989
10 changed files with 525 additions and 98 deletions
+39
View File
@@ -0,0 +1,39 @@
#!/usr/bin/env python3
"""Runs the mount -> umount -> cleanup cycle against the fixture image and checks
that every step succeeds, so `meson test` doesn't leak a layer on every run.
"""
import re
import subprocess
import sys
def run(slocker_lite: str, *args: str) -> subprocess.CompletedProcess:
return subprocess.run([slocker_lite, *args], capture_output=True, text=True)
def main() -> int:
slocker_lite, fixture_tar = sys.argv[1], sys.argv[2]
mount = run(slocker_lite, "-m", fixture_tar)
if mount.returncode != 0:
print(mount.stderr, file=sys.stderr)
return mount.returncode
match = re.search(r"\(layer (\S+)\)", mount.stdout)
if not match:
print(f"could not find layer ID in mount output: {mount.stdout!r}", file=sys.stderr)
return 1
layer_id = match.group(1)
for args in (("-u", layer_id), ("-c", layer_id)):
result = run(slocker_lite, *args)
if result.returncode != 0:
print(result.stderr, file=sys.stderr)
return result.returncode
return 0
if __name__ == "__main__":
sys.exit(main())