Mateusz Zalega | 950c26d | 2021-12-22 18:56:15 +0100 | [diff] [blame] | 1 | // 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) |
| 5 | package main |
| 6 | |
| 7 | import ( |
| 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. |
| 21 | func 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. |
| 57 | func 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. |
| 75 | func 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 | |
| 94 | func 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 | } |