treewide: introduce osbase package and move things around
All except localregistry moved from metropolis/pkg to osbase,
localregistry moved to metropolis/test as its only used there anyway.
Change-Id: If1a4bf377364bef0ac23169e1b90379c71b06d72
Reviewed-on: https://review.monogon.dev/c/monogon/+/3079
Tested-by: Jenkins CI
Reviewed-by: Serge Bazanski <serge@monogon.tech>
diff --git a/osbase/kmod/manager.go b/osbase/kmod/manager.go
new file mode 100644
index 0000000..6369212
--- /dev/null
+++ b/osbase/kmod/manager.go
@@ -0,0 +1,159 @@
+package kmod
+
+import (
+ "bufio"
+ "errors"
+ "fmt"
+ "os"
+ "path/filepath"
+ "strings"
+ "sync"
+
+ "golang.org/x/sys/unix"
+ "google.golang.org/protobuf/proto"
+
+ kmodpb "source.monogon.dev/osbase/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 && errors.Is(err, unix.EEXIST) {
+ return fmt.Errorf("error loading kernel module %v: %w", modMeta.Name, err)
+ }
+ s.loadedModules[modIdx] = true
+ return nil
+}