blob: 2372928e47cc98dba0f1a82dd373a94d111534e0 [file] [log] [blame]
// Copyright 2017 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package gpt
import (
"bytes"
"encoding/binary"
"os"
"reflect"
"testing"
"unicode/utf16"
"thinfs/mbr"
)
func TestReadHeader(t *testing.T) {
h, err := ReadHeader(bytes.NewReader(exampleDisk()[512:]))
if err != nil {
t.Fatal(err)
}
assertStructEqual(t, h, exampleGPT().Primary.Header)
}
func TestReadPartitionEntry(t *testing.T) {
p, err := ReadPartitionEntry(bytes.NewReader(exampleDisk()[1024:]))
if err != nil {
t.Fatal(err)
}
assertStructEqual(t, p, exampleGPT().Primary.Partitions[0])
}
func TestReadGPT(t *testing.T) {
g, err := ReadGPT(bytes.NewReader(exampleDisk()), 512, exampleDiskSize)
if err != nil {
t.Fatal(err)
}
eg := exampleGPT()
assertStructEqual(t, g.MBR, eg.MBR)
assertStructEqual(t, g.Primary.Header, eg.Primary.Header)
for i, pe := range eg.Primary.Partitions {
assertStructEqual(t, g.Primary.Partitions[i], pe)
}
assertStructEqual(t, g.Backup.Header, eg.Backup.Header)
for i, pe := range eg.Backup.Partitions {
assertStructEqual(t, g.Backup.Partitions[i], pe)
}
}
func TestGPTWrite(t *testing.T) {
var b bytes.Buffer
eg := exampleGPT()
eg.Update(512, 4096, 0, exampleDiskSize)
if _, err := eg.WriteTo(&b); err != nil {
t.Fatal(err)
}
got := b.Bytes()
want := exampleDisk()
if !bytes.Equal(got, want) {
t.Errorf("got\n%x\nwant\n%x", got, want)
}
}
func TestUpdate(t *testing.T) {
var (
logicalBlockSize uint64 = 512
physicalBlockSize uint64 = 4096
optimalTransferLengthGranularity uint64
)
eg := exampleGPT()
g := GPT{
Primary: PartitionTable{
Header: Header{
DiskGUID: eg.Primary.DiskGUID,
},
Partitions: []PartitionEntry{
eg.Primary.Partitions[0],
},
},
}
alignment := physicalBlockSize / logicalBlockSize
if err := g.Update(logicalBlockSize, physicalBlockSize, optimalTransferLengthGranularity, exampleDiskSize); err != nil {
t.Fatal(err)
}
t.Run("Primary", func(t *testing.T) {
assertStructEqual(t, g.Primary.Header, eg.Primary.Header)
})
t.Run("Backup", func(t *testing.T) {
assertStructEqual(t, g.Backup.Header, eg.Backup.Header)
})
t.Run("Optimal Transfer Alignment", func(t *testing.T) {
optimalTransferLengthGranularity = 8192
alignment = optimalTransferLengthGranularity / logicalBlockSize
if err := g.Update(logicalBlockSize, physicalBlockSize, optimalTransferLengthGranularity, exampleDiskSize); err != nil {
t.Fatal(err)
}
if got := g.Primary.Partitions[0].StartingLBA % alignment; got != 0 {
t.Errorf("StartingLBA alignment: got %d, want %d", got, 0)
}
})
t.Run("Larger disk size", func(t *testing.T) {
diskSize := exampleDiskSize + logicalBlockSize
if err := g.Update(logicalBlockSize, physicalBlockSize, optimalTransferLengthGranularity, diskSize); err != nil {
t.Fatal(err)
}
if got := g.Primary.AlternateLBA; got != eg.Primary.AlternateLBA+1 {
t.Errorf("AlternateLBA: got %d, want %d", got, eg.Primary.AlternateLBA+1)
}
})
}
func TestValidateGPT(t *testing.T) {
cases := []struct {
name string
gpt GPT
mut func(g *GPT)
err ErrInvalidGPT
}{
{
name: "valid gpt",
mut: func(g *GPT) {},
err: nil,
},
{
name: "bad primary signature",
mut: func(g *GPT) { g.Primary.Signature = Signature{} },
err: ErrInvalidGPT{ErrInvalidSignature},
},
{
name: "bad backup signature",
mut: func(g *GPT) { g.Backup.Signature = Signature{} },
err: ErrInvalidGPT{ErrInvalidSignature},
},
{
name: "bad primary MyLBA",
mut: func(g *GPT) { g.Primary.MyLBA = 0 },
err: ErrInvalidGPT{ErrInvalidAddress},
},
{
name: "bad backup AlternateLBA",
mut: func(g *GPT) { g.Backup.AlternateLBA = 0 },
err: ErrInvalidGPT{ErrInvalidAddress},
},
{
name: "bad primary AlternateLBA",
mut: func(g *GPT) { g.Primary.AlternateLBA = 0 },
err: ErrInvalidGPT{ErrInvalidAddress},
},
{
name: "bad backup MyLBA",
mut: func(g *GPT) { g.Backup.MyLBA = 0 },
err: ErrInvalidGPT{ErrInvalidAddress},
},
{
name: "bad primary CRC",
mut: func(g *GPT) { g.Primary.HeaderCRC32 = 0 },
err: ErrInvalidGPT{ErrHeaderCRC},
},
{
name: "bad backup CRC",
mut: func(g *GPT) { g.Backup.HeaderCRC32 = 0 },
err: ErrInvalidGPT{ErrHeaderCRC},
},
{
name: "bad primary partition array crc",
mut: func(g *GPT) { g.Primary.PartitionEntryArrayCRC32 = 0 },
err: ErrInvalidGPT{ErrHeaderCRC},
},
{
name: "bad backup partition array crc",
mut: func(g *GPT) { g.Backup.PartitionEntryArrayCRC32 = 0 },
err: ErrInvalidGPT{ErrHeaderCRC},
},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
var g = exampleGPT()
c.mut(&g)
e := g.Validate()
if e == nil {
if c.err == nil {
return
}
t.Errorf("got %v, want %v", e, c.err)
}
err := e.(ErrInvalidGPT)
for i, want := range c.err {
if i > len(err)-1 {
t.Errorf("missing expected error %s", want)
continue
}
if got := err[i]; got != want {
t.Errorf("got %#v, want %#v", got, want)
}
}
})
}
}
func TestGUID(t *testing.T) {
want := "C12A7328-F81F-11D2-BA4B-00A0C93EC93B"
g, err := NewGUID(want)
if err != nil {
t.Fatal(err)
}
if got := g.String(); got != want {
t.Errorf("got %q, want %q", got, want)
}
s := "2967380E-134C-4CBB-B6DA-17E7CE1CA45D"
b := []byte{
0x0e, 0x38, 0x67, 0x29,
0x4c, 0x13,
0xbb, 0x4c,
0xb6, 0xda, 0x17, 0xe7, 0xce, 0x1c, 0xa4, 0x5d}
g, err = NewGUID(s)
if err != nil {
t.Fatal(err)
}
var buf bytes.Buffer
binary.Write(&buf, binary.LittleEndian, &g)
if !bytes.Equal(b, buf.Bytes()) {
t.Errorf("got %x, want %x", buf.Bytes(), b)
}
if g.String() != s {
t.Errorf("got %x, want %x", g.String(), s)
}
if GUIDBIOS != GUIDS["bios"] {
t.Errorf("got %x, want %x", GUIDS["bios"], GUIDBIOS)
}
if want := "bios"; GUIDBIOS.Name() != want {
t.Errorf("got %q, want %q", GUIDBIOS.Name(), want)
}
// this guid is a non-named guid, assert that it is returned by name() as a
// fallback.
s = "99999999-9999-9999-9999-999999999999"
g, err = NewGUID(s)
if err != nil {
t.Fatal(err)
}
if g.Name() != s {
t.Errorf("got %x, want %x", g.Name(), s)
}
}
func TestGetPhysicalBlockSize(t *testing.T) {
t.Skip("GetPhysicalBlockSize requires elevated priviliges")
f, err := os.Open("/dev/sda")
if err != nil {
t.Fatal(err)
}
defer f.Close()
sz, err := GetPhysicalBlockSize(f)
if err != nil {
t.Fatal(err)
}
t.Log(sz)
}
func TestPartitionName(t *testing.T) {
name := "abcdefghijklmnopqrstuvwxyzabcdef"
want := [72]byte{
0x61, 0x00, 0x62, 0x00, 0x63, 0x00, 0x64, 0x00, 0x65, 0x00, 0x66, 0x00,
0x67, 0x00, 0x68, 0x00, 0x69, 0x00, 0x6a, 0x00, 0x6b, 0x00, 0x6c, 0x00,
0x6d, 0x00, 0x6e, 0x00, 0x6f, 0x00, 0x70, 0x00, 0x71, 0x00, 0x72, 0x00,
0x73, 0x00, 0x74, 0x00, 0x75, 0x00, 0x76, 0x00, 0x77, 0x00, 0x78, 0x00,
0x79, 0x00, 0x7a, 0x00, 0x61, 0x00, 0x62, 0x00, 0x63, 0x00, 0x64, 0x00,
0x65, 0x00, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
}
pn := NewPartitionName(name)
if pn != want {
t.Errorf("got %#v, want %#v", pn, want)
}
if got := pn.String(); got != name {
t.Errorf("got %q, want %q", got, name)
}
}
// returns the binary byte size of an arbitrary object. this is different from
// unsafe.Sizeof, which returns the C-ABI size of an object, which includes
// padding rules that don't apply to binary writes.
func byteSizeOf(v interface{}) int {
var b bytes.Buffer
binary.Write(&b, binary.LittleEndian, v)
return b.Len()
}
func TestSizes(t *testing.T) {
if want := byteSizeOf(PartitionEntry{}); PartitionEntrySize != want {
t.Errorf("PartitionEntrySize: got %d, want %d", PartitionEntrySize, want)
}
if want := byteSizeOf(Header{}); HeaderSize != want {
t.Errorf("HeaderSize: got %d, want %d", HeaderSize, want)
}
}
func TestNextAlignedLBA(t *testing.T) {
examples := []struct {
in [4]uint64
want uint64
}{
{[4]uint64{0, 512, 0, 0}, 0},
{[4]uint64{1, 512, 0, 0}, 1},
{[4]uint64{2, 512, 0, 0}, 2},
{[4]uint64{1, 512, 4096, 0}, 8},
{[4]uint64{1, 512, 4096, 65536}, 128},
{[4]uint64{50, 512, 4096, 0}, 56},
{[4]uint64{150, 512, 4096, 65536}, 256},
}
for _, ex := range examples {
if got, want := NextAlignedLBA(ex.in[0], ex.in[1], ex.in[2], ex.in[3]), ex.want; got != want {
t.Errorf("got %d, want %d, for %v", got, want, ex.in)
}
}
}
func TestAlignedRange(t *testing.T) {
examples := []struct {
in [5]uint64
want [2]uint64
}{
{[5]uint64{0, 1024, 512, 0, 0}, [2]uint64{0, 2}},
{[5]uint64{1, 1024, 512, 0, 0}, [2]uint64{1, 3}},
{[5]uint64{2, 1024, 512, 0, 0}, [2]uint64{2, 4}},
{[5]uint64{1, 1024, 512, 4096, 0}, [2]uint64{8, 15}},
{[5]uint64{1, 1024, 512, 4096, 65536}, [2]uint64{128, 255}},
{[5]uint64{50, 1024, 512, 4096, 0}, [2]uint64{56, 63}},
{[5]uint64{150, 1024, 512, 4096, 65536}, [2]uint64{256, 383}},
{[5]uint64{256, 65536 - 512, 512, 4096, 65536}, [2]uint64{256, 383}},
{[5]uint64{256, 65536, 512, 4096, 65536}, [2]uint64{256, 511}},
}
for _, ex := range examples {
gs, ge := AlignedRange(ex.in[0], ex.in[1], ex.in[2], ex.in[3], ex.in[4])
ws, we := ex.want[0], ex.want[1]
if gs != ws || ge != we {
t.Errorf("got [%d,%d], want [%d,%d], for %v", gs, ge, ws, we, ex.in)
}
}
}
// TODO(raggi): produce a set of examples to test instead of just one
const exampleDiskSize = 51200
func exampleDisk() []byte {
// The contents of this method are effectively a reformatted hexdump of a real
// image created with a disk image tool.
var d = make([]byte, exampleDiskSize)
copy(d[0x000001b0:], []byte{
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe,
0xff, 0xff, 0xee, 0xfe, 0xff, 0xff, 0x01, 0x00, 0x00, 0x00, 0x63, 0x00, 0x00, 0x00, 0x00, 0x00,
})
copy(d[0x000001f0:], []byte{
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x55, 0xaa,
0x45, 0x46, 0x49, 0x20, 0x50, 0x41, 0x52, 0x54, 0x00, 0x00, 0x01, 0x00, 0x5c, 0x00, 0x00, 0x00,
0x71, 0x65, 0x6f, 0xe5, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x63, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x42, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5a, 0x46, 0x91, 0x1a, 0x61, 0xba, 0xaf, 0x4c,
0x99, 0x91, 0xb7, 0xd6, 0xe4, 0x91, 0xed, 0x50, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x5a, 0x95, 0x81, 0x63, 0x00, 0x00, 0x00, 0x00,
})
copy(d[0x00000400:], []byte{
0x00, 0x53, 0x46, 0x48, 0x00, 0x00, 0xaa, 0x11, 0xaa, 0x11, 0x00, 0x30, 0x65, 0x43, 0xec, 0xac,
0xd4, 0xf2, 0x0c, 0x08, 0x62, 0xe0, 0xcf, 0x42, 0xbf, 0x54, 0xbf, 0x34, 0x00, 0xb4, 0x89, 0x4d,
0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x00, 0x69, 0x00, 0x73, 0x00, 0x6b, 0x00,
0x20, 0x00, 0x69, 0x00, 0x6d, 0x00, 0x61, 0x00, 0x67, 0x00, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00,
})
copy(d[0x00008600:], []byte{
0x00, 0x53, 0x46, 0x48, 0x00, 0x00, 0xaa, 0x11, 0xaa, 0x11, 0x00, 0x30, 0x65, 0x43, 0xec, 0xac,
0xd4, 0xf2, 0x0c, 0x08, 0x62, 0xe0, 0xcf, 0x42, 0xbf, 0x54, 0xbf, 0x34, 0x00, 0xb4, 0x89, 0x4d,
0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x00, 0x69, 0x00, 0x73, 0x00, 0x6b, 0x00,
0x20, 0x00, 0x69, 0x00, 0x6d, 0x00, 0x61, 0x00, 0x67, 0x00, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00,
})
copy(d[0x0000c600:], []byte{
0x45, 0x46, 0x49, 0x20, 0x50, 0x41, 0x52, 0x54, 0x00, 0x00, 0x01, 0x00, 0x5c, 0x00, 0x00, 0x00,
0x3d, 0x70, 0x6d, 0x59, 0x00, 0x00, 0x00, 0x00, 0x63, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x42, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5a, 0x46, 0x91, 0x1a, 0x61, 0xba, 0xaf, 0x4c,
0x99, 0x91, 0xb7, 0xd6, 0xe4, 0x91, 0xed, 0x50, 0x43, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x5a, 0x95, 0x81, 0x63, 0x00, 0x00, 0x00, 0x00,
})
return d
}
func exampleGPT() GPT {
bname := [72]byte{}
name16 := utf16.Encode([]rune("disk image"))
for i, r := range name16 {
binary.LittleEndian.PutUint16(bname[i*2:], r)
}
pe := PartitionEntry{
PartitionTypeGUID: GUIDAppleHFSPlus,
UniquePartitionGUID: mustNewGUID("080CF2D4-E062-42CF-BF54-BF3400B4894D"),
StartingLBA: uint64(40),
EndingLBA: uint64(63),
Attributes: uint64(0),
PartitionName: bname,
}
g := GPT{
MBR: mbr.MBR{
Signature: mbr.GPTSignature,
PartitionRecord: [4]mbr.PartitionRecord{
mbr.PartitionRecord{
StartingCHS: [3]byte{254, 255, 255},
OSType: mbr.GPTProtective,
EndingCHS: [3]byte{254, 255, 255},
StartingLBA: 1,
SizeInLBA: 99,
},
},
},
Primary: PartitionTable{
Header: Header{
Signature: EFISignature,
Revision: EFIRevision,
HeaderSize: HeaderSize,
HeaderCRC32: 3849282929,
MyLBA: 1,
AlternateLBA: uint64(exampleDiskSize/512 - 1),
FirstUsableLBA: 34,
LastUsableLBA: uint64((exampleDiskSize - 512 - 16384 - 512) / 512),
DiskGUID: mustNewGUID("1A91465A-BA61-4CAF-9991-B7D6E491ED50"),
PartitionEntryLBA: uint64(2),
NumberOfPartitionEntries: uint32(128),
SizeOfPartitionEntry: uint32(128),
PartitionEntryArrayCRC32: uint32(1669436762),
},
Partitions: make([]PartitionEntry, 128),
},
Backup: PartitionTable{
Header: Header{
Signature: EFISignature,
Revision: EFIRevision,
HeaderSize: HeaderSize,
HeaderCRC32: 1500344381,
MyLBA: uint64(exampleDiskSize/512 - 1),
AlternateLBA: 1,
FirstUsableLBA: 34,
LastUsableLBA: uint64((exampleDiskSize - 512 - 16384 - 512) / 512),
DiskGUID: mustNewGUID("1A91465A-BA61-4CAF-9991-B7D6E491ED50"),
PartitionEntryLBA: uint64(67),
NumberOfPartitionEntries: uint32(128),
SizeOfPartitionEntry: uint32(128),
PartitionEntryArrayCRC32: uint32(1669436762),
},
Partitions: make([]PartitionEntry, 128),
},
}
g.Primary.Partitions[0] = pe
g.Backup.Partitions[0] = pe
return g
}
// assertStructEqual compares flat structure a to flat structure b, printing an
// error message containing the field name for any field that does not match.
func assertStructEqual(t *testing.T, got, want interface{}) {
gotv := reflect.ValueOf(got)
wantv := reflect.ValueOf(want)
if gotv.Kind() != reflect.Struct || wantv.Kind() != reflect.Struct {
t.Fatalf("not a struct: %v or %v", gotv.Kind(), wantv.Kind())
}
for i := 0; i < wantv.NumField(); i++ {
if wantv.Type() != gotv.Type() {
t.Fatalf("mismatched types: %#v and %#v", gotv, wantv)
}
if !wantv.Field(i).CanInterface() {
t.Logf("cannot compare field: %v", wantv.Type().Field(i))
continue
}
goti := gotv.Field(i).Interface()
wanti := wantv.Field(i).Interface()
if goti != wanti {
t.Errorf("%s:\n got %v,\n want %v", wantv.Type().Field(i).Name, goti, wanti)
}
}
}