parent.close()
if sbx:
- mkosi.sandbox.main([os.fspath(s) for s in sbx])
+ mkosi.sandbox.enter([os.fspath(s) for s in sbx])
child.send(target(*args, **kwargs))
# if we get here we should have neither a prefix nor a setup command to execute and so we can
# execute the command directly.
- # mkosi.sandbox.main() updates os.environ but the environment passed to Popen() is not yet in
+ # mkosi.sandbox.enter() updates os.environ but the environment passed to Popen() is not yet in
# effect by the time the preexec function is called. To get around that, we update the
# environment ourselves here.
os.environ.clear()
os.environ.update(env)
- mkosi.sandbox.main(sandbox)
+ mkosi.sandbox.enter(sandbox)
# Python does its own executable lookup in $PATH before executing the preexec function, and
# hence before we have set up the sandbox which influences the lookup results. To get around
"""
-def is_main() -> bool:
- return __name__ == "__main__"
+class SandboxOSError(OSError):
+ # OSError carrying a human-readable message that the CLI prints to stderr. Library callers can
+ # catch OSError as usual; only the CLI wrapper inspects .message.
+ def __init__(self, errno: int, message: str, filename: str = "") -> None:
+ super().__init__(errno, os.strerror(errno), filename or None)
+ self.message = message
def oserror(syscall: str, filename: str = "", errno: int = 0) -> None:
errno = abs(errno) or ctypes.get_errno()
- if errno == ENOSYS and is_main():
- print(ENOSYS_MSG.format(syscall=syscall, kver=os.uname().version), file=sys.stderr)
+ if errno == ENOSYS:
+ raise SandboxOSError(
+ errno,
+ ENOSYS_MSG.format(syscall=syscall, kver=os.uname().version),
+ filename,
+ )
raise OSError(errno, os.strerror(errno), filename or None)
try:
unshare(CLONE_NEWUSER)
except OSError as e:
- if e.errno == EPERM and is_main():
- print(UNSHARE_EPERM_MSG, file=sys.stderr)
+ if e.errno == EPERM:
+ raise SandboxOSError(EPERM, UNSHARE_EPERM_MSG) from e
raise
finally:
os.write(event, ctypes.c_uint64(1))
"""
-def main(argv: list[str] = sys.argv[1:]) -> None:
+def enter(argv: list[str]) -> list[str]:
# We don't use argparse as it takes +- 10ms to import and since this is primarily for internal
# use, it's not necessary to have amazing UX for this CLI interface so it's trivial to write
# ourselves.
break
if argv:
- if not is_main():
- raise ValueError(f"A command line to execute can only be provided if {__name__} is executed")
-
argv.reverse()
- else:
- argv = ["bash"] if is_main() else []
# Make sure all destination paths are absolute.
for fsop in fsops:
except OSError as e:
# This can happen here as well as in become_user, it depends on exactly
# how the userns restrictions are implemented.
- if e.errno == EPERM and is_main():
- print(UNSHARE_EPERM_MSG, file=sys.stderr)
+ if e.errno == EPERM:
+ raise SandboxOSError(EPERM, UNSHARE_EPERM_MSG) from e
raise
# If we unshared the user namespace the mount propagation of root is changed to slave automatically.
os.environ["LISTEN_FDS"] = str(nfds)
os.environ["LISTEN_PID"] = str(os.getpid())
- if is_main():
- try:
- os.execvp(argv[0], argv)
- except OSError as e:
- # Let's return a recognizable error when the binary we're going to execute is not found.
- # We use 127 as that's the exit code used by shells when a program to execute is not found.
- if e.errno == ENOENT:
- sys.exit(127)
+ return argv
- raise
+
+def main(argv: list[str] = sys.argv[1:]) -> None:
+ try:
+ argv = enter(argv)
+ except SandboxOSError as e:
+ print(e.message, file=sys.stderr)
+ raise
+
+ argv = argv or ["bash"]
+
+ try:
+ os.execvp(argv[0], argv)
+ except OSError as e:
+ # Let's return a recognizable error when the binary we're going to execute is not found.
+ # We use 127 as that's the exit code used by shells when a program to execute is not found.
+ if e.errno == ENOENT:
+ sys.exit(127)
+
+ raise
-if is_main():
+if __name__ == "__main__":
main()