blob: 838a9f18a636e9ed7d4ed84e7e318d7c1e452cc4 [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
Jan Schär7d1538f2025-07-17 10:08:18 +020034type section struct {
35 name string
36 descr string
37 required bool
38 file *string
39}
40
Mateusz Zalega8ed99762022-01-25 19:02:22 +010041var (
42 // sections contains VMAs and source files of the payload PE sections. The
43 // file path pointers will be filled in when the flags are parsed. It's used
44 // to generate objcopy command line arguments. Entries that are "required"
45 // will cause the program to stop and print usage information if not provided
46 // as command line parameters.
Jan Schär7d1538f2025-07-17 10:08:18 +020047 sections = []section{
48 {"linux", "Linux kernel image", true, nil},
49 {"initrd", "initramfs", false, nil},
50 {"osrel", "OS release file in text format", false, nil},
51 {"cmdline", "a file containting additional kernel command line parameters", false, nil},
52 {"splash", "a splash screen image in BMP format", false, nil},
Mateusz Zalega8ed99762022-01-25 19:02:22 +010053 }
Lorenz Brun304d42c2022-02-24 17:53:08 +010054 initrdList stringList
Mateusz Zalega8ed99762022-01-25 19:02:22 +010055 objcopy = flag.String("objcopy", "", "objcopy executable")
56 stub = flag.String("stub", "", "the EFI stub executable")
57 output = flag.String("output", "", "objcopy output")
58 rootfs_dm_table = flag.String("rootfs_dm_table", "", "a text file containing the DeviceMapper rootfs target table")
59)
60
Jan Schär7d1538f2025-07-17 10:08:18 +020061func getSection(name string) *section {
62 for i := range sections {
63 s := &sections[i]
64 if s.name == name {
65 return s
66 }
67 }
68 return nil
69}
70
Mateusz Zalega8ed99762022-01-25 19:02:22 +010071func main() {
Lorenz Brun304d42c2022-02-24 17:53:08 +010072 flag.Var(&initrdList, "initrd", "Path to initramfs, can be given multiple times")
Mateusz Zalega8ed99762022-01-25 19:02:22 +010073 // Register parameters related to the EFI payload sections, then parse the flags.
Jan Schär7d1538f2025-07-17 10:08:18 +020074 for i := range sections {
75 s := &sections[i]
76 if s.name == "initrd" { // initrd is special because it accepts multiple payloads
Lorenz Brun304d42c2022-02-24 17:53:08 +010077 continue
78 }
Jan Schär7d1538f2025-07-17 10:08:18 +020079 s.file = flag.String(s.name, "", s.descr)
Mateusz Zalega8ed99762022-01-25 19:02:22 +010080 }
81 flag.Parse()
82
83 // Ensure all the required parameters are filled in.
Jan Schär7d1538f2025-07-17 10:08:18 +020084 for _, s := range sections {
Mateusz Zalega8ed99762022-01-25 19:02:22 +010085 if s.required && *s.file == "" {
Jan Schär7d1538f2025-07-17 10:08:18 +020086 log.Fatalf("-%s parameter is missing.", s.name)
Mateusz Zalega8ed99762022-01-25 19:02:22 +010087 }
88 }
89 if *objcopy == "" {
90 log.Fatalf("-objcopy parameter is missing.")
91 }
92 if *stub == "" {
93 log.Fatalf("-stub parameter is missing.")
94 }
95 if *output == "" {
96 log.Fatalf("-output parameter is missing.")
97 }
98
99 // If a DeviceMapper table was passed, configure the kernel to boot from the
100 // device described by it, while keeping any other kernel command line
101 // parameters that might have been passed through "-cmdline".
102 if *rootfs_dm_table != "" {
103 var cmdline string
Jan Schär7d1538f2025-07-17 10:08:18 +0200104 p := *getSection("cmdline").file
Mateusz Zalega8ed99762022-01-25 19:02:22 +0100105 if p != "" {
106 c, err := os.ReadFile(p)
107 if err != nil {
108 log.Fatalf("%v", err)
109 }
110 cmdline = string(c[:])
111
112 if strings.Contains(cmdline, "root=") {
113 log.Fatalf("A DeviceMapper table was passed, however the kernel command line already contains a \"root=\" statement.")
114 }
115 }
116
117 vt, err := os.ReadFile(*rootfs_dm_table)
118 if err != nil {
119 log.Fatalf("%v", err)
120 }
121 cmdline += fmt.Sprintf(" dm-mod.create=\"rootfs,,,ro,%s\" root=/dev/dm-0", vt)
122
123 out, err := os.CreateTemp(".", "cmdline")
124 if err != nil {
125 log.Fatalf("%v", err)
126 }
127 defer os.Remove(out.Name())
128 if _, err = out.Write([]byte(cmdline[:])); err != nil {
129 log.Fatalf("%v", err)
130 }
131 out.Close()
132
Jan Schär7d1538f2025-07-17 10:08:18 +0200133 *getSection("cmdline").file = out.Name()
Mateusz Zalega8ed99762022-01-25 19:02:22 +0100134 }
135
Lorenz Brun304d42c2022-02-24 17:53:08 +0100136 var initrdPath string
137 if len(initrdList) > 0 {
138 initrd, err := os.CreateTemp(".", "initrd")
139 if err != nil {
140 log.Fatalf("Failed to create temporary initrd: %v", err)
141 }
142 defer os.Remove(initrd.Name())
143 for _, initrdPath := range initrdList {
144 initrdSrc, err := os.Open(initrdPath)
145 if err != nil {
146 log.Fatalf("Failed to open initrd file: %v", err)
147 }
148 if _, err := io.Copy(initrd, initrdSrc); err != nil {
149 initrdSrc.Close()
150 log.Fatalf("Failed concatinating initrd: %v", err)
151 }
152 initrdSrc.Close()
153 }
154 initrdPath = initrd.Name()
155 }
Jan Schär7d1538f2025-07-17 10:08:18 +0200156 getSection("initrd").file = &initrdPath
Lorenz Brun304d42c2022-02-24 17:53:08 +0100157
Mateusz Zalega8ed99762022-01-25 19:02:22 +0100158 // Execute objcopy
159 var args []string
Jan Schär7d1538f2025-07-17 10:08:18 +0200160 for _, s := range sections {
161 if *s.file != "" {
162 args = append(args,
163 "--add-section", fmt.Sprintf(".%s=%s", s.name, *s.file),
164 fmt.Sprintf("--set-section-flags=.%s=data", s.name),
165 )
Mateusz Zalega8ed99762022-01-25 19:02:22 +0100166 }
167 }
Jan Schär7d1538f2025-07-17 10:08:18 +0200168 args = append(args,
Mateusz Zalega8ed99762022-01-25 19:02:22 +0100169 *stub,
170 *output,
Jan Schär7d1538f2025-07-17 10:08:18 +0200171 )
Mateusz Zalega8ed99762022-01-25 19:02:22 +0100172 cmd := exec.Command(*objcopy, args...)
173 cmd.Stderr = os.Stderr
174 cmd.Stdout = os.Stdout
175 err := cmd.Run()
176 if err == nil {
177 return
178 }
179 // Exit with objcopy's return code.
Tim Windelschmidtaf821c82024-04-23 15:03:52 +0200180 var e *exec.ExitError
181 if errors.As(err, &e) {
Mateusz Zalega8ed99762022-01-25 19:02:22 +0100182 os.Exit(e.ExitCode())
183 }
184 log.Fatalf("Could not start command: %v", err)
185}