blob: aee3a813867ccf895bd4ea5b3801a8240cc5241d [file] [log] [blame]
// Copyright 2020 The Monogon Project Authors.
//
// SPDX-License-Identifier: Apache-2.0
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// mkpayload is an objcopy wrapper that builds EFI unified kernel images. It
// performs actions that can't be realized by either objcopy or the
// buildsystem.
package main
import (
"flag"
"fmt"
"io"
"log"
"os"
"os/exec"
"strings"
)
type stringList []string
func (l *stringList) String() string {
if l == nil {
return ""
}
return strings.Join(*l, ", ")
}
func (l *stringList) Set(value string) error {
*l = append(*l, value)
return nil
}
var (
// sections contains VMAs and source files of the payload PE sections. The
// file path pointers will be filled in when the flags are parsed. It's used
// to generate objcopy command line arguments. Entries that are "required"
// will cause the program to stop and print usage information if not provided
// as command line parameters.
sections = map[string]struct {
descr string
vma string
required bool
file *string
}{
"linux": {"Linux kernel image", "0x2000000", true, nil},
"initrd": {"initramfs", "0x5000000", false, nil},
"osrel": {"OS release file in text format", "0x20000", false, nil},
"cmdline": {"a file containting additional kernel command line parameters", "0x30000", false, nil},
"splash": {"a splash screen image in BMP format", "0x40000", false, nil},
}
initrdList stringList
objcopy = flag.String("objcopy", "", "objcopy executable")
stub = flag.String("stub", "", "the EFI stub executable")
output = flag.String("output", "", "objcopy output")
rootfs_dm_table = flag.String("rootfs_dm_table", "", "a text file containing the DeviceMapper rootfs target table")
)
func main() {
flag.Var(&initrdList, "initrd", "Path to initramfs, can be given multiple times")
// Register parameters related to the EFI payload sections, then parse the flags.
for k, v := range sections {
if k == "initrd" { // initrd is special because it accepts multiple payloads
continue
}
v.file = flag.String(k, "", v.descr)
sections[k] = v
}
flag.Parse()
// Ensure all the required parameters are filled in.
for n, s := range sections {
if s.required && *s.file == "" {
log.Fatalf("-%s parameter is missing.", n)
}
}
if *objcopy == "" {
log.Fatalf("-objcopy parameter is missing.")
}
if *stub == "" {
log.Fatalf("-stub parameter is missing.")
}
if *output == "" {
log.Fatalf("-output parameter is missing.")
}
// If a DeviceMapper table was passed, configure the kernel to boot from the
// device described by it, while keeping any other kernel command line
// parameters that might have been passed through "-cmdline".
if *rootfs_dm_table != "" {
var cmdline string
p := *sections["cmdline"].file
if p != "" {
c, err := os.ReadFile(p)
if err != nil {
log.Fatalf("%v", err)
}
cmdline = string(c[:])
if strings.Contains(cmdline, "root=") {
log.Fatalf("A DeviceMapper table was passed, however the kernel command line already contains a \"root=\" statement.")
}
}
vt, err := os.ReadFile(*rootfs_dm_table)
if err != nil {
log.Fatalf("%v", err)
}
cmdline += fmt.Sprintf(" dm-mod.create=\"rootfs,,,ro,%s\" root=/dev/dm-0", vt)
out, err := os.CreateTemp(".", "cmdline")
if err != nil {
log.Fatalf("%v", err)
}
defer os.Remove(out.Name())
if _, err = out.Write([]byte(cmdline[:])); err != nil {
log.Fatalf("%v", err)
}
out.Close()
*sections["cmdline"].file = out.Name()
}
var initrdPath string
if len(initrdList) > 0 {
initrd, err := os.CreateTemp(".", "initrd")
if err != nil {
log.Fatalf("Failed to create temporary initrd: %v", err)
}
defer os.Remove(initrd.Name())
for _, initrdPath := range initrdList {
initrdSrc, err := os.Open(initrdPath)
if err != nil {
log.Fatalf("Failed to open initrd file: %v", err)
}
if _, err := io.Copy(initrd, initrdSrc); err != nil {
initrdSrc.Close()
log.Fatalf("Failed concatinating initrd: %v", err)
}
initrdSrc.Close()
}
initrdPath = initrd.Name()
}
sec := sections["initrd"]
sec.file = &initrdPath
sections["initrd"] = sec
// Execute objcopy
var args []string
for name, c := range sections {
if *c.file != "" {
args = append(args, []string{
"--add-section", fmt.Sprintf(".%s=%s", name, *c.file),
"--change-section-vma", fmt.Sprintf(".%s=%s", name, c.vma),
}...)
}
}
args = append(args, []string{
*stub,
*output,
}...)
cmd := exec.Command(*objcopy, args...)
cmd.Stderr = os.Stderr
cmd.Stdout = os.Stdout
err := cmd.Run()
if err == nil {
return
}
// Exit with objcopy's return code.
if e, ok := err.(*exec.ExitError); ok {
os.Exit(e.ExitCode())
}
log.Fatalf("Could not start command: %v", err)
}