From: Daan De Meyer Date: Wed, 20 Sep 2023 21:33:21 +0000 (+0200) Subject: Support booting directory images in qemu X-Git-Tag: v18~48^2~3 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=edc97537dd238c072485372b1ca43ce79dded54c;p=thirdparty%2Fmkosi.git Support booting directory images in qemu Using virtiofsd, we can boot straight into a virtiofs instance of a directory image. This does require the virtiofsd instance to run as (fake) root so we can't switch to the user running mkosi anymore when running unprivileged but that shouldn't be a problem. This also only works with kernels that have the virtiofs driver builtin which I don't think is the case in any major distros yet. --- diff --git a/mkosi/__init__.py b/mkosi/__init__.py index 405283e48..c5639fdfd 100644 --- a/mkosi/__init__.py +++ b/mkosi/__init__.py @@ -1130,7 +1130,7 @@ def install_uki(state: MkosiState, partitions: Sequence[Partition]) -> None: break if ( - state.config.output_format in (OutputFormat.cpio, OutputFormat.uki) and + state.config.output_format in (OutputFormat.cpio, OutputFormat.uki, OutputFormat.directory) and state.config.bootable == ConfigFeature.auto ): return @@ -2321,12 +2321,6 @@ def run_verb(args: MkosiArgs, presets: Sequence[MkosiConfig]) -> None: last = presets[-1] - if args.verb == Verb.qemu and last.output_format in ( - OutputFormat.directory, - OutputFormat.tar, - ): - die(f"{last.output_format} images cannot be booted in qemu.") - if args.verb in (Verb.shell, Verb.boot): opname = "acquire shell in" if args.verb == Verb.shell else "boot" if last.output_format in (OutputFormat.tar, OutputFormat.cpio): @@ -2346,7 +2340,6 @@ def run_verb(args: MkosiArgs, presets: Sequence[MkosiConfig]) -> None: for config in presets: try_import(f"mkosi.distributions.{config.distribution}") - invoked_as_root = os.getuid() == 0 name = InvokingUser.name() # Get the user UID/GID either on the host or in the user namespace running the build @@ -2413,11 +2406,9 @@ def run_verb(args: MkosiArgs, presets: Sequence[MkosiConfig]) -> None: # right after (and we're in a mount namespace so the /usr mount disappears when we exit) with mount_usr(last.tools_tree, umount=False), mount_passwd(name, uid, gid, umount=False): - # After mounting the last tools tree, if we're not going to execute systemd-nspawn, we don't need to - # be (fake) root anymore, so switch user to the invoking user. If we're going to invoke qemu and - # mkosi was executed as root, we also don't drop privileges as depending on the environment and - # options passed, running qemu might need root privileges as well. - if not args.verb.needs_root() and (args.verb != Verb.qemu or not invoked_as_root): + # After mounting the last tools tree, if we're not going to execute systemd-nspawn or qemu, we don't need to + # be (fake) root anymore, so switch user to the invoking user. + if not args.verb.needs_root() and args.verb != Verb.qemu: os.setresgid(gid, gid, gid) os.setresuid(uid, uid, uid) @@ -2427,7 +2418,7 @@ def run_verb(args: MkosiArgs, presets: Sequence[MkosiConfig]) -> None: run_shell(args, last) if args.verb == Verb.qemu: - run_qemu(args, last) + run_qemu(args, last, uid, gid) if args.verb == Verb.ssh: run_ssh(args, last) diff --git a/mkosi/qemu.py b/mkosi/qemu.py index 476a2a7a3..2c6f795ab 100644 --- a/mkosi/qemu.py +++ b/mkosi/qemu.py @@ -14,6 +14,7 @@ import tempfile import uuid from collections.abc import Iterator from pathlib import Path +from typing import Optional from mkosi.architecture import Architecture from mkosi.config import ( @@ -28,12 +29,7 @@ from mkosi.partition import finalize_root, find_partitions from mkosi.run import MkosiAsyncioThread, run, spawn from mkosi.tree import copy_tree, rmtree from mkosi.types import PathString -from mkosi.util import ( - InvokingUser, - format_bytes, - qemu_check_kvm_support, - qemu_check_vsock_support, -) +from mkosi.util import format_bytes, qemu_check_kvm_support, qemu_check_vsock_support def machine_cid(config: MkosiConfig) -> int: @@ -161,12 +157,10 @@ def start_swtpm() -> Iterator[Path]: @contextlib.contextmanager -def start_virtiofsd(directory: Path) -> Iterator[Path]: - uid, gid = InvokingUser.uid_gid() - +def start_virtiofsd(directory: Path, uid: Optional[int] = None, gid: Optional[int] = None) -> Iterator[Path]: with tempfile.TemporaryDirectory() as state: # Make sure virtiofsd is allowed to create its socket in this temporary directory. - os.chown(state, uid, gid) + os.chown(state, uid if uid is not None else os.getuid(), gid if gid is not None else os.getgid()) # Make sure we can use the socket name as a unique identifier for the fs as well but make sure it's not too # long as virtiofs tag names are limited to 36 bytes. @@ -181,19 +175,24 @@ def start_virtiofsd(directory: Path) -> Iterator[Path]: else: die("virtiofsd must be installed to use RuntimeMounts= with mkosi qemu") - # virtiofsd has to run unprivileged to use the --uid-map and --gid-map options, so we always run it as the user - # running mkosi. - proc = spawn([ + cmdline: list[PathString] = [ virtiofsd, "--socket-path", sock, "--shared-dir", directory, "--xattr", "--posix-acl", - # Map the user running mkosi to root in the virtual machine for the virtiofs instance to make sure all - # files created by root in the VM are owned by the user running mkosi on the host. - "--uid-map", f":0:{uid}:1:", - "--gid-map", f":0:{gid}:1:", - ], user=uid, group=gid) + ] + + # Map the given user/group to root in the virtual machine for the virtiofs instance to make sure all files + # created by root in the VM are owned by the user running mkosi on the host. + if uid is not None: + cmdline += ["--uid-map", f":0:{uid}:1:"] + if gid is not None: + cmdline += ["--gid-map", f":0:{gid}:1:"] + + # virtiofsd has to run unprivileged to use the --uid-map and --gid-map options, so run it as the given + # user/group if those are provided. + proc = spawn(cmdline, user=uid, group=gid) try: yield sock @@ -262,8 +261,8 @@ def copy_ephemeral(config: MkosiConfig, src: Path) -> Iterator[Path]: rmtree(tmp) -def run_qemu(args: MkosiArgs, config: MkosiConfig) -> None: - if config.output_format not in (OutputFormat.disk, OutputFormat.cpio, OutputFormat.uki): +def run_qemu(args: MkosiArgs, config: MkosiConfig, uid: int, gid: int) -> None: + if config.output_format not in (OutputFormat.disk, OutputFormat.cpio, OutputFormat.uki, OutputFormat.directory): die(f"{config.output_format} images cannot be booted in qemu") if ( @@ -285,7 +284,7 @@ def run_qemu(args: MkosiArgs, config: MkosiConfig) -> None: accel = "kvm" if config.qemu_firmware == QemuFirmware.auto: - if config.output_format == OutputFormat.cpio or config.architecture.to_efi() is None: + if config.output_format in (OutputFormat.cpio, OutputFormat.directory) or config.architecture.to_efi() is None: firmware = QemuFirmware.linux else: firmware = QemuFirmware.uefi @@ -353,7 +352,7 @@ def run_qemu(args: MkosiArgs, config: MkosiConfig) -> None: with contextlib.ExitStack() as stack: for src, target in config.runtime_trees: - sock = stack.enter_context(start_virtiofsd(src)) + sock = stack.enter_context(start_virtiofsd(src, uid, gid)) cmdline += [ "-chardev", f"socket,id={sock.name},path={sock}", "-device", f"vhost-user-fs-pci,queue-size=1024,chardev={sock.name},tag={sock.name}", @@ -409,7 +408,7 @@ def run_qemu(args: MkosiArgs, config: MkosiConfig) -> None: "--offline=yes", fname]) - if firmware == QemuFirmware.linux or config.output_format in (OutputFormat.cpio, OutputFormat.uki): + if firmware == QemuFirmware.linux or config.output_format in (OutputFormat.cpio, OutputFormat.uki, OutputFormat.directory): if config.output_format == OutputFormat.uki: kernel = fname if firmware == QemuFirmware.uefi else config.output_dir / config.output_split_kernel elif config.qemu_kernel: @@ -433,6 +432,14 @@ def run_qemu(args: MkosiArgs, config: MkosiConfig) -> None: root = finalize_root(find_partitions(fname)) if not root: die("Cannot perform a direct kernel boot without a root or usr partition") + elif config.output_format == OutputFormat.directory: + # This virtiofsd has to run as root so that it can write files owned by any uid:gid created by the VM. + sock = stack.enter_context(start_virtiofsd(fname)) + cmdline += [ + "-chardev", f"socket,id={sock.name},path={sock}", + "-device", f"vhost-user-fs-pci,queue-size=1024,chardev={sock.name},tag=/dev/root", + ] + root = "root=/dev/root rootfstype=virtiofs rw" else: root = ""