Skip to content

Commit 435e739

Browse files
committed
parse modules.builtin.modinfo and modules.alias
this is used to better resolve kmod info and determine builtin kmods also normalizes the format of the builtin modinfo uses _get_kmod_info to get kmod info instead of directly using _kmod_info Signed-off-by: Zen <[email protected]>
1 parent 1c9b7cf commit 435e739

File tree

2 files changed

+130
-43
lines changed

2 files changed

+130
-43
lines changed

src/ugrd/kmod/kmod.py

Lines changed: 129 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,23 @@
11
__author__ = "desultory"
2-
__version__ = "3.5.0"
2+
__version__ = "4.0.0"
33

44
from pathlib import Path
55
from platform import uname
6+
from re import search
67
from struct import error as StructError
78
from struct import unpack
89
from subprocess import run
910
from typing import Union
1011

1112
from ugrd.exceptions import AutodetectError, ValidationError
12-
from ugrd.kmod import (
13-
BuiltinModuleError,
14-
DependencyResolutionError,
15-
IgnoredModuleError,
16-
MissingModuleError,
17-
)
13+
from ugrd.kmod import BuiltinModuleError, DependencyResolutionError, IgnoredModuleError, MissingModuleError
1814
from zenlib.util import colorize as c_
1915
from zenlib.util import contains, unset
2016

17+
_KMOD_ALIASES = {}
2118
MODULE_METADATA_FILES = ["modules.order", "modules.builtin", "modules.builtin.modinfo"]
2219

