blob: 6ad2a9392b8a92e334591646e274c814bfbbbe04 [file] [log] [blame]
// fwprune is a buildsystem utility that filters linux-firmware repository
// contents to include only files required by the built-in kernel modules,
// that are specified in modules.builtin.modinfo.
// (see: https://www.kernel.org/doc/Documentation/kbuild/kbuild.txt)
package main
import (
"bytes"
"log"
"os"
"path/filepath"
"sort"
"strings"
"google.golang.org/protobuf/encoding/prototext"
"source.monogon.dev/metropolis/node/build/fsspec"
)
// fwPaths returns a slice of filesystem paths relative to the root of the
// linux-firmware repository, pointing at firmware files, according to contents
// of the kernel build side effect: modules.builtin.modinfo.
func fwPaths(mi []byte) []string {
// Use a map pset to deduplicate firmware paths.
pset := make(map[string]bool)
// Get a slice of entries of the form "unix.license=GPL" from mi. Then extract
// firmware information from it.
entries := bytes.Split(mi, []byte{0})
for _, entry := range entries {
// Skip empty entries.
if len(entry) == 0 {
continue
}
// Parse the entries. Split each entry into a key-value pair, separated
// by "=".
kv := strings.SplitN(string(entry), "=", 2)
key, value := kv[0], kv[1]
// Split the key into a module.attribute] pair, such as "unix.license".
ma := strings.SplitN(key, ".", 2)
// Skip, if it's not a firmware entry, according to the attribute.
if ma[1] != "firmware" {
continue
}
// If it is though, value holds a firmware path.
pset[value] = true
}
// Convert the deduplicated pset to a slice.
pslice := make([]string, 0, len(pset))
for p, _ := range pset {
pslice = append(pslice, p)
}
sort.Strings(pslice)
return pslice
}
// fwprune takes a modinfo file from the kernel and extracts a list of all
// firmware files requested by all modules in that file. It then takes all
// available firmware file paths (newline-separated in the firmwareList file)
// and tries to match the requested file paths as a suffix of them.
// For example if a module requests firmware foo/bar.bin and in the firmware list
// there is a file at path build-out/x/y/foo/bar.bin it will use that file.
// Finally it generates an fsspec placing each file under its requested path
// under /lib/firmware.
func main() {
if len(os.Args) != 4 {
log.Fatal("Usage: fwprune modules.builtin.modinfo firmwareListPath outSpec")
}
modinfo := os.Args[1]
firmwareListPath := os.Args[2]
outSpec := os.Args[3]
allFirmwareData, err := os.ReadFile(firmwareListPath)
if err != nil {
log.Fatalf("Failed to read firmware source list: %v", err)
}
allFirmwarePaths := strings.Split(string(allFirmwareData), "\n")
// Create a look-up table of all possible suffixes to their full paths as
// this is much faster at O(n) than calling strings.HasSuffix for every
// possible combination which is O(n^2).
suffixLUT := make(map[string]string)
for _, firmwarePath := range allFirmwarePaths {
pathParts := strings.Split(firmwarePath, string(os.PathSeparator))
for i := range pathParts {
suffixLUT[filepath.Join(pathParts[i:len(pathParts)]...)] = firmwarePath
}
}
// Get the firmware file paths used by modules according to modinfo data
mi, err := os.ReadFile(modinfo)
if err != nil {
log.Fatalf("While reading modinfo: %v", err)
}
fwp := fwPaths(mi)
var files []*fsspec.File
for _, p := range fwp {
sourcePath := suffixLUT[p]
if sourcePath == "" {
// This should not be fatal as sometimes linux-firmware cannot
// ship all firmware usable by the kernel for mostly legal reasons.
log.Printf("WARNING: Requested firmware %q not found", p)
continue
}
files = append(files, &fsspec.File{
Path: filepath.Join("/lib/firmware", p),
Mode: 0444,
SourcePath: sourcePath,
})
}
fsspecRaw, err := prototext.Marshal(&fsspec.FSSpec{File: files})
if err := os.WriteFile(outSpec, fsspecRaw, 0644); err != nil {
log.Fatalf("failed writing output: %v", err)
}
}