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  }