| 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" |
| ) |
| |
| // Add adds a package to the DKMS source tree. |
| // |
| // This checks the user source tree (by default, /usr/src/) |
| // for the package source files. If the sources don't exist there, |
| // this will check the local ${package-name}-${package-version} path |
| // and try to copy sources from there to the user source tree, then |
| // add the copied sources to the DKMS tree. |
| // |
| // If the DKMS tree does not already exist, this will create it. |
| // If the package has already been added, this does nothing. |
| func Add(pkg *Package, options *Options) error { |
| if IsAdded(pkg) { |
| glog.Info("package already added; skipping add step") |
| return nil |
| } |
| |
| glog.Infof("adding package %s-%s", pkg.Name, pkg.Version) |
| // We have to use the absolute path to the package sources since |
| // the symlink will be used from the DKMS tree, not the directory |
| // from which we're making the symlink. |
| sourceTreeSourceDir, err := filepath.Abs(pkg.SourceTreeDir()) |
| if err != nil { |
| return err |
| } |
| |
| // Try to copy the source files from a local directory to the |
| // source tree if they're not already there. |
| if !fs.IsDir(sourceTreeSourceDir) { |
| localSourceDir := fmt.Sprintf("%s-%s", pkg.Name, pkg.Version) |
| if !fs.IsDir(localSourceDir) { |
| return fmt.Errorf("could not find package sources in source tree at %s or in local directory %s", sourceTreeSourceDir, localSourceDir) |
| } |
| |
| err := fs.CopyDir(localSourceDir, sourceTreeSourceDir, 0777) |
| if err != nil { |
| return fmt.Errorf("could not copy local package sources from %s to source tree at %s: %v", localSourceDir, sourceTreeSourceDir, err) |
| } |
| } |
| |
| // Check that the source tree dir is non-empty. |
| // |
| // If there is no dkms.conf file, we can still do reasonable things with |
| // other source files. If there are no source files, we can still do |
| // reasonable things with dkms.conf (like downloading pre-built modules from |
| // the cache build directory). |
| // |
| // However, if both are missing, there is nothing we can reasonably do, so |
| // we should error out. |
| sourceTreeSourceDirFile, err := os.Open(sourceTreeSourceDir) |
| if err != nil { |
| return fmt.Errorf("could not open source tree dir %s: %v", sourceTreeSourceDir, err) |
| } |
| _, err = sourceTreeSourceDirFile.ReadDir(1) // Returns an error if the dir is empty |
| if err != nil { |
| return fmt.Errorf( |
| "source tree dir %s is empty; make sure package sources and/or a dkms.conf file are present: %v", |
| sourceTreeSourceDir, err, |
| ) |
| } |
| |
| dkmsTreeSourceDir := pkg.SourceDir() |
| |
| err = os.MkdirAll(filepath.Dir(dkmsTreeSourceDir), 0777) |
| if err != nil { |
| return err |
| } |
| |
| glog.Infof("symlinking package sources from %s to %s", sourceTreeSourceDir, dkmsTreeSourceDir) |
| if err := os.Symlink(sourceTreeSourceDir, dkmsTreeSourceDir); err != nil { |
| return err |
| } |
| |
| config, err := LoadConfig(pkg) |
| if err != nil { |
| return err |
| } |
| |
| if config.PostAdd != "" { |
| if err := utils.RunCommandString(dkmsTreeSourceDir, config.PostAdd); err != nil { |
| return err |
| } |
| } |
| |
| return nil |
| } |
| |
| // CachedAdd adds a package to the DKMS source tree, downloading |
| // sources from the cache if they are not available locally. |
| // |
| // If options.Upload is specified, this will upload the sources to |
| // the DKMS tree in the cache after they have been added to the |
| // local DKMS tree. |
| // |
| // See Add for more details on how sources are added locally. |
| func CachedAdd(ctx context.Context, pkg *Package, cache *gcs.GCSBucket, options *Options) error { |
| // First, try to add the package from only local sources. |
| if err := Add(pkg, options); err != nil { |
| // If the local Add failed for any reason, try to download the sources to |
| // the local user source tree. It should be safe to ignore the Add error |
| // since we'll call it later and report an error there if it fails again. |
| glog.Info(err) |
| glog.Info("package sources not found locally; checking cache") |
| |
| // Since the sources get uploaded to the DKMS tree instead of a remote |
| // source tree, we have to do some renaming to properly download them to |
| // the local source tree. This is better than directly downloading them |
| // to the DKMS tree since it allows us to call add as normal after, which |
| // will handle other setup and hooks correctly. |
| cacheDkmsTreeSourceDir := pkg.CacheSourceDir() |
| downloadDir, err := os.MkdirTemp(pkg.Trees.Source, pkg.Name) |
| defer os.RemoveAll(downloadDir) |
| if err != nil { |
| return fmt.Errorf("failed to create download directory for sources: %v", err) |
| } |
| |
| glog.Infof("downloading package sources from %s", cache.URI(cacheDkmsTreeSourceDir)) |
| if options.DownloadWorkers == 1 { |
| err = cache.DownloadDir(ctx, cacheDkmsTreeSourceDir, downloadDir) |
| } else { |
| err = cache.DownloadDirParallel(ctx, cacheDkmsTreeSourceDir, downloadDir, options.DownloadWorkers) |
| } |
| if err != nil { |
| return fmt.Errorf("failed to download package sources: %v", err) |
| } |
| |
| sourceTreeSourceDir := pkg.SourceTreeDir() |
| glog.Infof("renaming %s to %s", downloadDir, sourceTreeSourceDir) |
| if err := os.Rename(downloadDir, sourceTreeSourceDir); err != nil { |
| return fmt.Errorf("failed to move sources to DKMS tree: %v", err) |
| } |
| |
| if err := Add(pkg, options); err != nil { |
| return err |
| } |
| } |
| |
| if options.Upload && !IsAddedInCache(ctx, pkg, cache) { |
| glog.Info("uploading package sources to cache") |
| dkmsTreeSourceDir := pkg.SourceDir() |
| cacheDkmsTreeSourceDir := pkg.CacheSourceDir() |
| if err := cache.UploadDir(ctx, dkmsTreeSourceDir, cacheDkmsTreeSourceDir); err != nil { |
| return fmt.Errorf("failed to upload package sources: %v", err) |
| } |
| } |
| |
| return nil |
| } |
| |
| // IsAdded checks if a package has been added to the DKMS tree and if it |
| // has a valid dkms.conf file. |
| // Note that an empty or absent dkms.conf is considered valid, since default |
| // values can be used for all settings. |
| func IsAdded(pkg *Package) bool { |
| configPath := pkg.ConfigPath() |
| if !fs.IsFile(configPath) { |
| glog.Infof("could not find config at %s\n", configPath) |
| return fs.IsDir(pkg.SourceDir()) |
| } |
| |
| // make sure the config parses without error, but ignore the contents |
| if _, err := LoadConfig(pkg); err != nil { |
| glog.Warningf("dkms.conf found, but invalid: %v", err) |
| return false |
| } |
| |
| return true |
| } |
| |
| // IsAddedInCache checks if a package has been added to the DKMS tree in the |
| // cache and if it has a valid dkms.conf file. |
| // Note that an empty or absent dkms.conf is considered valid, since default |
| // values can be used for all settings. |
| func IsAddedInCache(ctx context.Context, pkg *Package, cache *gcs.GCSBucket) bool { |
| configPath := pkg.CacheConfigPath() |
| configExists, err := cache.Exists(ctx, configPath) |
| if err != nil { |
| glog.Errorf("error reading dkms.conf file from cache: %v", err) |
| return false |
| } |
| |
| if !configExists { |
| glog.Infof("could not find config at %s\n", cache.URI(configPath)) |
| sourceExists, err := cache.Exists(ctx, pkg.CacheSourceDir()) |
| if err != nil { |
| glog.Errorf("error reading package source directory from cache: %v", err) |
| return false |
| } |
| |
| return sourceExists |
| } |
| |
| // make sure the config parses without error, but ignore the contents |
| if _, err := LoadConfig(pkg); err != nil { |
| glog.Warningf("dkms.conf found, but invalid: %v", err) |
| return false |
| } |
| |
| return true |
| } |