| // Copyright 2026 Google LLC | |
| // | |
| // 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. | |
| package cosboot | |
| import ( | |
| "os" | |
| "os/exec" | |
| "path/filepath" | |
| "testing" | |
| "cos.googlesource.com/cos/tools.git/src/pkg/tools/partutil" | |
| "cos.googlesource.com/cos/tools.git/src/pkg/utils" | |
| ) | |
| func TestDiskBootPath(t *testing.T) { | |
| tests := []struct { | |
| name string | |
| input string | |
| want BootPath | |
| }{ | |
| { | |
| name: "GRUB", | |
| input: "testdata/shimefi.img", | |
| want: GRUBBoot, | |
| }, | |
| { | |
| name: "Kernel", | |
| input: "testdata/kernelefi.img", | |
| want: UKI, | |
| }, | |
| } | |
| for _, test := range tests { | |
| test := test | |
| t.Run(test.name, func(t *testing.T) { | |
| got, err := DiskBootPath(test.input) | |
| if err != nil { | |
| t.Fatalf("DiskBootPath(%q) = %v; want nil", test.input, err) | |
| } | |
| if got != test.want { | |
| t.Errorf("DiskBootPath(%q) = %v; want %v", test.input, got, test.want) | |
| } | |
| }) | |
| } | |
| } | |
| func TestBootEFIPath(t *testing.T) { | |
| // 1. Test standard x86_64 bootloader paths on existing test data | |
| tests := []struct { | |
| name string | |
| input string | |
| want string | |
| }{ | |
| { | |
| name: "GRUB x86_64", | |
| input: "testdata/shimefi.img", | |
| want: "/efi/boot/bootx64.efi", | |
| }, | |
| { | |
| name: "Kernel UKI x86_64", | |
| input: "testdata/kernelefi.img", | |
| want: "/efi/boot/bootx64.efi", | |
| }, | |
| } | |
| for _, test := range tests { | |
| test := test | |
| t.Run(test.name, func(t *testing.T) { | |
| got, err := BootEFIPath(test.input) | |
| if err != nil { | |
| t.Fatalf("BootEFIPath(%q) = %v; want nil", test.input, err) | |
| } | |
| if got != test.want { | |
| t.Errorf("BootEFIPath(%q) = %q; want %q", test.input, got, test.want) | |
| } | |
| }) | |
| } | |
| // 2. Test ARM64 bootloader fallback using partition extraction and patching | |
| t.Run("ARM64 Fallback", func(t *testing.T) { | |
| tmpDir, err := os.MkdirTemp("", "cos-bootefipathtest-") | |
| if err != nil { | |
| t.Fatal(err) | |
| } | |
| defer os.RemoveAll(tmpDir) | |
| copiedImage := filepath.Join(tmpDir, "test_arm64.img") | |
| if err := utils.CopyFile("testdata/shimefi.img", copiedImage); err != nil { | |
| t.Fatal(err) | |
| } | |
| // Find and extract the EFI partition | |
| sfdiskTable, err := partutil.ReadPartitionTableJSON(copiedImage) | |
| if err != nil { | |
| t.Fatal(err) | |
| } | |
| partTable := sfdiskTable.PartitionTable | |
| var partIndex int | |
| var found bool | |
| for i := range partTable.Partitions { | |
| if partTable.Partitions[i].Name == "EFI-SYSTEM" { | |
| partIndex = i | |
| found = true | |
| break | |
| } | |
| } | |
| if !found { | |
| t.Fatal("failed to find EFI partition") | |
| } | |
| efiPartNum := partIndex + 1 | |
| efiData, err := partutil.ReadPartitionData(copiedImage, efiPartNum) | |
| if err != nil { | |
| t.Fatal(err) | |
| } | |
| efiBin := filepath.Join(tmpDir, "efi.bin") | |
| if err := os.WriteFile(efiBin, efiData, 0644); err != nil { | |
| t.Fatal(err) | |
| } | |
| // Delete the x86_64 EFI loader from efi.bin | |
| cmdDel := exec.Command("mdel", "-i", efiBin, "::/efi/boot/bootx64.efi") | |
| if err := cmdDel.Run(); err != nil { | |
| t.Fatalf("failed to delete bootx64.efi: %v", err) | |
| } | |
| // Write modified partition back to disk image | |
| updatedData, err := os.ReadFile(efiBin) | |
| if err != nil { | |
| t.Fatal(err) | |
| } | |
| if err := partutil.WritePartitionData(copiedImage, efiPartNum, updatedData); err != nil { | |
| t.Fatal(err) | |
| } | |
| // Verify it now fails since no EFI loaders are present | |
| _, err = BootEFIPath(copiedImage) | |
| if err == nil { | |
| t.Error("BootEFIPath(copiedImage) succeeded; want error (no bootloader present)") | |
| } | |
| // Write dummy ARM64 loader file to efi.bin | |
| dummyLoader := filepath.Join(tmpDir, "dummy") | |
| if err := os.WriteFile(dummyLoader, []byte("dummy-arm64-loader"), 0644); err != nil { | |
| t.Fatal(err) | |
| } | |
| cmdCopy := exec.Command("mcopy", "-i", efiBin, dummyLoader, "::/efi/boot/bootaa64.efi") | |
| if err := cmdCopy.Run(); err != nil { | |
| t.Fatalf("failed to write bootaa64.efi: %v", err) | |
| } | |
| // Write modified partition back to disk image | |
| updatedData2, err := os.ReadFile(efiBin) | |
| if err != nil { | |
| t.Fatal(err) | |
| } | |
| if err := partutil.WritePartitionData(copiedImage, efiPartNum, updatedData2); err != nil { | |
| t.Fatal(err) | |
| } | |
| // Verify dynamic resolution successfully falls back to bootaa64.efi | |
| got, err := BootEFIPath(copiedImage) | |
| if err != nil { | |
| t.Fatalf("BootEFIPath(copiedImage) = %v; want nil", err) | |
| } | |
| if got != "/efi/boot/bootaa64.efi" { | |
| t.Errorf("BootEFIPath(copiedImage) = %q; want /efi/boot/bootaa64.efi", got) | |
| } | |
| }) | |
| } |