Skip to content

Commit bf675ee

Browse files
authored
Merge pull request #341 from desultory/dev
revamp module alias/resolution system, add mmc platform driver detection
2 parents 29cf0ab + 8d456c1 commit bf675ee

File tree

5 files changed

+181
-47
lines changed

5 files changed

+181
-47
lines changed

src/ugrd/fs/mounts.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from typing import Union
77

88
from ugrd.exceptions import AutodetectError, ValidationError
9+
from ugrd.kmod.platform import _get_platform_mmc_drivers
910
from zenlib.util import colorize as c_
1011
from zenlib.util import contains, pretty_print
1112

@@ -1075,6 +1076,7 @@ def resolve_blkdev_kmod(self, device) -> list[str]:
10751076
kmods.append("virtio_scsi")
10761077
elif device_name.startswith("mmcblk"):
10771078
kmods.append("mmc_block")
1079+
kmods.extend(_get_platform_mmc_drivers(self, device_name))
10781080
elif device_name.startswith("sr"):
10791081
kmods.append("sr_mod")
10801082
elif device_name.startswith("md"):

src/ugrd/kmod/kmod.py

Lines changed: 144 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,23 @@
11
__author__ = "desultory"
2-
__version__ = "3.5.0"
2+
__version__ = "4.1.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.info(f"Resolved kernel module alias: {c_(alias, 'blue')} -> {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,22 @@ 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 !!!
112+
113+
Returns the module info as a dictionary with the following keys:
114+
- filename: The path to the module file.
115+
- depends: A list of module dependencies.
116+
- softdep: A list of soft dependencies.
117+
- firmware: A list of firmware files required by the module.
118+
Raises:
119+
DependencyResolutionError: If the modinfo command fails, returns no output, or the module name can't be resolved.
88120
"""
89121
module = _normalize_kmod_name(self, module)
90122
if module in self["_kmod_modinfo"]:
91-
return self.logger.debug("[%s] Module info already exists." % module)
123+
return module, self["_kmod_modinfo"][module]
92124
args = ["modinfo", module, "--set-version", self["kernel_version"]]
93125

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

100132
if not cmd.stdout and cmd.stderr:
101-
raise DependencyResolutionError("[%s] Modinfo returned no output." % module)
133+
try:
134+
resolved_module = _resolve_kmod_alias(self, module)
135+
return _get_kmod_info(self, resolved_module)
136+
except MissingModuleError:
137+
raise DependencyResolutionError(
138+
"[%s] Modinfo returned no output and the alias name could no be resolved." % module
139+
)
102140

103-
module_info = {}
141+
module_info = {"filename": None, "depends": [], "softdep": [], "firmware": []}
104142
for line in cmd.stdout.decode().split("\n"):
105143
line = line.strip()
106144
if line.startswith("filename:"):
@@ -125,6 +163,51 @@ def _get_kmod_info(self, module: str):
125163

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

129212

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

161244
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.")
245+
self.logger.warning(
246+
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."
247+
)
163248
for module in modules:
164249
self["_kmod_auto"] = module.split()[0]
165250

166251

167-
@unset("no_kmod", "no_kmod is enabled, skipping.", log_level=30)
252+
@unset("no_kmod", "no_kmod is enabled, skipping module detection.", log_level=30)
168253
@contains("hostonly", "Skipping kmod autodetection, hostonly is disabled.", log_level=30)
169254
def autodetect_modules(self) -> None:
170255
"""Autodetects kernel modules from lsmod and/or lspci -k."""
@@ -309,24 +394,24 @@ def _add_kmod_firmware(self, kmod: str) -> None:
309394
310395
Attempts to run even if no_kmod is set; this will not work if there are no kmods/no kernel version set
311396
"""
312-
kmod = _normalize_kmod_name(self, kmod)
313-
314-
if kmod not in self["_kmod_modinfo"]:
397+
try:
398+
kmod, modinfo = _get_kmod_info(self, kmod)
399+
except DependencyResolutionError as e:
315400
if self["no_kmod"]:
316401
return self.logger.warning(
317402
"[%s] Kernel module info for firmware detection does not exist, but no_kmod is set." % kmod
318403
)
319-
raise DependencyResolutionError("Kernel module info does not exist: %s" % kmod)
404+
raise DependencyResolutionError("Kernel module info does not exist: %s" % kmod) from e
320405

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

325-
if not self["_kmod_modinfo"][kmod].get("firmware") or not self.get("kmod_pull_firmware"):
410+
if not modinfo["firmware"] or not self.get("kmod_pull_firmware"):
326411
# No firmware files to add, or kmod_pull_firmware is not set
327412
return
328413

329-
for firmware in self["_kmod_modinfo"][kmod]["firmware"]:
414+
for firmware in modinfo["firmware"]:
330415
_add_firmware_dep(self, kmod, firmware)
331416

332417

@@ -356,17 +441,18 @@ def _process_kmod_dependencies(self, kmod: str, mod_tree=None) -> None:
356441
357442
Iterate over dependencies, adding them to kernel_mdules if they (or sub-dependencies) are not in the ignore list.
358443
If the dependency is already in the module tree, skip it to prevent infinite recursion.
444+
445+
returns the name of the kernel module (in case it was an alias) and the list of dependencies.
359446
"""
360447
mod_tree = mod_tree or set()
361-
kmod = _normalize_kmod_name(self, kmod)
362-
_get_kmod_info(self, kmod)
448+
kmod, modinfo = _get_kmod_info(self, kmod)
363449

364450
# Get kernel module dependencies, softedeps if not ignored
365451
dependencies = []
366-
if harddeps := self["_kmod_modinfo"][kmod].get("depends"):
452+
if harddeps := modinfo["depends"]:
367453
dependencies += harddeps
368454

369-
if sofdeps := self["_kmod_modinfo"][kmod].get("softdep"):
455+
if sofdeps := modinfo["softdep"]:
370456
if self.get("kmod_ignore_softdeps", False):
371457
self.logger.warning("[%s] Soft dependencies were detected, but are being ignored: %s" % (kmod, sofdeps))
372458
else:
@@ -378,12 +464,11 @@ def _process_kmod_dependencies(self, kmod: str, mod_tree=None) -> None:
378464
if dependency in mod_tree:
379465
self.logger.debug("[%s] Dependency is already in mod_tree: %s" % (kmod, dependency))
380466
continue
467+
dependency, dep_modinfo = _get_kmod_info(self, dependency) # Get modinfo for the dependency
381468
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
469+
if dep_modinfo["filename"] == "(builtin)":
470+
self.logger.debug("[%s] Ignored dependency is a built-in module: %s" % (kmod, dependency))
471+
continue
387472
# If modinfo doesn't exist, or it's not builtin, simply raise an ignored module error
388473
raise IgnoredModuleError("[%s] Kernel module dependency is in ignore list: %s" % (kmod, dependency))
389474
if dependency in self["kernel_modules"]:
@@ -398,10 +483,12 @@ def _process_kmod_dependencies(self, kmod: str, mod_tree=None) -> None:
398483
continue
399484
self["kernel_modules"] = dependency
400485

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

490+
return kmod, dependencies
491+
405492

406493
def add_kmod_deps(self):
407494
"""Adds all kernel modules to the initramfs dependencies.
@@ -420,7 +507,8 @@ def add_kmod_deps(self):
420507
continue
421508

422509
# Add the kmod file to the initramfs dependenceis
423-
filename = self["_kmod_modinfo"][kmod]["filename"]
510+
kmod, modinfo = _get_kmod_info(self, kmod)
511+
filename = modinfo["filename"]
424512
if filename.endswith(".ko"):
425513
self["dependencies"] = filename
426514
elif filename.endswith(".ko.xz"):
@@ -440,16 +528,19 @@ def process_ignored_module(self, module: str) -> None:
440528
for key in ["kmod_init", "kernel_modules", "_kmod_auto"]:
441529
if module in self[key]:
442530
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)
531+
try:
532+
module, modinfo = _get_kmod_info(self, module)
533+
if modinfo["filename"] == "(builtin)":
534+
self.logger.debug("Removing built-in module from kmod_init: %s" % module)
535+
except DependencyResolutionError:
536+
if module == "zfs":
537+
self.logger.critical("ZFS module is required but missing.")
538+
self.logger.critical("Please build/install the required kmods before running this script.")
539+
self.logger.critical("Detected kernel version: %s" % self["kernel_version"])
540+
# https://github.com/projg2/installkernel-gentoo/commit/1c70dda8cd2700e5306d2ed74886b66ad7ccfb42
541+
exit(77)
542+
else:
543+
raise MissingModuleError("Required module cannot be imported and is not builtin: %s" % module)
453544
else:
454545
self.logger.debug("Removing ignored kernel module from %s: %s" % (key, module))
455546
self[key].remove(module)
@@ -469,15 +560,17 @@ def _process_optional_modules(self) -> None:
469560
self.logger.debug(f"Optional kmod_init module is already in kmod_init: {c_(kmod, 'yellow', bold=True)}")
470561
continue
471562
try:
472-
_process_kmod_dependencies(self, kmod)
563+
kmod, dependencies = _process_kmod_dependencies(self, kmod)
473564
self["kmod_init"] = kmod # add to kmod_init so it will be loaded
474565
except IgnoredModuleError as e:
475566
self.logger.warning(e)
476567
except BuiltinModuleError:
477568
self.logger.debug(f"Optional kmod_init module is built-in, skipping: {c_(kmod, 'yellow')}")
478569
continue
479570
except DependencyResolutionError as e:
480-
self.logger.warning(f"[{c_(kmod, 'yellow', bold=True)}] Failed to process optional kernel module dependencies: {e}")
571+
self.logger.warning(
572+
f"[{c_(kmod, 'yellow', bold=True)}] Failed to process optional kernel module dependencies: {e}"
573+
)
481574

