package kernel import ( "compress/gzip" "fmt" "io" "os" "path" "path/filepath" "regexp" "strings" "github.com/pkg/errors" warewulfconf "github.com/warewulf/warewulf/internal/pkg/config" "github.com/warewulf/warewulf/internal/pkg/util" "github.com/warewulf/warewulf/internal/pkg/wwlog" ) var ( kernelSearchPaths = []string{ // This is a printf format where the %s will be the kernel version "/boot/Image-%s", // this is the aarch64 for SUSE, vmlinux which is also present won't boot "/boot/vmlinuz-linux%.s", "/boot/vmlinuz-%s", "/boot/vmlinuz-%s.gz", "/lib/modules/%s/vmlinuz", "/lib/modules/%s/vmlinuz.gz", } kernelDrivers = []string{ "lib/modules/%s/*", "lib/firmware/*", "lib/modprobe.d", "lib/modules-load.d"} ) func KernelImageTopDir() string { conf := warewulfconf.Get() return path.Join(conf.Paths.WWProvisiondir, "kernel") } func KernelImage(kernelName string) string { if kernelName == "" { wwlog.Error("Kernel Name is not defined") return "" } if !util.ValidString(kernelName, "^[a-zA-Z0-9-._]+$") { wwlog.Error("Runtime overlay name contains illegal characters: %s", kernelName) return "" } return path.Join(KernelImageTopDir(), kernelName, "vmlinuz") } func GetKernelVersion(kernelName string) string { if kernelName == "" { wwlog.Error("Kernel Name is not defined") return "" } kernelVersion, err := os.ReadFile(KernelVersionFile(kernelName)) if err != nil { return "" } return string(kernelVersion) } func KmodsImage(kernelName string) string { if kernelName == "" { wwlog.Error("Kernel Name is not defined") return "" } if !util.ValidString(kernelName, "^[a-zA-Z0-9-._]+$") { wwlog.Error("Runtime overlay name contains illegal characters: %s", kernelName) return "" } return path.Join(KernelImageTopDir(), kernelName, "kmods.img") } func KernelVersionFile(kernelName string) string { if kernelName == "" { wwlog.Error("Kernel Name is not defined") return "" } if !util.ValidString(kernelName, "^[a-zA-Z0-9-._]+$") { wwlog.Error("Runtime overlay name contains illegal characters: %s", kernelName) return "" } return path.Join(KernelImageTopDir(), kernelName, "version") } func ListKernels() ([]string, error) { var ret []string err := os.MkdirAll(KernelImageTopDir(), 0755) if err != nil { return ret, errors.New("Could not create Kernel parent directory: " + KernelImageTopDir()) } wwlog.Debug("Searching for Kernel image directories: %s", KernelImageTopDir()) kernels, err := os.ReadDir(KernelImageTopDir()) if err != nil { return ret, err } for _, kernel := range kernels { wwlog.Verbose("Found Kernel: %s", kernel.Name()) ret = append(ret, kernel.Name()) } return ret, nil } /* Triggers the kernel extraction and build of the modules for the given kernel version. A name for this kernel and were to find has also to be supplied */ func Build(kernelVersion, kernelName, root string) error { kernelDestination := KernelImage(kernelName) driversDestination := KmodsImage(kernelName) versionDestination := KernelVersionFile(kernelName) // Create the destination paths just in case it doesn't exist err := os.MkdirAll(path.Dir(kernelDestination), 0755) if err != nil { return errors.Wrap(err, "failed to create kernel dest") } err = os.MkdirAll(path.Dir(driversDestination), 0755) if err != nil { return errors.Wrap(err, "failed to create driver dest") } err = os.MkdirAll(path.Dir(versionDestination), 0755) if err != nil { return fmt.Errorf("failed to create version dest: %s", err) } kernelSource, kernelVersFound, err := FindKernel(root) if err != nil { return err } else if kernelVersFound != kernelVersion { return fmt.Errorf("requested %s and found kernel version %s differ", kernelVersion, kernelVersFound) } else { wwlog.Info("Found kernel at: %s", kernelSource) } wwlog.Verbose("Setting up Kernel") if _, err := os.Stat(kernelSource); err == nil { kernel, err := os.Open(kernelSource) if err != nil { return errors.Wrap(err, "could not open kernel") } defer kernel.Close() gzipreader, err := gzip.NewReader(kernel) if err == nil { defer gzipreader.Close() writer, err := os.Create(kernelDestination) if err != nil { return errors.Wrap(err, "could not decompress kernel") } defer writer.Close() _, err = io.Copy(writer, gzipreader) if err != nil { return errors.Wrap(err, "could not write decompressed kernel") } } else { err := util.CopyFile(kernelSource, kernelDestination) if err != nil { return errors.Wrap(err, "could not copy kernel") } } } name := kernelName + " drivers" var kernelDriversSpecific []string for _, kPath := range kernelDrivers { if strings.Contains(kPath, "%s") { kernelDriversSpecific = append(kernelDriversSpecific, fmt.Sprintf(kPath, kernelVersion)) } else { kernelDriversSpecific = append(kernelDriversSpecific, kPath) } } wwlog.Debug("kernelDriversSpecific: %v", kernelDriversSpecific) wwlog.Verbose("Creating image for %s: %s", name, root) err = util.BuildFsImage( name, root, driversDestination, kernelDriversSpecific, []string{}, // ignore cross-device files true, "newc", // dereference symbolic links "-L") if err != nil { return err } wwlog.Verbose("Creating version file") file, err := os.Create(versionDestination) if err != nil { return errors.Wrap(err, "Failed to create version file") } defer file.Close() _, err = io.WriteString(file, kernelVersion) if err != nil { return errors.Wrap(err, "Could not write kernel version") } err = file.Sync() if err != nil { return errors.Wrap(err, "Could not sync kernel version") } return nil } func DeleteKernel(name string) error { fullPath := path.Join(KernelImageTopDir(), name) wwlog.Verbose("Removing path: %s", fullPath) return os.RemoveAll(fullPath) } /* Searches for kernel under a given path. First return result is the full path, second the version and an error if the kernel couldn't be found. */ func FindKernel(root string) (kPath string, version string, err error) { wwlog.Debug("root: %s", root) for _, searchPath := range kernelSearchPaths { testPattern := fmt.Sprintf(path.Join(root, searchPath), `*`) wwlog.Debug("Looking for kernel version with pattern at: %s", testPattern) potentialKernel, _ := filepath.Glob(testPattern) if len(potentialKernel) == 0 { continue } for _, foundKernel := range potentialKernel { wwlog.Debug("Parsing out kernel version for %s", foundKernel) re := regexp.MustCompile(fmt.Sprintf(path.Join(root, searchPath), `([\w\d-\.]*)`)) version := re.FindAllStringSubmatch(foundKernel, -1) if version == nil { return foundKernel, "", fmt.Errorf("could not parse kernel version") } wwlog.Verbose("found kernel version %s", strings.TrimSuffix(version[0][1], ".gz")) return foundKernel, strings.TrimSuffix(version[0][1], ".gz"), nil } } return "", "", fmt.Errorf("could not find kernel version") }