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  }