size: int
directory: Optional[Path]
options: Optional[str]
+ file_id: str
# We use negative numbers for specifying special constants
if len(parts) < 2:
die(f"Missing size in drive '{value}")
- if len(parts) > 4:
+ if len(parts) > 5:
die(f"Too many components in drive '{value}")
id = parts[0]
directory = parse_path(parts[2]) if len(parts) > 2 and parts[2] else None
options = parts[3] if len(parts) > 3 and parts[3] else None
+ file_id = parts[4] if len(parts) > 4 and parts[4] else id
- return QemuDrive(id=id, size=size, directory=directory, options=options)
+ return QemuDrive(id=id, size=size, directory=directory, options=options, file_id=file_id)
def config_parse_sector_size(value: Optional[str], old: Optional[int]) -> Optional[int]:
def config_drive_transformer(drives: list[dict[str, Any]], fieldtype: type[QemuDrive]) -> list[QemuDrive]:
# TODO: exchange for TypeGuard and list comprehension once on 3.10
ret = []
+
for d in drives:
assert "Id" in d
assert "Size" in d
- assert "Directory" in d
- assert "Options" in d
ret.append(
QemuDrive(
id=d["Id"],
- size=int(d["Size"]),
- directory=Path(d["Directory"]) if d["Directory"] else None,
- options=d["Options"],
+ size=d["Size"] if isinstance(d["Size"], int) else parse_bytes(d["Size"]),
+ directory=Path(d["Directory"]) if d.get("Directory") else None,
+ options=d.get("Options"),
+ file_id=d.get("FileId", d["Id"]),
)
)
+
return ret
def generic_version_transformer(
from mkosi.tree import copy_tree, rmtree
from mkosi.types import PathString
from mkosi.user import INVOKING_USER, become_root, become_root_cmd
-from mkosi.util import StrEnum, flock, flock_or_die, try_or
+from mkosi.util import StrEnum, flock, flock_or_die, groupby, try_or
from mkosi.versioncomp import GenericVersion
QEMU_KVM_DEVICE_VERSION = GenericVersion("9.0")
f"type=11,value=io.systemd.boot.kernel-cmdline-extra={' '.join(kcl).replace(',', ',,')}",
]
- for drive in config.qemu_drives:
- file = stack.enter_context(finalize_drive(drive))
+ for _, drives in groupby(config.qemu_drives, key=lambda d: d.file_id):
+ file = stack.enter_context(finalize_drive(drives[0]))
- arg = f"if=none,id={drive.id},file={file},format=raw"
- if drive.options:
- arg += f",{drive.options}"
+ for drive in drives:
+ arg = f"if=none,id={drive.id},file={file},format=raw,file.locking=off"
+ if drive.options:
+ arg += f",{drive.options}"
- cmdline += ["-drive", arg]
+ cmdline += ["-drive", arg]
cmdline += config.qemu_args
cmdline += args.cmdline
`QemuDrives=`, `--qemu-drive=`
: Add a qemu drive. Takes a colon-delimited string of format
- `<id>:<size>[:<directory>[:<options>]]`. `id` specifies the qemu id we
- assign to the drive. This can be used as the `drive=` property in
- various qemu devices. `size` specifies the size of the drive. This
- takes a size in bytes. Additionally, the suffixes `K`, `M` and `G` can
- be used to specify a size in kilobytes, megabytes and gigabytes
- respectively. `directory` optionally specifies the directory in which
- to create the file backing the drive. `options` optionally specifies
- extra comma-delimited properties which are passed verbatim to qemu's
- `-drive` option.
+ `<id>:<size>[:<directory>[:<options>[:<file-id>]]]`. `id` specifies
+ the qemu ID assigned to the drive. This can be used as the `drive=`
+ property in various qemu devices. `size` specifies the size of the
+ drive. This takes a size in bytes. Additionally, the suffixes `K`, `M`
+ and `G` can be used to specify a size in kilobytes, megabytes and
+ gigabytes respectively. `directory` optionally specifies the directory
+ in which to create the file backing the drive. `options` optionally
+ specifies extra comma-delimited properties which are passed verbatim
+ to qemu's `-drive` option. `file-id` specifies the ID of the file
+ backing the drive. Drives with the same file ID will share the
+ backing file. The directory and size of the file will be determined
+ from the first drive with a given file ID.
: Example usage:
import resource
import stat
import tempfile
-from collections.abc import Iterable, Iterator, Mapping, Sequence
+from collections.abc import Hashable, Iterable, Iterator, Mapping, Sequence
from pathlib import Path
from types import ModuleType
from typing import Any, Callable, Optional, TypeVar, no_type_check
T = TypeVar("T")
V = TypeVar("V")
+S = TypeVar("S", bound=Hashable)
def dictify(f: Callable[..., Iterator[tuple[T, V]]]) -> Callable[..., dict[T, V]]:
return fn()
except exception:
return default
+
+
+def groupby(seq: Sequence[T], key: Callable[[T], S]) -> list[tuple[S, list[T]]]:
+ grouped: dict[S, list[T]] = {}
+
+ for i in seq:
+ k = key(i)
+
+ if k not in grouped:
+ grouped[k] = []
+
+ grouped[k].append(i)
+
+ return [(key, group) for key, group in grouped.items()]
"QemuDrives": [
{
"Directory": "/foo/bar",
+ "FileId": "red",
"Id": "abc",
"Options": "abc,qed",
"Size": 200
},
{
"Directory": null,
+ "FileId": "wcd",
"Id": "abc",
"Options": "",
"Size": 200
proxy_url="https://my/proxy",
qemu_args=[],
qemu_cdrom=False,
- qemu_drives=[QemuDrive("abc", 200, Path("/foo/bar"), "abc,qed"), QemuDrive("abc", 200, None, "")],
+ qemu_drives=[
+ QemuDrive("abc", 200, Path("/foo/bar"), "abc,qed", "red"),
+ QemuDrive("abc", 200, None, "", "wcd"),
+ ],
qemu_firmware=QemuFirmware.linux,
qemu_firmware_variables=Path("/foo/bar"),
qemu_gui=True,