blob: 614da4575deaeffca9ce6f48f2b424369bf5d9ad [file] [log] [blame]
package dkms
import (
"context"
"fmt"
"os"
"os/exec"
"path"
"path/filepath"
"strings"
"cos.googlesource.com/cos/tools.git/src/pkg/fs"
"cos.googlesource.com/cos/tools.git/src/pkg/gcs"
"cos.googlesource.com/cos/tools.git/src/pkg/utils"
"github.com/golang/glog"
)
// Install installs all of the modules from a DKMS package into the install
// tree.
//
// This will add and build the package if it is not already added and built.
//
// For each module in the package, this will check if the version being
// installed is newer than the version of the module which is already
// installed, if applicable. If the module version is older than the one
// already installed, this will skip installing that module and emit an info
// message. options.ForceVersionOverride can be set to bypass the version
// check and install the module anyway.
//
// If options.ModprobeOnInstall is passed, then modprobe will be called on
// the module after it is copied to the install tree to insert it into the
// running kernel.
func Install(pkg *Package, options *Options) error {
if !options.Force && IsInstalled(pkg) {
glog.Info("package already installed; skipping installation")
return nil
}
if err := Build(pkg, options); err != nil {
return err
}
glog.Infof("installing package %s-%s", pkg.Name, pkg.Version)
config, err := LoadConfig(pkg)
if err != nil {
return err
}
if config.PreInstall != "" {
if utils.RunCommandString(pkg.BuildDir(), config.PreInstall); err != nil {
return fmt.Errorf("failed to run pre-install script: %v", err)
}
}
var modulesToInstall []Module
if options.ForceVersionOverride {
modulesToInstall = config.Modules
} else {
modules, err := ModulesToInstall(config.Modules, pkg.Trees.Install)
if err != nil {
return err
}
modulesToInstall = modules
}
for _, module := range modulesToInstall {
srcPath := module.BuiltPath()
dstPath := module.DestPath()
glog.Infof("copying %s to %s", srcPath, dstPath)
if err := os.MkdirAll(filepath.Dir(dstPath), 0777); err != nil {
return err
}
if err := fs.CopyFile(srcPath, dstPath, 0666); err != nil {
return err
}
}
if options.ModprobeOnInstall {
if !options.NoDepmod {
if err := Depmod(); err != nil {
return err
}
}
if err := ModprobeModules(config.Modules); err != nil {
return err
}
}
if config.PostInstall != "" {
if utils.RunCommandString(pkg.BuildDir(), config.PostInstall); err != nil {
return fmt.Errorf("failed to run post-install script: %v", err)
}
}
return nil
}
// CachedInstall installs all of the modules from a DKMS package into the
// install tree, using CachedBuild to build the package if necessary.
//
// See Install for more information on how modules are installed locally.
func CachedInstall(ctx context.Context, pkg *Package, cache *gcs.GCSBucket, options *Options) error {
if err := CachedBuild(ctx, pkg, cache, options); err != nil {
return err
}
return Install(pkg, options)
}
// ModulesToInstall returns the list of modules which should be installed.
//
// This takes a list of candidate modules to install and a directory where
// they would be installed and checks for each module if there is a newer
// version of that module already installed. If there is a newer or equal
// version already installed, the module is omitted from the output list
// and an info message is emitted.
func ModulesToInstall(modules []Module, installTree string) ([]Module, error) {
installedKernelModules, err := FindKernelModules(installTree)
if err != nil {
return nil, fmt.Errorf("could not find all installed kernel modules")
}
var result []Module
for _, module := range modules {
installName := module.DestName + ".ko"
installedModulePath, ok := installedKernelModules[installName]
if !ok {
result = append(result, module)
continue // module is not yet installed
}
installedVersion, err := ModuleVersion(installedModulePath)
if err != nil {
return nil, fmt.Errorf("could not determine installed module version for module %s: %s", module.DestName, err)
}
builtVersion, err := ModuleVersion(module.BuiltPath())
if err != nil {
return nil, fmt.Errorf("could not determine built module version for module %s: %v", module.BuiltName, err)
}
comparison := CompareVersions(builtVersion, installedVersion)
if comparison < 0 {
glog.Infof(
"installed version of module %s is newer than built version (%s > %s); not reinstalling; pass --force-version-override to install anyway",
module.DestName, installedVersion, builtVersion,
)
} else if comparison == 0 {
glog.Infof(
"module %s is already installed with version %s; not reinstalling; pass --force-version-override to install anyway",
module.DestName, installedVersion,
)
} else {
result = append(result, module)
}
}
return result, nil
}
// 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
}
// isInstalled returns whether or not all of a package's modules have been
// installed.
// If the package has an invalid dkms.conf, this returns false.
func isInstalled(pkg *Package) bool {
config, err := LoadConfig(pkg)
if err != nil {
return false
}
for _, module := range config.Modules {
if !fs.IsFile(module.DestPath()) {
return false
}
}
return true
}
// IsInstalled returns whether or not a package has been built in the DKMS
// build tree and all of its modules have been installed in the install tree.
func IsInstalled(pkg *Package) bool {
return isInstalled(pkg) && IsBuilt(pkg)
}
// 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 := insertedModules[module.DestName]
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
}