mirror of
https://github.com/qdm12/gluetun.git
synced 2025-12-11 04:38:54 -06:00
204 lines
5.0 KiB
Go
204 lines
5.0 KiB
Go
package mod
|
|
|
|
import (
|
|
"bufio"
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"path"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"golang.org/x/sys/unix"
|
|
)
|
|
|
|
type state uint8
|
|
|
|
const (
|
|
unloaded state = iota
|
|
loading
|
|
loaded
|
|
builtin
|
|
)
|
|
|
|
type moduleInfo struct {
|
|
state state
|
|
dependencyPaths []string
|
|
}
|
|
|
|
var ErrModulesDirectoryNotFound = errors.New("modules directory not found")
|
|
|
|
func getModulesInfo() (modulesInfo map[string]moduleInfo, err error) {
|
|
var utsName unix.Utsname
|
|
err = unix.Uname(&utsName)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("getting unix uname release: %w", err)
|
|
}
|
|
release := unix.ByteSliceToString(utsName.Release[:])
|
|
release = strings.TrimSpace(release)
|
|
|
|
modulePaths := []string{
|
|
filepath.Join("/lib/modules", release),
|
|
filepath.Join("/usr/lib/modules", release),
|
|
}
|
|
|
|
var modulesPath string
|
|
var found bool
|
|
for _, modulesPath = range modulePaths {
|
|
info, err := os.Stat(modulesPath)
|
|
if err == nil && info.IsDir() {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
|
|
if !found {
|
|
return nil, fmt.Errorf("%w: %s are not valid existing directories"+
|
|
"; have you bind mounted the /lib/modules directory?",
|
|
ErrModulesDirectoryNotFound, strings.Join(modulePaths, ", "))
|
|
}
|
|
|
|
dependencyFilepath := filepath.Join(modulesPath, "modules.dep")
|
|
dependencyFile, err := os.Open(dependencyFilepath)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("opening dependency file: %w", err)
|
|
}
|
|
|
|
modulesInfo = make(map[string]moduleInfo)
|
|
scanner := bufio.NewScanner(dependencyFile)
|
|
for scanner.Scan() {
|
|
line := scanner.Text()
|
|
parts := strings.Split(line, ":")
|
|
path := filepath.Join(modulesPath, strings.TrimSpace(parts[0]))
|
|
dependenciesString := strings.TrimSpace(parts[1])
|
|
|
|
if dependenciesString == "" {
|
|
modulesInfo[path] = moduleInfo{}
|
|
continue
|
|
}
|
|
|
|
dependencyNames := strings.Split(dependenciesString, " ")
|
|
dependencies := make([]string, len(dependencyNames))
|
|
for i := range dependencyNames {
|
|
dependencies[i] = filepath.Join(modulesPath, dependencyNames[i])
|
|
}
|
|
modulesInfo[path] = moduleInfo{dependencyPaths: dependencies}
|
|
}
|
|
|
|
err = scanner.Err()
|
|
if err != nil {
|
|
_ = dependencyFile.Close()
|
|
return nil, fmt.Errorf("modules dependency file scanning: %w", err)
|
|
}
|
|
|
|
err = dependencyFile.Close()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("closing dependency file: %w", err)
|
|
}
|
|
|
|
err = getBuiltinModules(modulesPath, modulesInfo)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("getting builtin modules: %w", err)
|
|
}
|
|
|
|
err = getLoadedModules(modulesInfo)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("getting loaded modules: %w", err)
|
|
}
|
|
|
|
return modulesInfo, nil
|
|
}
|
|
|
|
func getBuiltinModules(modulesDirPath string, modulesInfo map[string]moduleInfo) error {
|
|
file, err := os.Open(filepath.Join(modulesDirPath, "modules.builtin"))
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
return nil
|
|
}
|
|
return fmt.Errorf("opening builtin modules file: %w", err)
|
|
}
|
|
|
|
scanner := bufio.NewScanner(file)
|
|
for scanner.Scan() {
|
|
txt := scanner.Text()
|
|
path := filepath.Join(modulesDirPath, strings.TrimSpace(txt))
|
|
info := modulesInfo[path]
|
|
info.state = builtin
|
|
modulesInfo[path] = info
|
|
}
|
|
|
|
err = scanner.Err()
|
|
if err != nil {
|
|
_ = file.Close()
|
|
return fmt.Errorf("scanning builtin modules file: %w", err)
|
|
}
|
|
|
|
err = file.Close()
|
|
if err != nil {
|
|
return fmt.Errorf("closing builtin modules file: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func getLoadedModules(modulesInfo map[string]moduleInfo) (err error) {
|
|
file, err := os.Open("/proc/modules")
|
|
if err != nil {
|
|
// File cannot be opened, so assume no module is loaded
|
|
return nil //nolint:nilerr
|
|
}
|
|
|
|
scanner := bufio.NewScanner(file)
|
|
for scanner.Scan() {
|
|
parts := strings.Split(scanner.Text(), " ")
|
|
name := parts[0]
|
|
path, err := findModulePath(name, modulesInfo)
|
|
if err != nil {
|
|
_ = file.Close()
|
|
return fmt.Errorf("finding module path: %w", err)
|
|
}
|
|
info := modulesInfo[path]
|
|
info.state = loaded
|
|
modulesInfo[path] = info
|
|
}
|
|
|
|
err = scanner.Err()
|
|
if err != nil {
|
|
_ = file.Close()
|
|
return fmt.Errorf("scanning modules: %w", err)
|
|
}
|
|
|
|
err = file.Close()
|
|
if err != nil {
|
|
return fmt.Errorf("closing process modules file: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
var ErrModulePathNotFound = errors.New("module path not found")
|
|
|
|
func findModulePath(moduleName string, modulesInfo map[string]moduleInfo) (modulePath string, err error) {
|
|
// Kernel module names can have underscores or hyphens in their names,
|
|
// but only one or the other in one particular name.
|
|
nameHyphensOnly := strings.ReplaceAll(moduleName, "_", "-")
|
|
nameUnderscoresOnly := strings.ReplaceAll(moduleName, "-", "_")
|
|
|
|
validModuleExtensions := []string{".ko", ".ko.gz", ".ko.xz", ".ko.zst"}
|
|
const nameVariants = 2
|
|
validFilenames := make(map[string]struct{}, nameVariants*len(validModuleExtensions))
|
|
for _, ext := range validModuleExtensions {
|
|
validFilenames[nameHyphensOnly+ext] = struct{}{}
|
|
validFilenames[nameUnderscoresOnly+ext] = struct{}{}
|
|
}
|
|
|
|
for modulePath := range modulesInfo {
|
|
moduleFileName := path.Base(modulePath)
|
|
_, valid := validFilenames[moduleFileName]
|
|
if valid {
|
|
return modulePath, nil
|
|
}
|
|
}
|
|
|
|
return "", fmt.Errorf("%w: for %q", ErrModulePathNotFound, moduleName)
|
|
}
|