Skip to content

Commit cfa071a

Browse files
committed
improve module alias system
Signed-off-by: Zen <[email protected]>
1 parent 30910b4 commit cfa071a

File tree

2 files changed

+81
-47
lines changed

2 files changed

+81
-47
lines changed

src/ugrd/kmod/kmod.py

Lines changed: 80 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
__author__ = "desultory"
2-
__version__ = "3.6.0"
2+
__version__ = "4.0.0"
33

44
from pathlib import Path
55
from platform import uname
@@ -32,25 +32,15 @@ def _normalize_kmod_name(self, module: Union[str, list]) -> str:
3232
return module.replace("-", "_")
3333

3434

35-
def _get_kmod_aliases(self):
36-
"""Returns a processed dictionary of kernel module aliases."""
37-
if _KMOD_ALIASES:
38-
return _KMOD_ALIASES
39-
40-
if not self.get("kernel_version"):
41-
raise AutodetectError("Kernel version is not set, cannot resolve kernel module aliases.")
42-
43-
alias_file = Path("/lib/modules") / self["kernel_version"] / "modules.alias"
44-
if not alias_file.exists():
45-
raise FileNotFoundError(f"Kernel module alias file does not exist: {c_(alias_file, 'red', bold=True)}")
46-
47-
for line in alias_file.read_text().splitlines():
48-
_, alias, module = line.strip().split(" ", 2)
49-
# Strip bus type
50-
alias = alias.split(":", 1)[-1]
51-
alias = alias.split(",", 1)[-1]
52-
_KMOD_ALIASES[alias] = _normalize_kmod_name(self, module)
53-
return _KMOD_ALIASES
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)
5444

5545

5646
def _resolve_kmod_alias(self, module: str) -> str:
@@ -60,8 +50,7 @@ def _resolve_kmod_alias(self, module: str) -> str:
6050
"""
6151
module = module.replace("-", "_")
6252
module = module.replace("_", "[_-]") # Allow for both _ and - in the module name
63-
aliases = _get_kmod_aliases(self)
64-
for alias, kmod in aliases.items():
53+
for alias, kmod in _KMOD_ALIASES.items():
6554
if search(module, alias):
6655
self.logger.debug(f"Resolved kernel module alias: {c_(alias, 'green')} -> {c_(kmod, 'cyan')}")
6756
return kmod
@@ -116,14 +105,14 @@ def _process__kmod_auto_multi(self, module: str) -> None:
116105
self["_kmod_auto"].append(module)
117106

118107

119-
def _get_kmod_info(self, module: str):
108+
def _get_kmod_info(self, module: str) -> dict:
120109
"""
121110
Runs modinfo on a kernel module, parses the output and stored the results in self['_kmod_modinfo'].
122111
!!! Should be run after metadata is processed so the kver is set properly !!!
123112
"""
124113
module = _normalize_kmod_name(self, module)
125114
if module in self["_kmod_modinfo"]:
126-
return self.logger.debug("[%s] Module info already exists." % module)
115+
return self["_kmod_modinfo"][module]
127116
args = ["modinfo", module, "--set-version", self["kernel_version"]]
128117

129118
try:
@@ -138,10 +127,10 @@ def _get_kmod_info(self, module: str):
138127
return _get_kmod_info(self, resolved_module)
139128
except MissingModuleError:
140129
raise DependencyResolutionError(
141-
"[%s] Modinfo returned no output and alias name could no be resolved." % module
130+
"[%s] Modinfo returned no output and the alias name could no be resolved." % module
142131
)
143132

144-
module_info = {}
133+
module_info = {"filename": None, "depends": [], "softdep": [], "firmware": []}
145134
for line in cmd.stdout.decode().split("\n"):
146135
line = line.strip()
147136
if line.startswith("filename:"):
@@ -166,6 +155,51 @@ def _get_kmod_info(self, module: str):
166155

167156
self.logger.debug("[%s] Module info: %s" % (module, module_info))
168157
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"] += 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
169203

170204

171205
@contains("kmod_autodetect_lspci", "kmod_autodetect_lspci is not enabled, skipping.")
@@ -207,7 +241,7 @@ def _autodetect_modules_lsmod(self) -> None:
207241
self["_kmod_auto"] = module.split()[0]
208242

209243

210-
@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)
211245
@contains("hostonly", "Skipping kmod autodetection, hostonly is disabled.", log_level=30)
212246
def autodetect_modules(self) -> None:
213247
"""Autodetects kernel modules from lsmod and/or lspci -k."""
@@ -352,24 +386,24 @@ def _add_kmod_firmware(self, kmod: str) -> None:
352386
353387
Attempts to run even if no_kmod is set; this will not work if there are no kmods/no kernel version set
354388
"""
355-
kmod = _normalize_kmod_name(self, kmod)
356-
357-
if kmod not in self["_kmod_modinfo"]:
389+
try:
390+
modinfo = _get_kmod_info(self, kmod)
391+
except DependencyResolutionError as e:
358392
if self["no_kmod"]:
359393
return self.logger.warning(
360394
"[%s] Kernel module info for firmware detection does not exist, but no_kmod is set." % kmod
361395
)
362-
raise DependencyResolutionError("Kernel module info does not exist: %s" % kmod)
396+
raise DependencyResolutionError("Kernel module info does not exist: %s" % kmod) from e
363397

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

