| 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 and loads its config. | |
| // | |
| // After calling this function, the package's Config will be set. | |
| // | |
| // 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) { | |
| if !options.Force { | |
| glog.Info("package already added; skipping add step") | |
| return nil | |
| } | |
| if err := Remove(pkg); err != nil { | |
| return fmt.Errorf("failed to remove package before forcing rebuild: %v", err) | |
| } | |
| } | |
| 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 | |
| } | |
| pkg.Config = config | |
| if config.PostAdd != "" { | |
| if err := utils.RunCommandString(dkmsTreeSourceDir, config.PostAdd, os.Stderr); 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() | |
| if fs.IsDir(sourceTreeSourceDir) { | |
| glog.Warningf( | |
| "sources already existed at %s; if you want to download newer sources, remove the old sources first", | |
| sourceTreeSourceDir, | |
| ) | |
| } else { | |
| 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 its | |
| // config has been loaded. | |
| func IsAdded(pkg *Package) bool { | |
| return fs.IsDir(pkg.SourceDir()) && pkg.Config != nil | |
| } | |
| // 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 remote config parses without error, but ignore the contents | |
| config, err := cache.DownloadTextObject(ctx, configPath) | |
| if err != nil { | |
| return false | |
| } | |
| if _, err := ParseConfig(config, pkg); err != nil { | |
| glog.Warningf("dkms.conf found, but invalid: %v", err) | |
| return false | |
| } | |
| return true | |
| } |