github.com/mvdan/u-root-coreutils@v0.0.0-20230122170626-c2eef2898555/pkg/mount/gpt/gpt_test.go (about)

     1  // Copyright 2017-2018 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 gpt
     9  
    10  import (
    11  	"bytes"
    12  	"encoding/hex"
    13  	"io"
    14  	"reflect"
    15  	"testing"
    16  
    17  	"github.com/google/go-cmp/cmp"
    18  )
    19  
    20  const (
    21  	equalHeaderError = "p.Signature(0x5452415020494646) != b.Signature(0x5452415020494645); p.Revision(65537) != b.Revision(65536); p.HeaderSize(93) != b.HeaderSize(92); p.CurrentLBA(0x2) != b.BackupLBA(0x1); p.FirstLBA(0x23) != b.FirstLBA(0x22); p.LastLBA(0x43cf9f) != b.LastLBA(0x43cf9e); p.DiskGUID({0xbad41e2d 0x93ef 0xb04a 0x856e2e3a6a2d73bf}) != b.DiskGUID({0xbad41e2d 0x93ef 0xb04a 0x846e2e3a6a2d73bf}); p.NPart(127) != b.NPart(128); p.PartSize(127) != b.PartSize(128)"
    22  	equalPartsError  = "Partition 3: p.PartGUID({0xfe3a2a5d 0x4f32 0x41a7 0xb825accc3285a309}) != b.PartGUID({0xfe3a2a5d 0x4f32 0x41a7 0xb725accc3285a309}); Partition 8: p.UniqueGUID({0x513a98ed 0xc43e 0x144a 0x8399a47a7e6ae42c}) != b.UniqueGUID({0x513a98ed 0xc43e 0x144a 0x8398a47a7e6ae42c}); Partition 11: p.FirstLBA(0x3d001) != b.FirstLBA(0x3d000); Partition 21: p.LastLBA(0x1) != b.LastLBA(0x0); Partition 61: p.Name(0x000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000) != b.Name(0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000)"
    23  )
    24  
    25  var (
    26  	header = Header{
    27  		Signature:  Signature,
    28  		Revision:   Revision,
    29  		HeaderSize: HeaderSize,
    30  		CRC:        0x22519292,
    31  		Reserved:   0,
    32  		CurrentLBA: 1,
    33  		BackupLBA:  0x43cfbf,
    34  		FirstLBA:   0x22,
    35  		LastLBA:    0x43cf9e,
    36  		DiskGUID:   GUID{L: 0xbad41e2d, W1: 0x93ef, W2: 0xb04a, B: [8]byte{0x84, 0x6e, 0x2e, 0x3a, 0x6a, 0x2d, 0x73, 0xbf}},
    37  		PartStart:  2,
    38  		NPart:      MaxNPart,
    39  		PartSize:   0x80, // This is not constant, but was used for this chromeos disk.
    40  		PartCRC:    0x8d728e57,
    41  	}
    42  	disk = make([]byte, 0x100000000)
    43  )
    44  
    45  func InstallGPT() {
    46  	for i, d := range block {
    47  		copy(disk[i:], d)
    48  	}
    49  }
    50  
    51  // GPT is GUID Partition Table, so technically, this test name is
    52  // Test Guid Partition Table Table. :-)
    53  func TestGPTTable(t *testing.T) {
    54  	tests := []struct {
    55  		mangle int
    56  		msg    string
    57  	}{
    58  		{-1, ""},
    59  		{0x8, "Primary GPT revision (100ff) is not supported value (10000)"},
    60  		{0x0, "Primary GPT signature invalid (54524150204946ff), needs to be 5452415020494645"},
    61  		{0xf, "Primary GPT HeaderSize (ff00005c) is not supported value (5c)"},
    62  		{0x51, "Primary GPT MaxNPart (ff80) is above maximum of 80"},
    63  		{0x59, "Primary Partition CRC: Header {\n\t\"Signature\": 6075990659671082565,\n\t\"Revision\": 65536,\n\t\"HeaderSize\": 92,\n\t\"CRC\": 575771282,\n\t\"Reserved\": 0,\n\t\"CurrentLBA\": 1,\n\t\"BackupLBA\": 4444095,\n\t\"FirstLBA\": 34,\n\t\"LastLBA\": 4444062,\n\t\"DiskGUID\": {\n\t\t\"L\": 3134463533,\n\t\t\"W1\": 37871,\n\t\t\"W2\": 45130,\n\t\t\"B\": [\n\t\t\t132,\n\t\t\t110,\n\t\t\t46,\n\t\t\t58,\n\t\t\t106,\n\t\t\t45,\n\t\t\t115,\n\t\t\t191\n\t\t]\n\t},\n\t\"PartStart\": 2,\n\t\"NPart\": 128,\n\t\"PartSize\": 128,\n\t\"PartCRC\": 2373123927,\n\t\"Parts\": null\n}, computed checksum is 8d728e57, header has 8d72ff57"},
    64  		{0x10, "Primary Header CRC: computed checksum is 22519292, header has 225192ff"},
    65  	}
    66  
    67  	for _, test := range tests {
    68  		InstallGPT()
    69  		if test.mangle > -1 {
    70  			disk[BlockSize+test.mangle] = 0xff
    71  		}
    72  		r := bytes.NewReader(disk)
    73  		g, err := Table(r, BlockSize)
    74  		if err != nil {
    75  			if err.Error() != test.msg {
    76  				t.Errorf("New GPT: got %q, want %q", err, test.msg)
    77  				continue
    78  			}
    79  			t.Logf("Got expected error %q", err)
    80  			continue
    81  		}
    82  
    83  		if err == nil && test.msg != "" {
    84  			t.Errorf("New GPT: got nil, want %s", test.msg)
    85  			continue
    86  		}
    87  
    88  		if !reflect.DeepEqual(header, g.Header) {
    89  			t.Errorf("Check GUID equality from\n%v to\n%v: got false, want true", header, g.Header)
    90  			continue
    91  		}
    92  	}
    93  }
    94  
    95  // TestGPTTtables tests whether we can match the primary and backup
    96  // or, if they differ, we catch that error.
    97  // We know from other tests that the tables read fine.
    98  // This test verifies that they match and that therefore we
    99  // are able to read the backup table and test that it is ok.
   100  func TestGPTTables(t *testing.T) {
   101  	tests := []struct {
   102  		mangle int
   103  		what   string
   104  	}{
   105  		{-1, "No error test"},
   106  		{0x10, "Should differ test"},
   107  	}
   108  
   109  	for _, test := range tests {
   110  		InstallGPT()
   111  		if test.mangle > -1 {
   112  			disk[BlockSize+test.mangle] = 0xff
   113  		}
   114  		r := bytes.NewReader(disk)
   115  		_, err := New(r)
   116  		switch {
   117  		case err != nil && test.mangle > -1:
   118  			t.Logf("Got expected error %s", test.what)
   119  		case err != nil && test.mangle == -1:
   120  			t.Errorf("%s: got %s, want nil", test.what, err)
   121  			continue
   122  		case err == nil && test.mangle > -1:
   123  			t.Errorf("%s: got nil, want err", test.what)
   124  			continue
   125  		}
   126  		t.Logf("Passed %s", test.what)
   127  	}
   128  }
   129  
   130  // TestEqualHeader tests all variations of headers not being equal.
   131  // We test to make sure it works, then break some aspect of the header
   132  // and test that too.
   133  func TestEqualHeader(t *testing.T) {
   134  	InstallGPT()
   135  	r := bytes.NewReader(disk)
   136  	p, err := New(r)
   137  	if err != nil {
   138  		t.Fatalf("TestEqualHeader: Reading in gpt: got %v, want nil", err)
   139  	}
   140  
   141  	if err := EqualHeader(p.Primary.Header, p.Backup.Header); err != nil {
   142  		t.Fatalf("TestEqualHeader: got %v, want nil", err)
   143  	}
   144  	// Yes, we assume a certain order, but it sure simplifies the test :-)
   145  	p.Primary.Signature++
   146  	p.Primary.Revision++
   147  	p.Primary.HeaderSize++
   148  	p.Primary.CurrentLBA++
   149  	p.Primary.FirstLBA++
   150  	p.Primary.LastLBA++
   151  	p.Primary.DiskGUID.B[0]++
   152  	p.Primary.NPart--
   153  	p.Primary.PartSize--
   154  	p.Primary.PartCRC++
   155  	if err = EqualHeader(p.Primary.Header, p.Backup.Header); err == nil {
   156  		t.Fatalf("TestEqualHeader: got %v, want nil", err)
   157  	}
   158  	t.Logf("TestEqualHeader: EqualHeader returns %v", err)
   159  
   160  	if err.Error() != equalHeaderError {
   161  		t.Fatalf("TestEqualHeader: got %v, want %v", err.Error(), equalHeaderError)
   162  	}
   163  }
   164  
   165  func TestEqualParts(t *testing.T) {
   166  	InstallGPT()
   167  	r := bytes.NewReader(disk)
   168  	p, err := New(r)
   169  	if err != nil {
   170  		t.Fatalf("TestEqualParts: Reading in gpt: got %v, want nil", err)
   171  	}
   172  
   173  	if err = EqualParts(p.Primary, p.Backup); err != nil {
   174  		t.Fatalf("TestEqualParts: Checking equality: got %v, want nil", err)
   175  	}
   176  	// Test some equality things before we do the 'length is the same' test
   177  	// Note that testing the NParts header variable is done in the HeaderTest
   178  	p.Primary.Parts[3].PartGUID.B[0]++
   179  	p.Primary.Parts[8].UniqueGUID.B[1]++
   180  	p.Primary.Parts[11].FirstLBA++
   181  	p.Primary.Parts[21].LastLBA++
   182  	p.Primary.Parts[53].Attribute++
   183  	p.Primary.Parts[61].Name[1]++
   184  	if err = EqualParts(p.Primary, p.Backup); err == nil {
   185  		t.Errorf("TestEqualParts: Checking equality: got nil, want '%v'", equalPartsError)
   186  	}
   187  	if err.Error() != equalPartsError {
   188  		t.Errorf("TestEqualParts: Checking equality: got '%v', want '%v'", err, equalPartsError)
   189  	}
   190  
   191  	if err = EqualParts(p.Primary, p.Backup); err == nil {
   192  		t.Errorf("TestEqualParts: Checking number of parts: got nil, want 'Primary Number of partitions (127) differs from Backup (128)'")
   193  	}
   194  }
   195  
   196  // writeLog is a history of []byte written to the iodisk. Each write to iodisk creates a new writeLog entry.
   197  type writeLog [][]byte
   198  
   199  // iodisk is a fake disk that is used for testing.
   200  // Each write is logged into the `writes` map.
   201  // iodisk implements the WriterAt interface and can be passed to Write() for testing.
   202  type iodisk struct {
   203  	bytes []byte
   204  
   205  	// mapping of address=>writes.
   206  	// This is used for verifying that the correct data was written into the correct locations.
   207  	writes map[int64]writeLog
   208  }
   209  
   210  func newIOdisk(size int) *iodisk {
   211  	return &iodisk{
   212  		bytes:  make([]byte, size),
   213  		writes: make(map[int64]writeLog),
   214  	}
   215  }
   216  
   217  func (d *iodisk) WriteAt(b []byte, offset int64) (int, error) {
   218  	copy([]byte(d.bytes)[offset:], b)
   219  	d.writes[offset] = append(d.writes[offset], b)
   220  	return len(b), nil
   221  }
   222  
   223  func TestWrite(t *testing.T) {
   224  	InstallGPT()
   225  	r := bytes.NewReader(disk)
   226  	p, err := New(r)
   227  	if err != nil {
   228  		t.Fatalf("Reading partitions: got %v, want nil", err)
   229  	}
   230  	targ := newIOdisk(len(disk))
   231  
   232  	if err := Write(targ, p); err != nil {
   233  		t.Fatalf("Writing: got %v, want nil", err)
   234  	}
   235  	if n, err := New(bytes.NewReader([]byte(targ.bytes))); err != nil {
   236  		t.Logf("Old GPT: %s", p.Primary)
   237  		var b bytes.Buffer
   238  		w := hex.Dumper(&b)
   239  		io.Copy(w, bytes.NewReader(disk[:4096]))
   240  		t.Logf("%s\n", b.String())
   241  		t.Fatalf("Reading back new header: new:%s\n%v", n, err)
   242  	}
   243  
   244  	tests := []struct {
   245  		desc   string
   246  		offset int64
   247  		size   int64
   248  	}{
   249  		{
   250  			desc:   "Protective MBR",
   251  			offset: 0x00000000,
   252  			size:   BlockSize,
   253  		},
   254  		{
   255  			desc:   "Primary GPT header",
   256  			offset: 0x00000200,
   257  			size:   BlockSize,
   258  		},
   259  		{
   260  			desc:   "Backup GPT header",
   261  			offset: 0x879f7e00,
   262  			size:   BlockSize,
   263  		},
   264  	}
   265  
   266  	for _, tc := range tests {
   267  		t.Run(tc.desc, func(t *testing.T) {
   268  			// Verify that there was exactly one write.
   269  			if count := len(targ.writes[tc.offset]); count != 1 {
   270  				t.Fatalf("Expected exactly 1 write to address 0x%08x, got %d", tc.offset, count)
   271  			}
   272  			// Verify that the contents were exactly as expected.
   273  			if !cmp.Equal(targ.writes[tc.offset][0], disk[tc.offset:tc.offset+tc.size]) {
   274  				t.Fatalf("Data written to 0x%08x does not match the source data", tc.offset)
   275  			}
   276  		})
   277  	}
   278  }