package modules import ( "bufio" "fmt" "io" "log" "os" "path/filepath" "regexp" "strings" "gitlab.com/postmarketOS/postmarketos-mkinitfs/internal/filelist" "gitlab.com/postmarketOS/postmarketos-mkinitfs/internal/misc" ) type Modules struct { modulesListPath string kernVer string } // New returns a new Modules that will read in lists of kernel modules in the given path. func New(modulesListPath string, kernVer string) *Modules { return &Modules{ modulesListPath: modulesListPath, kernVer: kernVer, } } func (m *Modules) List() (*filelist.FileList, error) { files := filelist.NewFileList() libDir := "/usr/lib/modules" if exists, err := misc.Exists(libDir); !exists { libDir = "/lib/modules" } else if err != nil { return nil, fmt.Errorf("received unexpected error when getting status for %q: %w", libDir, err) } modDir := filepath.Join(libDir, m.kernVer) if exists, err := misc.Exists(modDir); !exists { // dir /lib/modules/ if kernel built without module support, so just print a message log.Printf("-- kernel module directory not found: %q, not including modules", modDir) return files, nil } else if err != nil { return nil, fmt.Errorf("received unexpected error when getting status for %q: %w", modDir, err) } // modules.* required by modprobe modprobeFiles, _ := filepath.Glob(filepath.Join(modDir, "modules.*")) for _, file := range modprobeFiles { files.Add(file, file) } // slurp up modules from lists in modulesListPath log.Printf("- Searching for kernel modules from %s", m.modulesListPath) fileInfo, err := os.ReadDir(m.modulesListPath) if err != nil { return files, nil } for _, file := range fileInfo { path := filepath.Join(m.modulesListPath, file.Name()) f, err := os.Open(path) if err != nil { return nil, fmt.Errorf("unable to open module list file %q: %w", path, err) } defer f.Close() log.Printf("-- Including modules from: %s\n", path) if list, err := slurpModules(f, modDir); err != nil { return nil, fmt.Errorf("unable to process module list file %q: %w", path, err) } else { files.Import(list) } } return files, nil } func slurpModules(fd io.Reader, modDir string) (*filelist.FileList, error) { files := filelist.NewFileList() s := bufio.NewScanner(fd) for s.Scan() { line := strings.TrimSpace(s.Text()) if len(line) == 0 || strings.HasPrefix(line, "#") { continue } dir, file := filepath.Split(line) if file == "" { // item is a directory dir = filepath.Join(modDir, dir) dirs, _ := filepath.Glob(dir) for _, d := range dirs { if modFilelist, err := getModulesInDir(d); err != nil { return nil, fmt.Errorf("unable to get modules dir %q: %w", d, err) } else { for _, file := range modFilelist { files.Add(file, file) } } } } else if dir == "" { // item is a module name if modFilelist, err := getModule(line, modDir); err != nil { return nil, fmt.Errorf("unable to get module file %q: %w", line, err) } else { for _, file := range modFilelist { files.Add(file, file) } } } else { log.Printf("Unknown module entry: %q", line) } } return files, s.Err() } func getModulesInDir(modPath string) (files []string, err error) { err = filepath.Walk(modPath, func(path string, _ os.FileInfo, err error) error { if err != nil { // Unable to walk path return err } // this assumes module names are in the format .ko[.format], // where ".format" (e.g. ".gz") is optional. if !strings.Contains(".ko", path) { return nil } files = append(files, path) return nil }) if err != nil { return nil, err } return } // Given a module name, e.g. 'dwc_wdt', resolve the full path to the module // file and all of its dependencies. // Note: it's not necessarily fatal if the module is not found, since it may // have been built into the kernel func getModule(modName string, modDir string) (files []string, err error) { modDep := filepath.Join(modDir, "modules.dep") if exists, err := misc.Exists(modDep); !exists { return nil, fmt.Errorf("kernel module.dep not found: %s", modDir) } else if err != nil { return nil, fmt.Errorf("received unexpected error when getting module.dep status: %w", err) } fd, err := os.Open(modDep) if err != nil { return nil, fmt.Errorf("unable to open modules.dep: %w", err) } defer fd.Close() deps, err := getModuleDeps(modName, fd) if err != nil { return nil, err } for _, dep := range deps { p := filepath.Join(modDir, dep) if exists, err := misc.Exists(p); !exists { return nil, fmt.Errorf("tried to include a module that doesn't exist in the modules directory (%s): %s", modDir, p) } else if err != nil { return nil, fmt.Errorf("received unexpected error when getting status for %q: %w", p, err) } files = append(files, p) } return } // Get the canonicalized name for the module as represented in the given modules.dep io.reader func getModuleDeps(modName string, modulesDep io.Reader) ([]string, error) { var deps []string // split the module name on - and/or _, build a regex for matching splitRe := regexp.MustCompile("[-_]+") modNameReStr := splitRe.ReplaceAllString(modName, "[-_]+") re := regexp.MustCompile("^" + modNameReStr + "$") s := bufio.NewScanner(modulesDep) for s.Scan() { line := strings.TrimSpace(s.Text()) if len(line) == 0 || strings.HasPrefix(line, "#") { continue } fields := strings.Fields(line) if len(fields) == 0 { continue } fields[0] = strings.TrimSuffix(fields[0], ":") found := re.FindAll([]byte(filepath.Base(stripExts(fields[0]))), -1) if len(found) > 0 { deps = append(deps, fields...) break } } if err := s.Err(); err != nil { log.Print("Unable to get module + dependencies: ", modName) return deps, err } return deps, nil } func stripExts(file string) string { return strings.Split(file, ".")[0] }