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
+}