from mkosi.mounts import mount, mount_overlay, mount_passwd, mount_usr
from mkosi.pager import page
from mkosi.partition import Partition, finalize_root, finalize_roothash
-from mkosi.qemu import copy_ephemeral, run_qemu, run_ssh
+from mkosi.qemu import QemuDeviceNode, copy_ephemeral, run_qemu, run_ssh
from mkosi.run import (
become_root,
bwrap,
for config in presets:
try_import(f"mkosi.distributions.{config.distribution}")
+ # After we unshare the user namespace, we might not have access to /dev/kvm or related device nodes anymore as
+ # access to these might be gated behind the kvm group and we won't be part of the kvm group anymore after unsharing
+ # the user namespace. To get around this, open all those device nodes now while we still can so we can pass them as
+ # file descriptors to qemu later. Note that we can't pass the kvm file descriptor to qemu until
+ # https://gitlab.com/qemu-project/qemu/-/issues/1936 is resolved.
+ qemu_device_fds = {
+ d: os.open(f"/dev/{d}", os.O_RDWR|os.O_CLOEXEC|os.O_NONBLOCK)
+ for d in QemuDeviceNode
+ if os.access(f"/dev/{d}", os.F_OK|os.R_OK|os.W_OK)
+ }
+
# Get the user UID/GID either on the host or in the user namespace running the build
become_root()
init_mount_namespace()
run_shell(args, last)
if args.verb == Verb.qemu:
- run_qemu(args, last)
+ run_qemu(args, last, qemu_device_fds)
if args.verb == Verb.ssh:
run_ssh(args, last)
import asyncio
import base64
import contextlib
+import enum
import hashlib
import logging
import os
import sys
import tempfile
import uuid
-from collections.abc import Iterator
+from collections.abc import Iterator, Mapping
from pathlib import Path
from mkosi.architecture import Architecture
from mkosi.run import MkosiAsyncioThread, run, spawn
from mkosi.tree import copy_tree, rmtree
from mkosi.types import PathString
-from mkosi.util import InvokingUser, qemu_check_kvm_support, qemu_check_vsock_support
+from mkosi.util import (
+ InvokingUser,
+ StrEnum,
+ qemu_check_kvm_support,
+ qemu_check_vsock_support,
+)
+
+
+class QemuDeviceNode(StrEnum):
+ vhost_vsock = enum.auto()
def machine_cid(config: MkosiConfig) -> int:
rmtree(tmp)
-def run_qemu(args: MkosiArgs, config: MkosiConfig) -> None:
+def run_qemu(args: MkosiArgs, config: MkosiConfig, qemu_device_fds: Mapping[QemuDeviceNode, 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")
use_vsock = (config.qemu_vsock == ConfigFeature.enabled or
(config.qemu_vsock == ConfigFeature.auto and qemu_check_vsock_support(log=True)))
if use_vsock:
- cmdline += ["-device", f"vhost-vsock-pci,guest-cid={machine_cid(config)}"]
+ cmdline += [
+ "-device",
+ f"vhost-vsock-pci,guest-cid={machine_cid(config)},vhostfd={qemu_device_fds[QemuDeviceNode.vhost_vsock]}"
+ ]
cmdline += ["-cpu", "max"]
cmdline += config.qemu_args
cmdline += args.cmdline
- run(
+ with spawn(
cmdline,
# On Debian/Ubuntu, only users in the kvm group can access /dev/kvm. The invoking user might be part of the
# kvm group, but the user namespace fake root user will definitely not be. Thus, we have to run qemu as the
group=InvokingUser.gid if not InvokingUser.invoked_as_root else None,
stdin=sys.stdin,
stdout=sys.stdout,
+ pass_fds=qemu_device_fds.values(),
env=os.environ,
log=False,
- )
+ ) as qemu:
+ # 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)
+
+ qemu.wait()
if status := int(notifications.get("EXIT_STATUS", 0)):
raise subprocess.CalledProcessError(status, cmdline)