m/b/fwprune: init

This adds a utility to filter out firmware that's actually used by the
kernel builtins.

Change-Id: If622ee8c5b056c9a75f1ca97bb1e40ae62cdf722
Reviewed-on: https://review.monogon.dev/c/monogon/+/506
Reviewed-by: Lorenz Brun <lorenz@monogon.tech>
diff --git a/metropolis/build/fwprune/BUILD.bazel b/metropolis/build/fwprune/BUILD.bazel
new file mode 100644
index 0000000..55aa5c5
--- /dev/null
+++ b/metropolis/build/fwprune/BUILD.bazel
@@ -0,0 +1,14 @@
+load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library")
+
+go_library(
+    name = "go_default_library",
+    srcs = ["main.go"],
+    importpath = "source.monogon.dev/metropolis/build/fwprune",
+    visibility = ["//visibility:private"],
+)
+
+go_binary(
+    name = "fwprune",
+    embed = [":go_default_library"],
+    visibility = ["//visibility:public"],
+)
diff --git a/metropolis/build/fwprune/main.go b/metropolis/build/fwprune/main.go
new file mode 100644
index 0000000..5791b9e
--- /dev/null
+++ b/metropolis/build/fwprune/main.go
@@ -0,0 +1,138 @@
+// 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)
+	}
+}