blob: 5791b9ec014ad42eb70e2df595d2f1256ffd8056 [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"
"fmt"
"io"
"log"
"os"
"path/filepath"
"sort"
"strings"
)
// 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
}
// fwDirs returns a slice of filesystem paths relative to the root of
// linux-firmware repository, pointing at directories that need to exist before
// files specified by fwp paths can be created.
func fwDirs(fwp []string) []string {
// Use a map dset to deduplicate directory paths.
dset := make(map[string]bool)
for _, p := range fwp {
dp := filepath.Dir(p)
dset[dp] = true
}
// Convert dset to a slice.
dslice := make([]string, 0, len(dset))
for d, _ := range dset {
dslice = append(dslice, d)
}
sort.Strings(dslice)
return dslice
}
// copyFile copies a file at filesystem path src to dst. dst must not point to
// an existing file. It may return an IO error.
func copyFile(dst, src string) error {
i, err := os.Open(src)
if err != nil {
return err
}
defer i.Close()
o, err := os.OpenFile(dst, os.O_WRONLY|os.O_CREATE, 0770)
if err != nil {
return err
}
defer o.Close()
if _, err := io.Copy(o, i); err != nil {
return err
}
return nil
}
func main() {
// The directory at fwdst will be filled with firmware required by the kernel
// builtins specified in modules.builtin.modinfo [1]. fwsrc must point to the
// linux-firmware repository [2]. All parameters must be filesystem paths. The
// necessary parts of the original directory layout will be recreated at fwdst.
// fwprune will output a list of directories and files it creates.
// [1] https://www.kernel.org/doc/Documentation/kbuild/kbuild.txt
// [2] https://git.kernel.org/pub/scm/linux/kernel/git/firmware/linux-firmware.git
if len(os.Args) != 4 {
// Print usage information, if misused.
fmt.Println("Usage: fwprune modules.builtin.modinfo fwsrc fwdst")
os.Exit(1)
}
modinfo := os.Args[1]
fwsrc := os.Args[2]
fwdst := os.Args[3]
// Get the firmware file paths.
mi, err := os.ReadFile(modinfo)
if err != nil {
log.Fatalf("While reading modinfo: %v", err)
}
fwp := fwPaths(mi)
// Recreate the necessary parts of the linux-firmware directory tree.
fwd := fwDirs(fwp)
for _, rd := range fwd {
d := filepath.Join(fwdst, rd)
if err := os.MkdirAll(d, 0770); err != nil {
log.Fatalf("Couldn't create a subdirectory: %v", err)
}
fmt.Println(d)
}
// Copy the files specified by fwp.
for _, p := range fwp {
dst := filepath.Join(fwdst, p)
src := filepath.Join(fwsrc, p)
if err := copyFile(dst, src); err != nil {
log.Fatalf("Couldn't provide %q: %v", dst, err)
}
fmt.Println(p)
}
}