from mkosi.sandbox import Mount
from mkosi.tree import copy_tree, rmtree
from mkosi.types import PathString
-from mkosi.user import INVOKING_USER, become_root
+from mkosi.user import INVOKING_USER, become_root, become_root_cmd
from mkosi.util import StrEnum, flock, flock_or_die, try_or
from mkosi.versioncomp import GenericVersion
def start_swtpm(config: Config) -> Iterator[Path]:
with tempfile.TemporaryDirectory(prefix="mkosi-swtpm") as state:
# swtpm_setup is noisy and doesn't have a --quiet option so we pipe it's stdout to /dev/null.
- run(["swtpm_setup", "--tpm-state", state, "--tpm2", "--pcr-banks", "sha256", "--config", "/dev/null"],
- sandbox=config.sandbox(binary="swtpm_setup", mounts=[Mount(state, state)]),
- stdout=None if ARG_DEBUG.get() else subprocess.DEVNULL)
+ run(
+ ["swtpm_setup", "--tpm-state", state, "--tpm2", "--pcr-banks", "sha256", "--config", "/dev/null"],
+ sandbox=config.sandbox(
+ binary="swtpm_setup",
+ mounts=[Mount(state, state)],
+ ),
+ scope=scope_cmd(
+ name=f"mkosi-swtpm-{config.machine_or_name()}",
+ description=f"swtpm for {config.machine_or_name()}",
+ ),
+ stdout=None if ARG_DEBUG.get() else subprocess.DEVNULL,
+ )
cmdline = ["swtpm", "socket", "--tpm2", "--tpmstate", f"dir={state}"]
pass_fds=(sock.fileno(),),
sandbox=config.sandbox(binary="swtpm", mounts=[Mount(state, state)]),
) as (proc, innerpid):
- allocate_scope(
- config,
- name=f"mkosi-swtpm-{config.machine_or_name()}",
- pid=innerpid,
- description=f"swtpm for {config.machine_or_name()}",
- )
yield path
kill(proc, innerpid, signal.SIGTERM)
cmdline += ["--fd", str(SD_LISTEN_FDS_START)]
+ uid = gid = None
+ runas = []
+ if uidmap and os.getuid() != INVOKING_USER.uid:
+ uid = INVOKING_USER.uid
+ gid = INVOKING_USER.gid
+ elif not uidmap and os.getuid() != 0:
+ runas = become_root_cmd()
+
with spawn(
cmdline,
pass_fds=(sock.fileno(),),
# in the user namespace it spawns, so by specifying --uid 0 --gid 0 we'll get a userns with the current
# uid/gid mapped to root in the userns. --cap-add=all is required to make virtiofsd work. Since it drops
# capabilities itself, we don't bother figuring out the exact set of capabilities it needs.
- user=INVOKING_USER.uid if uidmap else None,
- group=INVOKING_USER.gid if uidmap else None,
- preexec_fn=become_root if not uidmap else None,
+ user=uid,
+ group=gid,
sandbox=config.sandbox(
binary=virtiofsd,
mounts=[Mount(directory, directory)],
options=["--uid", "0", "--gid", "0", "--cap-add", "all"],
+ setup=runas,
),
- ) as (proc, innerpid):
- allocate_scope(
- config,
+ scope=scope_cmd(
name=f"mkosi-virtiofsd-{name}",
- pid=innerpid,
description=f"virtiofsd for {directory}",
- )
+ user=uid,
+ group=gid,
+ ),
+ ) as (proc, innerpid):
yield path
kill(proc, innerpid, signal.SIGTERM)
Mount(f.name, "/etc/systemd/journal-remote.conf"),
],
),
- user=config.forward_journal.parent.stat().st_uid if INVOKING_USER.invoked_as_root else None,
- group=config.forward_journal.parent.stat().st_gid if INVOKING_USER.invoked_as_root else None,
- foreground=False,
- ) as (proc, innerpid):
- allocate_scope(
- config,
+ scope=scope_cmd(
name=f"mkosi-journal-remote-{config.machine_or_name()}",
- pid=innerpid,
description=f"mkosi systemd-journal-remote for {config.machine_or_name()}",
- )
+ user=config.forward_journal.parent.stat().st_uid if INVOKING_USER.invoked_as_root else None,
+ group=config.forward_journal.parent.stat().st_gid if INVOKING_USER.invoked_as_root else None,
+ ),
+ foreground=False,
+ ) as (proc, innerpid):
yield
kill(proc, innerpid, signal.SIGTERM)
p.unlink(missing_ok=True)
-def allocate_scope(config: Config, *, name: str, pid: int, description: str) -> None:
- if os.getuid() != 0 and "DBUS_SESSION_BUS_ADDRESS" not in os.environ:
- return
-
- if (
- os.getuid() == 0 and
- "DBUS_SYSTEM_ADDRESS" not in os.environ and
- not Path("/run/dbus/system_bus_socket").exists()
- ):
- return
-
- scope = run(
- ["systemd-escape", "--mangle", f"{name}.scope"],
- stdout=subprocess.PIPE,
- foreground=False,
- ).stdout.strip()
-
- run(
- [
- "busctl",
- "call",
- "--system" if os.getuid() == 0 else "--user",
- "--quiet",
- "org.freedesktop.systemd1",
- "/org/freedesktop/systemd1",
- "org.freedesktop.systemd1.Manager",
- "StartTransientUnit",
- "ssa(sv)a(sa(sv))",
- scope,
- "fail",
- "4",
- "Description", "s", description,
- "CollectMode", "s", "inactive-or-failed",
- "PIDs", "au", "1", str(pid),
- "AddRef", "b", "1",
- "0",
- ],
- foreground=False,
- env=os.environ | config.environment,
- sandbox=config.sandbox(binary="busctl", relaxed=True),
- )
+def scope_cmd(
+ name: str,
+ description: str,
+ user: Optional[int] = None,
+ group: Optional[int] = None,
+) -> list[str]:
+ return [
+ "systemd-run",
+ "--system" if os.getuid() == 0 else "--user",
+ *(["--quiet"] if not ARG_DEBUG.get() else []),
+ "--unit", name,
+ "--description", description,
+ "--scope",
+ "--collect",
+ *(["--uid", str(user)] if user is not None else []),
+ *(["--gid", str(group)] if group is not None else []),
+ ]
def register_machine(config: Config, pid: int, fname: Path) -> None:
sys.stderr.fileno(),
)
+ name = f"mkosi-{config.machine_or_name().replace('_', '-')}"
with spawn(
cmdline,
stdin=stdin,
log=False,
foreground=True,
sandbox=config.sandbox(binary=None, network=True, devices=True, relaxed=True),
+ scope=scope_cmd(name=name, description=f"mkosi Virtual Machine {name}"),
) as (proc, innerpid):
# We have to close these before we wait for qemu otherwise we'll deadlock as qemu will never exit.
for fd in qemu_device_fds.values():
os.close(fd)
- name = f"mkosi-{config.machine_or_name().replace('_', '-')}"
- allocate_scope(
- config,
- name=name,
- pid=innerpid,
- description=f"mkosi Virtual Machine {name}",
- )
register_machine(config, innerpid, fname)
if proc.wait() == 0 and (status := int(notifications.get("EXIT_STATUS", 0))):
preexec_fn: Optional[Callable[[], None]] = None,
success_exit_status: Sequence[int] = (0,),
sandbox: AbstractContextManager[Sequence[PathString]] = contextlib.nullcontext([]),
+ scope: Sequence[str] = (),
) -> CompletedProcess:
if input is not None:
assert stdin is None # stdin and input cannot be specified together
preexec_fn=preexec_fn,
success_exit_status=success_exit_status,
sandbox=sandbox,
+ scope=scope,
innerpid=False,
) as (process, _):
out, err = process.communicate(input)
preexec_fn: Optional[Callable[[], None]] = None,
success_exit_status: Sequence[int] = (0,),
sandbox: AbstractContextManager[Sequence[PathString]] = contextlib.nullcontext([]),
+ scope: Sequence[str] = (),
innerpid: bool = True,
) -> Iterator[tuple[Popen, int]]:
assert sorted(set(pass_fds)) == list(pass_fds)
if "TMPDIR" in os.environ:
env["TMPDIR"] = os.environ["TMPDIR"]
+ if scope:
+ if not find_binary("systemd-run"):
+ scope = []
+ elif os.getuid() != 0 and "DBUS_SESSION_BUS_ADDRESS" in os.environ and "XDG_RUNTIME_DIR" in os.environ:
+ env["DBUS_SESSION_BUS_ADDRESS"] = os.environ["DBUS_SESSION_BUS_ADDRESS"]
+ env["XDG_RUNTIME_DIR"] = os.environ["XDG_RUNTIME_DIR"]
+ elif os.getuid() == 0 and "DBUS_SYSTEM_ADDRESS" in os.environ:
+ env["DBUS_SYSTEM_ADDRESS"] = os.environ["DBUS_SYSTEM_ADDRESS"]
+ else:
+ scope = []
+
+ if scope:
+ user = group = None
+
for e in ("SYSTEMD_LOG_LEVEL", "SYSTEMD_LOG_LOCATION"):
if e in os.environ:
env[e] = os.environ[e]
try:
with subprocess.Popen(
- prefix + cmdline,
+ [*scope, *prefix, *cmdline],
stdin=stdin,
stdout=stdout,
stderr=stderr,
log_process_failure(prefix, cmdline, returncode)
if ARG_DEBUG_SHELL.get():
subprocess.run(
- [*prefix, "bash"],
+ [*scope, *prefix, "bash"],
check=False,
stdin=sys.stdin,
text=True,