482575

483576
@unset("no_kmod", "no_kmod is enabled, skipping.", log_level=30)
@@ -486,6 +579,11 @@ def process_modules(self) -> None:
486579
_process_optional_modules(self)
487580
self.logger.debug("Processing kernel modules: %s" % self["kernel_modules"])
488581
for kmod in self["kernel_modules"].copy():
582+
""" Process all kernel modules
583+
for kmod_init modules, log an error if info can't be retreived, but continue processing.
584+
in successful cases, continue, if he module processing fails, add to the ignore list.
585+
Later, when ignored modules are processed, an exception is raised if the module is required.
586+
"""
489587
self.logger.debug("Processing kernel module: %s" % kmod)
490588
try:
491589
_process_kmod_dependencies(self, kmod)
@@ -507,13 +605,14 @@ def process_modules(self) -> None:
507605
self["kmod_ignore"] = kmod
508606

509607
for kmod in self["_kmod_auto"]:
608+
""" Do similar for automatic modules, but log warnings insead of errors if dependencies are missing. """
510609
if kmod in self["kernel_modules"]:
511610
self.logger.debug("Autodetected module is already in kernel_modules: %s" % kmod)
512611
continue
513612
self.logger.debug("Processing autodetected kernel module: %s" % kmod)
514613
try:
515-
_process_kmod_dependencies(self, kmod)
516-
self["kmod_init"] = kmod
614+
kmod_name, dependencies = _process_kmod_dependencies(self, kmod)
615+
self["kmod_init"] = kmod_name
517616
continue
518617
except BuiltinModuleError:
519618
continue # Don't add built-in modules to the ignore list

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)