| 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 |
| } |