368-
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"):
369403
# No firmware files to add, or kmod_pull_firmware is not set
370404
return
371405

372-
for firmware in self["_kmod_modinfo"][kmod]["firmware"]:
406+
for firmware in modinfo["firmware"]:
373407
_add_firmware_dep(self, kmod, firmware)
374408

375409

@@ -401,15 +435,14 @@ def _process_kmod_dependencies(self, kmod: str, mod_tree=None) -> None:
401435
If the dependency is already in the module tree, skip it to prevent infinite recursion.
402436
"""
403437
mod_tree = mod_tree or set()
404-
kmod = _normalize_kmod_name(self, kmod)
405-
_get_kmod_info(self, kmod)
438+
modinfo = _get_kmod_info(self, kmod)
406439

407440
# Get kernel module dependencies, softedeps if not ignored
408441
dependencies = []
409-
if harddeps := self["_kmod_modinfo"][kmod].get("depends"):
442+
if harddeps := modinfo["depends"]:
410443
dependencies += harddeps
411444

412-
if sofdeps := self["_kmod_modinfo"][kmod].get("softdep"):
445+
if sofdeps := modinfo["softdep"]:
413446
if self.get("kmod_ignore_softdeps", False):
414447
self.logger.warning("[%s] Soft dependencies were detected, but are being ignored: %s" % (kmod, sofdeps))
415448
else:
@@ -421,12 +454,11 @@ def _process_kmod_dependencies(self, kmod: str, mod_tree=None) -> None:
421454
if dependency in mod_tree:
422455
self.logger.debug("[%s] Dependency is already in mod_tree: %s" % (kmod, dependency))
423456
continue
457+
dep_modinfo = _get_kmod_info(self, dependency) # Get modinfo for the dependency
424458
if dependency in self["kmod_ignore"]: # Don't add modules with ignored dependencies
425-
_get_kmod_info(self, dependency) # Make sure modinfo is queried in case it's built-in
426-
if modinfo := self["_kmod_modinfo"].get(dependency):
427-
if modinfo["filename"] == "(builtin)": # If it's ignored because builtin, that's fine
428-
self.logger.debug("[%s] Ignored dependency is a built-in module: %s" % (kmod, dependency))
429-
continue
459+
if dep_modinfo["filename"] == "(builtin)":
460+
self.logger.debug("[%s] Ignored dependency is a built-in module: %s" % (kmod, dependency))
461+
continue
430462
# If modinfo doesn't exist, or it's not builtin, simply raise an ignored module error
431463
raise IgnoredModuleError("[%s] Kernel module dependency is in ignore list: %s" % (kmod, dependency))
432464
if dependency in self["kernel_modules"]:
@@ -441,7 +473,7 @@ def _process_kmod_dependencies(self, kmod: str, mod_tree=None) -> None:
441473
continue
442474
self["kernel_modules"] = dependency
443475

444-
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
445477
_add_kmod_firmware(self, kmod)
446478
raise BuiltinModuleError("Not adding built-in module to dependencies: %s" % kmod)
447479

@@ -463,7 +495,8 @@ def add_kmod_deps(self):
463495
continue
464496

465497
# Add the kmod file to the initramfs dependenceis
466-
filename = self["_kmod_modinfo"][kmod]["filename"]
498+
modinfo = _get_kmod_info(self, kmod)
499+
filename = modinfo["filename"]
467500
if filename.endswith(".ko"):
468501
self["dependencies"] = filename
469502
elif filename.endswith(".ko.xz"):
@@ -483,7 +516,8 @@ def process_ignored_module(self, module: str) -> None:
483516
for key in ["kmod_init", "kernel_modules", "_kmod_auto"]:
484517
if module in self[key]:
485518
if key == "kmod_init":
486-
if module in self["_kmod_modinfo"] and self["_kmod_modinfo"][module]["filename"] == "(builtin)":
519+
modinfo = _get_kmod_info(self, module)
520+
if modinfo["filename"] == "(builtin)":
487521
self.logger.debug("Removing built-in module from kmod_init: %s" % module)
488522
elif module == "zfs":
489523
self.logger.critical("ZFS module is required but missing.")

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)