m/n/b/mkpayload: support multiple initramfs files
Linux supports multiple concatinated initramfs files and requires using
multiple to provide certain data like microcode for the early loader.
This allows building such payloads with mkpayload.
Change-Id: Ie7ee7886bbfe481d7b723e0476a26ee26425a0b6
Reviewed-on: https://review.monogon.dev/c/monogon/+/548
Reviewed-by: Mateusz Zalega <mateusz@monogon.tech>
diff --git a/metropolis/installer/BUILD.bazel b/metropolis/installer/BUILD.bazel
index 834dd9a..61e455e 100644
--- a/metropolis/installer/BUILD.bazel
+++ b/metropolis/installer/BUILD.bazel
@@ -40,7 +40,7 @@
efi_unified_kernel_image(
name = "kernel",
cmdline = "console=ttyS0,115200 console=tty0 quiet",
- initramfs = ":initramfs",
+ initrd = [":initramfs"],
kernel = "//third_party/linux",
os_release = ":installer-release-info",
visibility = ["//visibility:public"],
diff --git a/metropolis/installer/test/BUILD.bazel b/metropolis/installer/test/BUILD.bazel
index 59c5803..c958ac2 100644
--- a/metropolis/installer/test/BUILD.bazel
+++ b/metropolis/installer/test/BUILD.bazel
@@ -34,7 +34,7 @@
efi_unified_kernel_image(
name = "kernel",
cmdline = "loglevel=0 console=ttyS0",
- initramfs = "//metropolis/installer:initramfs",
+ initrd = ["//metropolis/installer:initramfs"],
kernel = "//third_party/linux",
visibility = ["//visibility:private"],
)
diff --git a/metropolis/node/build/efi.bzl b/metropolis/node/build/efi.bzl
index f9d0f44..5f6bfd6 100644
--- a/metropolis/node/build/efi.bzl
+++ b/metropolis/node/build/efi.bzl
@@ -10,7 +10,6 @@
# Find the dependency paths to be passed to mkpayload.
deps = {
"linux": ctx.file.kernel,
- "initrd": ctx.file.initramfs,
"osrel": ctx.file.os_release,
"splash": ctx.file.splash,
"stub": ctx.file.stub,
@@ -39,6 +38,10 @@
args.append("-{}={}".format(name, file.path))
inputs.append(file)
+ for file in ctx.files.initrd:
+ args.append("-initrd={}".format(file.path))
+ inputs.append(file)
+
# Append the output parameter separately, as it doesn't belong with the
# runtime inputs.
image = ctx.actions.declare_file(ctx.attr.name + ".efi")
@@ -73,9 +76,27 @@
"cmdline": attr.string(
doc = "The kernel commandline to be embedded.",
),
- "initramfs": attr.label(
- doc = "The initramfs to be embedded.",
- allow_single_file = True,
+ "initrd": attr.label_list(
+ doc = """
+ List of payloads to concatenate and supply as the initrd parameter to Linux when it boots.
+ The name stems from the time Linux booted from an initial ram disk (initrd), but it's now
+ a catch-all for a bunch of different larger payload for early Linux initialization.
+
+ In Linux 5.15 this can first contain an arbitrary amount of uncompressed cpio archives
+ with directories being optional which is accessed by earlycpio. This is used for both
+ early microcode loading and ACPI table overrides. This can then be followed by an arbitrary
+ amount of compressed cpio archives (even with different compression methods) which will
+ together make up the initramfs. The initramfs is only booted into if it contains either
+ /init or whatever file is specified as init= in cmdline. Technically depending on kernel
+ flags you might be able to supply an actual initrd, i.e. an image of a disk loaded into
+ RAM, but that has been deprecated for nearly 2 decades and should really not be used.
+
+ For kernels designed to run on physical machines this should at least contain microcode,
+ optionally followed by a compressed initramfs. For kernels only used in virtualized
+ setups the microcode can be left out and if no initramfs is needed this option can
+ be omitted completely.
+ """,
+ allow_files = True,
),
"os_release": attr.label(
doc = """
diff --git a/metropolis/node/build/mkpayload/mkpayload.go b/metropolis/node/build/mkpayload/mkpayload.go
index 42ec062..aee3a81 100644
--- a/metropolis/node/build/mkpayload/mkpayload.go
+++ b/metropolis/node/build/mkpayload/mkpayload.go
@@ -22,12 +22,27 @@
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
@@ -46,6 +61,7 @@
"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")
@@ -53,8 +69,12 @@
)
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
}
@@ -113,6 +133,30 @@
*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 {