blob: 66e1bfd2b8554f5b3ac9efe55a601bd390590609 [file] [log] [blame]
package dkms
import (
"fmt"
"os"
"os/exec"
"path"
"sort"
"strings"
"cos.googlesource.com/cos/tools.git/src/pkg/modules"
"github.com/golang/glog"
)
// FindKernelModules returns a map from kernel module names to the paths to
// those modules for each module in a directory tree.
func FindKernelModules(root string) (map[string]string, error) {
modules := make(map[string]string)
if err := findKernelModules(root, modules); err != nil {
return nil, err
}
return modules, nil
}
func findKernelModules(root string, modules map[string]string) error {
entries, err := os.ReadDir(root)
if err != nil {
if os.IsNotExist(err) {
glog.Infof("skipping kernel module search for directory %s because it does not exist: %v", root, err)
return nil
}
return fmt.Errorf("could not read directory %s: %v", root, err)
}
for _, entry := range entries {
name := entry.Name()
if entry.IsDir() {
if err := findKernelModules(path.Join(root, name), modules); err != nil {
return err
}
} else if strings.HasSuffix(name, ".ko") {
modules[name] = path.Join(root, name)
}
}
return nil
}
// ModprobeModules modprobes a list of modules, ignoring ones which have
// already been inserted into the running kernel.
func ModprobeModules(modules []Module) error {
insertedModules, err := InsertedModules()
if err != nil {
return fmt.Errorf("could not get list of inserted modules: %v; skipping modprobe", err)
}
for _, module := range modules {
// Inserted module names only use underscores as separators
insertedModuleName := strings.ReplaceAll(module.DestName, "-", "_")
_, inserted := insertedModules[insertedModuleName]
if inserted {
glog.Infof("module %s is already inserted; skipping", module.DestName)
continue
}
if err := ModprobeModule(module.DestName); err != nil {
return err
}
}
return nil
}
// Depmod runs depmod to recalculate module dependencies.
// This is required for modprobe to work properly the first time a module has
// been installed, or if its dependencies change.
func Depmod() error {
cmd := exec.Command("depmod")
out, err := cmd.CombinedOutput()
if err == nil {
glog.Info(string(out))
} else {
glog.Error(string(out))
return fmt.Errorf("error running depmod to set up module dependencies: %v", err)
}
return nil
}
// ModprobeModule runs modprobe on a module to insert it into the running
// kernel.
func ModprobeModule(name string) error {
cmd := exec.Command("modprobe", name)
out, err := cmd.CombinedOutput()
if err == nil {
glog.Info(string(out))
} else {
glog.Error(string(out))
return fmt.Errorf("error running modprobe with module %s: %v", name, err)
}
return nil
}
// InsertedModules returns the list of modules which have been inserted into
// the running kernel.
func InsertedModules() (map[string]bool, error) {
cmd := exec.Command("lsmod")
out, err := cmd.CombinedOutput()
if err != nil {
glog.Error(string(out))
return nil, fmt.Errorf("error listing modules with lsmod: %v", err)
}
outStr := string(out)
moduleNames := make(map[string]bool)
lines := strings.Split(outStr, "\n")[1:]
for _, line := range lines {
fields := strings.Fields(line)
if len(fields) > 0 {
name := fields[0]
moduleNames[name] = true
}
}
return moduleNames, nil
}
// Insmod runs insmod on a module to insert it into the running
// kernel. Module params should be a (possibly empty) slice containing
// strings the form param1=value1.
func Insmod(path string, params []string) error {
insmodArgs := append([]string{path}, params...)
cmd := exec.Command("insmod", insmodArgs...)
out, err := cmd.CombinedOutput()
if err == nil {
if len(out) > 0 {
glog.Info(string(out))
}
} else {
glog.Error(string(out))
return fmt.Errorf("error running insmod for module at %s: %v", path, err)
}
return nil
}
// ModuleDependencies gets the list of dependencies for a module
// via a call to modinfo.
func ModuleDependencies(modulePath string) ([]string, error) {
cmd := exec.Command("modinfo", "-F", "depends", modulePath)
out, err := cmd.CombinedOutput()
if err != nil {
glog.Error(string(out))
return nil, fmt.Errorf("error getting dependencies for module at %s: %v", modulePath, err)
}
outString := strings.TrimSpace(string(out))
if len(outString) == 0 {
return []string{}, nil
}
modules := strings.Split(outString, ",")
return modules, nil
}
// InsertModules inserts a list of modules into the running kernel.
//
// The modules will be inserted in an order which respects their dependencies,
// as determined by modinfo, and which maintains the relative order of modules
// in the modules list as well as possible. See ModuleInsertionOrder for more
// detail.
//
// This also takes care not to insert or get dependencies for modules which
// are already inserted, which saves some time.
func InsertModules(modules []string, modulePaths map[string]string, moduleParams modules.ModuleParameters) error {
insertedModules, err := InsertedModules()
if err != nil {
return fmt.Errorf("could not get list of inserted modules: %v; skipping module insertion", err)
}
dependencyGraph, err := ModuleDependencyGraph(modules, modulePaths)
if err != nil {
return fmt.Errorf("could not insert modules %s: %v", modules, err)
}
insertionOrder := ModuleInsertionOrder(modules, dependencyGraph)
glog.Infof("planned module insertion order %v", insertionOrder)
for _, module := range insertionOrder {
// Inserted module names only use underscores as separators
insertedModuleName := strings.ReplaceAll(module, "-", "_")
if insertedModules[insertedModuleName] {
// Some non-dkms modules or modules from a previous run may already be inserted
glog.Infof("module %s already inserted; skipping", module)
continue
}
if err := Insmod(modulePaths[module+".ko"], moduleParams[module]); err != nil {
return err
}
insertedModules[insertedModuleName] = true
}
return nil
}
// ModuleDependencyGraph uses modinfo to compute a mapping from module names
// to the list of modules that must be inserted before a given module.
//
// For example, the output {a: [], b: [d], c: [a, b], d: []} indicates that
// module d must be inserted before module b, and modules a and b must be
// inserted before module c.
func ModuleDependencyGraph(modules []string, modulePaths map[string]string) (map[string][]string, error) {
var stack = make([]string, len(modules))
copy(stack, modules)
graph := make(map[string][]string)
for len(stack) > 0 {
module := stack[len(stack)-1]
stack = stack[:len(stack)-1]
_, added := graph[module]
if added {
continue
}
modulePath, exists := modulePaths[module+".ko"]
if !exists { // built-in modules do not have a path in the kernel modules tree
modulePath = module
}
deps, err := ModuleDependencies(modulePath)
if err != nil {
return nil, fmt.Errorf("could not build module dependency graph: %v", err)
}
stack = append(stack, deps...)
graph[module] = deps
}
return graph, nil
}
// ModuleInsertionOrder takes a list of modules and a graph of their
// dependencies and returns a valid order in which a list of modules can
// be inserted into the running kernel.
//
// The structure of the dependency graph is described in
// ModuleDependencyGraph.
//
// Where there is not a dependency preventing it, the ordered modules will be
// in the same relative order as in the slice provided. For example, if
// the module list is [a, b, c, d] and the dependency graph is
// {a: [], b: [d], c: [a, b], d: []}, then the returned ordering must be
// [a, d, b, c]}. While a different ordering like [d, b, a, c] would be valid
// with respect to the depencency graph, it would not be output because a
// comes before b in the given module list and there is no dependency forcing
// b to be inserted before a.
func ModuleInsertionOrder(modules []string, dependencyGraph map[string][]string) []string {
preferredModuleInsertionOrder := make(map[string]int)
for i, module := range modules {
preferredModuleInsertionOrder[module] = i
}
var order []string
added := make(map[string]bool)
for _, module := range modules {
addAllOrderedDependencies(module, dependencyGraph, preferredModuleInsertionOrder, &order, added)
}
return order
}
// addAllOrderedDependencies adds all of the transitive dependencies of an item
// in a dependency graph to a topological ordering.
//
// Ensures that no item is added twice, the ordering remains a topological
// order on the dependency graph, and that ties in the dependency graph are
// broken in favor of the item which is earlier in the preferred order.
func addAllOrderedDependencies(
item string,
dependencyGraph map[string][]string,
preferredOrder map[string]int,
order *[]string,
added map[string]bool,
) {
if added[item] {
return
}
dependencies := dependencyGraph[item]
sortByPreferredOrder(dependencies, preferredOrder)
for _, dep := range dependencies {
addAllOrderedDependencies(dep, dependencyGraph, preferredOrder, order, added)
}
*order = append(*order, item)
added[item] = true
}
func sortByPreferredOrder(items []string, preferredOrder map[string]int) {
sort.Slice(items, func(i, j int) bool {
a, inserted := preferredOrder[items[i]]
if !inserted {
return false
}
b, inserted := preferredOrder[items[j]]
if !inserted {
return true
}
return a < b
})
}