From 6caa830e74256dcfdf25ec266224e44b43ff9678 Mon Sep 17 00:00:00 2001 From: DaanDeMeyer Date: Mon, 25 Aug 2025 14:23:51 +0200 Subject: [PATCH] kmod: Beef up firmware symlink chasing - It turns out we need to handle absolutely symlinks after all as /usr/lib/firmware/regulatory.db is a symlink to /etc/alternatives in Debian and derivatives. - Our previous implementation didn't handle cases where a symlink target consisted out of two parts e.g. if C -> A/B then we wouldn't try to resolve A. Fix both issues by switching to a minimal implementation of the chase() function in systemd. To avoid having to include /etc stuff in the kernel modules initrd, we resolve get rid of any intermediate symlinks to /etc/alternatives that we encounter. Follow up for 221293e33defe30b111bc13957a0d52d2ea1c45b --- mkosi/kmod.py | 54 +++++++++++++++++++++++++++++++-------------------- mkosi/util.py | 4 ---- 2 files changed, 33 insertions(+), 25 deletions(-) diff --git a/mkosi/kmod.py b/mkosi/kmod.py index 799bb6a2d..8f1106bb1 100644 --- a/mkosi/kmod.py +++ b/mkosi/kmod.py @@ -10,10 +10,10 @@ from collections.abc import Iterable, Iterator, Reversible from pathlib import Path from mkosi.context import Context -from mkosi.log import complete_step, die, log_step +from mkosi.log import complete_step, log_step from mkosi.run import chroot_cmd, run from mkosi.sandbox import chase -from mkosi.util import chdir, parents_below, path_and_parents_below +from mkosi.util import chdir, parents_below def loaded_modules() -> list[str]: @@ -281,8 +281,7 @@ def resolve_module_dependencies( glob = "" if value.endswith("*") else ".*" - for fw in Path("usr/lib/firmware").glob(f"{value}{glob}"): - firmware.add(fw) + firmware.update(Path("usr/lib/firmware").glob(f"{value}{glob}")) elif key == "name": # The file names use dashes, but the module names use underscores. We track the names in @@ -355,9 +354,9 @@ def gen_required_kernel_modules( # /usr/lib/firmware makes use of symbolic links so we have to make sure the symlinks and their targets # are all included. - todo = firmware.copy() + fwcopy = firmware.copy() firmware.clear() - for fw in todo: + for fw in fwcopy: # Every path component from /usr/lib/firmware up to and including the firmware file itself might be a # symlink. We need to make sure we include all of them so we iterate over them and keep resolving # each symlink separately (and recursively) and add all of them to the list of firmware to add. @@ -384,25 +383,38 @@ def gen_required_kernel_modules( # /usr/lib/firmware/nvidia/tu106/gsp # /usr/lib/firmware/nvidia/tu117/gsp - for p in reversed(path_and_parents_below(context.root / fw, context.root / firmwared)): - # Make sure only the last component of the path we're working is a symlink. We don't want to - # yield any paths with multiple components in them that are symlinks as these paths are passed to - # cpio and it interprets intermediary path components that are symlinks as regular directories. - # Additionally, intermediary path components that are symlinks also mess up the path - # deduplication we want to get by putting all the firmware to add in a set as the paths will hash - # differently even though they're the same paths after resolution. - p = p.parent.resolve(strict=True).joinpath(p.name) + todo = list(reversed(fw.parts)) + current = context.root + while todo: + part = todo.pop() + if part == "/": + current = context.root + continue + elif part == "..": + current = current.parent + continue + elif part == ".": + continue + + current /= part + if not current.is_symlink(): + continue - while p.is_symlink(): - target = p.readlink() - if target.is_absolute(): - die(f"Found unexpected absolute symlink {target} in {firmwared}") + if current.readlink().is_relative_to("/etc/alternatives"): + target = chase(os.fspath(context.root), os.fspath(current.relative_to(context.root))) + current.unlink() + current.symlink_to(os.path.relpath(target, start=current.parent)) - firmware.add(p.relative_to(context.root)) - p = p.parent / target + firmware.add(current.relative_to(context.root)) + todo += list(reversed(current.readlink().parts)) + # Relative symlinks are resolved relative to the directory + # the symlink is located in. If the symlink is absolute we'll + # override the current path anyway so modifying it here doesn't + # matter. + current = current.parent # Finally, add the actual fully resolved path to the firmware file. - firmware.add((context.root / fw).resolve(strict=True).relative_to(context.root)) + firmware.add(current.relative_to(context.root)) yield from sorted( itertools.chain( diff --git a/mkosi/util.py b/mkosi/util.py index f7eb97e1e..64712b6a1 100644 --- a/mkosi/util.py +++ b/mkosi/util.py @@ -194,10 +194,6 @@ def parents_below(path: Path, below: Path) -> list[Path]: return parents[: parents.index(below)] -def path_and_parents_below(path: Path, below: Path) -> list[Path]: - return [path] + parents_below(path, below) - - @contextlib.contextmanager def resource_path(mod: ModuleType) -> Iterator[Path]: t = importlib.resources.files(mod) -- 2.47.3