yield section, "", ""
+def parse_kernel_module_filter_regexp(p: str) -> str:
+ if p in ("default", "host"):
+ return p
+ return f"re:{p}"
+
+
UKI_PROFILE_SETTINGS: list[ConfigSetting[Any]] = [
ConfigSetting(
dest="profile",
dest="kernel_modules_include",
metavar="REGEX",
section="Content",
- parse=config_make_list_parser(delimiter=","),
+ parse=config_make_list_parser(
+ delimiter=",",
+ parse=parse_kernel_module_filter_regexp,
+ ),
help="Include the specified kernel modules in the image",
),
ConfigSetting(
- dest="kernel_modules_include_host",
- metavar="BOOL",
+ dest="kernel_modules_exclude",
+ metavar="REGEX",
section="Content",
- parse=config_parse_boolean,
- help="Include the currently loaded modules on the host in the image",
+ parse=config_make_list_parser(
+ delimiter=",",
+ parse=parse_kernel_module_filter_regexp,
+ ),
+ help="Exclude the specified kernel modules from the image",
),
ConfigSetting(
- dest="kernel_modules_exclude",
- metavar="REGEX",
+ dest="kernel_modules_include",
+ name="KernelModules",
+ long="--kernel-modules",
+ metavar="GLOB",
section="Content",
parse=config_make_list_parser(delimiter=","),
- help="Exclude the specified kernel modules from the image",
+ help="Include/exclude the specified kernel modules in the image",
+ ),
+ ConfigSetting(
+ dest="kernel_modules_include_host",
+ metavar="BOOL",
+ section="Content",
+ parse=config_parse_boolean,
+ help="Include the currently loaded modules on the host in the image",
),
ConfigSetting(
dest="kernel_modules_initrd",
dest="kernel_modules_initrd_include",
metavar="REGEX",
section="Content",
- parse=config_make_list_parser(delimiter=","),
+ parse=config_make_list_parser(
+ delimiter=",",
+ parse=parse_kernel_module_filter_regexp,
+ ),
help="When building a kernel modules initrd, include the specified kernel modules",
),
+ ConfigSetting(
+ dest="kernel_modules_initrd_exclude",
+ metavar="REGEX",
+ section="Content",
+ parse=config_make_list_parser(
+ delimiter=",",
+ parse=parse_kernel_module_filter_regexp,
+ ),
+ help="When building a kernel modules initrd, exclude the specified kernel modules",
+ ),
+ ConfigSetting(
+ dest="kernel_modules_initrd_include",
+ name="KernelInitrdModules",
+ long="--kernel-initrd-modules",
+ metavar="GLOB",
+ section="Content",
+ parse=config_make_list_parser(delimiter=","),
+ help="Include/exclude modules in the initrd",
+ ),
ConfigSetting(
dest="kernel_modules_initrd_include_host",
metavar="BOOL",
help="When building a kernel modules initrd, include the currently loaded modules "
"on the host in the image",
),
- ConfigSetting(
- dest="kernel_modules_initrd_exclude",
- metavar="REGEX",
- section="Content",
- parse=config_make_list_parser(delimiter=","),
- help="When building a kernel modules initrd, exclude the specified kernel modules",
- ),
ConfigSetting(
dest="firmware_include",
metavar="REGEX",
]
SETTINGS_LOOKUP_BY_NAME = {name: s for s in SETTINGS for name in [s.name, *s.compat_names]}
SETTINGS_LOOKUP_BY_DEST = {s.dest: s for s in SETTINGS}
+SETTINGS_LOOKUP_BY_OPTION = {
+ name: s
+ for s in SETTINGS
+ for name in [s.long, *s.compat_longs, s.short] if name
+} # fmt: skip
SETTINGS_LOOKUP_BY_SPECIFIER = {s.specifier: s for s in SETTINGS if s.specifier}
MATCHES = (
) -> None:
assert option_string is not None
- s = SETTINGS_LOOKUP_BY_DEST[self.dest]
+ # For options that have the same dest, try to figure out the right
+ # option by matching the option name
+ s = SETTINGS_LOOKUP_BY_OPTION[self.option_strings[0]]
if values is None or isinstance(values, str):
values = [values]
Devicetree: {none_to_none(config.devicetree)}
Splash: {none_to_none(config.splash)}
Kernel Command Line: {line_join_list(config.kernel_command_line)}
- Kernel Modules Include: {line_join_list(config.kernel_modules_include)}
+ Kernel Modules: {line_join_list(config.kernel_modules_include)}
Kernel Modules Exclude: {line_join_list(config.kernel_modules_exclude)}
Kernel Modules Include Host: {yes_no(config.kernel_modules_include_host)}
Firmware Include: {line_join_list(config.firmware_include)}
Firmware Exclude: {line_join_list(config.firmware_exclude)}
Kernel Modules Initrd: {yes_no(config.kernel_modules_initrd)}
- Kernel Modules Initrd Include: {line_join_list(config.kernel_modules_initrd_include)}
+ Kernel Initrd Modules: {line_join_list(config.kernel_modules_initrd_include)}
Kernel Modules Initrd Exclude: {line_join_list(config.kernel_modules_initrd_exclude)}
Kernel Modules Initrd Include Host: {yes_no(config.kernel_modules_initrd_include_host)}
# SPDX-License-Identifier: LGPL-2.1-or-later
+import fnmatch
import itertools
import logging
import os
def loaded_modules() -> list[str]:
# Loaded modules are listed with underscores but the filenames might use dashes instead.
return [
- rf"/{line.split()[0].replace('_', '[_-]')}\.ko"
- for line in Path("/proc/modules").read_text().splitlines()
+ normalize_module_name(line.split()[0]) for line in Path("/proc/modules").read_text().splitlines()
]
+def globs_match_module(name, globs) -> bool:
+ # Strip '.ko' suffix and an optional compression suffix
+ name = re.sub(r"\.ko(\.(gz|xz|zst))?$", "", name)
+ # Check whether the suffixless-path matches any of the globs
+
+ for glob in reversed(globs):
+ # Patterns are evaluated in order and last matching one wins.
+ # Patterns may be prefixed with '-' to exclude modules.
+ if negative := glob.startswith("-"):
+ glob = glob[1:]
+ # As a special case, if a directory is specified, all items
+ # below that directory are matched.
+ if glob.endswith("/"):
+ glob += "*"
+
+ if (
+ # match the full path
+ (glob.startswith("/") and fnmatch.fnmatch(f"/{name}", glob))
+ # match a subset of the path, at path element boundary
+ or ("/" in glob and fnmatch.fnmatch(f"/{name}", f"*/{glob}"))
+ # match the basename
+ or fnmatch.fnmatch(name.split("/")[-1], glob)
+ ):
+ return not negative
+ return False
+
+
def filter_kernel_modules(
root: Path,
kver: str,
include: Iterable[str],
exclude: Iterable[str],
) -> list[Path]:
+ logging.debug(f"Kernel modules include: {' '.join(include)}")
+ logging.debug(f"Kernel modules exclude: {' '.join(exclude)}")
+
modulesd = Path("usr/lib/modules") / kver
with chdir(root):
- modules = set(modulesd.rglob("*.ko*"))
+ # The glob may match additional paths.
+ # Narrow this down to *.ko, *.ko.gz, *.ko.xz, *.ko.zst.
+ modules = {
+ m for m in modulesd.rglob("*.ko*") if m.name.endswith((".ko", ".ko.gz", ".ko.xz", ".ko.zst"))
+ }
+
+ n_modules = len(modules)
keep = set()
+
if include:
- regex = re.compile("|".join(include))
+ patterns = [p[3:] for p in include if p.startswith("re:")]
+ regex = re.compile("|".join(patterns))
+
+ globs = [normalize_module_glob(p) for p in include if not p.startswith("re:")]
+
for m in modules:
rel = os.fspath(Path(*m.parts[5:]))
- if regex.search(rel):
- keep.add(rel)
+
+ if (patterns and regex.search(rel)) or globs_match_module(normalize_module_name(rel), globs):
+ keep.add(m)
if exclude:
+ assert all(p.startswith("re:") for p in exclude)
+ patterns = [p[3:] for p in exclude]
+ regex = re.compile("|".join(patterns))
+
remove = set()
- regex = re.compile("|".join(exclude))
- for m in modules:
+ for m in keep:
rel = os.fspath(Path(*m.parts[5:]))
- if rel not in keep and regex.search(rel):
+ if regex.search(rel):
remove.add(m)
- modules -= remove
+ keep -= remove
+
+ logging.debug(f'Including {len(keep)}/{n_modules} kernel modules.')
- return sorted(modules)
+ return sorted(keep)
def filter_firmware(
def normalize_module_name(name: str) -> str:
+ # Replace '_' by '-'
return name.replace("_", "-")
+def normalize_module_glob(name: str) -> str:
+ # We want to replace '_' by '-', except when used in […]
+ ans = ""
+ while name:
+ i = (name + "[").index("[")
+ ans += name[:i].replace("_", "-")
+ name = name[i:]
+ i = (name + "]").index("]")
+ ans += name[: i + 1]
+ name = name[i + 1 :]
+ return ans
+
+
def module_path_to_name(path: Path) -> str:
return normalize_module_name(path.name.partition(".")[0])
# There is firmware in /usr/lib/firmware that is not depended on by any modules so if any firmware was
# installed we have to take the slow path to make sure we don't copy firmware into the initrd that is not
# depended on by any kernel modules.
- if modules_exclude or (context.root / firmwared).glob("*"):
+ if modules_include or modules_exclude or (context.root / firmwared).glob("*"):
modules = filter_kernel_modules(context.root, kver, include=modules_include, exclude=modules_exclude)
names = [module_path_to_name(m) for m in modules]
mods, firmware = resolve_module_dependencies(context, kver, names)
firmware_include: Iterable[str],
firmware_exclude: Iterable[str],
) -> None:
- if not modules_exclude and not firmware_exclude:
+ if not (modules_include or modules_exclude or firmware_exclude):
return
modulesd = Path("usr/lib/modules") / kver
"command",
"line"
],
+ "KernelInitrdModules": [],
+ "KernelModules": [
+ "loop"
+ ],
"KernelModulesExclude": [
"nvidia"
],
- "KernelModulesInclude": [
- "loop"
- ],
"KernelModulesIncludeHost": true,
"KernelModulesInitrd": true,
"KernelModulesInitrdExclude": [],
- "KernelModulesInitrdInclude": [],
"KernelModulesInitrdIncludeHost": true,
"Key": null,
"Keymap": "wow, so much keymap",
--- /dev/null
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+from mkosi import kmod
+
+
+def test_globs_match_module():
+ assert kmod.globs_match_module("drivers/ata/ahci.ko.xz", ["ahci"])
+ assert not kmod.globs_match_module("drivers/ata/ahci.ko.xz.2", ["ahci"])
+ assert not kmod.globs_match_module("drivers/ata/ahci.ko.xz", ["ata"])
+ assert not kmod.globs_match_module("drivers/ata/ahci.ko.xz", ["drivers"])
+ assert not kmod.globs_match_module("drivers/ata/ahci.ko.xz", ["/drivers"])
+ assert not kmod.globs_match_module("drivers/ata/ahci.ko.xz", ["/drivers"])
+ assert not kmod.globs_match_module("drivers/ata/ahci-2.ko.xz", ["ahci"])
+ assert not kmod.globs_match_module("drivers/ata/ahci2.ko.zst", ["ahci"])
+ assert kmod.globs_match_module("drivers/ata/ahci.ko", ["ata/*"])
+ assert not kmod.globs_match_module("drivers/ata/ahci.ko.xz", ["/ata/*"])
+ assert kmod.globs_match_module("drivers/ata/ahci.ko", ["drivers/*"])
+ assert kmod.globs_match_module("drivers/ata/ahci.ko", ["/drivers/*"])
+ assert not kmod.globs_match_module("drivers/ata/ahci.ko", ["ahci/*"])
+ assert not kmod.globs_match_module("drivers/ata/ahci.ko", ["bahci*"])
+ assert kmod.globs_match_module("drivers/ata/ahci.ko.zst", ["ahci*"])
+ assert kmod.globs_match_module("drivers/ata/ahci.ko.xz", ["ahc*"])
+ assert kmod.globs_match_module("drivers/ata/ahci.ko", ["ah*"])
+ assert kmod.globs_match_module("drivers/ata/ahci.ko", ["*"])
+ assert kmod.globs_match_module("drivers/ata/ahci.ko", ["ata/"])
+ assert kmod.globs_match_module("drivers/ata/ahci.ko", ["drivers/"])
+ assert kmod.globs_match_module("drivers/ata/ahci.ko", ["drivers/ata/"])
+
+ assert kmod.globs_match_module("drivers/ata/ahci.ko", ["-ahci", "*"])
+ assert kmod.globs_match_module("drivers/ata/ahci.ko", ["-ahci", "*", "ahciahci"])
+ assert kmod.globs_match_module("drivers/ata/ahci.ko.xz", ["-ahci", "*"])
+ assert kmod.globs_match_module("drivers/ata/ahci.ko.zst", ["-ahci", "*"])
+ assert kmod.globs_match_module("drivers/ata/ahci.ko.gz", ["-ahci", "*"])
+ assert kmod.globs_match_module("drivers/ata/ahci.ko.gz", ["-ahci", "drivers/"])
+ assert kmod.globs_match_module("drivers/ata/ahci.ko.gz", ["-ahci", "ata/"])
+ assert not kmod.globs_match_module("drivers/ata/ahci.ko.gz", ["-ahci", "ata/ata/"])
+ assert kmod.globs_match_module("drivers/ata/ahci.ko.gz", ["-ahci", "drivers/ata/"])
+ assert not kmod.globs_match_module("drivers/ata/ahci.ko", ["*", "-ahci"])
+ assert not kmod.globs_match_module("drivers/ata/ahci.ko", ["ahci", "-*"])
+ assert not kmod.globs_match_module("drivers/ata/ahci.ko.zst", ["-*"])
+ assert not kmod.globs_match_module("drivers/ata/ahci.ko.xz", ["-*"])
+
+
+def test_normalize_module_glob():
+ assert kmod.normalize_module_glob("raid[0-9]") == "raid[0-9]"
+ assert kmod.normalize_module_glob("raid[0_9]") == "raid[0_9]"
+ assert kmod.normalize_module_glob("raid[0_9]a_z") == "raid[0_9]a-z"
+ assert kmod.normalize_module_glob("0_9") == "0-9"
+ assert kmod.normalize_module_glob("[0_9") == "[0_9"
+ assert kmod.normalize_module_glob("0_9]") == "0-9]"
+ assert kmod.normalize_module_glob("raid[0_9]a_z[a_c]") == "raid[0_9]a-z[a_c]"