From 6928ee43c3034549e32f000f8b7bc16a6ebb7ed4 Mon Sep 17 00:00:00 2001 From: Andrey Smirnov Date: Tue, 14 Dec 2021 21:40:06 +0300 Subject: [PATCH] refactor: rewrite GPT serialize/deserialize functions Remove reads of GPT header by bytes, read whole header in memory. Use simple versions of functions to encode/decode fields without allocating any memory. Simplify things by removing functions and removing failure scenarios. Signed-off-by: Andrey Smirnov --- blockdevice/lba/lba.go | 41 -- blockdevice/partition/gpt/gpt.go | 14 +- blockdevice/partition/gpt/gpt_test.go | 29 ++ blockdevice/partition/gpt/header.go | 632 +++---------------------- blockdevice/partition/gpt/partition.go | 235 ++------- 5 files changed, 137 insertions(+), 814 deletions(-) diff --git a/blockdevice/lba/lba.go b/blockdevice/lba/lba.go index f64e40a..002c62f 100644 --- a/blockdevice/lba/lba.go +++ b/blockdevice/lba/lba.go @@ -5,50 +5,9 @@ package lba import ( - "fmt" "os" ) -// Buffer is an in-memory buffer for writing to byte slices in units of LBA. -type Buffer struct { - lba *LBA - b []byte -} - -// NewBuffer intializes and returns a `Buffer`. -func NewBuffer(lba *LBA, b []byte) *Buffer { - return &Buffer{lba: lba, b: b} -} - -// Read reads from a `Buffer`. -func (buf *Buffer) Read(off, length int64) (b []byte, err error) { - b = make([]byte, length) - - n := copy(b, buf.b[off:off+length]) - - if n != len(buf.b[off:off+length]) { - return nil, fmt.Errorf("expected to write %d bytes, read %d", len(b), n) - } - - return b, nil -} - -// Write writes to a `Buffer`. -func (buf *Buffer) Write(b []byte, off int64) (err error) { - n := copy(buf.b[off:off+int64(len(b))], b) - - if n != len(b) { - return fmt.Errorf("expected to write %d bytes, wrote %d", len(b), n) - } - - return nil -} - -// Bytes returns the buffer bytes. -func (buf *Buffer) Bytes() []byte { - return buf.b -} - // LBA represents logical block addressing. // //nolint:govet diff --git a/blockdevice/partition/gpt/gpt.go b/blockdevice/partition/gpt/gpt.go index 640d055..59653bd 100644 --- a/blockdevice/partition/gpt/gpt.go +++ b/blockdevice/partition/gpt/gpt.go @@ -72,9 +72,11 @@ func Open(f *os.File) (g *GPT, err error) { return nil, err } - b := lba.NewBuffer(l, make([]byte, l.LogicalBlockSize)) + h := &Header{LBA: l} - h := &Header{Buffer: b, LBA: l} + if err = h.verifySignature(); err != nil { + return nil, ErrPartitionTableDoesNotExist + } g = &GPT{ f: f, @@ -84,10 +86,6 @@ func Open(f *os.File) (g *GPT, err error) { markMBRBootable: buf[0] == 0x80, } - if err = h.DeserializeSignature(); err != nil { - return nil, ErrPartitionTableDoesNotExist - } - return g, nil } @@ -106,9 +104,7 @@ func New(f *os.File, setters ...Option) (g *GPT, err error) { return nil, err } - b := lba.NewBuffer(l, make([]byte, l.LogicalBlockSize)) - - h := &Header{Buffer: b, LBA: l} + h := &Header{LBA: l} h.Signature = MagicEFIPart h.Revision = binary.LittleEndian.Uint32([]byte{0x00, 0x00, 0x01, 0x00}) diff --git a/blockdevice/partition/gpt/gpt_test.go b/blockdevice/partition/gpt/gpt_test.go index f21a0d4..ccb2eab 100644 --- a/blockdevice/partition/gpt/gpt_test.go +++ b/blockdevice/partition/gpt/gpt_test.go @@ -7,6 +7,7 @@ package gpt_test import ( "encoding/binary" "os" + "os/exec" "testing" "github.com/stretchr/testify/suite" @@ -73,6 +74,8 @@ func (suite *GPTSuite) TestReset() { suite.Require().NoError(g.Read()) suite.Assert().Empty(g.Partitions().Items()) + + suite.validatePartitions() } func (suite *GPTSuite) TestPartitionAdd() { @@ -129,6 +132,8 @@ func (suite *GPTSuite) TestPartitionAdd() { suite.Require().NoError(g.Read()) assertPartitions(g.Partitions()) + + suite.validatePartitions() } func (suite *GPTSuite) TestRepairResize() { @@ -223,6 +228,8 @@ func (suite *GPTSuite) TestRepairResize() { suite.Require().NoError(g.Read()) assertPartitions(g.Partitions()) + + suite.validatePartitions() } func (suite *GPTSuite) TestPartitionAddOutOfSpace() { @@ -319,6 +326,8 @@ func (suite *GPTSuite) TestPartitionDelete() { partSystem := partitions[2] suite.Assert().EqualValues(3, partSystem.Number) suite.Assert().EqualValues((size-bootSize-efiSize-grubSize-configSize)/blockSize-headReserved-tailReserved, partSystem.Length()) + + suite.validatePartitions() } func (suite *GPTSuite) TestPartitionInsertAt() { @@ -409,6 +418,8 @@ func (suite *GPTSuite) TestPartitionInsertAt() { suite.Assert().EqualValues(headReserved+(oldBootSize+configSize+efiSize)/blockSize, partSystem.FirstLBA) suite.Require().NoError(g.Write()) + + suite.validatePartitions() } func (suite *GPTSuite) TestPartitionInsertOffsetAndResize() { @@ -481,6 +492,8 @@ func (suite *GPTSuite) TestPartitionInsertOffsetAndResize() { suite.Assert().EqualValues(configStart/blockSize, int(partConfig.FirstLBA)) suite.Require().NoError(g.Write()) + + suite.validatePartitions() } func (suite *GPTSuite) TestPartitionGUUID() { @@ -523,6 +536,8 @@ func (suite *GPTSuite) TestMarkPMBRBootable() { suite.Require().NoError(g.Write()) suite.validatePMBR(0x80) + + suite.validatePartitions() } func (suite *GPTSuite) validatePMBR(flag byte) { @@ -544,6 +559,20 @@ func (suite *GPTSuite) validatePMBR(flag byte) { suite.Assert().Equal([]byte{0x55, 0xaa}, buf[510:512]) // boot signature } +func (suite *GPTSuite) validatePartitions() { + for _, tool := range []string{"fdisk", "gdisk"} { + if toolPath, _ := exec.LookPath(tool); toolPath != "" { //nolint:errcheck + cmd := exec.Command(toolPath, "-l", suite.Dev.Name()) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + + suite.Assert().NoError(cmd.Run()) + } else { + suite.T().Logf("%s is not available", tool) + } + } +} + func TestGPTSuite(t *testing.T) { if os.Getuid() != 0 { t.Skip("can't run the test as non-root") diff --git a/blockdevice/partition/gpt/header.go b/blockdevice/partition/gpt/header.go index 1e57ba8..c6828ac 100644 --- a/blockdevice/partition/gpt/header.go +++ b/blockdevice/partition/gpt/header.go @@ -20,7 +20,6 @@ import ( // //nolint:maligned,govet type Header struct { - *lba.Buffer *lba.LBA Signature string @@ -38,636 +37,145 @@ type Header struct { PartitionEntriesChecksum uint32 } -func (h *Header) read() (err error) { - err = h.DeserializeSignature() - if err != nil { - return err - } - - err = h.DeserializeRevision() +func (h *Header) verifySignature() (err error) { + b, err := h.LBA.ReadAt(1, 0, 8) if err != nil { return err } - err = h.DeserializeSize() - if err != nil { - return err - } + h.Signature = string(b[:8]) - err = h.DeserializeCRC() - if err != nil { - return err - } - - err = h.DeserializeCurrentLBA() - if err != nil { - return err - } - - err = h.DeserializeBackupLBA() - if err != nil { - return err - } - - err = h.DeserializeFirstUsableLBA() - if err != nil { - return err - } - - err = h.DeserializeLastUsableLBA() - if err != nil { - return err - } - - err = h.DeserializeGUUID() - if err != nil { - return err - } - - err = h.DeserializeStartingLBA() - if err != nil { - return err - } - - err = h.DeserializeNumberOfPartitionEntries() - if err != nil { - return err - } - - err = h.DeserializePartitionEntrySize() - if err != nil { - return err - } - - err = h.DeserializePartitionEntriesCRC() - if err != nil { - return err - } - - return nil -} - -func (h *Header) write() (err error) { - p, err := h.Primary() - if err != nil { - return err - } - - err = h.WriteAt(1, 0x00, p) - if err != nil { - return err - } - - s, err := h.Secondary() - if err != nil { - return err - } - - err = h.WriteAt(h.TotalSectors-1, 0x00, s) - if err != nil { - return err + if h.Signature != MagicEFIPart { + return fmt.Errorf("expected signature of %q, got %q", MagicEFIPart, h.Signature) } return nil } -// Primary returns the serialized primary header. -func (h *Header) Primary() (b []byte, err error) { - err = h.SerializeSignature() - if err != nil { - return nil, err - } - - err = h.SerializeRevision() - if err != nil { - return nil, err - } - - err = h.SerializeSize() - if err != nil { - return nil, err - } - - // Reserved; must be zero. - - data := bytes.Repeat([]byte{0x00}, 4) - - err = h.Write(data, 0x14) - if err != nil { - return nil, err - } - - err = h.SerializeCurrentLBA() - if err != nil { - return nil, err - } - - err = h.SerializeBackupLBA() - if err != nil { - return nil, err - } - - err = h.SerializeFirstUsableLBA() - if err != nil { - return nil, err - } - - err = h.SerializeLastUsableLBA() - if err != nil { - return nil, err - } - - err = h.SerializeGUUID() - if err != nil { - return nil, err - } - - err = h.SerializeStartingLBA() - if err != nil { - return nil, err - } - - err = h.SerializeNumberOfPartitionEntries() - if err != nil { - return nil, err - } - - err = h.SerializePartitionEntrySize() - if err != nil { - return nil, err - } - - err = h.SerializePartitionEntriesCRC() - if err != nil { - return nil, err - } - - // Reserved; must be zeroes for the rest of the block (420 bytes for a sector size of 512 bytes; but can be more with larger sector sizes) - - data = bytes.Repeat([]byte{0x00}, int(h.LogicalBlockSize)-HeaderSize) - - err = h.Write(data, 0x5C) - if err != nil { - return nil, err - } - - err = h.SerializeCRC() - if err != nil { - return nil, err - } - - return h.Bytes(), nil -} - -// Secondary returns the serialized secondary header. -func (h *Header) Secondary() (b []byte, err error) { - b = make([]byte, len(h.Bytes())) - - copy(b, h.Bytes()) - - buf := lba.NewBuffer(h.LBA, b) - - // Current LBA (location of this header copy). - - data := make([]byte, 8) - - binary.LittleEndian.PutUint64(data, h.BackupLBA) - - err = buf.Write(data, 0x18) - if err != nil { - return nil, err - } - - // Backup LBA (location of the other header copy). - - data = make([]byte, 8) - - binary.LittleEndian.PutUint64(data, 1) - - err = buf.Write(data, 0x20) - if err != nil { - return nil, err - } - - // CRC32 of header (offset +0 up to header size) in little endian, with this field zeroed during calculation. - - data = make([]byte, 4) - - // Zero the CRC field during the calculation. - n := copy(b[16:20], bytes.Repeat([]byte{0x00}, 4)) - if n != 4 { - return nil, fmt.Errorf("expected to copy 4 bytes into header, copied %d", n) - } - - crc := crc32.ChecksumIEEE(b[0:HeaderSize]) - - binary.LittleEndian.PutUint32(data, crc) - - err = buf.Write(data, 0x10) - if err != nil { - return nil, err - } - - return b, nil -} - -// DeserializeSignature desirializes the signature ("EFI PART", 45h 46h 49h 20h 50h 41h 52h 54h or 0x5452415020494645ULL on little-endian machines). -func (h *Header) DeserializeSignature() (err error) { - data, err := h.ReadAt(1, 0x00, 8) - if err != nil { - return fmt.Errorf("signature read: %w", err) - } - - signature := string(data) - - if signature != MagicEFIPart { - return fmt.Errorf("expected signature of %q, got %q", MagicEFIPart, signature) - } - - h.Signature = signature - - return nil -} - -// SerializeSignature serializes the signature ("EFI PART", 45h 46h 49h 20h 50h 41h 52h 54h or 0x5452415020494645ULL on little-endian machines). -func (h *Header) SerializeSignature() (err error) { - err = h.Write([]byte(MagicEFIPart), 0x00) - if err != nil { - return err - } - - return nil -} - -// DeserializeRevision deserializes the revision (for GPT version 1.0 (through at least UEFI version 2.7 (May 2017)), the value is 00h 00h 01h 00h). -func (h *Header) DeserializeRevision() (err error) { - data, err := h.ReadAt(1, 0x08, 4) - if err != nil { - return fmt.Errorf("revision read: %w", err) - } - - h.Revision = binary.LittleEndian.Uint32(data) - - return nil -} - -// SerializeRevision serializes the revision (for GPT version 1.0 (through at least UEFI version 2.7 (May 2017)), the value is 00h 00h 01h 00h). -func (h *Header) SerializeRevision() (err error) { - data := make([]byte, 4) - - binary.LittleEndian.PutUint32(data, h.Revision) - - err = h.Write(data, 0x08) +func (h *Header) read() (err error) { + b, err := h.LBA.ReadAt(1, 0, HeaderSize) if err != nil { return err } - return nil -} + h.Signature = string(b[:8]) -// DeserializeSize deserializes the header size in little endian (in bytes, usually 5Ch 00h 00h 00h or 92 bytes). -func (h *Header) DeserializeSize() (err error) { - data, err := h.ReadAt(1, 0x0C, 4) - if err != nil { - return fmt.Errorf("header size read: %w", err) + if h.Signature != MagicEFIPart { + return fmt.Errorf("expected signature of %q, got %q", MagicEFIPart, h.Signature) } - h.Size = binary.LittleEndian.Uint32(data) + h.Revision = binary.LittleEndian.Uint32(b[8:12]) + h.Size = binary.LittleEndian.Uint32(b[12:16]) if h.Size < HeaderSize { return fmt.Errorf("header size too small: %d", h.Size) } - return nil -} - -// SerializeSize serializes the header size in little endian (in bytes, usually 5Ch 00h 00h 00h or 92 bytes). -func (h *Header) SerializeSize() (err error) { - data := make([]byte, 4) - - binary.LittleEndian.PutUint32(data, h.Size) - - err = h.Write(data, 0x0C) - if err != nil { - return err - } - - return nil -} - -// DeserializeCRC deserializes the CRC32 of header (offset +0 up to header size) in little endian, with this field zeroed during calculation. -func (h *Header) DeserializeCRC() (err error) { - data, err := h.ReadAt(1, 0x10, 4) - if err != nil { - return fmt.Errorf("header CRC32 read: %w", err) - } - - crc := binary.LittleEndian.Uint32(data) - - hdr, err := h.ReadAt(1, 0x00, int64(h.Size)) - if err != nil { - return fmt.Errorf("header read: %w", err) - } - - // Zero the CRC field during the calculation. - n := copy(hdr[16:20], []byte{0x00, 0x00, 0x00, 0x00}) - if n != 4 { - return fmt.Errorf("expected to copy 4 bytes into header, copied %d", n) - } - - checksum := crc32.ChecksumIEEE(hdr) - - if crc != checksum { - return fmt.Errorf("expected header checksum of %v, got %v", checksum, crc) - } - - h.Checksum = crc - - return nil -} - -// SerializeCRC serializes the CRC32 of header (offset +0 up to header size) in little endian, with this field zeroed during calculation. -func (h *Header) SerializeCRC() (err error) { - data := make([]byte, 4) - - // Zero the CRC field during the calculation. - n := copy(h.Bytes()[16:20], bytes.Repeat([]byte{0x00}, 4)) - if n != 4 { - return fmt.Errorf("expected to copy 4 bytes into header, copied %d", n) - } - - crc := crc32.ChecksumIEEE(h.Bytes()[0:HeaderSize]) - - binary.LittleEndian.PutUint32(data, crc) - - err = h.Write(data, 0x10) - if err != nil { - return err - } - - h.Checksum = crc - - return nil -} - -// DeserializeCurrentLBA deserializes the current LBA (location of this header copy). -func (h *Header) DeserializeCurrentLBA() (err error) { - data, err := h.ReadAt(1, 0x18, 8) - if err != nil { - return fmt.Errorf("current LBA read: %w", err) - } - - h.CurrentLBA = binary.LittleEndian.Uint64(data) - - return nil -} - -// SerializeCurrentLBA serializes the current LBA (location of this header copy). -func (h *Header) SerializeCurrentLBA() (err error) { - data := make([]byte, 8) - - binary.LittleEndian.PutUint64(data, h.CurrentLBA) - - err = h.Write(data, 0x18) - if err != nil { - return err - } - - return nil -} - -// DeserializeBackupLBA deserializes the backup LBA (location of the other header copy). -func (h *Header) DeserializeBackupLBA() (err error) { - data, err := h.ReadAt(1, 0x20, 8) - if err != nil { - return fmt.Errorf("backup LBA read: %w", err) - } - - h.BackupLBA = binary.LittleEndian.Uint64(data) - - return nil -} - -// SerializeBackupLBA serializes the backup LBA (location of the other header copy). -func (h *Header) SerializeBackupLBA() (err error) { - data := make([]byte, 8) - - binary.LittleEndian.PutUint64(data, h.BackupLBA) - - err = h.Write(data, 0x20) - if err != nil { - return err - } - - return nil -} - -// DeserializeFirstUsableLBA deserializes the first usable LBA for partitions (primary partition table last LBA + 1). -func (h *Header) DeserializeFirstUsableLBA() (err error) { - data, err := h.ReadAt(1, 0x28, 8) - if err != nil { - return fmt.Errorf("first usable LBA read: %w", err) - } - - h.FirstUsableLBA = binary.LittleEndian.Uint64(data) - - return nil -} - -// SerializeFirstUsableLBA serializes the first usable LBA for partitions (primary partition table last LBA + 1). -func (h *Header) SerializeFirstUsableLBA() (err error) { - data := make([]byte, 8) - - binary.LittleEndian.PutUint64(data, h.FirstUsableLBA) - - err = h.Write(data, 0x28) - if err != nil { - return err + if h.Size > uint32(h.LBA.LogicalBlockSize) { + return fmt.Errorf("header size too big: %d", h.Size) } - return nil -} - -// DeserializeLastUsableLBA deserializes the last usable LBA (secondary partition table first LBA − 1). -func (h *Header) DeserializeLastUsableLBA() (err error) { - data, err := h.ReadAt(1, 0x30, 8) - if err != nil { - return fmt.Errorf("last usable LBA read: %w", err) + if h.Size > HeaderSize { + // re-read the header to include all data + b, err = h.LBA.ReadAt(1, 0, HeaderSize) + if err != nil { + return err + } } - h.LastUsableLBA = binary.LittleEndian.Uint64(data) + h.Checksum = binary.LittleEndian.Uint32(b[16:20]) - return nil -} - -// SerializeLastUsableLBA serializes the last usable LBA (secondary partition table first LBA − 1). -func (h *Header) SerializeLastUsableLBA() (err error) { - data := make([]byte, 8) + // zero out checksum in the header before calculating CRC32 + copy(b[16:20], []byte{0x00, 0x00, 0x00, 0x00}) - binary.LittleEndian.PutUint64(data, h.LastUsableLBA) + checksum := crc32.ChecksumIEEE(b) - err = h.Write(data, 0x30) - if err != nil { - return err + if h.Checksum != checksum { + return fmt.Errorf("expected header checksum of %v, got %v", checksum, h.Checksum) } - return nil -} - -// DeserializeGUUID deserializes the disk GUID in mixed endian. -func (h *Header) DeserializeGUUID() (err error) { - data, err := h.ReadAt(1, 0x38, 16) - if err != nil { - return fmt.Errorf("guuid read: %w", err) - } + h.CurrentLBA = binary.LittleEndian.Uint64(b[24:32]) + h.BackupLBA = binary.LittleEndian.Uint64(b[32:40]) + h.FirstUsableLBA = binary.LittleEndian.Uint64(b[40:48]) + h.LastUsableLBA = binary.LittleEndian.Uint64(b[48:56]) - guid, err := uuid.FromBytes(endianness.FromMiddleEndian(data)) + h.GUUID, err = uuid.FromBytes(endianness.FromMiddleEndian(b[56:72])) if err != nil { return fmt.Errorf("invalid GUUID: %w", err) } - h.GUUID = guid - - return nil -} - -// SerializeGUUID serializes the disk GUID in mixed endian. -func (h *Header) SerializeGUUID() (err error) { - b, err := h.GUUID.MarshalBinary() - if err != nil { - return err - } - - err = h.Write(endianness.ToMiddleEndian(b), 0x38) - if err != nil { - return err - } - - return nil -} - -// DeserializeStartingLBA deserializes the starting LBA of array of partition entries (always 2 in primary copy). -func (h *Header) DeserializeStartingLBA() (err error) { - data, err := h.ReadAt(1, 0x48, 8) - if err != nil { - return fmt.Errorf("starting LBA of entries read: %w", err) - } + h.EntriesLBA = binary.LittleEndian.Uint64(b[72:80]) + h.NumberOfPartitionEntries = binary.LittleEndian.Uint32(b[80:84]) + h.PartitionEntrySize = binary.LittleEndian.Uint32(b[84:88]) - h.EntriesLBA = binary.LittleEndian.Uint64(data) + h.PartitionEntriesChecksum = binary.LittleEndian.Uint32(b[88:92]) return nil } -// SerializeStartingLBA serializes the starting LBA of array of partition entries (always 2 in primary copy). -func (h *Header) SerializeStartingLBA() (err error) { - data := make([]byte, 8) - - binary.LittleEndian.PutUint64(data, h.EntriesLBA) - - err = h.Write(data, 0x48) +func (h *Header) write() (err error) { + p, err := h.primary() if err != nil { return err } - return nil -} - -// DeserializeNumberOfPartitionEntries deserializes the number of partition entries in array. -func (h *Header) DeserializeNumberOfPartitionEntries() (err error) { - data, err := h.ReadAt(1, 0x50, 4) - if err != nil { - return fmt.Errorf("number of partitin entries read: %w", err) - } - - h.NumberOfPartitionEntries = binary.LittleEndian.Uint32(data) - - return nil -} - -// SerializeNumberOfPartitionEntries serializes the number of partition entries in array. -func (h *Header) SerializeNumberOfPartitionEntries() (err error) { - data := make([]byte, 4) - - binary.LittleEndian.PutUint32(data, h.NumberOfPartitionEntries) - - err = h.Write(data, 0x50) + err = h.WriteAt(1, 0x00, p) if err != nil { return err } - return nil -} + h.secondaryFromPrimary(p) -// DeserializePartitionEntrySize deserializes the size of a single partition entry (usually 80h or 128). -func (h *Header) DeserializePartitionEntrySize() (err error) { - data, err := h.ReadAt(1, 0x54, 4) + err = h.WriteAt(h.TotalSectors-1, 0x00, p) if err != nil { - return fmt.Errorf("last usable LBA read: %w", err) + return err } - h.PartitionEntrySize = binary.LittleEndian.Uint32(data) - return nil } -// SerializePartitionEntrySize serializes the size of a single partition entry (usually 80h or 128). -func (h *Header) SerializePartitionEntrySize() (err error) { - data := make([]byte, 4) +// primary returns the serialized primary header. +func (h *Header) primary() (b []byte, err error) { + b = make([]byte, h.LBA.LogicalBlockSize) - binary.LittleEndian.PutUint32(data, h.PartitionEntrySize) + copy(b[:8], MagicEFIPart) - err = h.Write(data, 0x54) - if err != nil { - return err - } + binary.LittleEndian.PutUint32(b[8:12], h.Revision) + binary.LittleEndian.PutUint32(b[12:16], h.Size) - return nil -} + // CRC is filled last 16:20 -// DeserializePartitionEntriesCRC deserializes the CRC32 of partition entries array in little endian. -func (h *Header) DeserializePartitionEntriesCRC() (err error) { - data, err := h.ReadAt(1, 0x58, 4) - if err != nil { - return fmt.Errorf("partition entries array CRC32 read: %w", err) - } + // 4 reserved bytes 20:24 - crc := binary.LittleEndian.Uint32(data) + binary.LittleEndian.PutUint64(b[24:32], h.CurrentLBA) + binary.LittleEndian.PutUint64(b[32:40], h.BackupLBA) + binary.LittleEndian.PutUint64(b[40:48], h.FirstUsableLBA) + binary.LittleEndian.PutUint64(b[48:56], h.LastUsableLBA) - entries, err := h.ReadAt(int64(h.EntriesLBA), 0x00, int64(h.NumberOfPartitionEntries*h.PartitionEntrySize)) + uuid, err := h.GUUID.MarshalBinary() if err != nil { - return fmt.Errorf("entries read: %w", err) + return nil, err } - checksum := crc32.ChecksumIEEE(entries) + copy(b[56:72], endianness.ToMiddleEndian(uuid)) - if crc != checksum { - return fmt.Errorf("expected partition checksum of %v, got %v", checksum, crc) - } - - h.PartitionEntriesChecksum = crc + binary.LittleEndian.PutUint64(b[72:80], h.EntriesLBA) + binary.LittleEndian.PutUint32(b[80:84], h.NumberOfPartitionEntries) + binary.LittleEndian.PutUint32(b[84:88], h.PartitionEntrySize) + binary.LittleEndian.PutUint32(b[88:92], h.PartitionEntriesChecksum) + binary.LittleEndian.PutUint32(b[16:20], crc32.ChecksumIEEE(b[0:h.Size])) - return nil + return b, nil } -// SerializePartitionEntriesCRC serializes the CRC32 of partition entries array in little endian. -func (h *Header) SerializePartitionEntriesCRC() (err error) { - data := make([]byte, 4) - - entries, err := h.ReadAt(int64(h.EntriesLBA), 0x00, int64(h.NumberOfPartitionEntries*h.PartitionEntrySize)) - if err != nil { - return err - } +// secondaryFromPrimary modifies in-place primary header to be secondary header. +func (h *Header) secondaryFromPrimary(b []byte) { + // swap current and backup LBAs + binary.LittleEndian.PutUint64(b[24:32], h.BackupLBA) + binary.LittleEndian.PutUint64(b[32:40], h.CurrentLBA) - crc := crc32.ChecksumIEEE(entries) - - binary.LittleEndian.PutUint32(data, crc) - - err = h.Write(data, 0x58) - if err != nil { - return err - } - - h.PartitionEntriesChecksum = crc + // CRC32 of header (offset +0 up to header size) in little endian, with this field zeroed during calculation. + copy(b[16:20], bytes.Repeat([]byte{0x00}, 4)) - return nil + binary.LittleEndian.PutUint32(b[16:20], crc32.ChecksumIEEE(b[0:h.Size])) } diff --git a/blockdevice/partition/gpt/partition.go b/blockdevice/partition/gpt/partition.go index 021d43e..305ea2e 100644 --- a/blockdevice/partition/gpt/partition.go +++ b/blockdevice/partition/gpt/partition.go @@ -8,13 +8,13 @@ import ( "bytes" "encoding/binary" "fmt" + "hash/crc32" "github.com/google/uuid" "golang.org/x/text/encoding/unicode" "github.com/talos-systems/go-blockdevice/blockdevice/endianness" "github.com/talos-systems/go-blockdevice/blockdevice/filesystem" - "github.com/talos-systems/go-blockdevice/blockdevice/lba" "github.com/talos-systems/go-blockdevice/blockdevice/util" ) @@ -68,6 +68,8 @@ func (p *Partition) Length() uint64 { func (p *Partitions) read() (err error) { partitions := make([]*Partition, 0, p.h.NumberOfPartitionEntries) + checksummer := crc32.NewIEEE() + for i := uint32(0); i < p.h.NumberOfPartitionEntries; i++ { offset := i * p.h.PartitionEntrySize @@ -76,36 +78,11 @@ func (p *Partitions) read() (err error) { return fmt.Errorf("partition read: %w", err) } - buf := lba.NewBuffer(p.h.LBA, data) + checksummer.Write(data) part := &Partition{Number: int32(i + 1), devname: p.devname} - err = part.DeserializeType(buf) - if err != nil { - return err - } - - err = part.DeserializeID(buf) - if err != nil { - return err - } - - err = part.DeserializeFirstLBA(buf) - if err != nil { - return err - } - - err = part.DeserializeLastLBA(buf) - if err != nil { - return err - } - - err = part.DeserializeAttributes(buf) - if err != nil { - return err - } - - err = part.DeserializeName(buf) + err = part.deserialize(data) if err != nil { return err } @@ -117,6 +94,10 @@ func (p *Partitions) read() (err error) { } } + if checksummer.Sum32() != p.h.PartitionEntriesChecksum { + return fmt.Errorf("expected partition checksum of %v, got %v", p.h.PartitionEntriesChecksum, checksummer.Sum32()) + } + p.p = partitions return nil @@ -130,226 +111,76 @@ func (p *Partitions) write() (data []byte, err error) { continue } - b := make([]byte, p.h.PartitionEntrySize) - buf := lba.NewBuffer(p.h.LBA, b) - - err = part.SerializeType(buf) - if err != nil { - return nil, err - } - - err = part.SerializeID(buf) - if err != nil { - return nil, err - } - - err = part.SerializeFirstLBA(buf) - if err != nil { - return nil, err - } - - err = part.SerializeLastLBA(buf) - if err != nil { - return nil, err - } - - err = part.SerializeAttributes(buf) - if err != nil { - return nil, err - } - - err = part.SerializeName(buf) - if err != nil { + if err = part.serialize(data[i*int(p.h.PartitionEntrySize):]); err != nil { return nil, err } - - copy(data[i*int(p.h.PartitionEntrySize):], b) } + p.h.PartitionEntriesChecksum = crc32.ChecksumIEEE(data) + return data, nil } -// DeserializeType deserializes the partition type GUID (mixed endian). -func (p *Partition) DeserializeType(buf *lba.Buffer) (err error) { - data, err := buf.Read(0x00, 16) - if err != nil { - return fmt.Errorf("type read: %w", err) - } +var utf16 = unicode.UTF16(unicode.LittleEndian, unicode.IgnoreBOM) - guid, err := uuid.FromBytes(endianness.FromMiddleEndian(data)) +func (p *Partition) deserialize(b []byte) (err error) { + p.Type, err = uuid.FromBytes(endianness.FromMiddleEndian(b[:16])) if err != nil { return fmt.Errorf("invalid GUUID: %w", err) } // TODO: Provide a method for getting the human readable name of the type. // See https://en.wikipedia.org/wiki/GUID_Partition_Table. - p.Type = guid - - return nil -} - -// SerializeType serializes the partition type GUID (mixed endian). -func (p *Partition) SerializeType(buf *lba.Buffer) (err error) { - b, err := p.Type.MarshalBinary() - if err != nil { - return err - } - - err = buf.Write(endianness.ToMiddleEndian(b), 0x00) - if err != nil { - return err - } - - return nil -} - -// DeserializeID deserializes the unique partition GUID (mixed endian). -func (p *Partition) DeserializeID(buf *lba.Buffer) (err error) { - data, err := buf.Read(0x10, 16) - if err != nil { - return fmt.Errorf("id read: %w", err) - } - guid, err := uuid.FromBytes(endianness.FromMiddleEndian(data)) + p.ID, err = uuid.FromBytes(endianness.FromMiddleEndian(b[16:32])) if err != nil { return fmt.Errorf("invalid GUUID: %w", err) } - p.ID = guid - - return nil -} - -// SerializeID serializes the unique partition GUID (mixed endian). -func (p *Partition) SerializeID(buf *lba.Buffer) (err error) { - b, err := p.ID.MarshalBinary() - if err != nil { - return err - } - - err = buf.Write(endianness.ToMiddleEndian(b), 0x10) - if err != nil { - return err - } - - return nil -} - -// DeserializeFirstLBA deserializes the first LBA (little endian). -func (p *Partition) DeserializeFirstLBA(buf *lba.Buffer) (err error) { - data, err := buf.Read(0x20, 8) - if err != nil { - return fmt.Errorf("first LBA read: %w", err) - } - - p.FirstLBA = binary.LittleEndian.Uint64(data) - - return nil -} - -// SerializeFirstLBA serializes the first LBA (little endian). -func (p *Partition) SerializeFirstLBA(buf *lba.Buffer) (err error) { - data := make([]byte, 8) - - binary.LittleEndian.PutUint64(data, p.FirstLBA) - - err = buf.Write(data, 0x20) - if err != nil { - return err - } - - return nil -} - -// DeserializeLastLBA deserializes the last LBA (inclusive, usually odd). -func (p *Partition) DeserializeLastLBA(buf *lba.Buffer) (err error) { - data, err := buf.Read(0x28, 8) - if err != nil { - return fmt.Errorf("last LBA read: %w", err) - } - - p.LastLBA = binary.LittleEndian.Uint64(data) - - return nil -} - -// SerializeLastLBA serializes the last LBA (inclusive, usually odd). -func (p *Partition) SerializeLastLBA(buf *lba.Buffer) (err error) { - data := make([]byte, 8) + p.FirstLBA = binary.LittleEndian.Uint64(b[32:40]) + p.LastLBA = binary.LittleEndian.Uint64(b[40:48]) + p.Attributes = binary.LittleEndian.Uint64(b[48:56]) - binary.LittleEndian.PutUint64(data, p.LastLBA) - - err = buf.Write(data, 0x28) + decoded, err := utf16.NewDecoder().Bytes(b[56:128]) if err != nil { return err } - return nil -} - -// DeserializeAttributes deserializes the attribute flags (e.g. bit 60 denotes read-only). -func (p *Partition) DeserializeAttributes(buf *lba.Buffer) (err error) { - data, err := buf.Read(0x30, 8) - if err != nil { - return fmt.Errorf("attributes read: %w", err) - } - - p.Attributes = binary.LittleEndian.Uint64(data) + p.Name = string(bytes.Trim(decoded, "\x00")) return nil } -// SerializeAttributes serializes the attribute flags (e.g. bit 60 denotes read-only). -func (p *Partition) SerializeAttributes(buf *lba.Buffer) (err error) { - data := make([]byte, 8) - - binary.LittleEndian.PutUint64(data, p.Attributes) - - err = buf.Write(data, 0x30) +func (p *Partition) serialize(b []byte) error { + uuid, err := p.Type.MarshalBinary() if err != nil { return err } - return nil -} - -// DeserializeName deserializes partition name (36 UTF-16LE code units). -func (p *Partition) DeserializeName(buf *lba.Buffer) (err error) { - data, err := buf.Read(0x38, 72) - if err != nil { - return fmt.Errorf("name read: %w", err) - } - - utf16 := unicode.UTF16(unicode.LittleEndian, unicode.IgnoreBOM) + copy(b[:16], endianness.ToMiddleEndian(uuid)) - decoded, err := utf16.NewDecoder().Bytes(data) + uuid, err = p.ID.MarshalBinary() if err != nil { return err } - p.Name = string(bytes.Trim(decoded, "\x00")) - - return nil -} + copy(b[16:32], endianness.ToMiddleEndian(uuid)) -// SerializeName serializes the partition name (36 UTF-16LE code units). -func (p *Partition) SerializeName(buf *lba.Buffer) (err error) { - utf16 := unicode.UTF16(unicode.LittleEndian, unicode.IgnoreBOM) + binary.LittleEndian.PutUint64(b[32:40], p.FirstLBA) + binary.LittleEndian.PutUint64(b[40:48], p.LastLBA) + binary.LittleEndian.PutUint64(b[48:56], p.Attributes) name, err := utf16.NewEncoder().Bytes([]byte(p.Name)) if err != nil { return err } - // TODO: Should we error if the name exceeds 72 bytes? - data := make([]byte, 72) - copy(data, name) - - err = buf.Write(data, 0x38) - if err != nil { - return err + if len(name) > 72 { + return fmt.Errorf("partition name is too long: %q", p.Name) } + copy(b[56:128], name) + return nil }