blob: 1630a7feb03f23d2bc3db90ea6abe72ae97600fe [file] [log] [blame]
Lorenz Brunc7b036b2023-06-01 12:23:57 +02001package kmod
2
3import (
4 "bufio"
5 "fmt"
6 "os"
7 "path/filepath"
8 "strings"
9 "sync"
10
11 "golang.org/x/sys/unix"
12 "google.golang.org/protobuf/proto"
13
14 kmodpb "source.monogon.dev/metropolis/pkg/kmod/spec"
15)
16
17// Manager contains all the logic and metadata required to efficiently load
18// Linux kernel modules. It has internal loading tracking, thus it's recommended
19// to only keep one Manager instance running per kernel. It can recreate its
20// internal state quite well, but there are edge cases where the kernel makes
21// it hard to do so (MODULE_STATE_UNFORMED) thus if possible that single
22// instance should be kept alive. It currently does not support unloading
23// modules, but that can be added to the existing design if deemed necessary.
24type Manager struct {
25 // Directory the modules are loaded from. The path in each module Meta
26 // message is relative to this.
27 modulesPath string
28 meta *kmodpb.Meta
29 // Extra map to quickly find module indexes from names
30 moduleIndexes map[string]uint32
31
32 // mutex protects loadedModules, rest is read-only
33 // This cannot use a RWMutex as locks cannot be upgraded
34 mutex sync.Mutex
35 loadedModules map[uint32]bool
36}
37
38// NewManager instantiates a kernel module loading manager. Please take a look
39// at the additional considerations on the Manager type itself.
40func NewManager(meta *kmodpb.Meta, modulesPath string) (*Manager, error) {
41 modIndexes := make(map[string]uint32)
42 for i, m := range meta.Modules {
43 modIndexes[m.Name] = uint32(i)
44 }
45 modulesFile, err := os.Open("/proc/modules")
46 if err != nil {
47 return nil, err
48 }
49 loadedModules := make(map[uint32]bool)
50 s := bufio.NewScanner(modulesFile)
51 for s.Scan() {
52 fields := strings.Fields(s.Text())
53 if len(fields) == 0 {
54 // Skip invalid lines
55 continue
56 }
57 modIdx, ok := modIndexes[fields[0]]
58 if !ok {
59 // Certain modules are only available as built-in and are thus not
60 // part of the module metadata. They do not need to be handled by
61 // this code, ignore them.
62 continue
63 }
64 loadedModules[modIdx] = true
65 }
66 return &Manager{
67 modulesPath: modulesPath,
68 meta: meta,
69 moduleIndexes: modIndexes,
70 loadedModules: loadedModules,
71 }, nil
72}
73
74// NewManagerFromPath instantiates a new kernel module loading manager from a
75// path containing a meta.pb file containing a kmod.Meta message as well as the
76// kernel modules within. Please take a look at the additional considerations on
77// the Manager type itself.
78func NewManagerFromPath(modulesPath string) (*Manager, error) {
79 moduleMetaRaw, err := os.ReadFile(filepath.Join(modulesPath, "meta.pb"))
80 if err != nil {
81 return nil, fmt.Errorf("error reading module metadata: %w", err)
82 }
83 var moduleMeta kmodpb.Meta
84 if err := proto.Unmarshal(moduleMetaRaw, &moduleMeta); err != nil {
85 return nil, fmt.Errorf("error decoding module metadata: %w", err)
86 }
87 return NewManager(&moduleMeta, modulesPath)
88}
89
90// ErrNotFound is returned when an attempt is made to load a module which does
91// not exist according to the loaded metadata.
92type ErrNotFound struct {
93 Name string
94}
95
96func (e *ErrNotFound) Error() string {
97 return fmt.Sprintf("module %q does not exist", e.Name)
98}
99
100// LoadModule loads the module with the given name. If the module is already
101// loaded or built-in, it returns no error. If it failed loading the module or
102// the module does not exist, it returns an error.
103func (s *Manager) LoadModule(name string) error {
104 modIdx, ok := s.moduleIndexes[name]
105 if !ok {
106 return &ErrNotFound{Name: name}
107 }
108 s.mutex.Lock()
109 defer s.mutex.Unlock()
110 return s.loadModuleRecursive(modIdx)
111}
112
113// LoadModulesForDevice loads all modules whose device match expressions
114// (modaliases) match the given device modalias. It only returns an error if
115// a module which matched the device or one of its dependencies caused an error
116// when loading. A device modalias string which matches nothing is not an error.
117func (s *Manager) LoadModulesForDevice(devModalias string) error {
118 matches := make(map[uint32]bool)
119 lookupModulesRec(s.meta.ModuleDeviceMatches, devModalias, matches)
120 s.mutex.Lock()
121 defer s.mutex.Unlock()
122 for m := range matches {
123 if err := s.loadModuleRecursive(m); err != nil {
124 return err
125 }
126 }
127 return nil
128}
129
130// Caller is REQUIRED to hold s.mutex!
131func (s *Manager) loadModuleRecursive(modIdx uint32) error {
132 if s.loadedModules[modIdx] {
133 return nil
134 }
135 modMeta := s.meta.Modules[modIdx]
136 if modMeta.Path == "" {
137 // Module is built-in, dependency always satisfied
138 return nil
139 }
140 for _, dep := range modMeta.Depends {
141 if err := s.loadModuleRecursive(dep); err != nil {
142 // Pass though as is, recursion can otherwise cause
143 // extremely large errors
144 return err
145 }
146 }
147 module, err := os.Open(filepath.Join(s.modulesPath, modMeta.Path))
148 if err != nil {
149 return fmt.Errorf("error opening kernel module: %w", err)
150 }
151 defer module.Close()
152 err = LoadModule(module, "", 0)
153 if err != nil && err != unix.EEXIST {
154 return fmt.Errorf("error loading kernel module %v: %w", modMeta.Name, err)
155 }
156 s.loadedModules[modIdx] = true
157 return nil
158}