f.write("fi\n")
+def expand_kernel_specifiers(text: str, kver: str, token: str, roothash: str, boot_count: str) -> str:
+ specifiers = {
+ "&": "&",
+ "e": token,
+ "k": kver,
+ "h": roothash,
+ "c": boot_count
+ }
+
+ def replacer(match: re.Match[str]) -> str:
+ m = match.group("specifier")
+ if specifier := specifiers.get(m):
+ return specifier
+
+ logging.warning(f"Unknown specifier '&{m}' found in {text}, ignoring")
+ return ""
+
+ return re.sub(r"&(?P<specifier>[&a-zA-Z])", replacer, text)
+
+
def install_uki(context: Context, kver: str, kimg: Path, token: str, partitions: Sequence[Partition]) -> None:
+ bootloader_entry_format = context.config.unified_kernel_image_format or "&e-&k"
+
roothash_value = ""
if roothash := finalize_roothash(partitions):
- roothash_value = f"-{roothash.partition("=")[2]}"
+ roothash_value = roothash.partition("=")[2]
+
+ if not context.config.unified_kernel_image_format:
+ bootloader_entry_format += "-&h"
boot_count = ""
if (context.root / "etc/kernel/tries").exists():
- boot_count = f'+{(context.root / "etc/kernel/tries").read_text().strip()}'
+ boot_count = (context.root / "etc/kernel/tries").read_text().strip()
+
+ if not context.config.unified_kernel_image_format:
+ bootloader_entry_format += "+&c"
+
+ bootloader_entry = expand_kernel_specifiers(
+ bootloader_entry_format,
+ kver=kver,
+ token=token,
+ roothash=roothash_value,
+ boot_count=boot_count,
+ )
if context.config.bootloader == Bootloader.uki:
if context.config.shim_bootloader != ShimBootloader.none:
else:
boot_binary = context.root / efi_boot_binary(context)
else:
- boot_binary = context.root / f"boot/EFI/Linux/{token}-{kver}{roothash_value}{boot_count}.efi"
+ boot_binary = context.root / f"boot/EFI/Linux/{bootloader_entry}.efi"
# Make sure the parent directory where we'll be writing the UKI exists.
with umask(~0o700):
return not (s == "." or s == ".." or "/" in s)
-def config_parse_output(value: Optional[str], old: Optional[str]) -> Optional[str]:
- if not value:
- return None
+def config_make_filename_parser(hint: str) -> ConfigParseCallback:
+ def config_parse_filename(value: Optional[str], old: Optional[str]) -> Optional[str]:
+ if not value:
+ return None
- if not is_valid_filename(value):
- die(f"{value!r} is not a valid filename.",
- hint="Output= or --output= requires a filename with no path components. "
- "Use OutputDirectory= or --output-dir= to configure the output directory.")
+ if not is_valid_filename(value):
+ die(f"{value!r} is not a valid filename.", hint=hint)
- return value
+ return value
+
+ return config_parse_filename
def match_path_exists(value: str) -> bool:
bios_bootloader: BiosBootloader
shim_bootloader: ShimBootloader
unified_kernel_images: ConfigFeature
+ unified_kernel_image_format: str
initrds: list[Path]
initrd_packages: list[str]
initrd_volatile_packages: list[str]
metavar="NAME",
section="Output",
specifier="o",
- parse=config_parse_output,
+ parse=config_make_filename_parser(
+ "Output= or --output= requires a filename with no path components. "
+ "Use OutputDirectory= or --output-dir= to configure the output directory."
+ ),
default_factory=config_default_output,
default_factory_depends=("image_id", "image_version"),
help="Output name",
parse=config_parse_feature,
help="Specify whether to use UKIs with grub/systemd-boot in UEFI mode",
),
+ ConfigSetting(
+ dest="unified_kernel_image_format",
+ section="Content",
+ parse=config_make_filename_parser(
+ "UnifiedKernelImageFormat= or --unified-kernel-image-format= "
+ "requires a filename with no path components."
+ ),
+ # The default value is set in `__init__.py` in `install_uki`.
+ # `None` is used to determin if the roothash and boot count format
+ # should be appended to the filename if they are found.
+ #default=
+ help="Specify the format used for the UKI filename",
+ ),
ConfigSetting(
dest="initrds",
long="--initrd",
BIOS Bootloader: {config.bios_bootloader}
Shim Bootloader: {config.shim_bootloader}
Unified Kernel Images: {config.unified_kernel_images}
+ Unified Kernel Image Format: {config.unified_kernel_image_format}
Initrds: {line_join_list(config.initrds)}
Initrd Packages: {line_join_list(config.initrd_packages)}
Initrd Volatile Packages: {line_join_list(config.initrd_volatile_packages)}
Otherwise Type 1 entries as defined by the Boot Loader Specification
will be used instead. If disabled, Type 1 entries will always be used.
+`UnifiedKernelImageFormat=`, `--unified-kernel-image-format=`
+: Takes a filename without any path components to specify the format that
+ unified kernel images should be installed as. This may include both the
+ regular specifiers (see **Specifiers**) and special delayed specifiers, that
+ are expanded during the installation of the files, which are described below.
+ The default format for this parameter is `&e-&k` with `-&h` being appended
+ if `roothash=` or `usrhash=` is found on the kernel command line and `+&c`
+ if `/etc/kernel/tries` is found in the image.
+
+ The following specifiers may be used:
+
+ | Specifier | Value |
+ |-----------|----------------------------------------------------|
+ | `&&` | `&` character |
+ | `&e` | Entry Token |
+ | `&k` | Kernel version |
+ | `&h` | `roothash=` or `usrhash=` value of kernel argument |
+ | `&c` | Number of tries used for boot attempt counting |
+
`Initrds=`, `--initrd`
: Use user-provided initrd(s). Takes a comma separated list of paths to initrd
files. This option may be used multiple times in which case the initrd lists
import pytest
+from mkosi import expand_kernel_specifiers
from mkosi.config import (
Architecture,
Compression,
assert {k: v for k, v in config.environment.items() if k in expected} == expected
+def test_kernel_specifiers(tmp_path: Path) -> None:
+ kver = "13.0.8-5.10.0-1057-oem" # taken from reporter of #1638
+ token = "MySystemImage"
+ roothash = "67e893261799236dcf20529115ba9fae4fd7c2269e1e658d42269503e5760d38"
+ boot_count = "3"
+
+ def test_expand_kernel_specifiers(text: str) -> str:
+ return expand_kernel_specifiers(
+ text,
+ kver=kver,
+ token=token,
+ roothash=roothash,
+ boot_count=boot_count,
+ )
+
+ assert test_expand_kernel_specifiers("&&") == "&"
+ assert test_expand_kernel_specifiers("&k") == kver
+ assert test_expand_kernel_specifiers("&e") == token
+ assert test_expand_kernel_specifiers("&h") == roothash
+ assert test_expand_kernel_specifiers("&c") == boot_count
+
+ assert test_expand_kernel_specifiers("Image_1.0.3") == "Image_1.0.3"
+ assert test_expand_kernel_specifiers("Image~&c+&h-&k-&e") == f"Image~{boot_count}+{roothash}-{kver}-{token}"
+
+
def test_output_id_version(tmp_path: Path) -> None:
d = tmp_path
"ToolsTreeRepositories": [
"abc"
],
+ "UnifiedKernelImageFormat": "myuki",
"UnifiedKernelImages": "auto",
"UnitProperties": [
"PROPERTY=VALUE"
tools_tree_packages=[],
tools_tree_release=None,
tools_tree_repositories=["abc"],
+ unified_kernel_image_format="myuki",
unified_kernel_images=ConfigFeature.auto,
unit_properties=["PROPERTY=VALUE"],
use_subvolumes=ConfigFeature.auto,