| // 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 ( | 
 | 	"debug/elf" | 
 | 	"flag" | 
 | 	"io/fs" | 
 | 	"log" | 
 | 	"os" | 
 | 	"path" | 
 | 	"path/filepath" | 
 | 	"regexp" | 
 | 	"sort" | 
 | 	"strings" | 
 |  | 
 | 	"google.golang.org/protobuf/encoding/prototext" | 
 | 	"google.golang.org/protobuf/proto" | 
 |  | 
 | 	"source.monogon.dev/metropolis/node/build/fsspec" | 
 | 	"source.monogon.dev/metropolis/pkg/kmod" | 
 | ) | 
 |  | 
 | // linkRegexp parses the Link: lines in the WHENCE file. This does not have | 
 | // an official grammar, the regexp has been written in an approximation of | 
 | // the original parsing algorithm at @linux-firmware//:copy_firmware.sh. | 
 | var linkRegexp = regexp.MustCompile(`(?m:^Link:\s*([^\s]+)\s+->\s+([^\s+]+)\s*$)`) | 
 |  | 
 | var ( | 
 | 	modinfoPath      = flag.String("modinfo", "", "Path to the modules.builtin.modinfo file built with the kernel") | 
 | 	modulesPath      = flag.String("modules", "", "Path to the directory containing the dynamically loaded kernel modules (.ko files)") | 
 | 	firmwareListPath = flag.String("firmware-file-list", "", "Path to a file containing a newline-separated list of paths to firmware files") | 
 | 	whenceFilePath   = flag.String("firmware-whence", "", "Path to the linux-firmware WHENCE file containing aliases for firmware files") | 
 | 	outMetaPath      = flag.String("out-meta", "", "Path where the resulting module metadata protobuf file should be created") | 
 | 	outFSSpecPath    = flag.String("out-fsspec", "", "Path where the resulting fsspec should be created") | 
 | ) | 
 |  | 
 | func main() { | 
 | 	flag.Parse() | 
 | 	if *modinfoPath == "" || *modulesPath == "" || *firmwareListPath == "" || | 
 | 		*whenceFilePath == "" || *outMetaPath == "" || *outFSSpecPath == "" { | 
 | 		log.Fatal("all flags are required and need to be provided") | 
 | 	} | 
 |  | 
 | 	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). | 
 | 	// For example a build output at out/a/b/c.bin will be entered into | 
 | 	// the suffix LUT as build as out/a/b/c.bin, a/b/c.bin, b/c.bin and c.bin. | 
 | 	// If the firmware then requests b/c.bin, the output path is contained in | 
 | 	// the suffix LUT. | 
 | 	suffixLUT := make(map[string]string) | 
 | 	for _, firmwarePath := range allFirmwarePaths { | 
 | 		pathParts := strings.Split(firmwarePath, string(os.PathSeparator)) | 
 | 		for i := range pathParts { | 
 | 			suffixLUT[path.Join(pathParts[i:]...)] = firmwarePath | 
 | 		} | 
 | 	} | 
 |  | 
 | 	// The linux-firmware repo contains a WHENCE file which contains (among | 
 | 	// other information) aliases for firmware which should be symlinked. | 
 | 	// Open this file and create a map of aliases in it. | 
 | 	linkMap := make(map[string]string) | 
 | 	metadata, err := os.ReadFile(*whenceFilePath) | 
 | 	if err != nil { | 
 | 		log.Fatalf("Failed to read metadata file: %v", err) | 
 | 	} | 
 | 	linksRaw := linkRegexp.FindAllStringSubmatch(string(metadata), -1) | 
 | 	for _, link := range linksRaw { | 
 | 		// For links we know the exact path referenced by kernel drives so | 
 | 		// a suffix LUT is unnecessary. | 
 | 		linkMap[link[1]] = link[2] | 
 | 	} | 
 |  | 
 | 	// Collect module metadata (modinfo) from both built-in modules via the | 
 | 	// kbuild-generated metadata file as well as from the loadable modules by | 
 | 	// walking them. | 
 | 	var files []*fsspec.File | 
 | 	var symlinks []*fsspec.SymbolicLink | 
 |  | 
 | 	mi, err := os.Open(*modinfoPath) | 
 | 	if err != nil { | 
 | 		log.Fatalf("While reading modinfo: %v", err) | 
 | 	} | 
 | 	modMeta, err := kmod.GetBuiltinModulesInfo(mi) | 
 | 	if err != nil { | 
 | 		log.Fatalf("Failed to read modules modinfo data: %v", err) | 
 | 	} | 
 |  | 
 | 	err = filepath.WalkDir(*modulesPath, func(p string, d fs.DirEntry, err error) error { | 
 | 		if err != nil { | 
 | 			log.Fatal(err) | 
 | 		} | 
 | 		if d.IsDir() { | 
 | 			return nil | 
 | 		} | 
 | 		mod, err := elf.Open(p) | 
 | 		if err != nil { | 
 | 			log.Fatal(err) | 
 | 		} | 
 | 		defer mod.Close() | 
 | 		out, err := kmod.GetModuleInfo(mod) | 
 | 		if err != nil { | 
 | 			log.Fatal(err) | 
 | 		} | 
 | 		relPath, err := filepath.Rel(*modulesPath, p) | 
 | 		if err != nil { | 
 | 			return err | 
 | 		} | 
 | 		// Add path information for MakeMetaFromModuleInfo. | 
 | 		out["path"] = []string{relPath} | 
 | 		modMeta = append(modMeta, out) | 
 | 		files = append(files, &fsspec.File{ | 
 | 			Path:       path.Join("/lib/modules", relPath), | 
 | 			SourcePath: filepath.Join(*modulesPath, relPath), | 
 | 			Mode:       0555, | 
 | 		}) | 
 | 		return nil | 
 | 	}) | 
 | 	if err != nil { | 
 | 		log.Fatalf("Error walking modules: %v", err) | 
 | 	} | 
 |  | 
 | 	// Generate loading metadata from all known modules. | 
 | 	meta, err := kmod.MakeMetaFromModuleInfo(modMeta) | 
 | 	if err != nil { | 
 | 		log.Fatal(err) | 
 | 	} | 
 | 	metaRaw, err := proto.Marshal(meta) | 
 | 	if err != nil { | 
 | 		log.Fatal(err) | 
 | 	} | 
 | 	if err := os.WriteFile(*outMetaPath, metaRaw, 0640); err != nil { | 
 | 		log.Fatal(err) | 
 | 	} | 
 | 	files = append(files, &fsspec.File{ | 
 | 		Path:       "/lib/modules/meta.pb", | 
 | 		SourcePath: *outMetaPath, | 
 | 		Mode:       0444, | 
 | 	}) | 
 |  | 
 | 	// Create set of all firmware paths required by modules | 
 | 	fwset := make(map[string]bool) | 
 | 	for _, m := range modMeta { | 
 | 		if len(m["path"]) == 0 && len(m.Firmware()) > 0 { | 
 | 			log.Fatalf("Module %v is built-in, but requires firmware. Linux does not support this in all configurations.", m.Name()) | 
 | 		} | 
 | 		for _, fw := range m.Firmware() { | 
 | 			fwset[fw] = true | 
 | 		} | 
 | 	} | 
 |  | 
 | 	// Convert set to list and sort for determinism | 
 | 	fwp := make([]string, 0, len(fwset)) | 
 | 	for p := range fwset { | 
 | 		fwp = append(fwp, p) | 
 | 	} | 
 | 	sort.Strings(fwp) | 
 |  | 
 | 	// This function is called for every requested firmware file and adds and | 
 | 	// resolves symlinks until it finds the target file and adds that too. | 
 | 	populatedPaths := make(map[string]bool) | 
 | 	var chaseReference func(string) | 
 | 	chaseReference = func(p string) { | 
 | 		if populatedPaths[p] { | 
 | 			// Bail if path is already populated. Because of the DAG-like | 
 | 			// property of links in filesystems everything transitively pointed | 
 | 			// to by anything at this path has already been included. | 
 | 			return | 
 | 		} | 
 | 		placedPath := path.Join("/lib/firmware", p) | 
 | 		if linkTarget := linkMap[p]; linkTarget != "" { | 
 | 			symlinks = append(symlinks, &fsspec.SymbolicLink{ | 
 | 				Path:       placedPath, | 
 | 				TargetPath: linkTarget, | 
 | 			}) | 
 | 			populatedPaths[p] = true | 
 | 			// Symlinks are relative to their place, resolve them to be relative | 
 | 			// to the firmware root directory. | 
 | 			chaseReference(path.Join(path.Dir(p), linkTarget)) | 
 | 			return | 
 | 		} | 
 | 		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) | 
 | 			return | 
 | 		} | 
 | 		files = append(files, &fsspec.File{ | 
 | 			Path:       path.Join("/lib/firmware", p), | 
 | 			Mode:       0444, | 
 | 			SourcePath: sourcePath, | 
 | 		}) | 
 | 		populatedPaths[p] = true | 
 | 	} | 
 |  | 
 | 	for _, p := range fwp { | 
 | 		chaseReference(p) | 
 | 	} | 
 | 	// Format output in a both human- and machine-readable form | 
 | 	marshalOpts := prototext.MarshalOptions{Multiline: true, Indent: "  "} | 
 | 	fsspecRaw, err := marshalOpts.Marshal(&fsspec.FSSpec{File: files, SymbolicLink: symlinks}) | 
 | 	if err := os.WriteFile(*outFSSpecPath, fsspecRaw, 0644); err != nil { | 
 | 		log.Fatalf("failed writing output: %v", err) | 
 | 	} | 
 | } |