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