blob: 9eeee86ffb04f9ec6985e29089b0e149d601eb59 [file] [log] [blame]
package dkms
import (
"context"
"fmt"
"os"
"path/filepath"
"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 err := 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.InsertOnInstall {
kernelModulePaths, err := FindKernelModules(pkg.Trees.KernelModules)
if err != nil {
return err
}
installedModulePaths, err := FindKernelModules(pkg.Trees.Install)
if err != nil {
return err
}
modulePaths := make(map[string]string)
for module, path := range kernelModulePaths {
modulePaths[module] = path
}
// Modules in the install tree should take precedence over ones in the kernel tree
for module, path := range installedModulePaths {
modulePaths[module] = path
}
var moduleNames []string
for _, module := range modulesToInstall {
moduleNames = append(moduleNames, module.DestName)
}
if err := InsertModules(moduleNames, modulePaths, pkg.ModuleParams); 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 err := 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
}
// 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)
}