github.com/mvdan/u-root-coreutils@v0.0.0-20230122170626-c2eef2898555/pkg/kmodule/kmodule_linux.go (about) 1 // Copyright 2017-2018 the u-root Authors. All rights reserved 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 // Package kmodule interfaces with Linux kernel modules. 6 // 7 // kmodule allows loading and unloading kernel modules with dependencies, as 8 // well as locating them through probing. 9 package kmodule 10 11 import ( 12 "bufio" 13 "fmt" 14 "io" 15 "os" 16 "path" 17 "path/filepath" 18 "strings" 19 20 "github.com/klauspost/compress/zstd" 21 "github.com/klauspost/pgzip" 22 "github.com/ulikunitz/xz" 23 "golang.org/x/sys/unix" 24 ) 25 26 // Flags to finit_module(2) / FileInit. 27 const ( 28 // Ignore symbol version hashes. 29 MODULE_INIT_IGNORE_MODVERSIONS = 0x1 30 31 // Ignore kernel version magic. 32 MODULE_INIT_IGNORE_VERMAGIC = 0x2 33 ) 34 35 // Init loads the kernel module given by image with the given options. 36 func Init(image []byte, opts string) error { 37 return unix.InitModule(image, opts) 38 } 39 40 // FileInit loads the kernel module contained by `f` with the given opts and 41 // flags. Uncompresses modules with a .xz and .gz suffix before loading. 42 // 43 // FileInit falls back to init_module(2) via Init when the finit_module(2) 44 // syscall is not available and when loading compressed modules. 45 func FileInit(f *os.File, opts string, flags uintptr) error { 46 var r io.Reader 47 var err error 48 switch filepath.Ext(f.Name()) { 49 case ".xz": 50 if r, err = xz.NewReader(f); err != nil { 51 return err 52 } 53 case ".gz": 54 if r, err = pgzip.NewReader(f); err != nil { 55 return err 56 } 57 case ".zst": 58 if r, err = zstd.NewReader(f); err != nil { 59 return err 60 } 61 } 62 63 if r == nil { 64 err := unix.FinitModule(int(f.Fd()), opts, int(flags)) 65 if err == unix.ENOSYS { 66 if flags != 0 { 67 return err 68 } 69 // Fall back to init_module(2). 70 r = f 71 } else { 72 return err 73 } 74 } 75 76 img, err := io.ReadAll(r) 77 if err != nil { 78 return err 79 } 80 return Init(img, opts) 81 } 82 83 // Delete removes a kernel module. 84 func Delete(name string, flags uintptr) error { 85 return unix.DeleteModule(name, int(flags)) 86 } 87 88 type modState uint8 89 90 const ( 91 unloaded modState = iota 92 loading 93 loaded 94 builtin 95 ) 96 97 type dependency struct { 98 state modState 99 deps []string 100 } 101 102 type depMap map[string]*dependency 103 104 // ProbeOpts contains optional parameters to Probe. 105 // 106 // An empty ProbeOpts{} should lead to the default behavior. 107 type ProbeOpts struct { 108 DryRunCB func(string) 109 RootDir string 110 KVer string 111 IgnoreProcMods bool 112 } 113 114 // Probe loads the given kernel module and its dependencies. 115 // It is calls ProbeOptions with the default ProbeOpts. 116 func Probe(name string, modParams string) error { 117 return ProbeOptions(name, modParams, ProbeOpts{}) 118 } 119 120 // ProbeOptions loads the given kernel module and its dependencies. 121 // This functions takes ProbeOpts. 122 func ProbeOptions(name, modParams string, opts ProbeOpts) error { 123 deps, err := genDeps(opts) 124 if err != nil { 125 return fmt.Errorf("could not generate dependency map %v", err) 126 } 127 128 modPath, err := findModPath(name, deps) 129 if err != nil { 130 return fmt.Errorf("could not find module path %q: %v", name, err) 131 } 132 133 dep := deps[modPath] 134 135 if dep.state == builtin || dep.state == loaded { 136 return nil 137 } 138 139 dep.state = loading 140 for _, d := range dep.deps { 141 if err := loadDeps(d, deps, opts); err != nil { 142 return err 143 } 144 } 145 return loadModule(modPath, modParams, opts) 146 } 147 148 func checkBuiltin(moduleDir string, deps depMap) error { 149 f, err := os.Open(filepath.Join(moduleDir, "modules.builtin")) 150 if os.IsNotExist(err) { 151 return nil 152 } else if err != nil { 153 return fmt.Errorf("could not open builtin file: %v", err) 154 } 155 defer f.Close() 156 157 scanner := bufio.NewScanner(f) 158 for scanner.Scan() { 159 txt := scanner.Text() 160 modPath := filepath.Join(moduleDir, strings.TrimSpace(txt)) 161 if deps[modPath] == nil { 162 deps[modPath] = new(dependency) 163 } 164 deps[modPath].state = builtin 165 } 166 167 return scanner.Err() 168 } 169 170 func genDeps(opts ProbeOpts) (depMap, error) { 171 deps := make(depMap) 172 rel := opts.KVer 173 174 if rel == "" { 175 var u unix.Utsname 176 if err := unix.Uname(&u); err != nil { 177 return nil, fmt.Errorf("could not get release (uname -r): %v", err) 178 } 179 rel = unix.ByteSliceToString(u.Release[:]) 180 } 181 182 var moduleDir string 183 for _, n := range []string{"/lib/modules", "/usr/lib/modules"} { 184 moduleDir = filepath.Join(opts.RootDir, n, strings.TrimSpace(rel)) 185 if _, err := os.Stat(moduleDir); err == nil { 186 break 187 } 188 } 189 190 f, err := os.Open(filepath.Join(moduleDir, "modules.dep")) 191 if err != nil { 192 return nil, fmt.Errorf("could not open dependency file: %v", err) 193 } 194 defer f.Close() 195 196 scanner := bufio.NewScanner(f) 197 for scanner.Scan() { 198 txt := scanner.Text() 199 nameDeps := strings.Split(txt, ":") 200 modPath, modDeps := nameDeps[0], nameDeps[1] 201 modPath = filepath.Join(moduleDir, strings.TrimSpace(modPath)) 202 203 var dependency dependency 204 if len(modDeps) > 0 { 205 for _, dep := range strings.Split(strings.TrimSpace(modDeps), " ") { 206 dependency.deps = append(dependency.deps, filepath.Join(moduleDir, dep)) 207 } 208 } 209 deps[modPath] = &dependency 210 } 211 212 if err := scanner.Err(); err != nil { 213 return nil, err 214 } 215 216 if err = checkBuiltin(moduleDir, deps); err != nil { 217 return nil, err 218 } 219 220 if !opts.IgnoreProcMods { 221 fm, err := os.Open("/proc/modules") 222 if err == nil { 223 defer fm.Close() 224 genLoadedMods(fm, deps) 225 } 226 } 227 228 return deps, nil 229 } 230 231 func findModPath(name string, m depMap) (string, error) { 232 // Kernel modules do not have any consistency with use of hyphens and underscores 233 // matching from the module's name to the module's file path. Thus try matching 234 // the provided name using either. 235 nameH := strings.Replace(name, "_", "-", -1) 236 nameU := strings.Replace(name, "-", "_", -1) 237 238 for mp := range m { 239 switch path.Base(mp) { 240 case nameH + ".ko", nameH + ".ko.gz", nameH + ".ko.xz", nameH + ".ko.zst": 241 return mp, nil 242 case nameU + ".ko", nameU + ".ko.gz", nameU + ".ko.xz", nameU + ".ko.zst": 243 return mp, nil 244 } 245 } 246 247 return "", fmt.Errorf("could not find path for module %q", name) 248 } 249 250 func loadDeps(path string, m depMap, opts ProbeOpts) error { 251 dependency, ok := m[path] 252 if !ok { 253 return fmt.Errorf("could not find dependency %q", path) 254 } 255 256 if dependency.state == loading { 257 return fmt.Errorf("circular dependency! %q already LOADING", path) 258 } else if (dependency.state == loaded) || (dependency.state == builtin) { 259 return nil 260 } 261 262 m[path].state = loading 263 264 for _, dep := range dependency.deps { 265 if err := loadDeps(dep, m, opts); err != nil { 266 return err 267 } 268 } 269 270 // done with dependencies, load module 271 if err := loadModule(path, "", opts); err != nil { 272 return err 273 } 274 m[path].state = loaded 275 276 return nil 277 } 278 279 func loadModule(path, modParams string, opts ProbeOpts) error { 280 if opts.DryRunCB != nil { 281 opts.DryRunCB(path) 282 return nil 283 } 284 285 f, err := os.Open(path) 286 if err != nil { 287 return err 288 } 289 defer f.Close() 290 291 if err := FileInit(f, modParams, 0); err != nil && err != unix.EEXIST { 292 return err 293 } 294 295 return nil 296 } 297 298 func genLoadedMods(r io.Reader, deps depMap) error { 299 scanner := bufio.NewScanner(r) 300 for scanner.Scan() { 301 arr := strings.Split(scanner.Text(), " ") 302 name := arr[0] 303 modPath, err := findModPath(name, deps) 304 if err != nil { 305 return fmt.Errorf("could not find module path %q: %v", name, err) 306 } 307 if deps[modPath] == nil { 308 deps[modPath] = new(dependency) 309 } 310 deps[modPath].state = loaded 311 } 312 return scanner.Err() 313 }