20+
2321
def _normalize_kmod_name(self, module: Union[str, list]) -> str:
2422
"""Replaces -'s with _'s in a kernel module name.
2523
ignores modules defined in kmod_no_normalize.
@@ -34,6 +32,32 @@ def _normalize_kmod_name(self, module: Union[str, list]) -> str:
3432
return module.replace("-", "_")
3533

3634

35+
def _normalize_kmod_alias(self, alias: str) -> str:
36+
"""Gets the base alias name from kmod alias info
37+
gets data after : and , if present.
38+
"""
39+
if not alias:
40+
return ""
41+
alias = alias.split(":", 1)[-1] # Strip bus type
42+
alias = alias.split(",", 1)[-1]
43+
return _normalize_kmod_name(self, alias)
44+
45+
46+
def _resolve_kmod_alias(self, module: str) -> str:
47+
"""Attempts to resolve a kernel module alias to a module name.
48+
Uses /lib/modules/<kernel_version>/modules.alias to find the module name.
49+
normalizes - to _ then replaces _ with [_-] to allow for both _ and - in the module name.
50+
"""
51+
module = module.replace("-", "_")
52+
module = module.replace("_", "[_-]") # Allow for both _ and - in the module name
53+
for alias, kmod in _KMOD_ALIASES.items():
54+
if search(module, alias):
55+
self.logger.debug(f"Resolved kernel module alias: {c_(alias, 'green')} -> {c_(kmod, 'cyan')}")
56+
return kmod
57+
58+
raise MissingModuleError(f"Failed to resolve kernel module alias: {module}")
59+
60+
3761
def _process_kernel_modules_multi(self, module: str) -> None:
3862
"""Adds kernel modules to self['kernel_modules']."""
3963
module = _normalize_kmod_name(self, module)
@@ -81,14 +105,14 @@ def _process__kmod_auto_multi(self, module: str) -> None:
81105
self["_kmod_auto"].append(module)
82106

83107

84-
def _get_kmod_info(self, module: str):
108+
def _get_kmod_info(self, module: str) -> dict:
85109
"""
86110
Runs modinfo on a kernel module, parses the output and stored the results in self['_kmod_modinfo'].
87111
!!! Should be run after metadata is processed so the kver is set properly !!!
88112
"""
89113
module = _normalize_kmod_name(self, module)
90114
if module in self["_kmod_modinfo"]:
91-
return self.logger.debug("[%s] Module info already exists." % module)
115+
return self["_kmod_modinfo"][module]
92116
args = ["modinfo", module, "--set-version", self["kernel_version"]]
93117

94118
try:
@@ -98,9 +122,15 @@ def _get_kmod_info(self, module: str):
98122
raise DependencyResolutionError("[%s] Failed to run modinfo command: %s" % (module, " ".join(args))) from e
99123

100124
if not cmd.stdout and cmd.stderr:
101-
raise DependencyResolutionError("[%s] Modinfo returned no output." % module)
125+
try:
126+
resolved_module = _resolve_kmod_alias(self, module)
127+
return _get_kmod_info(self, resolved_module)
128+
except MissingModuleError:
129+
raise DependencyResolutionError(
130+
"[%s] Modinfo returned no output and the alias name could no be resolved." % module
131+
)
102132

103-
module_info = {}
133+
module_info = {"filename": None, "depends": [], "softdep": [], "firmware": []}
104134
for line in cmd.stdout.decode().split("\n"):
105135
line = line.strip()
106136
if line.startswith("filename:"):
@@ -125,6 +155,51 @@ def _get_kmod_info(self, module: str):
125155

126156
self.logger.debug("[%s] Module info: %s" % (module, module_info))
127157
self["_kmod_modinfo"][module] = module_info
158+
return module_info
159+
160+
161+
@unset("no_kmod", "no_kmod is enabled, skipping module alias enumeration.", log_level=30)
162+
def get_module_aliases(self):
163+
"""Processes the kernel module aliases from /lib/modules/<kernel_version>/modules.alias."""
164+
alias_file = Path("/lib/modules") / self["kernel_version"] / "modules.alias"
165+
if not alias_file.exists():
166+
self.logger.error(f"Kernel module alias file does not exist: {c_(alias_file, 'red', bold=True)}")
167+
else:
168+
for line in alias_file.read_text().splitlines():
169+
_, alias, module = line.strip().split(" ", 2)
170+
_KMOD_ALIASES[_normalize_kmod_alias(self, alias)] = _normalize_kmod_name(self, module)
171+
172+
173+
@unset("no_kmod", "no_kmod is enabled, skipping builtin module enumeration.", log_level=30)
174+
def get_builtin_module_info(self) -> None:
175+
"""Gets the kernel module aliases from /lib/modules/<kernel_version>/modules.builtin.modinfo.
176+
puts it in _kmod_modinfo.
177+
also populates the _KMOD_ALIASES global variable with the aliases.
178+
"""
179+
180+
builtin_modinfo_file = Path("/lib/modules") / self["kernel_version"] / "modules.builtin.modinfo"
181+
if not builtin_modinfo_file.exists():
182+
self.logger.error(f"Builtin modinfo file does not exist: {c_(builtin_modinfo_file, 'red', bold=True)}")
183+
else:
184+
for line in builtin_modinfo_file.read_bytes().split(b"\x00"):
185+
""" Lines are in the format <name>.<parameter>=<value>"""
186+
line = line.decode("utf-8", errors="ignore").strip()
187+
if not line or "." not in line or "=" not in line:
188+
continue
189+
name, parameter = line.split(".", 1)
190+
name = _normalize_kmod_name(self, name)
191+
parameter, value = parameter.split("=", 1)
192+
modinfo = self["_kmod_modinfo"].get(
193+
name, {"filename": "(builtin)", "depends": [], "softdep": [], "firmware": []}
194+
)
195+
if parameter == "firmware":
196+
modinfo["firmware"].append(value)
197+
elif parameter != "alias":
198+
continue
199+
200+
alias = _normalize_kmod_alias(self, value)
201+
self["_kmod_modinfo"][name] = modinfo
202+
_KMOD_ALIASES[alias] = name # Store the alias in the global aliases dict
128203

129204

130205
@contains("kmod_autodetect_lspci", "kmod_autodetect_lspci is not enabled, skipping.")
@@ -159,12 +234,14 @@ def _autodetect_modules_lsmod(self) -> None:
159234
modules = [line.split()[0] for line in f.readlines()]
160235

161236
if len(modules) > 25:
162-
self.logger.warning(f"[{len(modules)}] More than 25 kernel modules were autodetected from the running kernel. If lsmod detection is required for your use case, please file a bug report so more appropriate detection methods can be implemented.")
237+
self.logger.warning(
238+
f"[{len(modules)}] More than 25 kernel modules were autodetected from the running kernel. If lsmod detection is required for your use case, please file a bug report so more appropriate detection methods can be implemented."
239+
)
163240
for module in modules:
164241
self["_kmod_auto"] = module.split()[0]
165242

166243

167-
@unset("no_kmod", "no_kmod is enabled, skipping.", log_level=30)
244+
@unset("no_kmod", "no_kmod is enabled, skipping module detection.", log_level=30)
168245
@contains("hostonly", "Skipping kmod autodetection, hostonly is disabled.", log_level=30)
169246
def autodetect_modules(self) -> None:
170247
"""Autodetects kernel modules from lsmod and/or lspci -k."""
@@ -309,24 +386,24 @@ def _add_kmod_firmware(self, kmod: str) -> None:
309386
310387
Attempts to run even if no_kmod is set; this will not work if there are no kmods/no kernel version set
311388
"""
312-
kmod = _normalize_kmod_name(self, kmod)
313-
314-
if kmod not in self["_kmod_modinfo"]:
389+
try:
390+
modinfo = _get_kmod_info(self, kmod)
391+
except DependencyResolutionError as e:
315392
if self["no_kmod"]:
316393
return self.logger.warning(
317394
"[%s] Kernel module info for firmware detection does not exist, but no_kmod is set." % kmod
318395
)
319-
raise DependencyResolutionError("Kernel module info does not exist: %s" % kmod)
396+
raise DependencyResolutionError("Kernel module info does not exist: %s" % kmod) from e
320397

