blob: ab42f378c7533932dedd6133c38d130065ccff78 [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"
)
// 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
}