m/p/kmod: init
This adds a package for working with Linux kernel loadable modules in
Go. It contains syscall wrappers for loading and unloading modules, a
metadata format for fast lookup of modules handling devices using a
custom radix tree, parsers for module info metadata and various utility
functions and data structures.
A significant amount of the code in here has no formal spec and is
written against behavior and information extracted from the reference
kmod code as well as the Linux kernel itself.
Change-Id: I3d527f3631f4dd1832b9cfba2d50aeb03a2b88a8
Reviewed-on: https://review.monogon.dev/c/monogon/+/1789
Reviewed-by: Serge Bazanski <serge@monogon.tech>
Tested-by: Jenkins CI
diff --git a/metropolis/pkg/kmod/modinfo.go b/metropolis/pkg/kmod/modinfo.go
new file mode 100644
index 0000000..adaf5d1
--- /dev/null
+++ b/metropolis/pkg/kmod/modinfo.go
@@ -0,0 +1,206 @@
+package kmod
+
+import (
+ "bufio"
+ "bytes"
+ "debug/elf"
+ "errors"
+ "fmt"
+ "io"
+ "strings"
+)
+
+// ModuleInfo contains Linux kernel module metadata. It maps keys to a list
+// of values. For known keys accessor functions are provided.
+type ModuleInfo map[string][]string
+
+// Get returns the first value of the given key or an empty string if the key
+// does not exist.
+func (i ModuleInfo) Get(key string) string {
+ if len(i[key]) > 0 {
+ return i[key][0]
+ }
+ return ""
+}
+
+// Name returns the name of the module as defined in kbuild.
+func (i ModuleInfo) Name() string {
+ return i.Get("name")
+}
+
+// Authors returns the list of a authors of the module.
+func (i ModuleInfo) Authors() []string {
+ return i["author"]
+}
+
+// Description returns a human-readable description of the module or an empty
+// string if it is not available.
+func (i ModuleInfo) Description() string {
+ return i.Get("description")
+}
+
+// GetDependencies returns a list of module names which need to be loaded
+// before this one.
+func (i ModuleInfo) GetDependencies() []string {
+ if len(i["depends"]) == 1 && i["depends"][0] == "" {
+ return nil
+ }
+ return i["depends"]
+}
+
+type OptionalDependencies struct {
+ // Pre contains a list of module names to be optionally loaded before the
+ // module itself.
+ Pre []string
+ // Post contains a list of module names to be optionally loaded after the
+ // module itself.
+ Post []string
+}
+
+// GetOptionalDependencies returns a set of modules which are recommended to
+// be loaded before and after this module. These are optional, but enhance
+// the functionality of this module.
+func (i ModuleInfo) GetOptionalDependencies() OptionalDependencies {
+ var out OptionalDependencies
+ for _, s := range i["softdep"] {
+ tokens := strings.Fields(s)
+ const (
+ MODE_IDLE = 0
+ MODE_PRE = 1
+ MODE_POST = 2
+ )
+ var state int = MODE_IDLE
+ for _, token := range tokens {
+ switch token {
+ case "pre:":
+ state = MODE_PRE
+ case "post:":
+ state = MODE_POST
+ default:
+ switch state {
+ case MODE_PRE:
+ out.Pre = append(out.Pre, token)
+ case MODE_POST:
+ out.Post = append(out.Post, token)
+ default:
+ }
+ }
+ }
+ }
+ return out
+}
+
+// Aliases returns a list of match expressions for matching devices handled
+// by this module. Every returned string consists of a literal as well as '*'
+// wildcards matching one or more characters. These should be matched against
+// the kobject MODALIAS field or the modalias sysfs file.
+func (i ModuleInfo) Aliases() []string {
+ return i["alias"]
+}
+
+// Firmware returns a list of firmware file paths required by this module.
+// These paths are usually relative to the root of a linux-firmware install
+// unless the firmware is non-redistributable.
+func (i ModuleInfo) Firmware() []string {
+ return i["firmware"]
+}
+
+// License returns the licenses use of this module is governed by. For mainline
+// modules, the list of valid license strings is documented in the kernel's
+// Documentation/process/license-rules.rst file under the `MODULE_LICENSE`
+// section.
+func (i ModuleInfo) Licenses() []string {
+ return i["license"]
+}
+
+// IsInTree returns true if the module was built in the Linux source tree and
+// not externally. This does not necessarily mean that the module is in the
+// mainline kernel.
+func (i ModuleInfo) IsInTree() bool {
+ return i.Get("intree") == "Y"
+}
+
+// vermagic and retpoline are intentionally not exposed here, if you need them
+// you should know how to get them out of the map yourself as AFAIK these
+// are not a stable interface and most programs should not process them.
+
+func nullByteSplit(data []byte, atEOF bool) (advance int, token []byte, err error) {
+ if atEOF && len(data) == 0 {
+ return 0, nil, nil
+ }
+ if i := bytes.IndexByte(data, 0x00); i >= 0 {
+ return i + 1, bytes.TrimLeft(data[0:i], "\x00"), nil
+ }
+ if atEOF {
+ return len(data), data, nil
+ }
+ return 0, nil, nil
+}
+
+// GetModuleInfo looks for a ".modinfo" section in the passed ELF Linux kernel
+// module and parses it into a ModuleInfo structure.
+func GetModuleInfo(e *elf.File) (ModuleInfo, error) {
+ for _, section := range e.Sections {
+ if section.Name == ".modinfo" {
+ out := make(ModuleInfo)
+ s := bufio.NewScanner(io.NewSectionReader(section, 0, int64(section.Size)))
+ s.Split(nullByteSplit)
+
+ for s.Scan() {
+ // Format is <key>=<value>
+ key, value, ok := bytes.Cut(s.Bytes(), []byte("="))
+ if !ok {
+ continue
+ }
+ keyStr := string(key)
+ out[keyStr] = append(out[keyStr], string(value))
+ }
+ return out, nil
+ }
+ }
+ return nil, errors.New("no .modinfo section found")
+}
+
+// GetBuiltinModulesInfo parses all modinfo structures for builtin modules from
+// a modinfo file (modules.builtin.modinfo).
+func GetBuiltinModulesInfo(f io.Reader) ([]ModuleInfo, error) {
+ var out []ModuleInfo
+ s := bufio.NewScanner(f)
+ s.Split(nullByteSplit)
+
+ currModule := make(ModuleInfo)
+ for s.Scan() {
+ if s.Err() != nil {
+ return nil, fmt.Errorf("failed scanning for next token: %w", s.Err())
+ }
+ // Format is <module>.<key>=<value>
+ modName, entry, ok := bytes.Cut(s.Bytes(), []byte{'.'})
+ if !ok {
+ continue
+ }
+ if string(modName) != currModule.Name() {
+ if currModule.Name() != "" {
+ out = append(out, currModule)
+ }
+ currModule = make(ModuleInfo)
+ currModule["name"] = []string{string(modName)}
+ }
+ key, value, ok := bytes.Cut(entry, []byte("="))
+ if !ok {
+ continue
+ }
+ keyStr := string(key)
+ currModule[keyStr] = append(currModule[keyStr], string(value))
+ }
+ if currModule.Name() != "" {
+ out = append(out, currModule)
+ }
+ seenModNames := make(map[string]bool)
+ for _, m := range out {
+ if seenModNames[m.Name()] {
+ return nil, fmt.Errorf("duplicate/out-of-order module metadata for module %q", m)
+ }
+ seenModNames[m.Name()] = true
+ }
+ return out, nil
+}