321-
if self["_kmod_modinfo"][kmod].get("firmware") and not self["kmod_pull_firmware"]:
398+
if modinfo["firmware"] and not self["kmod_pull_firmware"]:
322399
# Log a warning if the kernel module has firmware files, but kmod_pull_firmware is not set
323400
self.logger.warning("[%s] Kernel module has firmware files, but kmod_pull_firmware is not set." % kmod)
324401

325-
if not self["_kmod_modinfo"][kmod].get("firmware") or not self.get("kmod_pull_firmware"):
402+
if not modinfo["firmware"] or not self.get("kmod_pull_firmware"):
326403
# No firmware files to add, or kmod_pull_firmware is not set
327404
return
328405

329-
for firmware in self["_kmod_modinfo"][kmod]["firmware"]:
406+
for firmware in modinfo["firmware"]:
330407
_add_firmware_dep(self, kmod, firmware)
331408

332409

@@ -358,15 +435,14 @@ def _process_kmod_dependencies(self, kmod: str, mod_tree=None) -> None:
358435
If the dependency is already in the module tree, skip it to prevent infinite recursion.
359436
"""
360437
mod_tree = mod_tree or set()
361-
kmod = _normalize_kmod_name(self, kmod)
362-
_get_kmod_info(self, kmod)
438+
modinfo = _get_kmod_info(self, kmod)
363439

364440
# Get kernel module dependencies, softedeps if not ignored
365441
dependencies = []
366-
if harddeps := self["_kmod_modinfo"][kmod].get("depends"):
442+
if harddeps := modinfo["depends"]:
367443
dependencies += harddeps
368444

369-
if sofdeps := self["_kmod_modinfo"][kmod].get("softdep"):
445+
if sofdeps := modinfo["softdep"]:
370446
if self.get("kmod_ignore_softdeps", False):
371447
self.logger.warning("[%s] Soft dependencies were detected, but are being ignored: %s" % (kmod, sofdeps))
372448
else:
@@ -378,12 +454,11 @@ def _process_kmod_dependencies(self, kmod: str, mod_tree=None) -> None:
378454
if dependency in mod_tree:
379455
self.logger.debug("[%s] Dependency is already in mod_tree: %s" % (kmod, dependency))
380456
continue
457+
dep_modinfo = _get_kmod_info(self, dependency) # Get modinfo for the dependency
381458
if dependency in self["kmod_ignore"]: # Don't add modules with ignored dependencies
382-
_get_kmod_info(self, dependency) # Make sure modinfo is queried in case it's built-in
383-
if modinfo := self["_kmod_modinfo"].get(dependency):
384-
if modinfo["filename"] == "(builtin)": # If it's ignored because builtin, that's fine
385-
self.logger.debug("[%s] Ignored dependency is a built-in module: %s" % (kmod, dependency))
386-
continue
459+
if dep_modinfo["filename"] == "(builtin)":
460+
self.logger.debug("[%s] Ignored dependency is a built-in module: %s" % (kmod, dependency))
461+
continue
387462
# If modinfo doesn't exist, or it's not builtin, simply raise an ignored module error
388463
raise IgnoredModuleError("[%s] Kernel module dependency is in ignore list: %s" % (kmod, dependency))
389464
if dependency in self["kernel_modules"]:
@@ -398,7 +473,7 @@ def _process_kmod_dependencies(self, kmod: str, mod_tree=None) -> None:
398473
continue
399474
self["kernel_modules"] = dependency
400475

401-
if self["_kmod_modinfo"][kmod]["filename"] == "(builtin)": # for built-in modules, just add firmware and return
476+
if modinfo["filename"] == "(builtin)": # for built-in modules, just add firmware and return
402477
_add_kmod_firmware(self, kmod)
403478
raise BuiltinModuleError("Not adding built-in module to dependencies: %s" % kmod)
404479

@@ -420,7 +495,8 @@ def add_kmod_deps(self):
420495
continue
421496

422497
# Add the kmod file to the initramfs dependenceis
423-
filename = self["_kmod_modinfo"][kmod]["filename"]
498+
modinfo = _get_kmod_info(self, kmod)
499+
filename = modinfo["filename"]
424500
if filename.endswith(".ko"):
425501
self["dependencies"] = filename
426502
elif filename.endswith(".ko.xz"):
@@ -440,16 +516,19 @@ def process_ignored_module(self, module: str) -> None:
440516
for key in ["kmod_init", "kernel_modules", "_kmod_auto"]:
441517
if module in self[key]:
442518
if key == "kmod_init":
443-
if module in self["_kmod_modinfo"] and self["_kmod_modinfo"][module]["filename"] == "(builtin)":
444-
self.logger.debug("Removing built-in module from kmod_init: %s" % module)
445-
elif module == "zfs":
446-
self.logger.critical("ZFS module is required but missing.")
447-
self.logger.critical("Please build/install the required kmods before running this script.")
448-
self.logger.critical("Detected kernel version: %s" % self["kernel_version"])
449-
# https://github.com/projg2/installkernel-gentoo/commit/1c70dda8cd2700e5306d2ed74886b66ad7ccfb42
450-
exit(77)
451-
else:
452-
raise MissingModuleError("Required module cannot be imported and is not builtin: %s" % module)
519+
try:
520+
modinfo = _get_kmod_info(self, module)
521+
if modinfo["filename"] == "(builtin)":
522+
self.logger.debug("Removing built-in module from kmod_init: %s" % module)
523+
except DependencyResolutionError:
524+
if module == "zfs":
525+
self.logger.critical("ZFS module is required but missing.")
526+
self.logger.critical("Please build/install the required kmods before running this script.")
527+
self.logger.critical("Detected kernel version: %s" % self["kernel_version"])
528+
# https://github.com/projg2/installkernel-gentoo/commit/1c70dda8cd2700e5306d2ed74886b66ad7ccfb42
529+
exit(77)
530+
else:
531+
raise MissingModuleError("Required module cannot be imported and is not builtin: %s" % module)
453532
else:
454533
self.logger.debug("Removing ignored kernel module from %s: %s" % (key, module))
455534
self[key].remove(module)
@@ -477,7 +556,9 @@ def _process_optional_modules(self) -> None:
477556
self.logger.debug(f"Optional kmod_init module is built-in, skipping: {c_(kmod, 'yellow')}")
478557
continue
479558
except DependencyResolutionError as e:
480-
self.logger.warning(f"[{c_(kmod, 'yellow', bold=True)}] Failed to process optional kernel module dependencies: {e}")
559+
self.logger.warning(
560+
f"[{c_(kmod, 'yellow', bold=True)}] Failed to process optional kernel module dependencies: {e}"
561+
)
481562

482563

483564
@unset("no_kmod", "no_kmod is enabled, skipping.", log_level=30)
@@ -486,6 +567,11 @@ def process_modules(self) -> None:
486567
_process_optional_modules(self)
487568
self.logger.debug("Processing kernel modules: %s" % self["kernel_modules"])
488569
for kmod in self["kernel_modules"].copy():
570+
""" Process all kernel modules
571+
for kmod_init modules, log an error if info can't be retreived, but continue processing.
572+
in successful cases, continue, if he module processing fails, add to the ignore list.
573+
Later, when ignored modules are processed, an exception is raised if the module is required.
574+
"""
489575
self.logger.debug("Processing kernel module: %s" % kmod)
490576
try:
491577
_process_kmod_dependencies(self, kmod)
@@ -507,6 +593,7 @@ def process_modules(self) -> None:
507593
self["kmod_ignore"] = kmod
508594

509595
for kmod in self["_kmod_auto"]:
596+
""" Do similar for automatic modules, but log warnings insead of errors if dependencies are missing. """
510597
if kmod in self["kernel_modules"]:
511598
self.logger.debug("Autodetected module is already in kernel_modules: %s" % kmod)
512599
continue

src/ugrd/kmod/kmod.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ kmod_no_normalize = "NoDupFlatList" # Kernel modules to not normalize (i.e. not
3131
"_process__kmod_auto_multi" ]
3232

3333
[imports.build_enum]
34-
"ugrd.kmod.kmod" = [ "get_kernel_version", "autodetect_modules" ]
34+
"ugrd.kmod.kmod" = [ "get_kernel_version", "get_module_aliases", "get_builtin_module_info", "autodetect_modules" ]
3535

3636
[imports.build_late]
3737
"ugrd.kmod.kmod" = [ "process_modules", "process_ignored_modules", "process_module_metadata", "add_kmod_deps" ]

0 commit comments

Comments
 (0)