blob: c1433f324210ec10f49f0cb185bbdab595927bd9 [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
42 vma string
43 required bool
44 file *string
45 }{
46 "linux": {"Linux kernel image", "0x2000000", true, nil},
47 "initrd": {"initramfs", "0x5000000", false, nil},
48 "osrel": {"OS release file in text format", "0x20000", false, nil},
49 "cmdline": {"a file containting additional kernel command line parameters", "0x30000", false, nil},
50 "splash": {"a splash screen image in BMP format", "0x40000", false, nil},
51 }
Lorenz Brun304d42c2022-02-24 17:53:08 +010052 initrdList stringList
Mateusz Zalega8ed99762022-01-25 19:02:22 +010053 objcopy = flag.String("objcopy", "", "objcopy executable")
54 stub = flag.String("stub", "", "the EFI stub executable")
55 output = flag.String("output", "", "objcopy output")
56 rootfs_dm_table = flag.String("rootfs_dm_table", "", "a text file containing the DeviceMapper rootfs target table")
57)
58
59func main() {
Lorenz Brun304d42c2022-02-24 17:53:08 +010060 flag.Var(&initrdList, "initrd", "Path to initramfs, can be given multiple times")
Mateusz Zalega8ed99762022-01-25 19:02:22 +010061 // Register parameters related to the EFI payload sections, then parse the flags.
62 for k, v := range sections {
Lorenz Brun304d42c2022-02-24 17:53:08 +010063 if k == "initrd" { // initrd is special because it accepts multiple payloads
64 continue
65 }
Mateusz Zalega8ed99762022-01-25 19:02:22 +010066 v.file = flag.String(k, "", v.descr)
67 sections[k] = v
68 }
69 flag.Parse()
70
71 // Ensure all the required parameters are filled in.
72 for n, s := range sections {
73 if s.required && *s.file == "" {
74 log.Fatalf("-%s parameter is missing.", n)
75 }
76 }
77 if *objcopy == "" {
78 log.Fatalf("-objcopy parameter is missing.")
79 }
80 if *stub == "" {
81 log.Fatalf("-stub parameter is missing.")
82 }
83 if *output == "" {
84 log.Fatalf("-output parameter is missing.")
85 }
86
87 // If a DeviceMapper table was passed, configure the kernel to boot from the
88 // device described by it, while keeping any other kernel command line
89 // parameters that might have been passed through "-cmdline".
90 if *rootfs_dm_table != "" {
91 var cmdline string
92 p := *sections["cmdline"].file
93 if p != "" {
94 c, err := os.ReadFile(p)
95 if err != nil {
96 log.Fatalf("%v", err)
97 }
98 cmdline = string(c[:])
99
100 if strings.Contains(cmdline, "root=") {
101 log.Fatalf("A DeviceMapper table was passed, however the kernel command line already contains a \"root=\" statement.")
102 }
103 }
104
105 vt, err := os.ReadFile(*rootfs_dm_table)
106 if err != nil {
107 log.Fatalf("%v", err)
108 }
109 cmdline += fmt.Sprintf(" dm-mod.create=\"rootfs,,,ro,%s\" root=/dev/dm-0", vt)
110
111 out, err := os.CreateTemp(".", "cmdline")
112 if err != nil {
113 log.Fatalf("%v", err)
114 }
115 defer os.Remove(out.Name())
116 if _, err = out.Write([]byte(cmdline[:])); err != nil {
117 log.Fatalf("%v", err)
118 }
119 out.Close()
120
121 *sections["cmdline"].file = out.Name()
122 }
123
Lorenz Brun304d42c2022-02-24 17:53:08 +0100124 var initrdPath string
125 if len(initrdList) > 0 {
126 initrd, err := os.CreateTemp(".", "initrd")
127 if err != nil {
128 log.Fatalf("Failed to create temporary initrd: %v", err)
129 }
130 defer os.Remove(initrd.Name())
131 for _, initrdPath := range initrdList {
132 initrdSrc, err := os.Open(initrdPath)
133 if err != nil {
134 log.Fatalf("Failed to open initrd file: %v", err)
135 }
136 if _, err := io.Copy(initrd, initrdSrc); err != nil {
137 initrdSrc.Close()
138 log.Fatalf("Failed concatinating initrd: %v", err)
139 }
140 initrdSrc.Close()
141 }
142 initrdPath = initrd.Name()
143 }
144 sec := sections["initrd"]
145 sec.file = &initrdPath
146 sections["initrd"] = sec
147
Mateusz Zalega8ed99762022-01-25 19:02:22 +0100148 // Execute objcopy
149 var args []string
150 for name, c := range sections {
151 if *c.file != "" {
152 args = append(args, []string{
153 "--add-section", fmt.Sprintf(".%s=%s", name, *c.file),
154 "--change-section-vma", fmt.Sprintf(".%s=%s", name, c.vma),
155 }...)
156 }
157 }
158 args = append(args, []string{
159 *stub,
160 *output,
161 }...)
162 cmd := exec.Command(*objcopy, args...)
163 cmd.Stderr = os.Stderr
164 cmd.Stdout = os.Stdout
165 err := cmd.Run()
166 if err == nil {
167 return
168 }
169 // Exit with objcopy's return code.
Tim Windelschmidtaf821c82024-04-23 15:03:52 +0200170 var e *exec.ExitError
171 if errors.As(err, &e) {
Mateusz Zalega8ed99762022-01-25 19:02:22 +0100172 os.Exit(e.ExitCode())
173 }
174 log.Fatalf("Could not start command: %v", err)
175}