treewide: use architecture-specific UEFI boot path
The default UEFI boot path depends on the architecture. This change is
needed for aarch64 support.
Change-Id: I69916ac5063e963b67ecdcdd814b0678c61da9fb
Reviewed-on: https://review.monogon.dev/c/monogon/+/4195
Reviewed-by: Tim Windelschmidt <tim@monogon.tech>
Tested-by: Jenkins CI
diff --git a/cloud/agent/install.go b/cloud/agent/install.go
index 2ba9859..e212d9a 100644
--- a/cloud/agent/install.go
+++ b/cloud/agent/install.go
@@ -112,6 +112,7 @@
System: 4096,
Data: 128,
},
+ Architecture: osImage.Config.ProductInfo.Architecture(),
SystemImage: systemImage,
EFIPayload: efiPayload,
ABLoader: structfs.Bytes(abloader),
diff --git a/metropolis/cli/metroctl/core/BUILD.bazel b/metropolis/cli/metroctl/core/BUILD.bazel
index cd95ac1..d785fad 100644
--- a/metropolis/cli/metroctl/core/BUILD.bazel
+++ b/metropolis/cli/metroctl/core/BUILD.bazel
@@ -19,9 +19,11 @@
"//metropolis/node/core/rpc/resolver",
"//metropolis/proto/api",
"//osbase/blockdev",
+ "//osbase/build/mkimage/osimage",
"//osbase/fat32",
"//osbase/gpt",
"//osbase/oci",
+ "//osbase/oci/osimage",
"//osbase/structfs",
"@io_k8s_client_go//pkg/apis/clientauthentication/v1:clientauthentication",
"@io_k8s_client_go//tools/clientcmd",
diff --git a/metropolis/cli/metroctl/core/install.go b/metropolis/cli/metroctl/core/install.go
index 92e8303..0927ba1 100644
--- a/metropolis/cli/metroctl/core/install.go
+++ b/metropolis/cli/metroctl/core/install.go
@@ -13,9 +13,11 @@
"source.monogon.dev/metropolis/proto/api"
"source.monogon.dev/osbase/blockdev"
+ "source.monogon.dev/osbase/build/mkimage/osimage"
"source.monogon.dev/osbase/fat32"
"source.monogon.dev/osbase/gpt"
"source.monogon.dev/osbase/oci"
+ ociosimage "source.monogon.dev/osbase/oci/osimage"
"source.monogon.dev/osbase/structfs"
)
@@ -29,7 +31,7 @@
// Optional NodeParameters to be embedded for use by the installer.
NodeParams *api.NodeParameters
- // Optional OS image for use by the installer.
+ // OS image for use by the installer.
Image *oci.Image
}
@@ -41,12 +43,18 @@
return errors.New("installer is mandatory")
}
+ osImage, err := ociosimage.Read(args.Image)
+ if err != nil {
+ return fmt.Errorf("failed to read OS image: %w", err)
+ }
+ bootPath, err := osimage.EFIBootPath(osImage.Config.ProductInfo.Architecture())
+ if err != nil {
+ return err
+ }
+
var espRoot structfs.Tree
- // This needs to be a "Removable Media" according to the UEFI Specification
- // V2.9 Section 3.5.1.1. This file is booted by any compliant UEFI firmware
- // in absence of another bootable boot entry.
- if err := espRoot.PlaceFile("EFI/BOOT/BOOTx64.EFI", args.Installer); err != nil {
+ if err := espRoot.PlaceFile(bootPath, args.Installer); err != nil {
return err
}
@@ -59,17 +67,14 @@
return err
}
}
- if args.Image != nil {
- imageLayout, err := oci.CreateLayout(args.Image)
- if err != nil {
- return err
- }
- if err := espRoot.PlaceDir("metropolis-installer/osimage", imageLayout); err != nil {
- return err
- }
+ imageLayout, err := oci.CreateLayout(args.Image)
+ if err != nil {
+ return err
+ }
+ if err := espRoot.PlaceDir("metropolis-installer/osimage", imageLayout); err != nil {
+ return err
}
var targetDev blockdev.BlockDev
- var err error
targetDev, err = blockdev.Open(args.TargetPath)
if err != nil {
if errors.Is(err, os.ErrNotExist) {
diff --git a/metropolis/cli/takeover/install.go b/metropolis/cli/takeover/install.go
index 1bd00e6..37068ed 100644
--- a/metropolis/cli/takeover/install.go
+++ b/metropolis/cli/takeover/install.go
@@ -88,6 +88,7 @@
System: 4096,
Data: 128,
},
+ Architecture: osImage.Config.ProductInfo.Architecture(),
SystemImage: systemImage,
EFIPayload: efiPayload,
ABLoader: structfs.Bytes(abloader),
diff --git a/metropolis/installer/main.go b/metropolis/installer/main.go
index baf9cc8..20a8240 100644
--- a/metropolis/installer/main.go
+++ b/metropolis/installer/main.go
@@ -186,6 +186,7 @@
// whenever it's writing to block devices, such as now.
Data: 128,
},
+ Architecture: osImage.Config.ProductInfo.Architecture(),
SystemImage: systemImage,
EFIPayload: efiPayload,
ABLoader: structfs.Bytes(abloader),
diff --git a/metropolis/installer/test/BUILD.bazel b/metropolis/installer/test/BUILD.bazel
index 903f46e..d68f78a 100644
--- a/metropolis/installer/test/BUILD.bazel
+++ b/metropolis/installer/test/BUILD.bazel
@@ -25,6 +25,7 @@
"//osbase/build/mkimage/osimage",
"//osbase/cmd",
"//osbase/oci",
+ "//osbase/oci/osimage",
"//osbase/structfs",
"@com_github_diskfs_go_diskfs//:go-diskfs",
"@com_github_diskfs_go_diskfs//disk",
diff --git a/metropolis/installer/test/run_test.go b/metropolis/installer/test/run_test.go
index 5008be0..bba323f 100644
--- a/metropolis/installer/test/run_test.go
+++ b/metropolis/installer/test/run_test.go
@@ -27,6 +27,7 @@
"source.monogon.dev/osbase/build/mkimage/osimage"
"source.monogon.dev/osbase/cmd"
"source.monogon.dev/osbase/oci"
+ ociosimage "source.monogon.dev/osbase/oci/osimage"
"source.monogon.dev/osbase/structfs"
)
@@ -59,6 +60,7 @@
// installerImage is a filesystem path pointing at the installer image that
// is generated during the test, and is removed afterwards.
installerImage string
+ bootPath string
)
// runQemu starts a new QEMU process, expecting the given output to appear
@@ -124,9 +126,9 @@
return fmt.Errorf("couldn't read the installer ESP: %w", err)
}
// Make sure the EFI payload exists by attempting to open it.
- efiPayload, err := fs.OpenFile("/"+osimage.EFIPayloadPath, os.O_RDONLY)
+ efiPayload, err := fs.OpenFile("/"+bootPath, os.O_RDONLY)
if err != nil {
- return fmt.Errorf("couldn't open the installer's EFI Payload at %q: %w", osimage.EFIPayloadPath, err)
+ return fmt.Errorf("couldn't open the installer's EFI Payload at %q: %w", bootPath, err)
}
efiPayload.Close()
return nil
@@ -145,6 +147,15 @@
log.Fatal(err)
}
+ osImage, err := ociosimage.Read(image)
+ if err != nil {
+ log.Fatal(err)
+ }
+ bootPath, err = osimage.EFIBootPath(osImage.Config.ProductInfo.Architecture())
+ if err != nil {
+ log.Fatal(err)
+ }
+
iargs := mctl.MakeInstallerImageArgs{
Installer: installer,
TargetPath: installerImage,
diff --git a/metropolis/node/core/update/e2e/BUILD.bazel b/metropolis/node/core/update/e2e/BUILD.bazel
index a5dcc5e..19738eb 100644
--- a/metropolis/node/core/update/e2e/BUILD.bazel
+++ b/metropolis/node/core/update/e2e/BUILD.bazel
@@ -28,6 +28,7 @@
"//osbase/blockdev",
"//osbase/build/mkimage/osimage",
"//osbase/oci",
+ "//osbase/oci/osimage",
"//osbase/oci/registry",
"//osbase/structfs",
"@io_bazel_rules_go//go/runfiles",
diff --git a/metropolis/node/core/update/e2e/e2e_test.go b/metropolis/node/core/update/e2e/e2e_test.go
index ec51281..8bc2bb9 100644
--- a/metropolis/node/core/update/e2e/e2e_test.go
+++ b/metropolis/node/core/update/e2e/e2e_test.go
@@ -23,6 +23,7 @@
"source.monogon.dev/osbase/blockdev"
"source.monogon.dev/osbase/build/mkimage/osimage"
"source.monogon.dev/osbase/oci"
+ ociosimage "source.monogon.dev/osbase/oci/osimage"
"source.monogon.dev/osbase/oci/registry"
"source.monogon.dev/osbase/structfs"
)
@@ -149,6 +150,11 @@
t.Fatal(err)
}
+ osImageY, err := ociosimage.Read(imageY)
+ if err != nil {
+ t.Fatal(err)
+ }
+
registryServer := registry.NewServer()
registryServer.AddImage("testos", "y", imageY)
registryServer.AddImage("testos", "z", imageZ)
@@ -184,10 +190,11 @@
}
if _, err := osimage.Write(&osimage.Params{
- Output: rootDisk,
- ABLoader: loader,
- EFIPayload: boot,
- SystemImage: system,
+ Output: rootDisk,
+ Architecture: osImageY.Config.ProductInfo.Architecture(),
+ ABLoader: loader,
+ EFIPayload: boot,
+ SystemImage: system,
PartitionSize: osimage.PartitionSizeInfo{
ESP: 128,
System: 256,
diff --git a/metropolis/node/core/update/update.go b/metropolis/node/core/update/update.go
index d620677..ee53f90 100644
--- a/metropolis/node/core/update/update.go
+++ b/metropolis/node/core/update/update.go
@@ -428,7 +428,12 @@
var abloader []byte
func (s *Service) fixupPreloader() error {
- abLoaderFile, err := os.Open(filepath.Join(s.ESPPath, osimage.EFIPayloadPath))
+ efiBootPath, err := osimage.EFIBootPath(productinfo.Get().Info.Architecture())
+ if err != nil {
+ return err
+ }
+ efiBootFilePath := filepath.Join(s.ESPPath, efiBootPath)
+ abLoaderFile, err := os.Open(efiBootFilePath)
if err != nil {
s.Logger.Warningf("A/B preloader not available, attempting to restore: %v", err)
} else {
@@ -458,7 +463,7 @@
return fmt.Errorf("while sync'ing preloader swap file: %w", err)
}
preloader.Close()
- if err := os.Rename(filepath.Join(s.ESPPath, "preloader.swp"), filepath.Join(s.ESPPath, osimage.EFIPayloadPath)); err != nil {
+ if err := os.Rename(filepath.Join(s.ESPPath, "preloader.swp"), efiBootFilePath); err != nil {
return fmt.Errorf("while swapping preloader: %w", err)
}
s.Logger.Info("Successfully wrote current preloader")
@@ -468,6 +473,11 @@
// fixupEFI checks for the existence and correctness of the EFI boot entry
// repairs/recreates it if needed.
func (s *Service) fixupEFI() error {
+ efiBootPath, err := osimage.EFIBootPath(productinfo.Get().Info.Architecture())
+ if err != nil {
+ return err
+ }
+ efiBootVarPath := "/" + efiBootPath
varNames, err := efivarfs.List(efivarfs.ScopeGlobal)
if err != nil {
return fmt.Errorf("failed to list EFI variables: %w", err)
@@ -504,7 +514,7 @@
}
switch p := e.FilePath[1].(type) {
case efivarfs.FilePath:
- if string(p) == "/"+osimage.EFIPayloadPath {
+ if string(p) == efiBootVarPath {
if validBootEntryIdx == -1 {
validBootEntryIdx = int(idx)
} else {
@@ -540,7 +550,7 @@
PartitionUUID: s.ESPPart.ID,
},
},
- efivarfs.FilePath("/" + osimage.EFIPayloadPath),
+ efivarfs.FilePath(efiBootVarPath),
},
})
if err == nil {
diff --git a/osbase/build/mkimage/def.bzl b/osbase/build/mkimage/def.bzl
index 33323d7..b8239f5 100644
--- a/osbase/build/mkimage/def.bzl
+++ b/osbase/build/mkimage/def.bzl
@@ -3,6 +3,8 @@
arguments = ctx.actions.args()
arguments.add_all([
+ "-architecture",
+ ctx.attr.architecture,
"-efi",
ctx.file.kernel.path,
"-system",
@@ -31,13 +33,14 @@
return [DefaultInfo(files = depset([img_file]), runfiles = ctx.runfiles(files = [img_file]))]
-node_image = rule(
+_node_image = rule(
implementation = _node_image_impl,
doc = """
Build a disk image from an EFI kernel payload, ABLoader and system partition
contents. See //osbase/build/mkimage for more information.
""",
attrs = {
+ "architecture": attr.string(mandatory = True),
"kernel": attr.label(
doc = "EFI binary containing a kernel.",
mandatory = True,
@@ -70,3 +73,18 @@
),
},
)
+
+def _node_image_macro_impl(**kwargs):
+ _node_image(
+ architecture = select({
+ "@platforms//cpu:x86_64": "x86_64",
+ "@platforms//cpu:aarch64": "aarch64",
+ }),
+ **kwargs
+ )
+
+node_image = macro(
+ inherit_attrs = _node_image,
+ attrs = {"architecture": None},
+ implementation = _node_image_macro_impl,
+)
diff --git a/osbase/build/mkimage/main.go b/osbase/build/mkimage/main.go
index bef7517..3e8d740 100644
--- a/osbase/build/mkimage/main.go
+++ b/osbase/build/mkimage/main.go
@@ -42,6 +42,7 @@
flag.StringVar(&biosBootCodePayload, "bios_bootcode", "", "Optional path to the BIOS bootcode which gets placed at the start of the first block of the image. Limited to 440 bytes, padding is not required. It is only used by legacy BIOS boot.")
flag.StringVar(&nodeParams, "node_parameters", "", "Path to Node Parameters to be written to the ESP (default: don't write Node Parameters)")
flag.StringVar(&outputPath, "out", "", "Path to the resulting disk image or block device")
+ flag.StringVar(&cfg.Architecture, "architecture", "", "CPU architecture")
flag.Int64Var(&cfg.PartitionSize.Data, "data_partition_size", 2048, "Override the data partition size (default 2048 MiB). Used only when generating image files.")
flag.Int64Var(&cfg.PartitionSize.ESP, "esp_partition_size", 128, "Override the ESP partition size (default: 128MiB)")
flag.Int64Var(&cfg.PartitionSize.System, "system_partition_size", 1024, "Override the System partition size (default: 1024MiB)")
diff --git a/osbase/build/mkimage/osimage/osimage.go b/osbase/build/mkimage/osimage/osimage.go
index 4edb077..611c2ed 100644
--- a/osbase/build/mkimage/osimage/osimage.go
+++ b/osbase/build/mkimage/osimage/osimage.go
@@ -31,12 +31,27 @@
DataLabel = "METROPOLIS-NODE-DATA"
ESPLabel = "ESP"
- EFIPayloadPath = "EFI/BOOT/BOOTx64.EFI"
EFIBootAPath = "EFI/metropolis/boot-a.efi"
EFIBootBPath = "EFI/metropolis/boot-b.efi"
nodeParamsPath = "metropolis/parameters.pb"
)
+var EFIBootName = map[string]string{
+ "x86_64": "BOOTx64.EFI",
+ "aarch64": "BOOTAA64.EFI",
+}
+
+// EFIBootPath returns the default file path according to the UEFI Specification
+// v2.11 Section 3.5.1.1. This file is booted by any compliant UEFI firmware in
+// absence of another bootable boot entry.
+func EFIBootPath(architecture string) (string, error) {
+ bootName, ok := EFIBootName[architecture]
+ if !ok {
+ return "", fmt.Errorf("unsupported architecture %q", architecture)
+ }
+ return "EFI/BOOT/" + bootName, nil
+}
+
// PartitionSizeInfo contains parameters used during partition table
// initialization and, in case of image files, space allocation.
type PartitionSizeInfo struct {
@@ -58,6 +73,8 @@
type Params struct {
// Output is the block device to which the OS image is written.
Output blockdev.BlockDev
+ // Architecture is the CPU architecture of the OS image.
+ Architecture string
// ABLoader provides the A/B loader which then loads the EFI loader for the
// correct slot.
ABLoader structfs.Blob
@@ -86,6 +103,7 @@
type plan struct {
*Params
+ efiBootPath string
efiRoot structfs.Tree
tbl *gpt.Table
efiPartition *gpt.Partition
@@ -134,7 +152,7 @@
PartitionUUID: i.efiPartition.ID,
},
},
- efivarfs.FilePath("/" + EFIPayloadPath),
+ efivarfs.FilePath("/" + i.efiBootPath),
},
}, nil
}
@@ -165,7 +183,11 @@
return nil, err
}
// Place the A/B loader at the EFI bootloader autodiscovery path.
- if err := params.efiRoot.PlaceFile(EFIPayloadPath, params.ABLoader); err != nil {
+ params.efiBootPath, err = EFIBootPath(p.Architecture)
+ if err != nil {
+ return nil, err
+ }
+ if err := params.efiRoot.PlaceFile(params.efiBootPath, params.ABLoader); err != nil {
return nil, err
}
if params.NodeParameters != nil {