diff --git a/partition/gpt/partition.go b/partition/gpt/partition.go index a454ec23..30f864c9 100644 --- a/partition/gpt/partition.go +++ b/partition/gpt/partition.go @@ -291,3 +291,8 @@ func (p *Partition) sectorSizes() (physical, logical int) { func (p *Partition) Equal(o *Partition) bool { return p != nil && o != nil && *p == *o } + +// UUID returns the partitions UUID +func (p *Partition) UUID() string { + return p.GUID +} diff --git a/partition/gpt/table.go b/partition/gpt/table.go index f3632218..0487b619 100644 --- a/partition/gpt/table.go +++ b/partition/gpt/table.go @@ -588,6 +588,11 @@ func (t *Table) GetPartitions() []part.Partition { return parts } +// UUID returns the partition table UUID (disk UUID) +func (t *Table) UUID() string { + return t.GUID +} + // Verify will attempt to evaluate the headers func (t *Table) Verify(f util.File, diskSize uint64) error { // Determine the size of disk that GPT expects diff --git a/partition/mbr/partition.go b/partition/mbr/partition.go index 4afab694..a63428da 100644 --- a/partition/mbr/partition.go +++ b/partition/mbr/partition.go @@ -27,6 +27,8 @@ type Partition struct { // we need this for calculations logicalSectorSize int physicalSectorSize int + // partitionUUID is set when retrieving partitions from a Table + partitionUUID string } // PartitionEqualBytes compares if the bytes for 2 partitions are equal, ignoring CHS start and end @@ -46,7 +48,7 @@ func PartitionEqualBytes(b1, b2 []byte) bool { bytes.Equal(b1[12:16], b2[12:16]) } -// Equal compares if another partition is equal to this one, ignoring CHS start and end +// Equal compares if another partition is equal to this one, ignoring the UUID and CHS start and end func (p *Partition) Equal(p2 *Partition) bool { if p2 == nil { return false @@ -204,3 +206,9 @@ func (p *Partition) sectorSizes() (physical, logical int) { } return physical, logical } + +// UUID returns the partitions UUID. For MBR based partition tables this is the +// partition table UUID with the partition number as a suffix. +func (p *Partition) UUID() string { + return p.partitionUUID +} diff --git a/partition/mbr/table.go b/partition/mbr/table.go index 6d0ed1d0..c64dab5f 100644 --- a/partition/mbr/table.go +++ b/partition/mbr/table.go @@ -2,6 +2,7 @@ package mbr import ( "bytes" + "encoding/binary" "fmt" "github.com/diskfs/go-diskfs/partition/part" @@ -13,7 +14,7 @@ type Table struct { Partitions []*Partition LogicalSectorSize int // logical size of a sector PhysicalSectorSize int // physical size of the sector - initialized bool + partitionTableUUID string } const ( @@ -23,6 +24,9 @@ const ( partitionEntriesStart = 446 partitionEntriesCount = 4 signatureStart = 510 + // the partition table UUID is stored in 4 bytes in the MBR + partitionTableUUIDStart = 440 + partitionTableUUIDEnd = 444 ) // partitionEntrySize standard size of an MBR partition @@ -54,20 +58,7 @@ func comparePartitionArray(p1, p2 []*Partition) bool { return matches } -// ensure that a blank table is initialized -func (t *Table) initTable() { - // default settings - if t.LogicalSectorSize == 0 { - t.LogicalSectorSize = 512 - } - if t.PhysicalSectorSize == 0 { - t.PhysicalSectorSize = 512 - } - - t.initialized = true -} - -// Equal check if another table is equal to this one, ignoring CHS start and end for the partitions +// Equal check if another table is equal to this one, ignoring the partition table UUID and CHS start and end for the partitions func (t *Table) Equal(t2 *Table) bool { if t2 == nil { return false @@ -85,13 +76,14 @@ func tableFromBytes(b []byte) (*Table, error) { if len(b) != mbrSize { return nil, fmt.Errorf("data for partition was %d bytes instead of expected %d", len(b), mbrSize) } - mbrSignature := b[signatureStart:] // validate signature + mbrSignature := b[signatureStart:] if !bytes.Equal(mbrSignature, getMbrSignature()) { return nil, fmt.Errorf("invalid MBR Signature %v", mbrSignature) } + ptUUID := readPartitionTableUUID(b) parts := make([]*Partition, 0, partitionEntriesCount) count := int(partitionEntriesCount) for i := 0; i < count; i++ { @@ -102,6 +94,7 @@ func tableFromBytes(b []byte) (*Table, error) { if err != nil { return nil, fmt.Errorf("error reading partition entry %d: %v", i, err) } + p.partitionUUID = formatPartitionUUID(ptUUID, i+1) parts = append(parts, p) } @@ -109,11 +102,30 @@ func tableFromBytes(b []byte) (*Table, error) { Partitions: parts, LogicalSectorSize: logicalSectorSize, PhysicalSectorSize: 512, + partitionTableUUID: ptUUID, } return table, nil } +func readPartitionTableUUID(b []byte) string { + ptUUID := b[partitionTableUUIDStart:partitionTableUUIDEnd] + return fmt.Sprintf("%x", binary.LittleEndian.Uint32(ptUUID)) +} + +// UUID returns the partition table UUID used to identify disks +func (t *Table) UUID() string { + return t.partitionTableUUID +} + +// formatPartitionUUID creates the partition UUID which is created by using the +// partition table UUID and the partition index. +// Format string taken from libblkid: +// https://github.com/util-linux/util-linux/blob/master/libblkid/src/partitions/partitions.c#L1387C42-L1387C52 +func formatPartitionUUID(ptUUID string, index int) string { + return fmt.Sprintf("%.33s-%02x", ptUUID, index) +} + // Type report the type of table, always the string "mbr" func (t *Table) Type() string { return "mbr" diff --git a/partition/mbr/table_internal_test.go b/partition/mbr/table_internal_test.go index b152ed7e..b9efb913 100644 --- a/partition/mbr/table_internal_test.go +++ b/partition/mbr/table_internal_test.go @@ -10,12 +10,15 @@ import ( const ( mbrFile = "./testdata/mbr.img" + // retrieved via `blkid ./testdata/mbr.img` + testPartitionTableUUID = "10e9203d" ) func GetValidTable() *Table { table := &Table{ LogicalSectorSize: 512, PhysicalSectorSize: 512, + partitionTableUUID: testPartitionTableUUID, } parts := []*Partition{ { @@ -29,11 +32,12 @@ func GetValidTable() *Table { EndCylinder: 0x00, Start: partitionStart, Size: partitionSize, + partitionUUID: formatPartitionUUID(testPartitionTableUUID, 1), }, } - // add 127 Unused partitions to the table + // add 3 unused partitions to the table for i := 1; i < 4; i++ { - parts = append(parts, &Partition{Type: Empty}) + parts = append(parts, &Partition{Type: Empty, partitionUUID: formatPartitionUUID(testPartitionTableUUID, i+1)}) } table.Partitions = parts return table @@ -89,5 +93,18 @@ func TestTableFromBytes(t *testing.T) { if table == nil && expected != nil || !table.Equal(expected) { t.Errorf("actual table was %v instead of expected %v", table, expected) } + if table.partitionTableUUID != testPartitionTableUUID { + t.Errorf("expected partition table UUID %s, but found %s", testPartitionTableUUID, table.partitionTableUUID) + } + for i := range table.Partitions { + if table.Partitions[i].UUID() != expected.Partitions[i].UUID() { + t.Errorf( + "expected partition nr %d to have UUID %s, but found %s", + i+1, + expected.Partitions[i].UUID(), + table.Partitions[i].UUID(), + ) + } + } }) } diff --git a/partition/part/partition.go b/partition/part/partition.go index 2a9a3dbe..f87ba9fa 100644 --- a/partition/part/partition.go +++ b/partition/part/partition.go @@ -12,4 +12,5 @@ type Partition interface { GetStart() int64 ReadContents(util.File, io.Writer) (int64, error) WriteContents(util.File, io.Reader) (uint64, error) + UUID() string } diff --git a/partition/table.go b/partition/table.go index 5e1e84b3..62f56503 100644 --- a/partition/table.go +++ b/partition/table.go @@ -12,4 +12,5 @@ type Table interface { GetPartitions() []part.Partition Repair(diskSize uint64) error Verify(f util.File, diskSize uint64) error + UUID() string }