blob: f4861342aa46958b9bc821fc420ee1483a0e7c0a [file] [log] [blame]
Tim Windelschmidt6d33a432025-02-04 14:34:25 +01001// Copyright The Monogon Project Authors.
Mateusz Zalega8ed99762022-01-25 19:02:22 +01002// SPDX-License-Identifier: Apache-2.0
Mateusz Zalega8ed99762022-01-25 19:02:22 +01003
4// mkpayload is an objcopy wrapper that builds EFI unified kernel images. It
5// performs actions that can't be realized by either objcopy or the
6// buildsystem.
7package main
8
9import (
Tim Windelschmidtaf821c82024-04-23 15:03:52 +020010 "errors"
Mateusz Zalega8ed99762022-01-25 19:02:22 +010011 "flag"
12 "fmt"
Lorenz Brun304d42c2022-02-24 17:53:08 +010013 "io"
Mateusz Zalega8ed99762022-01-25 19:02:22 +010014 "log"
15 "os"
16 "os/exec"
17 "strings"
18)
19
Lorenz Brun304d42c2022-02-24 17:53:08 +010020type stringList []string
21
22func (l *stringList) String() string {
23 if l == nil {
24 return ""
25 }
26 return strings.Join(*l, ", ")
27}
28
29func (l *stringList) Set(value string) error {
30 *l = append(*l, value)
31 return nil
32}
33
Mateusz Zalega8ed99762022-01-25 19:02:22 +010034var (
35 // sections contains VMAs and source files of the payload PE sections. The
36 // file path pointers will be filled in when the flags are parsed. It's used
37 // to generate objcopy command line arguments. Entries that are "required"
38 // will cause the program to stop and print usage information if not provided
39 // as command line parameters.
40 sections = map[string]struct {
41 descr string
Mateusz Zalega8ed99762022-01-25 19:02:22 +010042 required bool
43 file *string
44 }{
Lorenz Brun163525e2025-05-22 15:30:14 +020045 "linux": {"Linux kernel image", true, nil},
46 "initrd": {"initramfs", false, nil},
47 "osrel": {"OS release file in text format", false, nil},
48 "cmdline": {"a file containting additional kernel command line parameters", false, nil},
49 "splash": {"a splash screen image in BMP format", false, nil},
Mateusz Zalega8ed99762022-01-25 19:02:22 +010050 }
Lorenz Brun304d42c2022-02-24 17:53:08 +010051 initrdList stringList
Mateusz Zalega8ed99762022-01-25 19:02:22 +010052 objcopy = flag.String("objcopy", "", "objcopy executable")
53 stub = flag.String("stub", "", "the EFI stub executable")
54 output = flag.String("output", "", "objcopy output")
55 rootfs_dm_table = flag.String("rootfs_dm_table", "", "a text file containing the DeviceMapper rootfs target table")
56)
57
58func main() {
Lorenz Brun304d42c2022-02-24 17:53:08 +010059 flag.Var(&initrdList, "initrd", "Path to initramfs, can be given multiple times")
Mateusz Zalega8ed99762022-01-25 19:02:22 +010060 // Register parameters related to the EFI payload sections, then parse the flags.
61 for k, v := range sections {
Lorenz Brun304d42c2022-02-24 17:53:08 +010062 if k == "initrd" { // initrd is special because it accepts multiple payloads
63 continue
64 }
Mateusz Zalega8ed99762022-01-25 19:02:22 +010065 v.file = flag.String(k, "", v.descr)
66 sections[k] = v
67 }
68 flag.Parse()
69
70 // Ensure all the required parameters are filled in.
71 for n, s := range sections {
72 if s.required && *s.file == "" {
73 log.Fatalf("-%s parameter is missing.", n)
74 }
75 }
76 if *objcopy == "" {
77 log.Fatalf("-objcopy parameter is missing.")
78 }
79 if *stub == "" {
80 log.Fatalf("-stub parameter is missing.")
81 }
82 if *output == "" {
83 log.Fatalf("-output parameter is missing.")
84 }
85
86 // If a DeviceMapper table was passed, configure the kernel to boot from the
87 // device described by it, while keeping any other kernel command line
88 // parameters that might have been passed through "-cmdline".
89 if *rootfs_dm_table != "" {
90 var cmdline string
91 p := *sections["cmdline"].file
92 if p != "" {
93 c, err := os.ReadFile(p)
94 if err != nil {
95 log.Fatalf("%v", err)
96 }
97 cmdline = string(c[:])
98
99 if strings.Contains(cmdline, "root=") {
100 log.Fatalf("A DeviceMapper table was passed, however the kernel command line already contains a \"root=\" statement.")
101 }
102 }
103
104 vt, err := os.ReadFile(*rootfs_dm_table)
105 if err != nil {
106 log.Fatalf("%v", err)
107 }
108 cmdline += fmt.Sprintf(" dm-mod.create=\"rootfs,,,ro,%s\" root=/dev/dm-0", vt)
109
110 out, err := os.CreateTemp(".", "cmdline")
111 if err != nil {
112 log.Fatalf("%v", err)
113 }
114 defer os.Remove(out.Name())
115 if _, err = out.Write([]byte(cmdline[:])); err != nil {
116 log.Fatalf("%v", err)
117 }
118 out.Close()
119
120 *sections["cmdline"].file = out.Name()
121 }
122
Lorenz Brun304d42c2022-02-24 17:53:08 +0100123 var initrdPath string
124 if len(initrdList) > 0 {
125 initrd, err := os.CreateTemp(".", "initrd")
126 if err != nil {
127 log.Fatalf("Failed to create temporary initrd: %v", err)
128 }
129 defer os.Remove(initrd.Name())
130 for _, initrdPath := range initrdList {
131 initrdSrc, err := os.Open(initrdPath)
132 if err != nil {
133 log.Fatalf("Failed to open initrd file: %v", err)
134 }
135 if _, err := io.Copy(initrd, initrdSrc); err != nil {
136 initrdSrc.Close()
137 log.Fatalf("Failed concatinating initrd: %v", err)
138 }
139 initrdSrc.Close()
140 }
141 initrdPath = initrd.Name()
142 }
143 sec := sections["initrd"]
144 sec.file = &initrdPath
145 sections["initrd"] = sec
146
Mateusz Zalega8ed99762022-01-25 19:02:22 +0100147 // Execute objcopy
148 var args []string
149 for name, c := range sections {
150 if *c.file != "" {
151 args = append(args, []string{
152 "--add-section", fmt.Sprintf(".%s=%s", name, *c.file),
Lorenz Brun163525e2025-05-22 15:30:14 +0200153 fmt.Sprintf("--set-section-flags=.%s=data", name),
Mateusz Zalega8ed99762022-01-25 19:02:22 +0100154 }...)
155 }
156 }
157 args = append(args, []string{
158 *stub,
159 *output,
160 }...)
161 cmd := exec.Command(*objcopy, args...)
162 cmd.Stderr = os.Stderr
163 cmd.Stdout = os.Stdout
164 err := cmd.Run()
165 if err == nil {
166 return
167 }
168 // Exit with objcopy's return code.
Tim Windelschmidtaf821c82024-04-23 15:03:52 +0200169 var e *exec.ExitError
170 if errors.As(err, &e) {
Mateusz Zalega8ed99762022-01-25 19:02:22 +0100171 os.Exit(e.ExitCode())
172 }
173 log.Fatalf("Could not start command: %v", err)
174}