| // 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) |
| } |