11__author__ = "desultory"
2- __version__ = "3.5 .0"
2+ __version__ = "4.0 .0"
33
44from pathlib import Path
55from platform import uname
6+ from re import search
67from struct import error as StructError
78from struct import unpack
89from subprocess import run
910from typing import Union
1011
1112from 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
1814from zenlib .util import colorize as c_
1915from zenlib .util import contains , unset
2016
17+ _KMOD_ALIASES = {}
2118MODULE_METADATA_FILES = ["modules.order" , "modules.builtin" , "modules.builtin.modinfo" ]
2219
20+
2321def _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+
3761def _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 )
169246def 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
0 commit comments