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/manager.go b/metropolis/pkg/kmod/manager.go
new file mode 100644
index 0000000..1630a7f
--- /dev/null
+++ b/metropolis/pkg/kmod/manager.go
@@ -0,0 +1,158 @@
+package kmod
+
+import (
+	"bufio"
+	"fmt"
+	"os"
+	"path/filepath"
+	"strings"
+	"sync"
+
+	"golang.org/x/sys/unix"
+	"google.golang.org/protobuf/proto"
+
+	kmodpb "source.monogon.dev/metropolis/pkg/kmod/spec"
+)
+
+// Manager contains all the logic and metadata required to efficiently load
+// Linux kernel modules. It has internal loading tracking, thus it's recommended
+// to only keep one Manager instance running per kernel. It can recreate its
+// internal state quite well, but there are edge cases where the kernel makes
+// it hard to do so (MODULE_STATE_UNFORMED) thus if possible that single
+// instance should be kept alive. It currently does not support unloading
+// modules, but that can be added to the existing design if deemed necessary.
+type Manager struct {
+	// Directory the modules are loaded from. The path in each module Meta
+	// message is relative to this.
+	modulesPath string
+	meta        *kmodpb.Meta
+	// Extra map to quickly find module indexes from names
+	moduleIndexes map[string]uint32
+
+	// mutex protects loadedModules, rest is read-only
+	// This cannot use a RWMutex as locks cannot be upgraded
+	mutex         sync.Mutex
+	loadedModules map[uint32]bool
+}
+
+// NewManager instantiates a kernel module loading manager. Please take a look
+// at the additional considerations on the Manager type itself.
+func NewManager(meta *kmodpb.Meta, modulesPath string) (*Manager, error) {
+	modIndexes := make(map[string]uint32)
+	for i, m := range meta.Modules {
+		modIndexes[m.Name] = uint32(i)
+	}
+	modulesFile, err := os.Open("/proc/modules")
+	if err != nil {
+		return nil, err
+	}
+	loadedModules := make(map[uint32]bool)
+	s := bufio.NewScanner(modulesFile)
+	for s.Scan() {
+		fields := strings.Fields(s.Text())
+		if len(fields) == 0 {
+			// Skip invalid lines
+			continue
+		}
+		modIdx, ok := modIndexes[fields[0]]
+		if !ok {
+			// Certain modules are only available as built-in and are thus not
+			// part of the module metadata. They do not need to be handled by
+			// this code, ignore them.
+			continue
+		}
+		loadedModules[modIdx] = true
+	}
+	return &Manager{
+		modulesPath:   modulesPath,
+		meta:          meta,
+		moduleIndexes: modIndexes,
+		loadedModules: loadedModules,
+	}, nil
+}
+
+// NewManagerFromPath instantiates a new kernel module loading manager from a
+// path containing a meta.pb file containing a kmod.Meta message as well as the
+// kernel modules within. Please take a look at the additional considerations on
+// the Manager type itself.
+func NewManagerFromPath(modulesPath string) (*Manager, error) {
+	moduleMetaRaw, err := os.ReadFile(filepath.Join(modulesPath, "meta.pb"))
+	if err != nil {
+		return nil, fmt.Errorf("error reading module metadata: %w", err)
+	}
+	var moduleMeta kmodpb.Meta
+	if err := proto.Unmarshal(moduleMetaRaw, &moduleMeta); err != nil {
+		return nil, fmt.Errorf("error decoding module metadata: %w", err)
+	}
+	return NewManager(&moduleMeta, modulesPath)
+}
+
+// ErrNotFound is returned when an attempt is made to load a module which does
+// not exist according to the loaded metadata.
+type ErrNotFound struct {
+	Name string
+}
+
+func (e *ErrNotFound) Error() string {
+	return fmt.Sprintf("module %q does not exist", e.Name)
+}
+
+// LoadModule loads the module with the given name. If the module is already
+// loaded or  built-in, it returns no error. If it failed loading the module or
+// the module does not exist, it returns an error.
+func (s *Manager) LoadModule(name string) error {
+	modIdx, ok := s.moduleIndexes[name]
+	if !ok {
+		return &ErrNotFound{Name: name}
+	}
+	s.mutex.Lock()
+	defer s.mutex.Unlock()
+	return s.loadModuleRecursive(modIdx)
+}
+
+// LoadModulesForDevice loads all modules whose device match expressions
+// (modaliases) match the given device modalias. It only returns an error if
+// a module which matched the device or one of its dependencies caused an error
+// when loading. A device modalias string which matches nothing is not an error.
+func (s *Manager) LoadModulesForDevice(devModalias string) error {
+	matches := make(map[uint32]bool)
+	lookupModulesRec(s.meta.ModuleDeviceMatches, devModalias, matches)
+	s.mutex.Lock()
+	defer s.mutex.Unlock()
+	for m := range matches {
+		if err := s.loadModuleRecursive(m); err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+// Caller is REQUIRED to hold s.mutex!
+func (s *Manager) loadModuleRecursive(modIdx uint32) error {
+	if s.loadedModules[modIdx] {
+		return nil
+	}
+	modMeta := s.meta.Modules[modIdx]
+	if modMeta.Path == "" {
+		// Module is built-in, dependency always satisfied
+		return nil
+	}
+	for _, dep := range modMeta.Depends {
+		if err := s.loadModuleRecursive(dep); err != nil {
+			// Pass though as is, recursion can otherwise cause
+			// extremely large errors
+			return err
+		}
+	}
+	module, err := os.Open(filepath.Join(s.modulesPath, modMeta.Path))
+	if err != nil {
+		return fmt.Errorf("error opening kernel module: %w", err)
+	}
+	defer module.Close()
+	err = LoadModule(module, "", 0)
+	if err != nil && err != unix.EEXIST {
+		return fmt.Errorf("error loading kernel module %v: %w", modMeta.Name, err)
+	}
+	s.loadedModules[modIdx] = true
+	return nil
+}