github.com/mvdan/u-root-coreutils@v0.0.0-20230122170626-c2eef2898555/pkg/mount/block/vm_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  //go:build !race
     6  // +build !race
     7  
     8  package block
     9  
    10  import (
    11  	"fmt"
    12  	"log"
    13  	"os"
    14  	"reflect"
    15  	"syscall"
    16  	"testing"
    17  
    18  	"github.com/rekby/gpt"
    19  	"github.com/mvdan/u-root-coreutils/pkg/mount"
    20  	"github.com/mvdan/u-root-coreutils/pkg/pci"
    21  	"github.com/mvdan/u-root-coreutils/pkg/qemu"
    22  	"github.com/mvdan/u-root-coreutils/pkg/vmtest"
    23  )
    24  
    25  // VM setup:
    26  //
    27  //  /dev/sda is ./testdata/mbrdisk
    28  //	  /dev/sda1 is ext4
    29  //	  /dev/sda2 is vfat
    30  //	  /dev/sda3 is fat32
    31  //	  /dev/sda4 is xfs
    32  //
    33  //  /dev/sdb is ../testdata/12Kzeros
    34  //	  /dev/sdb1 exists, but is not formatted.
    35  //
    36  //  /dev/sdc and /dev/nvme0n1 are ../testdata/gptdisk
    37  //    /dev/sdc1 and /dev/nvme0n1p1 exist (EFI system partition), but is not formatted
    38  //    /dev/sdc2 and /dev/nvme0n1p2 exist (Linux), but is not formatted
    39  //
    40  //  /dev/sdd is ./testdata/gptdisk_label
    41  //    /dev/sdd1 is ext4 with no GPT partition label
    42  //    /dev/sdd2 is ext4 with GPT partition label "TEST_LABEL"
    43  //
    44  //   ARM tests will load drives as virtio-blk devices (/dev/vd*)
    45  
    46  func TestVM(t *testing.T) {
    47  	o := &vmtest.Options{
    48  		QEMUOpts: qemu.Options{
    49  			Devices: []qemu.Device{
    50  				// CONFIG_ATA_PIIX is required for this option to work.
    51  				qemu.ArbitraryArgs{"-hda", "testdata/mbrdisk"},
    52  				qemu.ArbitraryArgs{"-hdb", "../testdata/12Kzeros"},
    53  				qemu.ArbitraryArgs{"-hdc", "../testdata/gptdisk"},
    54  				qemu.ArbitraryArgs{"-hdd", "testdata/gptdisk_label"},
    55  				qemu.ArbitraryArgs{"-drive", "file=../testdata/gptdisk2,if=none,id=NVME1"},
    56  				// use-intel-id uses the vendor=0x8086 and device=0x5845 ids for NVME
    57  				qemu.ArbitraryArgs{"-device", "nvme,drive=NVME1,serial=nvme-1,use-intel-id"},
    58  
    59  				// With NVMe devices enabled, kernel crashes when not using q35 machine model.
    60  				qemu.ArbitraryArgs{"-machine", "q35"},
    61  			},
    62  		},
    63  	}
    64  	vmtest.GolangTest(t, []string{"github.com/mvdan/u-root-coreutils/pkg/mount/block"}, o)
    65  }
    66  
    67  func TestBlockDevMount(t *testing.T) {
    68  	if os.Getuid() != 0 {
    69  		t.Skip("Skipping since we are not root")
    70  	}
    71  
    72  	prefix := getDevicePrefix()
    73  	devs := testDevs(t)
    74  	devs = devs.FilterName("sdd1")
    75  	want := BlockDevices{
    76  		&BlockDev{Name: prefix + "d1", FsUUID: "02175989-d49f-4e8e-836e-99300af66fc1"},
    77  	}
    78  	if !reflect.DeepEqual(devs, want) {
    79  		t.Fatalf("Test block devices: \n\t%v \nwant: \n\t%v", devs, want)
    80  	}
    81  
    82  	mountPath := t.TempDir()
    83  
    84  	// Only testing for the right calls to the mount pkg here.
    85  	// Mounting itself is out of scope here and covered in pkg mount.
    86  
    87  	dev := devs[0] // FSType unset
    88  	mp, err := dev.Mount(mountPath, mount.ReadOnly)
    89  	if err != nil {
    90  		t.Errorf("%s.Mount() = _,%v \nunexpected error", dev.Name, err)
    91  	}
    92  	if err := mp.Unmount(0); err != nil {
    93  		t.Fatal(err)
    94  	}
    95  
    96  	dev.FSType = "ext4" // FSType set
    97  	mp, err = dev.Mount(mountPath, mount.ReadOnly)
    98  	if err != nil {
    99  		t.Errorf("%s.Mount() = _,%v \nunexpected error", dev.Name, err)
   100  	}
   101  	if err := mp.Unmount(0); err != nil {
   102  		t.Fatal(err)
   103  	}
   104  }
   105  
   106  func TestBlockDevGPTTable(t *testing.T) {
   107  	if os.Getuid() != 0 {
   108  		t.Skip("Skipping since we are not root")
   109  	}
   110  
   111  	devpath := fmt.Sprintf("/dev/%sc", getDevicePrefix())
   112  
   113  	sdcDisk, err := Device(devpath)
   114  	if err != nil {
   115  		t.Fatal(err)
   116  	}
   117  
   118  	parts, err := sdcDisk.GPTTable()
   119  	if err != nil {
   120  		t.Fatal(err)
   121  	}
   122  	wantParts := []gpt.Partition{
   123  		{FirstLBA: 34, LastLBA: 200},
   124  		{FirstLBA: 201, LastLBA: 366},
   125  	}
   126  	for i, p := range parts.Partitions {
   127  		if !p.IsEmpty() {
   128  			want := wantParts[i]
   129  			if p.FirstLBA != want.FirstLBA {
   130  				t.Errorf("partition %d: got FirstLBA %d want %d", i, p.FirstLBA, want.FirstLBA)
   131  			}
   132  			if p.LastLBA != want.LastLBA {
   133  				t.Errorf("partition %d: got LastLBA %d want %d", i, p.LastLBA, want.LastLBA)
   134  			}
   135  
   136  			t.Logf("partition: %v", p)
   137  		}
   138  	}
   139  }
   140  
   141  func TestBlockDevSizes(t *testing.T) {
   142  	if os.Getuid() != 0 {
   143  		t.Skip("Skipping since we are not root")
   144  	}
   145  
   146  	prefix := getDevicePrefix()
   147  	devs := testDevs(t)
   148  
   149  	sizes := map[string]uint64{
   150  		"nvme0n1":     50 * 4096,
   151  		"nvme0n1p1":   167 * 512,
   152  		"nvme0n1p2":   166 * 512,
   153  		prefix + "a":  36864 * 512,
   154  		prefix + "a1": 1024 * 512,
   155  		prefix + "a2": 1023 * 512,
   156  		prefix + "a3": 1024 * 512,
   157  		prefix + "a4": 32768 * 512,
   158  		prefix + "b":  24 * 512,
   159  		prefix + "b1": 23 * 512,
   160  		prefix + "c":  50 * 4096,
   161  		prefix + "c1": 167 * 512,
   162  		prefix + "c2": 166 * 512,
   163  		prefix + "d":  512 * 4096,
   164  		prefix + "d1": 1025 * 512,
   165  		prefix + "d2": 990 * 512,
   166  	}
   167  	for _, dev := range devs {
   168  		size, err := dev.Size()
   169  		if err != nil {
   170  			t.Errorf("Size(%s) error: %v", dev, err)
   171  		}
   172  		if size != sizes[dev.Name] {
   173  			t.Errorf("Size(%s) = %v, want %v", dev, size, sizes[dev.Name])
   174  		}
   175  	}
   176  
   177  	wantBlkSize := 512
   178  	for _, dev := range devs {
   179  		size, err := dev.BlockSize()
   180  		if err != nil {
   181  			t.Errorf("BlockSize(%s) = %v", dev, err)
   182  		}
   183  		if size != wantBlkSize {
   184  			t.Errorf("BlockSize(%s) = %d, want %d", dev, size, wantBlkSize)
   185  		}
   186  
   187  		pSize, err := dev.PhysicalBlockSize()
   188  		if err != nil {
   189  			t.Errorf("PhysicalBlockSize(%s) = %v", dev, err)
   190  		}
   191  		if pSize != wantBlkSize {
   192  			t.Errorf("PhysicalBlockSize(%s) = %d, want %d", dev, pSize, wantBlkSize)
   193  		}
   194  
   195  		kSize, err := dev.KernelBlockSize()
   196  		if err != nil {
   197  			t.Errorf("KernelBlockSize(%s) = %v", dev, err)
   198  		}
   199  		if pSize != wantBlkSize {
   200  			t.Errorf("KernelBlockSize(%s) = %d, want %d", dev, kSize, wantBlkSize)
   201  		}
   202  	}
   203  }
   204  
   205  func TestBlockDevReadPartitionTable(t *testing.T) {
   206  	if os.Getuid() != 0 {
   207  		t.Skip("Skipping since we are not root")
   208  	}
   209  
   210  	prefix := getDevicePrefix()
   211  	devs := testDevs(t)
   212  
   213  	wantRR := map[string]error{
   214  		"nvme0n1":     nil,
   215  		"nvme0n1p1":   syscall.EINVAL,
   216  		"nvme0n1p2":   syscall.EINVAL,
   217  		prefix + "a":  nil,
   218  		prefix + "a1": syscall.EINVAL,
   219  		prefix + "a2": syscall.EINVAL,
   220  		prefix + "a3": syscall.EINVAL,
   221  		prefix + "a4": syscall.EINVAL,
   222  		prefix + "b":  nil,
   223  		prefix + "b1": syscall.EINVAL,
   224  		prefix + "c":  nil,
   225  		prefix + "c1": syscall.EINVAL,
   226  		prefix + "c2": syscall.EINVAL,
   227  		prefix + "d":  nil,
   228  		prefix + "d1": syscall.EINVAL,
   229  		prefix + "d2": syscall.EINVAL,
   230  	}
   231  	for _, dev := range devs {
   232  		if err := dev.ReadPartitionTable(); err != wantRR[dev.Name] {
   233  			t.Errorf("ReadPartitionTable(%s) : \n\t%v \nwant: \n\t%v", dev, err, wantRR[dev.Name])
   234  		}
   235  	}
   236  }
   237  
   238  func TestBlockDevicesFilterHavingPartitions(t *testing.T) {
   239  	if os.Getuid() != 0 {
   240  		t.Skip("Skipping since we are not root")
   241  	}
   242  
   243  	prefix := getDevicePrefix()
   244  	devs := testDevs(t)
   245  
   246  	devs = devs.FilterHavingPartitions([]int{1, 2})
   247  
   248  	want := BlockDevices{
   249  		&BlockDev{Name: "nvme0n1"},
   250  		&BlockDev{Name: prefix + "a"},
   251  		&BlockDev{Name: prefix + "c"},
   252  		&BlockDev{Name: prefix + "d"},
   253  	}
   254  	if !reflect.DeepEqual(devs, want) {
   255  		t.Fatalf("Filtered block devices: \n\t%v \nwant: \n\t%v", devs, want)
   256  	}
   257  }
   258  
   259  func TestBlockDevicesFilterPartID(t *testing.T) {
   260  	if os.Getuid() != 0 {
   261  		t.Skip("Skipping since we are not root")
   262  	}
   263  
   264  	devname := fmt.Sprintf("%sc2", getDevicePrefix())
   265  	devs := testDevs(t)
   266  
   267  	for _, tt := range []struct {
   268  		guid string
   269  		want BlockDevices
   270  	}{
   271  		{
   272  			guid: "C9865081-266C-4A23-A948-C03DAB506198",
   273  			want: BlockDevices{
   274  				&BlockDev{Name: "nvme0n1p2"},
   275  				&BlockDev{Name: devname},
   276  			},
   277  		},
   278  		{
   279  			guid: "c9865081-266c-4a23-a948-c03dab506198",
   280  			want: BlockDevices{
   281  				&BlockDev{Name: "nvme0n1p2"},
   282  				&BlockDev{Name: devname},
   283  			},
   284  		},
   285  		{
   286  			guid: "",
   287  			want: nil,
   288  		},
   289  	} {
   290  		parts := devs.FilterPartID(tt.guid)
   291  		if !reflect.DeepEqual(parts, tt.want) {
   292  			t.Errorf("FilterPartID(%s) : \n\t%v \nwant: \n\t%v", tt.guid, parts, tt.want)
   293  		}
   294  	}
   295  }
   296  
   297  func TestBlockDevicesFilterPartType(t *testing.T) {
   298  	if os.Getuid() != 0 {
   299  		t.Skip("Skipping since we are not root")
   300  	}
   301  
   302  	prefix := getDevicePrefix()
   303  	devs := testDevs(t)
   304  
   305  	for _, tt := range []struct {
   306  		guid string
   307  		want BlockDevices
   308  	}{
   309  		{
   310  			// EFI system partition.
   311  			guid: "C12A7328-F81F-11D2-BA4B-00A0C93EC93B",
   312  			want: BlockDevices{
   313  				&BlockDev{Name: "nvme0n1p1"},
   314  				&BlockDev{Name: prefix + "c1"},
   315  			},
   316  		},
   317  		{
   318  			// EFI system partition. mixed case.
   319  			guid: "c12a7328-f81F-11D2-BA4B-00A0C93ec93B",
   320  			want: BlockDevices{
   321  				&BlockDev{Name: "nvme0n1p1"},
   322  				&BlockDev{Name: prefix + "c1"},
   323  			},
   324  		},
   325  		{
   326  			// This is some random Linux GUID.
   327  			guid: "0FC63DAF-8483-4772-8E79-3D69D8477DE4",
   328  			want: BlockDevices{
   329  				&BlockDev{Name: "nvme0n1p2"},
   330  				&BlockDev{Name: prefix + "c2"},
   331  				&BlockDev{Name: prefix + "d1", FsUUID: "02175989-d49f-4e8e-836e-99300af66fc1"},
   332  				&BlockDev{Name: prefix + "d2", FsUUID: "f3323a7f-a90a-4342-9508-d042afed287d"},
   333  			},
   334  		},
   335  	} {
   336  		parts := devs.FilterPartType(tt.guid)
   337  		if !reflect.DeepEqual(parts, tt.want) {
   338  			t.Errorf("FilterPartType(%s) : \n\t%v \nwant: \n\t%v", tt.guid, parts, tt.want)
   339  		}
   340  	}
   341  }
   342  
   343  func TestBlockDevicesFilterPartLabel(t *testing.T) {
   344  	if os.Getuid() != 0 {
   345  		t.Skip("Skipping since we are not root")
   346  	}
   347  
   348  	prefix := getDevicePrefix()
   349  	devs := testDevs(t)
   350  
   351  	label := "TEST_LABEL"
   352  	want := BlockDevices{
   353  		&BlockDev{Name: prefix + "d2", FsUUID: "f3323a7f-a90a-4342-9508-d042afed287d"},
   354  	}
   355  
   356  	parts := devs.FilterPartLabel(label)
   357  	if !reflect.DeepEqual(parts, want) {
   358  		t.Errorf("FilterPartLabel(%s) : \n\t%v \nwant: \n\t%v", label, parts, want)
   359  	}
   360  }
   361  
   362  func TestBlockDevicesFilterBlockPCIString(t *testing.T) {
   363  	if os.Getuid() != 0 {
   364  		t.Skip("Skipping since we are not root")
   365  	}
   366  
   367  	prefix := getDevicePrefix()
   368  	devs := testDevs(t)
   369  
   370  	Debug = log.Printf
   371  	devs, err := devs.FilterBlockPCIString("0x8086:0x5845")
   372  	if err != nil {
   373  		t.Fatal(err)
   374  	}
   375  
   376  	want := BlockDevices{
   377  		&BlockDev{Name: prefix + "a"},
   378  		&BlockDev{Name: prefix + "a1", FsUUID: "2183ead8-a510-4b3d-9777-19c7090f66d9"},
   379  		&BlockDev{Name: prefix + "a2", FsUUID: "ace5-5144"},
   380  		&BlockDev{Name: prefix + "a3", FsUUID: "a896-d7b8"},
   381  		&BlockDev{Name: prefix + "a4", FsUUID: "dca5f234-726b-47e2-b16e-07d3dbde7d8c"},
   382  		&BlockDev{Name: prefix + "b"},
   383  		&BlockDev{Name: prefix + "b1"},
   384  		&BlockDev{Name: prefix + "c"},
   385  		&BlockDev{Name: prefix + "c1"},
   386  		&BlockDev{Name: prefix + "c2"},
   387  		&BlockDev{Name: prefix + "d"},
   388  		&BlockDev{Name: prefix + "d1", FsUUID: "02175989-d49f-4e8e-836e-99300af66fc1"},
   389  		&BlockDev{Name: prefix + "d2", FsUUID: "f3323a7f-a90a-4342-9508-d042afed287d"},
   390  	}
   391  	if !reflect.DeepEqual(devs, want) {
   392  		t.Fatalf("Filtered block devices: \n\t%v \nwant: \n\t%v", devs, want)
   393  	}
   394  }
   395  
   396  func TestBlockDevicesFilterBlockPCI(t *testing.T) {
   397  	if os.Getuid() != 0 {
   398  		t.Skip("Skipping since we are not root")
   399  	}
   400  
   401  	prefix := getDevicePrefix()
   402  	devs := testDevs(t)
   403  
   404  	p := &pci.PCI{Vendor: 0x8086, Device: 0x5845}
   405  	pl := pci.Devices{p}
   406  
   407  	Debug = log.Printf
   408  	devs = devs.FilterBlockPCI(pl)
   409  
   410  	want := BlockDevices{
   411  		&BlockDev{Name: prefix + "a"},
   412  		&BlockDev{Name: prefix + "a1", FsUUID: "2183ead8-a510-4b3d-9777-19c7090f66d9"},
   413  		&BlockDev{Name: prefix + "a2", FsUUID: "ace5-5144"},
   414  		&BlockDev{Name: prefix + "a3", FsUUID: "a896-d7b8"},
   415  		&BlockDev{Name: prefix + "a4", FsUUID: "dca5f234-726b-47e2-b16e-07d3dbde7d8c"},
   416  		&BlockDev{Name: prefix + "b"},
   417  		&BlockDev{Name: prefix + "b1"},
   418  		&BlockDev{Name: prefix + "c"},
   419  		&BlockDev{Name: prefix + "c1"},
   420  		&BlockDev{Name: prefix + "c2"},
   421  		&BlockDev{Name: prefix + "d"},
   422  		&BlockDev{Name: prefix + "d1", FsUUID: "02175989-d49f-4e8e-836e-99300af66fc1"},
   423  		&BlockDev{Name: prefix + "d2", FsUUID: "f3323a7f-a90a-4342-9508-d042afed287d"},
   424  	}
   425  	if !reflect.DeepEqual(devs, want) {
   426  		t.Fatalf("Filtered block devices: \n\t%v \nwant: \n\t%v", devs, want)
   427  	}
   428  }
   429  
   430  func getDevicePrefix() string {
   431  	if _, err := os.Stat("/dev/sdc"); err != nil {
   432  		return "vd"
   433  	}
   434  	return "sd"
   435  }
   436  
   437  func testDevs(t *testing.T) BlockDevices {
   438  	t.Helper()
   439  
   440  	prefix := getDevicePrefix()
   441  
   442  	devs, err := GetBlockDevices()
   443  	if err != nil {
   444  		t.Fatal(err)
   445  	}
   446  	devs = devs.FilterZeroSize()
   447  
   448  	want := BlockDevices{
   449  		&BlockDev{Name: "nvme0n1"},
   450  		&BlockDev{Name: "nvme0n1p1"},
   451  		&BlockDev{Name: "nvme0n1p2"},
   452  		&BlockDev{Name: prefix + "a"},
   453  		&BlockDev{Name: prefix + "a1", FsUUID: "2183ead8-a510-4b3d-9777-19c7090f66d9"},
   454  		&BlockDev{Name: prefix + "a2", FsUUID: "ace5-5144"},
   455  		&BlockDev{Name: prefix + "a3", FsUUID: "a896-d7b8"},
   456  		&BlockDev{Name: prefix + "a4", FsUUID: "dca5f234-726b-47e2-b16e-07d3dbde7d8c"},
   457  		&BlockDev{Name: prefix + "b"},
   458  		&BlockDev{Name: prefix + "b1"},
   459  		&BlockDev{Name: prefix + "c"},
   460  		&BlockDev{Name: prefix + "c1"},
   461  		&BlockDev{Name: prefix + "c2"},
   462  		&BlockDev{Name: prefix + "d"},
   463  		&BlockDev{Name: prefix + "d1", FsUUID: "02175989-d49f-4e8e-836e-99300af66fc1"},
   464  		&BlockDev{Name: prefix + "d2", FsUUID: "f3323a7f-a90a-4342-9508-d042afed287d"},
   465  	}
   466  	if !reflect.DeepEqual(devs, want) {
   467  		t.Fatalf("Test block devices: \n\t%v \nwant: \n\t%v", devs, want)
   468  	}
   469  
   470  	return devs
   471  }