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