m/n/b/mkpayload: init
mkpayload is an objcopy wrapper meant to perform actions that neither
the buildsystem or objcopy could perform by themselves. This is needed
by the upcoming dm-verity rootfs integration.
Change-Id: I8ad097a1ad26bec0fb2db4f8b14e75a1b038f8fb
Reviewed-on: https://review.monogon.dev/c/monogon/+/524
Reviewed-by: Sergiusz Bazanski <serge@monogon.tech>
diff --git a/metropolis/node/build/mkpayload/mkpayload.go b/metropolis/node/build/mkpayload/mkpayload.go
new file mode 100644
index 0000000..42ec062
--- /dev/null
+++ b/metropolis/node/build/mkpayload/mkpayload.go
@@ -0,0 +1,142 @@
+// 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"
+ "log"
+ "os"
+ "os/exec"
+ "strings"
+)
+
+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},
+ }
+ 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() {
+ // Register parameters related to the EFI payload sections, then parse the flags.
+ for k, v := range sections {
+ 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()
+ }
+
+ // 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)
+}