|  | // Copyright 2020 The Monogon Project Authors. | 
|  | // | 
|  | // SPDX-License-Identifier: Apache-2.0 | 
|  | // | 
|  | // Licensed under the Apache License, Version 2.0 (the "License"); | 
|  | // you may not use this file except in compliance with the License. | 
|  | // You may obtain a copy of the License at | 
|  | // | 
|  | //     http://www.apache.org/licenses/LICENSE-2.0 | 
|  | // | 
|  | // Unless required by applicable law or agreed to in writing, software | 
|  | // distributed under the License is distributed on an "AS IS" BASIS, | 
|  | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
|  | // See the License for the specific language governing permissions and | 
|  | // limitations under the License. | 
|  |  | 
|  | // mkpayload is an objcopy wrapper that builds EFI unified kernel images. It | 
|  | // performs actions that can't be realized by either objcopy or the | 
|  | // buildsystem. | 
|  | package main | 
|  |  | 
|  | import ( | 
|  | "flag" | 
|  | "fmt" | 
|  | "io" | 
|  | "log" | 
|  | "os" | 
|  | "os/exec" | 
|  | "strings" | 
|  | ) | 
|  |  | 
|  | type stringList []string | 
|  |  | 
|  | func (l *stringList) String() string { | 
|  | if l == nil { | 
|  | return "" | 
|  | } | 
|  | return strings.Join(*l, ", ") | 
|  | } | 
|  |  | 
|  | func (l *stringList) Set(value string) error { | 
|  | *l = append(*l, value) | 
|  | return nil | 
|  | } | 
|  |  | 
|  | var ( | 
|  | // sections contains VMAs and source files of the payload PE sections. The | 
|  | // file path pointers will be filled in when the flags are parsed. It's used | 
|  | // to generate objcopy command line arguments. Entries that are "required" | 
|  | // will cause the program to stop and print usage information if not provided | 
|  | // as command line parameters. | 
|  | sections = map[string]struct { | 
|  | descr    string | 
|  | vma      string | 
|  | required bool | 
|  | file     *string | 
|  | }{ | 
|  | "linux":   {"Linux kernel image", "0x2000000", true, nil}, | 
|  | "initrd":  {"initramfs", "0x5000000", false, nil}, | 
|  | "osrel":   {"OS release file in text format", "0x20000", false, nil}, | 
|  | "cmdline": {"a file containting additional kernel command line parameters", "0x30000", false, nil}, | 
|  | "splash":  {"a splash screen image in BMP format", "0x40000", false, nil}, | 
|  | } | 
|  | initrdList      stringList | 
|  | objcopy         = flag.String("objcopy", "", "objcopy executable") | 
|  | stub            = flag.String("stub", "", "the EFI stub executable") | 
|  | output          = flag.String("output", "", "objcopy output") | 
|  | rootfs_dm_table = flag.String("rootfs_dm_table", "", "a text file containing the DeviceMapper rootfs target table") | 
|  | ) | 
|  |  | 
|  | func main() { | 
|  | flag.Var(&initrdList, "initrd", "Path to initramfs, can be given multiple times") | 
|  | // Register parameters related to the EFI payload sections, then parse the flags. | 
|  | for k, v := range sections { | 
|  | if k == "initrd" { // initrd is special because it accepts multiple payloads | 
|  | continue | 
|  | } | 
|  | v.file = flag.String(k, "", v.descr) | 
|  | sections[k] = v | 
|  | } | 
|  | flag.Parse() | 
|  |  | 
|  | // Ensure all the required parameters are filled in. | 
|  | for n, s := range sections { | 
|  | if s.required && *s.file == "" { | 
|  | log.Fatalf("-%s parameter is missing.", n) | 
|  | } | 
|  | } | 
|  | if *objcopy == "" { | 
|  | log.Fatalf("-objcopy parameter is missing.") | 
|  | } | 
|  | if *stub == "" { | 
|  | log.Fatalf("-stub parameter is missing.") | 
|  | } | 
|  | if *output == "" { | 
|  | log.Fatalf("-output parameter is missing.") | 
|  | } | 
|  |  | 
|  | // If a DeviceMapper table was passed, configure the kernel to boot from the | 
|  | // device described by it, while keeping any other kernel command line | 
|  | // parameters that might have been passed through "-cmdline". | 
|  | if *rootfs_dm_table != "" { | 
|  | var cmdline string | 
|  | p := *sections["cmdline"].file | 
|  | if p != "" { | 
|  | c, err := os.ReadFile(p) | 
|  | if err != nil { | 
|  | log.Fatalf("%v", err) | 
|  | } | 
|  | cmdline = string(c[:]) | 
|  |  | 
|  | if strings.Contains(cmdline, "root=") { | 
|  | log.Fatalf("A DeviceMapper table was passed, however the kernel command line already contains a \"root=\" statement.") | 
|  | } | 
|  | } | 
|  |  | 
|  | vt, err := os.ReadFile(*rootfs_dm_table) | 
|  | if err != nil { | 
|  | log.Fatalf("%v", err) | 
|  | } | 
|  | cmdline += fmt.Sprintf(" dm-mod.create=\"rootfs,,,ro,%s\" root=/dev/dm-0", vt) | 
|  |  | 
|  | out, err := os.CreateTemp(".", "cmdline") | 
|  | if err != nil { | 
|  | log.Fatalf("%v", err) | 
|  | } | 
|  | defer os.Remove(out.Name()) | 
|  | if _, err = out.Write([]byte(cmdline[:])); err != nil { | 
|  | log.Fatalf("%v", err) | 
|  | } | 
|  | out.Close() | 
|  |  | 
|  | *sections["cmdline"].file = out.Name() | 
|  | } | 
|  |  | 
|  | var initrdPath string | 
|  | if len(initrdList) > 0 { | 
|  | initrd, err := os.CreateTemp(".", "initrd") | 
|  | if err != nil { | 
|  | log.Fatalf("Failed to create temporary initrd: %v", err) | 
|  | } | 
|  | defer os.Remove(initrd.Name()) | 
|  | for _, initrdPath := range initrdList { | 
|  | initrdSrc, err := os.Open(initrdPath) | 
|  | if err != nil { | 
|  | log.Fatalf("Failed to open initrd file: %v", err) | 
|  | } | 
|  | if _, err := io.Copy(initrd, initrdSrc); err != nil { | 
|  | initrdSrc.Close() | 
|  | log.Fatalf("Failed concatinating initrd: %v", err) | 
|  | } | 
|  | initrdSrc.Close() | 
|  | } | 
|  | initrdPath = initrd.Name() | 
|  | } | 
|  | sec := sections["initrd"] | 
|  | sec.file = &initrdPath | 
|  | sections["initrd"] = sec | 
|  |  | 
|  | // Execute objcopy | 
|  | var args []string | 
|  | for name, c := range sections { | 
|  | if *c.file != "" { | 
|  | args = append(args, []string{ | 
|  | "--add-section", fmt.Sprintf(".%s=%s", name, *c.file), | 
|  | "--change-section-vma", fmt.Sprintf(".%s=%s", name, c.vma), | 
|  | }...) | 
|  | } | 
|  | } | 
|  | args = append(args, []string{ | 
|  | *stub, | 
|  | *output, | 
|  | }...) | 
|  | cmd := exec.Command(*objcopy, args...) | 
|  | cmd.Stderr = os.Stderr | 
|  | cmd.Stdout = os.Stdout | 
|  | err := cmd.Run() | 
|  | if err == nil { | 
|  | return | 
|  | } | 
|  | // Exit with objcopy's return code. | 
|  | if e, ok := err.(*exec.ExitError); ok { | 
|  | os.Exit(e.ExitCode()) | 
|  | } | 
|  | log.Fatalf("Could not start command: %v", err) | 
|  | } |