github.com/mvdan/u-root-coreutils@v0.0.0-20230122170626-c2eef2898555/pkg/boot/linux_test.go (about) 1 // Copyright 2017-2020 the u-root Authors. All rights reserved 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package boot 6 7 import ( 8 "bytes" 9 "fmt" 10 "io" 11 "net/url" 12 "os" 13 "path/filepath" 14 "strings" 15 "testing" 16 17 "github.com/google/go-cmp/cmp" 18 "github.com/mvdan/u-root-coreutils/pkg/boot/linux" 19 "github.com/mvdan/u-root-coreutils/pkg/curl" 20 "github.com/mvdan/u-root-coreutils/pkg/mount" 21 "github.com/mvdan/u-root-coreutils/pkg/uio" 22 "github.com/mvdan/u-root-coreutils/pkg/vfile" 23 "golang.org/x/sys/unix" 24 ) 25 26 func TestLinuxLabel(t *testing.T) { 27 dir := t.TempDir() 28 29 osKernel, err := os.Create(filepath.Join(dir, "kernel")) 30 if err != nil { 31 t.Fatal(err) 32 } 33 34 osInitrd, err := os.Create(filepath.Join(dir, "initrd")) 35 if err != nil { 36 t.Fatal(err) 37 } 38 39 k, _ := url.Parse("http://127.0.0.1/kernel") 40 i1, _ := url.Parse("http://127.0.0.1/initrd1") 41 i2, _ := url.Parse("http://127.0.0.1/initrd2") 42 httpKernel, _ := curl.LazyFetch(k) 43 httpInitrd1, _ := curl.LazyFetch(i1) 44 httpInitrd2, _ := curl.LazyFetch(i2) 45 46 for _, tt := range []struct { 47 desc string 48 img *LinuxImage 49 want string 50 }{ 51 { 52 desc: "os.File", 53 img: &LinuxImage{ 54 Kernel: osKernel, 55 Initrd: osInitrd, 56 }, 57 want: fmt.Sprintf("Linux(kernel=%s/kernel initrd=%s/initrd)", dir, dir), 58 }, 59 { 60 desc: "lazy file", 61 img: &LinuxImage{ 62 Kernel: uio.NewLazyFile(filepath.Join(dir, "kernel")), 63 Initrd: uio.NewLazyFile(filepath.Join(dir, "initrd")), 64 }, 65 want: fmt.Sprintf("Linux(kernel=%s/kernel initrd=%s/initrd)", dir, dir), 66 }, 67 { 68 desc: "concat lazy file", 69 img: &LinuxImage{ 70 Kernel: uio.NewLazyFile(filepath.Join(dir, "kernel")), 71 Initrd: CatInitrds( 72 uio.NewLazyFile(filepath.Join(dir, "initrd")), 73 uio.NewLazyFile(filepath.Join(dir, "initrd")), 74 ), 75 }, 76 want: fmt.Sprintf("Linux(kernel=%s/kernel initrd=%s/initrd,%s/initrd)", dir, dir, dir), 77 }, 78 { 79 desc: "curl lazy file", 80 img: &LinuxImage{ 81 Kernel: httpKernel, 82 Initrd: CatInitrds( 83 httpInitrd1, 84 httpInitrd2, 85 ), 86 }, 87 want: "Linux(kernel=http://127.0.0.1/kernel initrd=http://127.0.0.1/initrd1,http://127.0.0.1/initrd2)", 88 }, 89 { 90 desc: "verified file", 91 img: &LinuxImage{ 92 Kernel: &vfile.File{Reader: nil, FileName: "/boot/foobar"}, 93 Initrd: CatInitrds( 94 &vfile.File{Reader: nil, FileName: "/boot/initrd1"}, 95 &vfile.File{Reader: nil, FileName: "/boot/initrd2"}, 96 ), 97 }, 98 want: "Linux(kernel=/boot/foobar initrd=/boot/initrd1,/boot/initrd2)", 99 }, 100 { 101 desc: "no initrd", 102 img: &LinuxImage{ 103 Kernel: &vfile.File{Reader: nil, FileName: "/boot/foobar"}, 104 Initrd: nil, 105 }, 106 want: "Linux(kernel=/boot/foobar)", 107 }, 108 { 109 desc: "dtb file", 110 img: &LinuxImage{ 111 Kernel: &vfile.File{Reader: nil, FileName: "/boot/foobar"}, 112 Initrd: &vfile.File{Reader: nil, FileName: "/boot/initrd"}, 113 KexecOpts: linux.KexecOptions{ 114 DTB: &vfile.File{Reader: nil, FileName: "/boot/board.dtb"}, 115 }, 116 }, 117 want: "Linux(kernel=/boot/foobar initrd=/boot/initrd dtb=/boot/board.dtb)", 118 }, 119 { 120 desc: "dtb file, no initrd", 121 img: &LinuxImage{ 122 Kernel: &vfile.File{Reader: nil, FileName: "/boot/foobar"}, 123 KexecOpts: linux.KexecOptions{ 124 DTB: &vfile.File{Reader: nil, FileName: "/boot/board.dtb"}, 125 }, 126 }, 127 want: "Linux(kernel=/boot/foobar dtb=/boot/board.dtb)", 128 }, 129 } { 130 t.Run(tt.desc, func(t *testing.T) { 131 got := tt.img.Label() 132 if got != tt.want { 133 t.Errorf("Label() = %s, want %s", got, tt.want) 134 } 135 }) 136 } 137 } 138 139 func TestCopyToFile(t *testing.T) { 140 want := "abcdefg hijklmnop" 141 buf := bytes.NewReader([]byte(want)) 142 143 f, err := copyToFileIfNotRegular(buf, true) 144 if err != nil { 145 t.Fatal(err) 146 } 147 defer os.RemoveAll(f.Name()) 148 got, err := io.ReadAll(f) 149 if err != nil { 150 t.Fatal(err) 151 } 152 if string(got) != want { 153 t.Errorf("got %s, expected %s", string(got), want) 154 } 155 } 156 157 func TestLinuxRank(t *testing.T) { 158 testRank := 2 159 img := &LinuxImage{BootRank: testRank} 160 l := img.Rank() 161 if l != testRank { 162 t.Fatalf("Expected Image rank %d, got %d", testRank, l) 163 } 164 } 165 166 func checkReadOnly(t *testing.T, f *os.File) { 167 t.Helper() 168 wr := unix.O_RDWR | unix.O_WRONLY 169 if am, err := unix.FcntlInt(f.Fd(), unix.F_GETFL, 0); err == nil && am&wr != 0 { 170 t.Errorf("file %v opened for write, want read only", f) 171 } 172 } 173 174 // checkFilePath checks if src and dst file are same file of fsrc were actually a os.File. 175 func checkFilePath(t *testing.T, fsrc io.ReaderAt, fdst *os.File) { 176 t.Helper() 177 if f, ok := fsrc.(*os.File); ok { 178 if r, _ := mount.IsTmpRamfs(f.Name()); r { 179 // Src is a file on tmpfs. 180 if f.Name() != fdst.Name() { 181 t.Errorf("Got a copied file %s, want skipping copy and use original file %s", fdst.Name(), f.Name()) 182 } 183 } 184 } 185 } 186 187 func setupTestFile(t *testing.T, path, content string) *os.File { 188 t.Helper() 189 f, err := os.Create(path) 190 if err != nil { 191 t.Fatal(err) 192 } 193 n, err := f.Write([]byte(content)) 194 if err != nil { 195 t.Fatal(err) 196 } 197 if n != len([]byte(content)) { 198 t.Fatalf("want %d bytes written, but got %d", len([]byte(content)), n) 199 } 200 if err := f.Close(); err != nil { 201 t.Fatalf("could not close test file: %v", err) 202 } 203 nf, err := os.Open(path) 204 if err != nil { 205 t.Fatalf("could not open test file: %v", err) 206 } 207 return nf 208 } 209 210 // GenerateCatDummyInitrd return padded string from the given list of strings following the same padding format of CatInitrds. 211 func GenerateCatDummyInitrd(t *testing.T, initrds ...string) string { 212 var ins []io.ReaderAt 213 for _, c := range initrds { 214 ins = append(ins, strings.NewReader(c)) 215 } 216 final := CatInitrds(ins...) 217 d, err := io.ReadAll(uio.Reader(final)) 218 if err != nil { 219 t.Fatalf("failed to generate concatenated initrd : %v", err) 220 } 221 return string(d) 222 } 223 224 type wantData struct { 225 loadedImage *LoadedLinuxImage 226 cleanup func() 227 err error 228 } 229 230 func TestLoadLinuxImage(t *testing.T) { 231 testDir := t.TempDir() 232 233 for _, tt := range []struct { 234 name string 235 li *LinuxImage 236 want wantData 237 }{ 238 { 239 name: "kernel is nil", 240 li: &LinuxImage{Kernel: nil}, 241 want: wantData{ 242 loadedImage: &LoadedLinuxImage{ 243 Kernel: nil, 244 }, 245 err: errNilKernel, 246 }, 247 }, 248 { 249 name: "basic happy case w/o initrd", 250 li: &LinuxImage{ 251 Kernel: strings.NewReader("testkernel"), 252 }, 253 want: wantData{ 254 loadedImage: &LoadedLinuxImage{ 255 Kernel: setupTestFile(t, filepath.Join(testDir, "basic_happy_case_wo_initrd_bzimage"), "testkernel"), 256 }, 257 err: nil, 258 }, 259 }, 260 { 261 name: "basic happy case w/ initrd", 262 li: &LinuxImage{ 263 Kernel: strings.NewReader("testkernel"), 264 Initrd: strings.NewReader("testinitrd"), 265 }, 266 want: wantData{ 267 loadedImage: &LoadedLinuxImage{ 268 Kernel: setupTestFile(t, filepath.Join(testDir, "basic_happy_case_w_initrd_bzImage"), "testkernel"), 269 Initrd: setupTestFile(t, filepath.Join(testDir, "basic_happy_case_w_initrd_initramfs"), "testinitrd"), 270 }, 271 err: nil, 272 }, 273 }, 274 { 275 name: "empty initrd, with DTB present", // Expect DTB appended to loaded initrd. 276 li: &LinuxImage{ 277 Kernel: strings.NewReader("testkernel"), 278 Initrd: nil, 279 KexecOpts: linux.KexecOptions{ 280 DTB: strings.NewReader("testdtb"), 281 }, 282 }, 283 want: wantData{ 284 loadedImage: &LoadedLinuxImage{ 285 Kernel: setupTestFile(t, filepath.Join(testDir, "empty_inird_w_dtb_present_bzImage"), "testkernel"), 286 Initrd: setupTestFile(t, filepath.Join(testDir, "empty_inird_w_dtb_present_initramfs"), "testdtb"), 287 }, 288 err: nil, 289 }, 290 }, 291 { 292 name: "non-empty initrd, with DTB present", // Expect DTB appended to loaded initrd. 293 li: &LinuxImage{ 294 Kernel: strings.NewReader("testkernel"), 295 Initrd: strings.NewReader("testinitrd"), 296 KexecOpts: linux.KexecOptions{ 297 DTB: strings.NewReader("testdtb"), 298 }, 299 }, 300 want: wantData{ 301 loadedImage: &LoadedLinuxImage{ 302 Kernel: setupTestFile(t, filepath.Join(testDir, "non_empty_inird_w_dtb_present_bzImage"), "testkernel"), 303 Initrd: setupTestFile(t, filepath.Join(testDir, "non_empty_inird_w_dtb_present_initramfs"), GenerateCatDummyInitrd(t, "testinitrd", "testdtb")), 304 }, 305 err: nil, 306 }, 307 }, 308 { 309 name: "oringnal kernel and initrd are files, skip copying", 310 li: &LinuxImage{ 311 Kernel: setupTestFile(t, filepath.Join(testDir, "original_kernel_and_initrd_are_files_skip_copying_bzImage"), "testkernel"), 312 Initrd: setupTestFile(t, filepath.Join(testDir, "original_kernel_and_initrd_are_files_skip_copying_initramfs"), "testinitrd"), 313 }, 314 want: wantData{ 315 loadedImage: &LoadedLinuxImage{ 316 Kernel: setupTestFile(t, filepath.Join(testDir, "original_kernel_and_initrd_are_files_skip_copying_2_bzImage"), "testkernel"), 317 Initrd: setupTestFile(t, filepath.Join(testDir, "original_kernel_and_initrd_are_files_skip_copying_2_initramfs"), "testinitrd"), 318 }, 319 }, 320 }, 321 } { 322 t.Run(tt.name, func(t *testing.T) { 323 gotImage, _, gotErr := loadLinuxImage(tt.li, true) 324 if gotErr != nil { 325 if gotErr != tt.want.err { 326 t.Errorf("got error %v, want %v", gotErr, tt.want.err) 327 } 328 return 329 } 330 // Kernel is opened as read only, and contents match that from original LinuxImage. 331 checkReadOnly(t, gotImage.Kernel) 332 // If src is a read-only *os.File on tmpfs, shoukd skip copying. 333 checkFilePath(t, tt.li.Kernel, gotImage.Kernel) 334 kernelBytes, err := io.ReadAll(gotImage.Kernel) 335 if err != nil { 336 t.Fatalf("could not read kernel from loaded image: %v", err) 337 } 338 wantBytes, err := io.ReadAll(tt.want.loadedImage.Kernel) 339 if err != nil { 340 t.Fatalf("could not read expected kernel: %v", err) 341 } 342 if string(kernelBytes) != string(wantBytes) { 343 t.Errorf("got kernel %s, want %s", string(kernelBytes), string(wantBytes)) 344 } 345 // Initrd, if present, is opened as read only, and contents match that from original LinuxImage. 346 // OR original initrd, with DTB appended. 347 if tt.li.Initrd != nil { 348 checkReadOnly(t, gotImage.Initrd) 349 // If src is a read-only *os.File on tmpfs, should skip copying. 350 checkFilePath(t, tt.li.Initrd, gotImage.Initrd) 351 initrdBytes, err := io.ReadAll(gotImage.Initrd) 352 if err != nil { 353 t.Fatalf("could not read initrd from loaded image: %v", err) 354 } 355 wantInitrdBytes, err := io.ReadAll(tt.want.loadedImage.Initrd) 356 if err != nil { 357 t.Fatalf("could not read expected initrd: %v", err) 358 } 359 // Initrd involves appending, use cmp.Diff for catching the diff, easier to debug. 360 if diff := cmp.Diff(string(initrdBytes), string(wantInitrdBytes)); diff != "" { 361 t.Errorf("got initrd %s, want %s, diff (+got, -want): %s", string(initrdBytes), string(wantInitrdBytes), diff) 362 } 363 } 364 }) 365 } 366 }