blob: a66d458bdc9191dc2a976a9cd8805cbc120de654 [file] [log] [blame]
Mateusz Zalega8ed99762022-01-25 19:02:22 +01001// Copyright 2020 The Monogon Project Authors.
2//
3// SPDX-License-Identifier: Apache-2.0
4//
5// Licensed under the Apache License, Version 2.0 (the "License");
6// you may not use this file except in compliance with the License.
7// You may obtain a copy of the License at
8//
9// http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing, software
12// distributed under the License is distributed on an "AS IS" BASIS,
13// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14// See the License for the specific language governing permissions and
15// limitations under the License.
16
17// mkpayload is an objcopy wrapper that builds EFI unified kernel images. It
18// performs actions that can't be realized by either objcopy or the
19// buildsystem.
20package main
21
22import (
Tim Windelschmidtaf821c82024-04-23 15:03:52 +020023 "errors"
Mateusz Zalega8ed99762022-01-25 19:02:22 +010024 "flag"
25 "fmt"
Lorenz Brun304d42c2022-02-24 17:53:08 +010026 "io"
Mateusz Zalega8ed99762022-01-25 19:02:22 +010027 "log"
28 "os"
29 "os/exec"
30 "strings"
31)
32
Lorenz Brun304d42c2022-02-24 17:53:08 +010033type stringList []string
34
35func (l *stringList) String() string {
36 if l == nil {
37 return ""
38 }
39 return strings.Join(*l, ", ")
40}
41
42func (l *stringList) Set(value string) error {
43 *l = append(*l, value)
44 return nil
45}
46
Mateusz Zalega8ed99762022-01-25 19:02:22 +010047var (
48 // sections contains VMAs and source files of the payload PE sections. The
49 // file path pointers will be filled in when the flags are parsed. It's used
50 // to generate objcopy command line arguments. Entries that are "required"
51 // will cause the program to stop and print usage information if not provided
52 // as command line parameters.
53 sections = map[string]struct {
54 descr string
55 vma string
56 required bool
57 file *string
58 }{
59 "linux": {"Linux kernel image", "0x2000000", true, nil},
60 "initrd": {"initramfs", "0x5000000", false, nil},
61 "osrel": {"OS release file in text format", "0x20000", false, nil},
62 "cmdline": {"a file containting additional kernel command line parameters", "0x30000", false, nil},
63 "splash": {"a splash screen image in BMP format", "0x40000", false, nil},
64 }
Lorenz Brun304d42c2022-02-24 17:53:08 +010065 initrdList stringList
Mateusz Zalega8ed99762022-01-25 19:02:22 +010066 objcopy = flag.String("objcopy", "", "objcopy executable")
67 stub = flag.String("stub", "", "the EFI stub executable")
68 output = flag.String("output", "", "objcopy output")
69 rootfs_dm_table = flag.String("rootfs_dm_table", "", "a text file containing the DeviceMapper rootfs target table")
70)
71
72func main() {
Lorenz Brun304d42c2022-02-24 17:53:08 +010073 flag.Var(&initrdList, "initrd", "Path to initramfs, can be given multiple times")
Mateusz Zalega8ed99762022-01-25 19:02:22 +010074 // Register parameters related to the EFI payload sections, then parse the flags.
75 for k, v := range sections {
Lorenz Brun304d42c2022-02-24 17:53:08 +010076 if k == "initrd" { // initrd is special because it accepts multiple payloads
77 continue
78 }
Mateusz Zalega8ed99762022-01-25 19:02:22 +010079 v.file = flag.String(k, "", v.descr)
80 sections[k] = v
81 }
82 flag.Parse()
83
84 // Ensure all the required parameters are filled in.
85 for n, s := range sections {
86 if s.required && *s.file == "" {
87 log.Fatalf("-%s parameter is missing.", n)
88 }
89 }
90 if *objcopy == "" {
91 log.Fatalf("-objcopy parameter is missing.")
92 }
93 if *stub == "" {
94 log.Fatalf("-stub parameter is missing.")
95 }
96 if *output == "" {
97 log.Fatalf("-output parameter is missing.")
98 }
99
100 // If a DeviceMapper table was passed, configure the kernel to boot from the
101 // device described by it, while keeping any other kernel command line
102 // parameters that might have been passed through "-cmdline".
103 if *rootfs_dm_table != "" {
104 var cmdline string
105 p := *sections["cmdline"].file
106 if p != "" {
107 c, err := os.ReadFile(p)
108 if err != nil {
109 log.Fatalf("%v", err)
110 }
111 cmdline = string(c[:])
112
113 if strings.Contains(cmdline, "root=") {
114 log.Fatalf("A DeviceMapper table was passed, however the kernel command line already contains a \"root=\" statement.")
115 }
116 }
117
118 vt, err := os.ReadFile(*rootfs_dm_table)
119 if err != nil {
120 log.Fatalf("%v", err)
121 }
122 cmdline += fmt.Sprintf(" dm-mod.create=\"rootfs,,,ro,%s\" root=/dev/dm-0", vt)
123
124 out, err := os.CreateTemp(".", "cmdline")
125 if err != nil {
126 log.Fatalf("%v", err)
127 }
128 defer os.Remove(out.Name())
129 if _, err = out.Write([]byte(cmdline[:])); err != nil {
130 log.Fatalf("%v", err)
131 }
132 out.Close()
133
134 *sections["cmdline"].file = out.Name()
135 }
136
Lorenz Brun304d42c2022-02-24 17:53:08 +0100137 var initrdPath string
138 if len(initrdList) > 0 {
139 initrd, err := os.CreateTemp(".", "initrd")
140 if err != nil {
141 log.Fatalf("Failed to create temporary initrd: %v", err)
142 }
143 defer os.Remove(initrd.Name())
144 for _, initrdPath := range initrdList {
145 initrdSrc, err := os.Open(initrdPath)
146 if err != nil {
147 log.Fatalf("Failed to open initrd file: %v", err)
148 }
149 if _, err := io.Copy(initrd, initrdSrc); err != nil {
150 initrdSrc.Close()
151 log.Fatalf("Failed concatinating initrd: %v", err)
152 }
153 initrdSrc.Close()
154 }
155 initrdPath = initrd.Name()
156 }
157 sec := sections["initrd"]
158 sec.file = &initrdPath
159 sections["initrd"] = sec
160
Mateusz Zalega8ed99762022-01-25 19:02:22 +0100161 // Execute objcopy
162 var args []string
163 for name, c := range sections {
164 if *c.file != "" {
165 args = append(args, []string{
166 "--add-section", fmt.Sprintf(".%s=%s", name, *c.file),
167 "--change-section-vma", fmt.Sprintf(".%s=%s", name, c.vma),
168 }...)
169 }
170 }
171 args = append(args, []string{
172 *stub,
173 *output,
174 }...)
175 cmd := exec.Command(*objcopy, args...)
176 cmd.Stderr = os.Stderr
177 cmd.Stdout = os.Stdout
178 err := cmd.Run()
179 if err == nil {
180 return
181 }
182 // Exit with objcopy's return code.
Tim Windelschmidtaf821c82024-04-23 15:03:52 +0200183 var e *exec.ExitError
184 if errors.As(err, &e) {
Mateusz Zalega8ed99762022-01-25 19:02:22 +0100185 os.Exit(e.ExitCode())
186 }
187 log.Fatalf("Could not start command: %v", err)
188}