blob: aee3a813867ccf895bd4ea5b3801a8240cc5241d [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 (
23 "flag"
24 "fmt"
Lorenz Brun304d42c2022-02-24 17:53:08 +010025 "io"
Mateusz Zalega8ed99762022-01-25 19:02:22 +010026 "log"
27 "os"
28 "os/exec"
29 "strings"
30)
31
Lorenz Brun304d42c2022-02-24 17:53:08 +010032type stringList []string
33
34func (l *stringList) String() string {
35 if l == nil {
36 return ""
37 }
38 return strings.Join(*l, ", ")
39}
40
41func (l *stringList) Set(value string) error {
42 *l = append(*l, value)
43 return nil
44}
45
Mateusz Zalega8ed99762022-01-25 19:02:22 +010046var (
47 // sections contains VMAs and source files of the payload PE sections. The
48 // file path pointers will be filled in when the flags are parsed. It's used
49 // to generate objcopy command line arguments. Entries that are "required"
50 // will cause the program to stop and print usage information if not provided
51 // as command line parameters.
52 sections = map[string]struct {
53 descr string
54 vma string
55 required bool
56 file *string
57 }{
58 "linux": {"Linux kernel image", "0x2000000", true, nil},
59 "initrd": {"initramfs", "0x5000000", false, nil},
60 "osrel": {"OS release file in text format", "0x20000", false, nil},
61 "cmdline": {"a file containting additional kernel command line parameters", "0x30000", false, nil},
62 "splash": {"a splash screen image in BMP format", "0x40000", false, nil},
63 }
Lorenz Brun304d42c2022-02-24 17:53:08 +010064 initrdList stringList
Mateusz Zalega8ed99762022-01-25 19:02:22 +010065 objcopy = flag.String("objcopy", "", "objcopy executable")
66 stub = flag.String("stub", "", "the EFI stub executable")
67 output = flag.String("output", "", "objcopy output")
68 rootfs_dm_table = flag.String("rootfs_dm_table", "", "a text file containing the DeviceMapper rootfs target table")
69)
70
71func 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.
74 for k, v := range sections {
Lorenz Brun304d42c2022-02-24 17:53:08 +010075 if k == "initrd" { // initrd is special because it accepts multiple payloads
76 continue
77 }
Mateusz Zalega8ed99762022-01-25 19:02:22 +010078 v.file = flag.String(k, "", v.descr)
79 sections[k] = v
80 }
81 flag.Parse()
82
83 // Ensure all the required parameters are filled in.
84 for n, s := range sections {
85 if s.required && *s.file == "" {
86 log.Fatalf("-%s parameter is missing.", n)
87 }
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
104 p := *sections["cmdline"].file
105 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
133 *sections["cmdline"].file = out.Name()
134 }
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 }
156 sec := sections["initrd"]
157 sec.file = &initrdPath
158 sections["initrd"] = sec
159
Mateusz Zalega8ed99762022-01-25 19:02:22 +0100160 // Execute objcopy
161 var args []string
162 for name, c := range sections {
163 if *c.file != "" {
164 args = append(args, []string{
165 "--add-section", fmt.Sprintf(".%s=%s", name, *c.file),
166 "--change-section-vma", fmt.Sprintf(".%s=%s", name, c.vma),
167 }...)
168 }
169 }
170 args = append(args, []string{
171 *stub,
172 *output,
173 }...)
174 cmd := exec.Command(*objcopy, args...)
175 cmd.Stderr = os.Stderr
176 cmd.Stdout = os.Stdout
177 err := cmd.Run()
178 if err == nil {
179 return
180 }
181 // Exit with objcopy's return code.
182 if e, ok := err.(*exec.ExitError); ok {
183 os.Exit(e.ExitCode())
184 }
185 log.Fatalf("Could not start command: %v", err)
186}