Skip to content

Commit

Permalink
MBR: add partition table UUID and partition UUID (#198)
Browse files Browse the repository at this point in the history
This allows to retrieve the partition table UUID and partition UUID for
MBR formatted disks. The partition table UUID (aka "disk identifier" or
"disk signature") can be used to identify a disk when using the MBR
format scheme. The partition UUID is then just the partition table UUID
with the partitions index number appended.
  • Loading branch information
thirdeyenick authored Dec 17, 2023
1 parent c46802c commit 3cd0731
Show file tree
Hide file tree
Showing 7 changed files with 68 additions and 19 deletions.
5 changes: 5 additions & 0 deletions partition/gpt/partition.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
5 changes: 5 additions & 0 deletions partition/gpt/table.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
10 changes: 9 additions & 1 deletion partition/mbr/partition.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
}
44 changes: 28 additions & 16 deletions partition/mbr/table.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package mbr

import (
"bytes"
"encoding/binary"
"fmt"

"github.com/diskfs/go-diskfs/partition/part"
Expand All @@ -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 (
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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++ {
Expand All @@ -102,18 +94,38 @@ 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)
}

table := &Table{
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"
Expand Down
21 changes: 19 additions & 2 deletions partition/mbr/table_internal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{
{
Expand All @@ -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
Expand Down Expand Up @@ -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(),
)
}
}
})
}
1 change: 1 addition & 0 deletions partition/part/partition.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
1 change: 1 addition & 0 deletions partition/table.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ type Table interface {
GetPartitions() []part.Partition
Repair(diskSize uint64) error
Verify(f util.File, diskSize uint64) error
UUID() string
}

0 comments on commit 3cd0731

Please sign in to comment.