blob: 5791b9ec014ad42eb70e2df595d2f1256ffd8056 [file] [log] [blame]
Mateusz Zalega950c26d2021-12-22 18:56:15 +01001// fwprune is a buildsystem utility that filters linux-firmware repository
2// contents to include only files required by the built-in kernel modules,
3// that are specified in modules.builtin.modinfo.
4// (see: https://www.kernel.org/doc/Documentation/kbuild/kbuild.txt)
5package main
6
7import (
8 "bytes"
9 "fmt"
10 "io"
11 "log"
12 "os"
13 "path/filepath"
14 "sort"
15 "strings"
16)
17
18// fwPaths returns a slice of filesystem paths relative to the root of the
19// linux-firmware repository, pointing at firmware files, according to contents
20// of the kernel build side effect: modules.builtin.modinfo.
21func fwPaths(mi []byte) []string {
22 // Use a map pset to deduplicate firmware paths.
23 pset := make(map[string]bool)
24 // Get a slice of entries of the form "unix.license=GPL" from mi. Then extract
25 // firmware information from it.
26 entries := bytes.Split(mi, []byte{0})
27 for _, entry := range entries {
28 // Skip empty entries.
29 if len(entry) == 0 {
30 continue
31 }
32 // Parse the entries. Split each entry into a key-value pair, separated
33 // by "=".
34 kv := strings.SplitN(string(entry), "=", 2)
35 key, value := kv[0], kv[1]
36 // Split the key into a module.attribute] pair, such as "unix.license".
37 ma := strings.SplitN(key, ".", 2)
38 // Skip, if it's not a firmware entry, according to the attribute.
39 if ma[1] != "firmware" {
40 continue
41 }
42 // If it is though, value holds a firmware path.
43 pset[value] = true
44 }
45 // Convert the deduplicated pset to a slice.
46 pslice := make([]string, 0, len(pset))
47 for p, _ := range pset {
48 pslice = append(pslice, p)
49 }
50 sort.Strings(pslice)
51 return pslice
52}
53
54// fwDirs returns a slice of filesystem paths relative to the root of
55// linux-firmware repository, pointing at directories that need to exist before
56// files specified by fwp paths can be created.
57func fwDirs(fwp []string) []string {
58 // Use a map dset to deduplicate directory paths.
59 dset := make(map[string]bool)
60 for _, p := range fwp {
61 dp := filepath.Dir(p)
62 dset[dp] = true
63 }
64 // Convert dset to a slice.
65 dslice := make([]string, 0, len(dset))
66 for d, _ := range dset {
67 dslice = append(dslice, d)
68 }
69 sort.Strings(dslice)
70 return dslice
71}
72
73// copyFile copies a file at filesystem path src to dst. dst must not point to
74// an existing file. It may return an IO error.
75func copyFile(dst, src string) error {
76 i, err := os.Open(src)
77 if err != nil {
78 return err
79 }
80 defer i.Close()
81
82 o, err := os.OpenFile(dst, os.O_WRONLY|os.O_CREATE, 0770)
83 if err != nil {
84 return err
85 }
86 defer o.Close()
87
88 if _, err := io.Copy(o, i); err != nil {
89 return err
90 }
91 return nil
92}
93
94func main() {
95 // The directory at fwdst will be filled with firmware required by the kernel
96 // builtins specified in modules.builtin.modinfo [1]. fwsrc must point to the
97 // linux-firmware repository [2]. All parameters must be filesystem paths. The
98 // necessary parts of the original directory layout will be recreated at fwdst.
99 // fwprune will output a list of directories and files it creates.
100 // [1] https://www.kernel.org/doc/Documentation/kbuild/kbuild.txt
101 // [2] https://git.kernel.org/pub/scm/linux/kernel/git/firmware/linux-firmware.git
102 if len(os.Args) != 4 {
103 // Print usage information, if misused.
104 fmt.Println("Usage: fwprune modules.builtin.modinfo fwsrc fwdst")
105 os.Exit(1)
106 }
107 modinfo := os.Args[1]
108 fwsrc := os.Args[2]
109 fwdst := os.Args[3]
110
111 // Get the firmware file paths.
112 mi, err := os.ReadFile(modinfo)
113 if err != nil {
114 log.Fatalf("While reading modinfo: %v", err)
115 }
116 fwp := fwPaths(mi)
117
118 // Recreate the necessary parts of the linux-firmware directory tree.
119 fwd := fwDirs(fwp)
120 for _, rd := range fwd {
121 d := filepath.Join(fwdst, rd)
122 if err := os.MkdirAll(d, 0770); err != nil {
123 log.Fatalf("Couldn't create a subdirectory: %v", err)
124 }
125 fmt.Println(d)
126 }
127
128 // Copy the files specified by fwp.
129 for _, p := range fwp {
130 dst := filepath.Join(fwdst, p)
131 src := filepath.Join(fwsrc, p)
132
133 if err := copyFile(dst, src); err != nil {
134 log.Fatalf("Couldn't provide %q: %v", dst, err)
135 }
136 fmt.Println(p)
137 }
138}