`--unshare-ipc`
: Specifying this option makes `mkosi-sandbox` unshare an IPC namespace if possible.
+`--exec-fd FD`
+: The specified `FD` will be closed when `mkosi-sandbox` calls `execvp()`. This is useful
+ to wait until all setup logic has completed before continuing execution in the parent
+ process invoking `mkosi-sandbox`.
+
`--version`
: Show package version.
return CompletedProcess(cmdline, process.returncode, out, err)
+def fd_move_above(fd: int, above: int) -> int:
+ dup = fcntl.fcntl(fd, fcntl.F_DUPFD, above)
+ os.close(fd)
+ return dup
+
+
def preexec(
*,
foreground: bool,
prefix = [os.fspath(x) for x in sbx]
if prefix:
- prefix += ["--"]
+ prfd, pwfd = os.pipe2(os.O_CLOEXEC)
+
+ # Make sure the write end of the pipe (which we pass to the subprocess) is higher than all the
+ # file descriptors we'll pass to the subprocess, so that it doesn't accidentally get closed by
+ # the logic in preexec().
+ if pass_fds:
+ pwfd = fd_move_above(pwfd, list(pass_fds)[-1])
+
+ exec_prefix = ["--exec-fd", f"{SD_LISTEN_FDS_START + len(pass_fds)}", "--"]
+ pass_fds = [*pass_fds, pwfd]
+ else:
+ exec_prefix = []
+ prfd, pwfd = None, None
try:
with subprocess.Popen(
- [*prefix, *cmdline],
+ [*prefix, *exec_prefix, *cmdline],
stdin=stdin,
stdout=stdout,
stderr=stderr,
pass_fds=pass_fds,
),
) as proc:
+ if pwfd is not None:
+ os.close(pwfd)
+
+ if prfd is not None:
+ os.read(prfd, 1)
+ os.close(prfd)
+
+ def failed() -> bool:
+ return check and (rc := proc.poll()) is not None and rc not in success_exit_status
+
try:
- yield proc
+ # Don't bother yielding if we've already failed by the time we get here. We'll raise an
+ # exception later on so it's not a problem that we don't yield at all.
+ if not failed():
+ yield proc
except BaseException:
proc.terminate()
raise
finally:
returncode = proc.wait()
- if check and returncode not in success_exit_status:
+ if failed():
if log:
log_process_failure(prefix, cmd, returncode)
if ARG_DEBUG_SHELL.get():
CLONE_NEWUSER = 0x10000000
EPERM = 1
ENOENT = 2
+F_GETFD = 1
+F_SETFD = 2
+FD_CLOEXEC = 1
LINUX_CAPABILITY_U32S_3 = 2
LINUX_CAPABILITY_VERSION_3 = 0x20080522
MNT_DETACH = 2
libc.umount2.argtypes = (ctypes.c_char_p, ctypes.c_int)
libc.capget.argtypes = (ctypes.c_void_p, ctypes.c_void_p)
libc.capset.argtypes = (ctypes.c_void_p, ctypes.c_void_p)
+libc.fcntl.argtypes = (ctypes.c_int, ctypes.c_int, ctypes.c_int)
def oserror(filename: str = "") -> None:
return os.path.commonpath((one, two)) == two
+def fd_make_cloexec(fd: int) -> None:
+ flags = libc.fcntl(fd, F_GETFD, 0)
+ libc.fcntl(fd, F_SETFD, flags | FD_CLOEXEC)
+
+
class FSOperation:
def __init__(self, dst: str) -> None:
self.dst = dst
--suppress-chown Make chown() syscalls in the sandbox a noop
--unshare-net Unshare the network namespace if possible
--unshare-ipc Unshare the IPC namespace if possible
+ --exec-fd FD Close FD before execve()
See the mkosi-sandbox(1) man page for details.\
"""
unshare_net = True
elif arg == "--unshare-ipc":
unshare_ipc = True
+ elif arg == "--exec-fd":
+ fd_make_cloexec(int(argv.pop()))
elif arg.startswith("-"):
raise ValueError(f"Unrecognized option {arg}")
else: