blob: adaf5d142175b5bcfe372f32ec5f33abd6e2075c [file] [log] [blame]
package kmod
import (
"bufio"
"bytes"
"debug/elf"
"errors"
"fmt"
"io"
"strings"
)
// ModuleInfo contains Linux kernel module metadata. It maps keys to a list
// of values. For known keys accessor functions are provided.
type ModuleInfo map[string][]string
// Get returns the first value of the given key or an empty string if the key
// does not exist.
func (i ModuleInfo) Get(key string) string {
if len(i[key]) > 0 {
return i[key][0]
}
return ""
}
// Name returns the name of the module as defined in kbuild.
func (i ModuleInfo) Name() string {
return i.Get("name")
}
// Authors returns the list of a authors of the module.
func (i ModuleInfo) Authors() []string {
return i["author"]
}
// Description returns a human-readable description of the module or an empty
// string if it is not available.
func (i ModuleInfo) Description() string {
return i.Get("description")
}
// GetDependencies returns a list of module names which need to be loaded
// before this one.
func (i ModuleInfo) GetDependencies() []string {
if len(i["depends"]) == 1 && i["depends"][0] == "" {
return nil
}
return i["depends"]
}
type OptionalDependencies struct {
// Pre contains a list of module names to be optionally loaded before the
// module itself.
Pre []string
// Post contains a list of module names to be optionally loaded after the
// module itself.
Post []string
}
// GetOptionalDependencies returns a set of modules which are recommended to
// be loaded before and after this module. These are optional, but enhance
// the functionality of this module.
func (i ModuleInfo) GetOptionalDependencies() OptionalDependencies {
var out OptionalDependencies
for _, s := range i["softdep"] {
tokens := strings.Fields(s)
const (
MODE_IDLE = 0
MODE_PRE = 1
MODE_POST = 2
)
var state int = MODE_IDLE
for _, token := range tokens {
switch token {
case "pre:":
state = MODE_PRE
case "post:":
state = MODE_POST
default:
switch state {
case MODE_PRE:
out.Pre = append(out.Pre, token)
case MODE_POST:
out.Post = append(out.Post, token)
default:
}
}
}
}
return out
}
// Aliases returns a list of match expressions for matching devices handled
// by this module. Every returned string consists of a literal as well as '*'
// wildcards matching one or more characters. These should be matched against
// the kobject MODALIAS field or the modalias sysfs file.
func (i ModuleInfo) Aliases() []string {
return i["alias"]
}
// Firmware returns a list of firmware file paths required by this module.
// These paths are usually relative to the root of a linux-firmware install
// unless the firmware is non-redistributable.
func (i ModuleInfo) Firmware() []string {
return i["firmware"]
}
// License returns the licenses use of this module is governed by. For mainline
// modules, the list of valid license strings is documented in the kernel's
// Documentation/process/license-rules.rst file under the `MODULE_LICENSE`
// section.
func (i ModuleInfo) Licenses() []string {
return i["license"]
}
// IsInTree returns true if the module was built in the Linux source tree and
// not externally. This does not necessarily mean that the module is in the
// mainline kernel.
func (i ModuleInfo) IsInTree() bool {
return i.Get("intree") == "Y"
}
// vermagic and retpoline are intentionally not exposed here, if you need them
// you should know how to get them out of the map yourself as AFAIK these
// are not a stable interface and most programs should not process them.
func nullByteSplit(data []byte, atEOF bool) (advance int, token []byte, err error) {
if atEOF && len(data) == 0 {
return 0, nil, nil
}
if i := bytes.IndexByte(data, 0x00); i >= 0 {
return i + 1, bytes.TrimLeft(data[0:i], "\x00"), nil
}
if atEOF {
return len(data), data, nil
}
return 0, nil, nil
}
// GetModuleInfo looks for a ".modinfo" section in the passed ELF Linux kernel
// module and parses it into a ModuleInfo structure.
func GetModuleInfo(e *elf.File) (ModuleInfo, error) {
for _, section := range e.Sections {
if section.Name == ".modinfo" {
out := make(ModuleInfo)
s := bufio.NewScanner(io.NewSectionReader(section, 0, int64(section.Size)))
s.Split(nullByteSplit)
for s.Scan() {
// Format is <key>=<value>
key, value, ok := bytes.Cut(s.Bytes(), []byte("="))
if !ok {
continue
}
keyStr := string(key)
out[keyStr] = append(out[keyStr], string(value))
}
return out, nil
}
}
return nil, errors.New("no .modinfo section found")
}
// GetBuiltinModulesInfo parses all modinfo structures for builtin modules from
// a modinfo file (modules.builtin.modinfo).
func GetBuiltinModulesInfo(f io.Reader) ([]ModuleInfo, error) {
var out []ModuleInfo
s := bufio.NewScanner(f)
s.Split(nullByteSplit)
currModule := make(ModuleInfo)
for s.Scan() {
if s.Err() != nil {
return nil, fmt.Errorf("failed scanning for next token: %w", s.Err())
}
// Format is <module>.<key>=<value>
modName, entry, ok := bytes.Cut(s.Bytes(), []byte{'.'})
if !ok {
continue
}
if string(modName) != currModule.Name() {
if currModule.Name() != "" {
out = append(out, currModule)
}
currModule = make(ModuleInfo)
currModule["name"] = []string{string(modName)}
}
key, value, ok := bytes.Cut(entry, []byte("="))
if !ok {
continue
}
keyStr := string(key)
currModule[keyStr] = append(currModule[keyStr], string(value))
}
if currModule.Name() != "" {
out = append(out, currModule)
}
seenModNames := make(map[string]bool)
for _, m := range out {
if seenModNames[m.Name()] {
return nil, fmt.Errorf("duplicate/out-of-order module metadata for module %q", m)
}
seenModNames[m.Name()] = true
}
return out, nil
}