From: Daan De Meyer Date: Thu, 11 May 2023 13:18:38 +0000 (+0200) Subject: Move qemu logic into qemu.py X-Git-Tag: v15~164^2~1 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=31aa3fc7c80c7ee5864a52ddfcdc6d527a693d58;p=thirdparty%2Fmkosi.git Move qemu logic into qemu.py We also introduce a new function qemu_check_vsock_support() --- diff --git a/mkosi/__init__.py b/mkosi/__init__.py index d64be4e68..2239de829 100644 --- a/mkosi/__init__.py +++ b/mkosi/__init__.py @@ -1,7 +1,5 @@ # SPDX-License-Identifier: LGPL-2.1+ -import asyncio -import base64 import contextlib import datetime import errno @@ -14,7 +12,6 @@ import os import re import resource import shutil -import socket import subprocess import sys import tempfile @@ -37,20 +34,13 @@ from mkosi.log import Style, color_error, complete_step, die, log_step from mkosi.manifest import Manifest from mkosi.mounts import dissect_and_mount, mount_overlay, scandir_recursive from mkosi.pager import page +from mkosi.qemu import machine_cid, run_qemu from mkosi.remove import unlink_try_hard -from mkosi.run import ( - MkosiAsyncioThread, - become_root, - fork_and_wait, - run, - run_workspace_command, - spawn, -) +from mkosi.run import become_root, fork_and_wait, run, run_workspace_command, spawn from mkosi.state import MkosiState from mkosi.types import PathString from mkosi.util import ( Compression, - Distribution, InvokingUser, ManifestFormat, OutputFormat, @@ -59,8 +49,6 @@ from mkosi.util import ( format_rlimit, is_apt_distribution, prepend_to_environ_path, - qemu_check_kvm_support, - tmp_dir, ) MKOSI_COMMANDS_NEED_BUILD = (Verb.shell, Verb.boot, Verb.qemu, Verb.serve) @@ -1827,12 +1815,6 @@ def check_root() -> None: die("Must be invoked as root.") -def machine_cid(config: MkosiConfig) -> int: - cid = int.from_bytes(hashlib.sha256(config.output_with_version.encode()).digest()[:4], byteorder='little') - # Make sure we don't return any of the well-known CIDs. - return max(3, min(cid, 0xFFFFFFFF - 1)) - - def nspawn_knows_arg(arg: str) -> bool: # Specify some extra incompatible options so nspawn doesn't try to boot a container in the current # directory if it has a compatible layout. @@ -1906,282 +1888,6 @@ def run_shell(args: MkosiArgs, config: MkosiConfig) -> None: run(cmdline, stdin=sys.stdin, stdout=sys.stdout, env=os.environ, log=False) -def find_qemu_binary(config: MkosiConfig) -> str: - binaries = ["qemu", "qemu-kvm", f"qemu-system-{config.architecture}"] - for binary in binaries: - if shutil.which(binary) is not None: - return binary - - die("Couldn't find QEMU/KVM binary") - - -def find_qemu_firmware(config: MkosiConfig) -> tuple[Path, bool]: - FIRMWARE_LOCATIONS = { - "x86_64": ["/usr/share/ovmf/x64/OVMF_CODE.secboot.fd"], - "i386": [ - "/usr/share/edk2/ovmf-ia32/OVMF_CODE.secboot.fd", - "/usr/share/OVMF/OVMF32_CODE_4M.secboot.fd" - ], - }.get(config.architecture, []) - - for firmware in FIRMWARE_LOCATIONS: - if os.path.exists(firmware): - return Path(firmware), True - - FIRMWARE_LOCATIONS = { - "x86_64": [ - "/usr/share/ovmf/ovmf_code_x64.bin", - "/usr/share/ovmf/x64/OVMF_CODE.fd", - "/usr/share/qemu/ovmf-x86_64.bin", - ], - "i386": ["/usr/share/ovmf/ovmf_code_ia32.bin", "/usr/share/edk2/ovmf-ia32/OVMF_CODE.fd"], - "aarch64": ["/usr/share/AAVMF/AAVMF_CODE.fd"], - "armhfp": ["/usr/share/AAVMF/AAVMF32_CODE.fd"], - }.get(config.architecture, []) - - for firmware in FIRMWARE_LOCATIONS: - if os.path.exists(firmware): - logging.warning("Couldn't find OVMF firmware blob with secure boot support, " - "falling back to OVMF firmware blobs without secure boot support.") - return Path(firmware), False - - # If we can't find an architecture specific path, fall back to some generic paths that might also work. - - FIRMWARE_LOCATIONS = [ - "/usr/share/edk2/ovmf/OVMF_CODE.secboot.fd", - "/usr/share/edk2-ovmf/OVMF_CODE.secboot.fd", # GENTOO: - "/usr/share/qemu/OVMF_CODE.secboot.fd", - "/usr/share/ovmf/OVMF.secboot.fd", - "/usr/share/OVMF/OVMF_CODE.secboot.fd", - ] - - for firmware in FIRMWARE_LOCATIONS: - if os.path.exists(firmware): - return Path(firmware), True - - FIRMWARE_LOCATIONS = [ - "/usr/share/edk2/ovmf/OVMF_CODE.fd", - "/usr/share/edk2-ovmf/OVMF_CODE.fd", # GENTOO: - "/usr/share/qemu/OVMF_CODE.fd", - "/usr/share/ovmf/OVMF.fd", - "/usr/share/OVMF/OVMF_CODE.fd", - ] - - for firmware in FIRMWARE_LOCATIONS: - if os.path.exists(firmware): - logging.warn("Couldn't find OVMF firmware blob with secure boot support, " - "falling back to OVMF firmware blobs without secure boot support.") - return Path(firmware), False - - die("Couldn't find OVMF UEFI firmware blob.") - - -def find_ovmf_vars(config: MkosiConfig) -> Path: - OVMF_VARS_LOCATIONS = [] - - if config.architecture == "x86_64": - OVMF_VARS_LOCATIONS += ["/usr/share/ovmf/x64/OVMF_VARS.fd"] - elif config.architecture == "i386": - OVMF_VARS_LOCATIONS += [ - "/usr/share/edk2/ovmf-ia32/OVMF_VARS.fd", - "/usr/share/OVMF/OVMF32_VARS_4M.fd", - ] - elif config.architecture == "armhfp": - OVMF_VARS_LOCATIONS += ["/usr/share/AAVMF/AAVMF32_VARS.fd"] - elif config.architecture == "aarch64": - OVMF_VARS_LOCATIONS += ["/usr/share/AAVMF/AAVMF_VARS.fd"] - - OVMF_VARS_LOCATIONS += ["/usr/share/edk2/ovmf/OVMF_VARS.fd", - "/usr/share/edk2-ovmf/OVMF_VARS.fd", # GENTOO: - "/usr/share/qemu/OVMF_VARS.fd", - "/usr/share/ovmf/OVMF_VARS.fd", - "/usr/share/OVMF/OVMF_VARS.fd"] - - for location in OVMF_VARS_LOCATIONS: - if os.path.exists(location): - return Path(location) - - die("Couldn't find OVMF UEFI variables file.") - - -@contextlib.contextmanager -def start_swtpm() -> Iterator[Optional[Path]]: - - if not shutil.which("swtpm"): - yield None - return - - with tempfile.TemporaryDirectory() as swtpm_state: - swtpm_sock = Path(swtpm_state) / Path("sock") - - cmd = ["swtpm", - "socket", - "--tpm2", - "--tpmstate", f"dir={swtpm_state}", - "--ctrl", f"type=unixio,path={swtpm_sock}", - ] - - swtpm_proc = spawn(cmd) - - try: - yield swtpm_sock - finally: - swtpm_proc.wait() - - -@contextlib.contextmanager -def vsock_notify_handler() -> Iterator[tuple[str, dict[str, str]]]: - """ - This yields a vsock address and a dict that will be filled in with the notifications from the VM. The - dict should only be accessed after the context manager has been finalized. - """ - with socket.socket(socket.AF_VSOCK, socket.SOCK_SEQPACKET) as vsock: - vsock.bind((socket.VMADDR_CID_ANY, -1)) - vsock.listen() - vsock.setblocking(False) - - messages = {} - - async def notify() -> None: - loop = asyncio.get_running_loop() - - try: - while True: - s, _ = await loop.sock_accept(vsock) - - for msg in (await loop.sock_recv(s, 4096)).decode().split("\n"): - if not msg: - continue - - k, _, v = msg.partition("=") - messages[k] = v - - except asyncio.CancelledError: - pass - - with MkosiAsyncioThread(notify()): - yield f"vsock:{socket.VMADDR_CID_HOST}:{vsock.getsockname()[1]}", messages - - -def run_qemu(args: MkosiArgs, config: MkosiConfig) -> None: - accel = "tcg" - if config.qemu_kvm == ConfigFeature.enabled or (config.qemu_kvm == ConfigFeature.auto and qemu_check_kvm_support()): - accel = "kvm" - - firmware, fw_supports_sb = find_qemu_firmware(config) - smm = "on" if fw_supports_sb else "off" - - if config.architecture == "aarch64": - machine = f"type=virt,accel={accel}" - else: - machine = f"type=q35,accel={accel},smm={smm}" - - cmdline: list[PathString] = [ - find_qemu_binary(config), - "-machine", machine, - "-smp", config.qemu_smp, - "-m", config.qemu_mem, - "-object", "rng-random,filename=/dev/urandom,id=rng0", - "-device", "virtio-rng-pci,rng=rng0,id=rng-device0", - "-nic", "user,model=virtio-net-pci", - ] - - try: - os.open("/dev/vhost-vsock", os.O_RDWR|os.O_CLOEXEC) - cmdline += ["-device", f"vhost-vsock-pci,guest-cid={machine_cid(config)}"] - except OSError as e: - if e.errno == errno.ENOENT: - logging.warning("/dev/vhost-vsock not found. Not adding a vsock device to the virtual machine.") - elif e.errno in (errno.EPERM, errno.EACCES): - logging.warning("Permission denied to access /dev/vhost-vsock. Not adding a vsock device to the virtual machine.") - - cmdline += ["-cpu", "max"] - - if config.qemu_gui: - cmdline += ["-vga", "virtio"] - else: - # -nodefaults removes the default CDROM device which avoids an error message during boot - # -serial mon:stdio adds back the serial device removed by -nodefaults. - cmdline += [ - "-nographic", - "-nodefaults", - "-chardev", "stdio,mux=on,id=console,signal=off", - "-serial", "chardev:console", - "-mon", "console", - ] - - for k, v in config.credentials.items(): - cmdline += ["-smbios", f"type=11,value=io.systemd.credential.binary:{k}={base64.b64encode(v.encode()).decode()}"] - cmdline += ["-smbios", f"type=11,value=io.systemd.stub.kernel-cmdline-extra={' '.join(config.kernel_command_line_extra)}"] - - cmdline += ["-drive", f"if=pflash,format=raw,readonly=on,file={firmware}"] - - notifications: dict[str, str] = {} - - with contextlib.ExitStack() as stack: - if fw_supports_sb: - ovmf_vars = stack.enter_context(tempfile.NamedTemporaryFile(prefix=".mkosi-", dir=tmp_dir())) - copy_path(find_ovmf_vars(config), Path(ovmf_vars.name)) - cmdline += [ - "-global", "ICH9-LPC.disable_s3=1", - "-global", "driver=cfi.pflash01,property=secure,value=on", - "-drive", f"file={ovmf_vars.name},if=pflash,format=raw", - ] - - if config.ephemeral: - f = stack.enter_context(tempfile.NamedTemporaryFile(prefix=".mkosi-", dir=config.output_dir)) - fname = Path(f.name) - - # So on one hand we want CoW off, since this stuff will - # have a lot of random write accesses. On the other we - # want the copy to be snappy, hence we do want CoW. Let's - # ask for both, and let the kernel figure things out: - # let's turn off CoW on the file, but start with a CoW - # copy. On btrfs that works: the initial copy is made as - # CoW but later changes do not result in CoW anymore. - - run(["chattr", "+C", fname], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, check=False) - copy_path(config.output_dir / config.output, fname) - else: - fname = config.output_dir / config.output - - # Debian images fail to boot with virtio-scsi, see: https://github.com/systemd/mkosi/issues/725 - if config.output_format == OutputFormat.cpio: - kernel = config.output_dir / config.output_split_kernel - if not kernel.exists() and "-kernel" not in args.cmdline: - die("No kernel found, please install a kernel in the cpio or provide a -kernel argument to mkosi qemu") - cmdline += ["-kernel", kernel, - "-initrd", fname, - "-append", " ".join(config.kernel_command_line + config.kernel_command_line_extra)] - if config.distribution == Distribution.debian: - cmdline += ["-drive", f"if=virtio,id=hd,file={fname},format=raw"] - else: - cmdline += ["-drive", f"if=none,id=hd,file={fname},format=raw", - "-device", "virtio-scsi-pci,id=scsi", - "-device", "scsi-hd,drive=hd,bootindex=1"] - - swtpm_socket = stack.enter_context(start_swtpm()) - if swtpm_socket is not None: - cmdline += ["-chardev", f"socket,id=chrtpm,path={swtpm_socket}", - "-tpmdev", "emulator,id=tpm0,chardev=chrtpm"] - - if config.architecture == "x86_64": - cmdline += ["-device", "tpm-tis,tpmdev=tpm0"] - elif config.architecture == "aarch64": - cmdline += ["-device", "tpm-tis-device,tpmdev=tpm0"] - - addr, notifications = stack.enter_context(vsock_notify_handler()) - cmdline += ["-smbios", f"type=11,value=io.systemd.credential:vmm.notify_socket={addr}"] - - cmdline += config.qemu_args - cmdline += args.cmdline - - run(cmdline, stdin=sys.stdin, stdout=sys.stdout, env=os.environ, log=False) - - if "EXIT_STATUS" in notifications: - raise subprocess.CalledProcessError(int(notifications["EXIT_STATUS"]), cmdline) - - def run_ssh(args: MkosiArgs, config: MkosiConfig) -> None: cmd = [ "ssh", diff --git a/mkosi/qemu.py b/mkosi/qemu.py new file mode 100644 index 000000000..c7e5afa8f --- /dev/null +++ b/mkosi/qemu.py @@ -0,0 +1,304 @@ +# SPDX-License-Identifier: LGPL-2.1+ + +import asyncio +import base64 +import contextlib +import hashlib +import logging +import os +import shutil +import socket +import subprocess +import sys +import tempfile +from pathlib import Path +from typing import Iterator, Optional + +from mkosi.config import ConfigFeature, MkosiArgs, MkosiConfig +from mkosi.install import copy_path +from mkosi.log import die +from mkosi.run import MkosiAsyncioThread, run, spawn +from mkosi.types import PathString +from mkosi.util import ( + Distribution, + OutputFormat, + qemu_check_kvm_support, + qemu_check_vsock_support, + tmp_dir, +) + + +def machine_cid(config: MkosiConfig) -> int: + cid = int.from_bytes(hashlib.sha256(config.output_with_version.encode()).digest()[:4], byteorder='little') + # Make sure we don't return any of the well-known CIDs. + return max(3, min(cid, 0xFFFFFFFF - 1)) + + +def find_qemu_binary(config: MkosiConfig) -> str: + binaries = ["qemu", "qemu-kvm", f"qemu-system-{config.architecture}"] + for binary in binaries: + if shutil.which(binary) is not None: + return binary + + die("Couldn't find QEMU/KVM binary") + + +def find_qemu_firmware(config: MkosiConfig) -> tuple[Path, bool]: + FIRMWARE_LOCATIONS = { + "x86_64": ["/usr/share/ovmf/x64/OVMF_CODE.secboot.fd"], + "i386": [ + "/usr/share/edk2/ovmf-ia32/OVMF_CODE.secboot.fd", + "/usr/share/OVMF/OVMF32_CODE_4M.secboot.fd" + ], + }.get(config.architecture, []) + + for firmware in FIRMWARE_LOCATIONS: + if os.path.exists(firmware): + return Path(firmware), True + + FIRMWARE_LOCATIONS = { + "x86_64": [ + "/usr/share/ovmf/ovmf_code_x64.bin", + "/usr/share/ovmf/x64/OVMF_CODE.fd", + "/usr/share/qemu/ovmf-x86_64.bin", + ], + "i386": ["/usr/share/ovmf/ovmf_code_ia32.bin", "/usr/share/edk2/ovmf-ia32/OVMF_CODE.fd"], + "aarch64": ["/usr/share/AAVMF/AAVMF_CODE.fd"], + "armhfp": ["/usr/share/AAVMF/AAVMF32_CODE.fd"], + }.get(config.architecture, []) + + for firmware in FIRMWARE_LOCATIONS: + if os.path.exists(firmware): + logging.warning("Couldn't find OVMF firmware blob with secure boot support, " + "falling back to OVMF firmware blobs without secure boot support.") + return Path(firmware), False + + # If we can't find an architecture specific path, fall back to some generic paths that might also work. + + FIRMWARE_LOCATIONS = [ + "/usr/share/edk2/ovmf/OVMF_CODE.secboot.fd", + "/usr/share/edk2-ovmf/OVMF_CODE.secboot.fd", # GENTOO: + "/usr/share/qemu/OVMF_CODE.secboot.fd", + "/usr/share/ovmf/OVMF.secboot.fd", + "/usr/share/OVMF/OVMF_CODE.secboot.fd", + ] + + for firmware in FIRMWARE_LOCATIONS: + if os.path.exists(firmware): + return Path(firmware), True + + FIRMWARE_LOCATIONS = [ + "/usr/share/edk2/ovmf/OVMF_CODE.fd", + "/usr/share/edk2-ovmf/OVMF_CODE.fd", # GENTOO: + "/usr/share/qemu/OVMF_CODE.fd", + "/usr/share/ovmf/OVMF.fd", + "/usr/share/OVMF/OVMF_CODE.fd", + ] + + for firmware in FIRMWARE_LOCATIONS: + if os.path.exists(firmware): + logging.warn("Couldn't find OVMF firmware blob with secure boot support, " + "falling back to OVMF firmware blobs without secure boot support.") + return Path(firmware), False + + die("Couldn't find OVMF UEFI firmware blob.") + + +def find_ovmf_vars(config: MkosiConfig) -> Path: + OVMF_VARS_LOCATIONS = [] + + if config.architecture == "x86_64": + OVMF_VARS_LOCATIONS += ["/usr/share/ovmf/x64/OVMF_VARS.fd"] + elif config.architecture == "i386": + OVMF_VARS_LOCATIONS += [ + "/usr/share/edk2/ovmf-ia32/OVMF_VARS.fd", + "/usr/share/OVMF/OVMF32_VARS_4M.fd", + ] + elif config.architecture == "armhfp": + OVMF_VARS_LOCATIONS += ["/usr/share/AAVMF/AAVMF32_VARS.fd"] + elif config.architecture == "aarch64": + OVMF_VARS_LOCATIONS += ["/usr/share/AAVMF/AAVMF_VARS.fd"] + + OVMF_VARS_LOCATIONS += ["/usr/share/edk2/ovmf/OVMF_VARS.fd", + "/usr/share/edk2-ovmf/OVMF_VARS.fd", # GENTOO: + "/usr/share/qemu/OVMF_VARS.fd", + "/usr/share/ovmf/OVMF_VARS.fd", + "/usr/share/OVMF/OVMF_VARS.fd"] + + for location in OVMF_VARS_LOCATIONS: + if os.path.exists(location): + return Path(location) + + die("Couldn't find OVMF UEFI variables file.") + + +@contextlib.contextmanager +def start_swtpm() -> Iterator[Optional[Path]]: + + if not shutil.which("swtpm"): + yield None + return + + with tempfile.TemporaryDirectory() as swtpm_state: + swtpm_sock = Path(swtpm_state) / Path("sock") + + cmd = ["swtpm", + "socket", + "--tpm2", + "--tpmstate", f"dir={swtpm_state}", + "--ctrl", f"type=unixio,path={swtpm_sock}", + ] + + swtpm_proc = spawn(cmd) + + try: + yield swtpm_sock + finally: + swtpm_proc.wait() + + +@contextlib.contextmanager +def vsock_notify_handler() -> Iterator[tuple[str, dict[str, str]]]: + """ + This yields a vsock address and a dict that will be filled in with the notifications from the VM. The + dict should only be accessed after the context manager has been finalized. + """ + with socket.socket(socket.AF_VSOCK, socket.SOCK_SEQPACKET) as vsock: + vsock.bind((socket.VMADDR_CID_ANY, -1)) + vsock.listen() + vsock.setblocking(False) + + messages = {} + + async def notify() -> None: + loop = asyncio.get_running_loop() + + try: + while True: + s, _ = await loop.sock_accept(vsock) + + for msg in (await loop.sock_recv(s, 4096)).decode().split("\n"): + if not msg: + continue + + k, _, v = msg.partition("=") + messages[k] = v + + except asyncio.CancelledError: + pass + + with MkosiAsyncioThread(notify()): + yield f"vsock:{socket.VMADDR_CID_HOST}:{vsock.getsockname()[1]}", messages + + +def run_qemu(args: MkosiArgs, config: MkosiConfig) -> None: + accel = "tcg" + if config.qemu_kvm == ConfigFeature.enabled or (config.qemu_kvm == ConfigFeature.auto and qemu_check_kvm_support()): + accel = "kvm" + + firmware, fw_supports_sb = find_qemu_firmware(config) + smm = "on" if fw_supports_sb else "off" + + if config.architecture == "aarch64": + machine = f"type=virt,accel={accel}" + else: + machine = f"type=q35,accel={accel},smm={smm}" + + cmdline: list[PathString] = [ + find_qemu_binary(config), + "-machine", machine, + "-smp", config.qemu_smp, + "-m", config.qemu_mem, + "-object", "rng-random,filename=/dev/urandom,id=rng0", + "-device", "virtio-rng-pci,rng=rng0,id=rng-device0", + "-nic", "user,model=virtio-net-pci", + ] + + if qemu_check_vsock_support(log=True): + cmdline += ["-device", f"vhost-vsock-pci,guest-cid={machine_cid(config)}"] + + cmdline += ["-cpu", "max"] + + if config.qemu_gui: + cmdline += ["-vga", "virtio"] + else: + # -nodefaults removes the default CDROM device which avoids an error message during boot + # -serial mon:stdio adds back the serial device removed by -nodefaults. + cmdline += [ + "-nographic", + "-nodefaults", + "-chardev", "stdio,mux=on,id=console,signal=off", + "-serial", "chardev:console", + "-mon", "console", + ] + + for k, v in config.credentials.items(): + cmdline += ["-smbios", f"type=11,value=io.systemd.credential.binary:{k}={base64.b64encode(v.encode()).decode()}"] + cmdline += ["-smbios", f"type=11,value=io.systemd.stub.kernel-cmdline-extra={' '.join(config.kernel_command_line_extra)}"] + + cmdline += ["-drive", f"if=pflash,format=raw,readonly=on,file={firmware}"] + + notifications: dict[str, str] = {} + + with contextlib.ExitStack() as stack: + if fw_supports_sb: + ovmf_vars = stack.enter_context(tempfile.NamedTemporaryFile(prefix=".mkosi-", dir=tmp_dir())) + copy_path(find_ovmf_vars(config), Path(ovmf_vars.name)) + cmdline += [ + "-global", "ICH9-LPC.disable_s3=1", + "-global", "driver=cfi.pflash01,property=secure,value=on", + "-drive", f"file={ovmf_vars.name},if=pflash,format=raw", + ] + + if config.ephemeral: + f = stack.enter_context(tempfile.NamedTemporaryFile(prefix=".mkosi-", dir=config.output_dir)) + fname = Path(f.name) + + # So on one hand we want CoW off, since this stuff will + # have a lot of random write accesses. On the other we + # want the copy to be snappy, hence we do want CoW. Let's + # ask for both, and let the kernel figure things out: + # let's turn off CoW on the file, but start with a CoW + # copy. On btrfs that works: the initial copy is made as + # CoW but later changes do not result in CoW anymore. + + run(["chattr", "+C", fname], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, check=False) + copy_path(config.output_dir / config.output, fname) + else: + fname = config.output_dir / config.output + + # Debian images fail to boot with virtio-scsi, see: https://github.com/systemd/mkosi/issues/725 + if config.output_format == OutputFormat.cpio: + kernel = config.output_dir / config.output_split_kernel + if not kernel.exists() and "-kernel" not in args.cmdline: + die("No kernel found, please install a kernel in the cpio or provide a -kernel argument to mkosi qemu") + cmdline += ["-kernel", kernel, + "-initrd", fname, + "-append", " ".join(config.kernel_command_line + config.kernel_command_line_extra)] + if config.distribution == Distribution.debian: + cmdline += ["-drive", f"if=virtio,id=hd,file={fname},format=raw"] + else: + cmdline += ["-drive", f"if=none,id=hd,file={fname},format=raw", + "-device", "virtio-scsi-pci,id=scsi", + "-device", "scsi-hd,drive=hd,bootindex=1"] + + swtpm_socket = stack.enter_context(start_swtpm()) + if swtpm_socket is not None: + cmdline += ["-chardev", f"socket,id=chrtpm,path={swtpm_socket}", + "-tpmdev", "emulator,id=tpm0,chardev=chrtpm"] + + if config.architecture == "x86_64": + cmdline += ["-device", "tpm-tis,tpmdev=tpm0"] + elif config.architecture == "aarch64": + cmdline += ["-device", "tpm-tis-device,tpmdev=tpm0"] + + addr, notifications = stack.enter_context(vsock_notify_handler()) + cmdline += ["-smbios", f"type=11,value=io.systemd.credential:vmm.notify_socket={addr}"] + + cmdline += config.qemu_args + cmdline += args.cmdline + + run(cmdline, stdin=sys.stdin, stdout=sys.stdout, env=os.environ, log=False) + + if "EXIT_STATUS" in notifications: + raise subprocess.CalledProcessError(int(notifications["EXIT_STATUS"]), cmdline) diff --git a/mkosi/util.py b/mkosi/util.py index 07feb02ad..58e493ab5 100644 --- a/mkosi/util.py +++ b/mkosi/util.py @@ -3,8 +3,10 @@ import ast import contextlib import enum +import errno import functools import itertools +import logging import os import pwd import re @@ -281,3 +283,21 @@ def qemu_check_kvm_support() -> bool: return True except OSError: return False + + +def qemu_check_vsock_support(log: bool) -> bool: + try: + os.open("/dev/vhost-vsock", os.O_RDWR|os.O_CLOEXEC) + except OSError as e: + if e.errno == errno.ENOENT: + if log: + logging.warning("/dev/vhost-vsock not found. Not adding a vsock device to the virtual machine.") + return False + elif e.errno in (errno.EPERM, errno.EACCES): + if log: + logging.warning("Permission denied to access /dev/vhost-vsock. Not adding a vsock device to the virtual machine.") + return False + + raise e + + return True