| Tim Windelschmidt | 6d33a43 | 2025-02-04 14:34:25 +0100 | [diff] [blame] | 1 | // Copyright The Monogon Project Authors. |
| 2 | // SPDX-License-Identifier: Apache-2.0 |
| 3 | |
| Lorenz Brun | c7b036b | 2023-06-01 12:23:57 +0200 | [diff] [blame] | 4 | package kmod |
| 5 | |
| 6 | import ( |
| 7 | "bufio" |
| 8 | "bytes" |
| 9 | "debug/elf" |
| 10 | "errors" |
| 11 | "fmt" |
| 12 | "io" |
| 13 | "strings" |
| 14 | ) |
| 15 | |
| 16 | // ModuleInfo contains Linux kernel module metadata. It maps keys to a list |
| 17 | // of values. For known keys accessor functions are provided. |
| 18 | type ModuleInfo map[string][]string |
| 19 | |
| 20 | // Get returns the first value of the given key or an empty string if the key |
| 21 | // does not exist. |
| 22 | func (i ModuleInfo) Get(key string) string { |
| 23 | if len(i[key]) > 0 { |
| 24 | return i[key][0] |
| 25 | } |
| 26 | return "" |
| 27 | } |
| 28 | |
| 29 | // Name returns the name of the module as defined in kbuild. |
| 30 | func (i ModuleInfo) Name() string { |
| 31 | return i.Get("name") |
| 32 | } |
| 33 | |
| 34 | // Authors returns the list of a authors of the module. |
| 35 | func (i ModuleInfo) Authors() []string { |
| 36 | return i["author"] |
| 37 | } |
| 38 | |
| 39 | // Description returns a human-readable description of the module or an empty |
| 40 | // string if it is not available. |
| 41 | func (i ModuleInfo) Description() string { |
| 42 | return i.Get("description") |
| 43 | } |
| 44 | |
| 45 | // GetDependencies returns a list of module names which need to be loaded |
| 46 | // before this one. |
| 47 | func (i ModuleInfo) GetDependencies() []string { |
| 48 | if len(i["depends"]) == 1 && i["depends"][0] == "" { |
| 49 | return nil |
| 50 | } |
| Lorenz Brun | ba82bc6 | 2025-07-22 20:17:05 +0200 | [diff] [blame^] | 51 | out := make([]string, 0, len(i["depends"])) |
| 52 | for _, dep := range i["depends"] { |
| 53 | parts := strings.Split(dep, ",") |
| 54 | for _, part := range parts { |
| 55 | out = append(out, strings.ReplaceAll(part, "-", "_")) |
| 56 | } |
| 57 | } |
| 58 | return out |
| Lorenz Brun | c7b036b | 2023-06-01 12:23:57 +0200 | [diff] [blame] | 59 | } |
| 60 | |
| 61 | type OptionalDependencies struct { |
| 62 | // Pre contains a list of module names to be optionally loaded before the |
| 63 | // module itself. |
| 64 | Pre []string |
| 65 | // Post contains a list of module names to be optionally loaded after the |
| 66 | // module itself. |
| 67 | Post []string |
| 68 | } |
| 69 | |
| 70 | // GetOptionalDependencies returns a set of modules which are recommended to |
| 71 | // be loaded before and after this module. These are optional, but enhance |
| 72 | // the functionality of this module. |
| 73 | func (i ModuleInfo) GetOptionalDependencies() OptionalDependencies { |
| 74 | var out OptionalDependencies |
| 75 | for _, s := range i["softdep"] { |
| 76 | tokens := strings.Fields(s) |
| 77 | const ( |
| 78 | MODE_IDLE = 0 |
| 79 | MODE_PRE = 1 |
| 80 | MODE_POST = 2 |
| 81 | ) |
| Tim Windelschmidt | 5e460a9 | 2024-04-11 01:33:09 +0200 | [diff] [blame] | 82 | var state = MODE_IDLE |
| Lorenz Brun | c7b036b | 2023-06-01 12:23:57 +0200 | [diff] [blame] | 83 | for _, token := range tokens { |
| 84 | switch token { |
| 85 | case "pre:": |
| 86 | state = MODE_PRE |
| 87 | case "post:": |
| 88 | state = MODE_POST |
| 89 | default: |
| 90 | switch state { |
| 91 | case MODE_PRE: |
| 92 | out.Pre = append(out.Pre, token) |
| 93 | case MODE_POST: |
| 94 | out.Post = append(out.Post, token) |
| 95 | default: |
| 96 | } |
| 97 | } |
| 98 | } |
| 99 | } |
| 100 | return out |
| 101 | } |
| 102 | |
| 103 | // Aliases returns a list of match expressions for matching devices handled |
| 104 | // by this module. Every returned string consists of a literal as well as '*' |
| 105 | // wildcards matching one or more characters. These should be matched against |
| 106 | // the kobject MODALIAS field or the modalias sysfs file. |
| 107 | func (i ModuleInfo) Aliases() []string { |
| 108 | return i["alias"] |
| 109 | } |
| 110 | |
| 111 | // Firmware returns a list of firmware file paths required by this module. |
| 112 | // These paths are usually relative to the root of a linux-firmware install |
| 113 | // unless the firmware is non-redistributable. |
| 114 | func (i ModuleInfo) Firmware() []string { |
| 115 | return i["firmware"] |
| 116 | } |
| 117 | |
| Tim Windelschmidt | 51daf25 | 2024-04-18 23:18:43 +0200 | [diff] [blame] | 118 | // Licenses returns the licenses use of this module is governed by. |
| 119 | // For mainline modules, the list of valid license strings is |
| 120 | // documented in the kernel's Documentation/process/license-rules.rst file |
| 121 | // under the `MODULE_LICENSE` section. |
| Lorenz Brun | c7b036b | 2023-06-01 12:23:57 +0200 | [diff] [blame] | 122 | func (i ModuleInfo) Licenses() []string { |
| 123 | return i["license"] |
| 124 | } |
| 125 | |
| 126 | // IsInTree returns true if the module was built in the Linux source tree and |
| 127 | // not externally. This does not necessarily mean that the module is in the |
| 128 | // mainline kernel. |
| 129 | func (i ModuleInfo) IsInTree() bool { |
| 130 | return i.Get("intree") == "Y" |
| 131 | } |
| 132 | |
| 133 | // vermagic and retpoline are intentionally not exposed here, if you need them |
| 134 | // you should know how to get them out of the map yourself as AFAIK these |
| 135 | // are not a stable interface and most programs should not process them. |
| 136 | |
| 137 | func nullByteSplit(data []byte, atEOF bool) (advance int, token []byte, err error) { |
| 138 | if atEOF && len(data) == 0 { |
| 139 | return 0, nil, nil |
| 140 | } |
| 141 | if i := bytes.IndexByte(data, 0x00); i >= 0 { |
| 142 | return i + 1, bytes.TrimLeft(data[0:i], "\x00"), nil |
| 143 | } |
| 144 | if atEOF { |
| 145 | return len(data), data, nil |
| 146 | } |
| 147 | return 0, nil, nil |
| 148 | } |
| 149 | |
| 150 | // GetModuleInfo looks for a ".modinfo" section in the passed ELF Linux kernel |
| 151 | // module and parses it into a ModuleInfo structure. |
| 152 | func GetModuleInfo(e *elf.File) (ModuleInfo, error) { |
| 153 | for _, section := range e.Sections { |
| 154 | if section.Name == ".modinfo" { |
| 155 | out := make(ModuleInfo) |
| 156 | s := bufio.NewScanner(io.NewSectionReader(section, 0, int64(section.Size))) |
| 157 | s.Split(nullByteSplit) |
| 158 | |
| 159 | for s.Scan() { |
| 160 | // Format is <key>=<value> |
| 161 | key, value, ok := bytes.Cut(s.Bytes(), []byte("=")) |
| 162 | if !ok { |
| 163 | continue |
| 164 | } |
| 165 | keyStr := string(key) |
| 166 | out[keyStr] = append(out[keyStr], string(value)) |
| 167 | } |
| 168 | return out, nil |
| 169 | } |
| 170 | } |
| 171 | return nil, errors.New("no .modinfo section found") |
| 172 | } |
| 173 | |
| 174 | // GetBuiltinModulesInfo parses all modinfo structures for builtin modules from |
| 175 | // a modinfo file (modules.builtin.modinfo). |
| 176 | func GetBuiltinModulesInfo(f io.Reader) ([]ModuleInfo, error) { |
| 177 | var out []ModuleInfo |
| 178 | s := bufio.NewScanner(f) |
| 179 | s.Split(nullByteSplit) |
| 180 | |
| 181 | currModule := make(ModuleInfo) |
| 182 | for s.Scan() { |
| 183 | if s.Err() != nil { |
| 184 | return nil, fmt.Errorf("failed scanning for next token: %w", s.Err()) |
| 185 | } |
| 186 | // Format is <module>.<key>=<value> |
| 187 | modName, entry, ok := bytes.Cut(s.Bytes(), []byte{'.'}) |
| 188 | if !ok { |
| 189 | continue |
| 190 | } |
| 191 | if string(modName) != currModule.Name() { |
| 192 | if currModule.Name() != "" { |
| 193 | out = append(out, currModule) |
| 194 | } |
| 195 | currModule = make(ModuleInfo) |
| 196 | currModule["name"] = []string{string(modName)} |
| 197 | } |
| 198 | key, value, ok := bytes.Cut(entry, []byte("=")) |
| 199 | if !ok { |
| 200 | continue |
| 201 | } |
| 202 | keyStr := string(key) |
| 203 | currModule[keyStr] = append(currModule[keyStr], string(value)) |
| 204 | } |
| 205 | if currModule.Name() != "" { |
| 206 | out = append(out, currModule) |
| 207 | } |
| Lorenz Brun | c7b036b | 2023-06-01 12:23:57 +0200 | [diff] [blame] | 208 | return out, nil |
| 209 | } |