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