github.com/mvdan/u-root-coreutils@v0.0.0-20230122170626-c2eef2898555/pkg/mount/gpt/gpt.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  // Package gpt implements reading and writing of GUID Partition tables.
     6  //
     7  // GPTs are dumped in JSON format and written in same.  One complication is
     8  // that we frequently only want to write a very small subset of a GPT. For
     9  // example, we might only want to change the GUID. As it happens it is simpler
    10  // (and more useful) just to read and write the whole thing. In for a penny, in
    11  // for a pound.
    12  package gpt
    13  
    14  import (
    15  	"bytes"
    16  	"encoding/binary"
    17  	"encoding/json"
    18  	"fmt"
    19  	"hash/crc32"
    20  	"io"
    21  	"log"
    22  )
    23  
    24  const (
    25  	BlockSize         = 512
    26  	HeaderOff         = 0x200
    27  	HeaderSize        = 0x5c               // They claim it can vary. Give me a break.
    28  	Signature  uint64 = 0x5452415020494645 // ("EFI PART", 45h 46h 49h 20h 50h 41h 52h 54h on little-endian machines)
    29  	Revision          = 0x10000
    30  	MaxNPart          = 0x80
    31  )
    32  
    33  type GUID struct {
    34  	L  uint32
    35  	W1 uint16
    36  	W2 uint16
    37  	B  [8]byte
    38  }
    39  
    40  type (
    41  	MBR    [BlockSize]byte
    42  	Header struct {
    43  		Signature  uint64
    44  		Revision   uint32 // (for GPT version 1.0 (through at least UEFI version 2.7 (May 2017)), the value is 00h 00h 01h 00h)
    45  		HeaderSize uint32 // size in little endian (in bytes, usually 5Ch 00h 00h 00h or 92 bytes)
    46  		CRC        uint32 // CRC32/zlib of header (offset +0 up to header size) in little endian, with this field zeroed during calculation
    47  		Reserved   uint32 // ; must be zero
    48  		CurrentLBA uint64 // (location of this header copy)
    49  		BackupLBA  uint64 // (location of the other header copy)
    50  		FirstLBA   uint64 // usable LBA for partitions (primary partition table last LBA + 1)
    51  		LastLBA    uint64 // usable LBA (secondary partition table first LBA - 1)
    52  		DiskGUID   GUID   // (also referred as UUID on UNIXes)
    53  		PartStart  uint64 // LBA of array of partition entries (always 2 in primary copy)
    54  		NPart      uint32 // Number of partition entries in array
    55  		PartSize   uint32 // Size of a single partition entry (usually 80h or 128)
    56  		PartCRC    uint32 // CRC32/zlib of partition array in little endian
    57  	}
    58  )
    59  
    60  type (
    61  	PartAttr uint64
    62  	PartName [72]byte
    63  	Part     struct {
    64  		PartGUID   GUID     // Partition type GUID
    65  		UniqueGUID GUID     // Unique partition GUID
    66  		FirstLBA   uint64   // LBA (little endian)
    67  		LastLBA    uint64   // LBA (inclusive, usually odd)
    68  		Attribute  PartAttr // flags (e.g. bit 60 denotes read-only)
    69  		Name       PartName // Partition name (36 UTF-16LE code units)
    70  	}
    71  )
    72  
    73  type GPT struct {
    74  	Header
    75  	Parts []Part
    76  }
    77  
    78  func (g *GUID) String() string {
    79  	return fmt.Sprintf("%08x-%04x-%04x-%02x-%02x", g.L, g.W1, g.W2, g.B[0:2], g.B[2:])
    80  }
    81  
    82  // PartitionTable defines all the partition table information.
    83  // This includes the MBR and two GPTs. The GPTs are
    84  // similar but not identical, as they contain "pointers"
    85  // to each other in the BackupLBA in the Header.
    86  // The design is defective in that if a given Header has
    87  // an error, you are supposed to just assume that the BackupLBA
    88  // is intact, which is a pretty bogus assumption. This is why
    89  // you do standards like this in the open, not in hiding.
    90  // I hope someone from Intel is reading this.
    91  type PartitionTable struct {
    92  	MasterBootRecord *MBR
    93  	Primary          *GPT
    94  	Backup           *GPT
    95  }
    96  
    97  func (m *MBR) String() string {
    98  	b, err := json.MarshalIndent(m, "", "\t")
    99  	if err != nil {
   100  		log.Fatalf("Can't marshal %v", *m)
   101  	}
   102  	return string(b)
   103  }
   104  
   105  func (g *GPT) String() string {
   106  	b, err := json.MarshalIndent(g, "", "\t")
   107  	if err != nil {
   108  		log.Fatalf("Can't marshal %v", *g)
   109  	}
   110  	return string(b)
   111  }
   112  
   113  func (p *PartitionTable) String() string {
   114  	b, err := json.MarshalIndent(p, "", "\t")
   115  	if err != nil {
   116  		log.Fatalf("Can't marshal %v", *p)
   117  	}
   118  	return string(b)
   119  }
   120  
   121  func errAppend(err error, s string, a ...interface{}) error {
   122  	var p string
   123  	if err != nil {
   124  		p = err.Error() + "; "
   125  	}
   126  	return fmt.Errorf(p+s, a...)
   127  }
   128  
   129  // EqualHeader compares two headers and returns true if they match.
   130  // Those fields which by definition must differ are ignored.
   131  func EqualHeader(p, b Header) error {
   132  	var err error
   133  	if p.Signature != b.Signature {
   134  		err = errAppend(err, "p.Signature(%#x) != b.Signature(%#x)", p.Signature, b.Signature)
   135  	}
   136  	if p.Revision != b.Revision {
   137  		err = errAppend(err, "p.Revision(%v) != b.Revision(%v)", p.Revision, b.Revision)
   138  	}
   139  	if p.HeaderSize != b.HeaderSize {
   140  		err = errAppend(err, "p.HeaderSize(%v) != b.HeaderSize(%v)", p.HeaderSize, b.HeaderSize)
   141  	}
   142  	if p.CurrentLBA != b.BackupLBA {
   143  		err = errAppend(err, "p.CurrentLBA(%#x) != b.BackupLBA(%#x)", p.CurrentLBA, b.BackupLBA)
   144  	}
   145  	if p.BackupLBA != b.CurrentLBA {
   146  		err = errAppend(err, "p.BackupLBA(%#x) != b.CurrentLBA(%#x)", p.BackupLBA, b.CurrentLBA)
   147  	}
   148  	if p.FirstLBA != b.FirstLBA {
   149  		err = errAppend(err, "p.FirstLBA(%#x) != b.FirstLBA(%#x)", p.FirstLBA, b.FirstLBA)
   150  	}
   151  	if p.LastLBA != b.LastLBA {
   152  		err = errAppend(err, "p.LastLBA(%#x) != b.LastLBA(%#x)", p.LastLBA, b.LastLBA)
   153  	}
   154  	if p.DiskGUID != b.DiskGUID {
   155  		err = errAppend(err, "p.DiskGUID(%#x) != b.DiskGUID(%#x)", p.DiskGUID, b.DiskGUID)
   156  	}
   157  	if p.NPart != b.NPart {
   158  		err = errAppend(err, "p.NPart(%v) != b.NPart(%v)", p.NPart, b.NPart)
   159  	}
   160  	if p.PartSize != b.PartSize {
   161  		err = errAppend(err, "p.PartSize(%v) != b.PartSize(%v)", p.PartSize, b.PartSize)
   162  	}
   163  	return err
   164  }
   165  
   166  func EqualPart(p, b Part) (err error) {
   167  	if p.PartGUID != b.PartGUID {
   168  		err = errAppend(err, "p.PartGUID(%#x) != b.PartGUID(%#x)", p.PartGUID, b.PartGUID)
   169  	}
   170  	if p.UniqueGUID != b.UniqueGUID {
   171  		err = errAppend(err, "p.UniqueGUID(%#x) != b.UniqueGUID(%#x)", p.UniqueGUID, b.UniqueGUID)
   172  	}
   173  	if p.FirstLBA != b.FirstLBA {
   174  		err = errAppend(err, "p.FirstLBA(%#x) != b.FirstLBA(%#x)", p.FirstLBA, b.FirstLBA)
   175  	}
   176  	if p.LastLBA != b.LastLBA {
   177  		err = errAppend(err, "p.LastLBA(%#x) != b.LastLBA(%#x)", p.LastLBA, b.LastLBA)
   178  	}
   179  	// TODO: figure out what Attributes actually matter. We're not able to tell what differences
   180  	// matter and what differences don't.
   181  	if false && p.Attribute != b.Attribute {
   182  		err = errAppend(err, "p.Attribute(%#x) != b.Attribute(%#x)", p.Attribute, b.Attribute)
   183  	}
   184  	if p.Name != b.Name {
   185  		err = errAppend(err, "p.Name(%#x) != b.Name(%#x)", p.Name, b.Name)
   186  	}
   187  	return err
   188  }
   189  
   190  // EqualParts compares the Parts arrays from two GPTs
   191  // and returns an error if they differ.
   192  // If they length differs we just give up, since there's no way
   193  // to know which should have matched.
   194  // Otherwise, we do a 1:1 comparison.
   195  func EqualParts(p, b *GPT) (err error) {
   196  	if len(p.Parts) != len(b.Parts) {
   197  		return fmt.Errorf("primary Number of partitions (%d) differs from Backup (%d)", len(p.Parts), len(b.Parts))
   198  	}
   199  	for i := range p.Parts {
   200  		if e := EqualPart(p.Parts[i], b.Parts[i]); e != nil {
   201  			err = errAppend(err, "Partition %d: %v", i, e)
   202  		}
   203  	}
   204  	return err
   205  }
   206  
   207  // Table reads a GPT table at the given offset.  It checks that
   208  // the Signature, Revision, HeaderSize, and MaxPart are reasonable. It
   209  // also verifies the header and partition table CRC32 values.
   210  func Table(r io.ReaderAt, off int64) (*GPT, error) {
   211  	which := "Primary"
   212  	if off != BlockSize {
   213  		which = "Backup"
   214  	}
   215  	g := &GPT{}
   216  	if err := binary.Read(io.NewSectionReader(r, off, HeaderSize), binary.LittleEndian, &g.Header); err != nil {
   217  		return nil, err
   218  	}
   219  
   220  	if g.Signature != Signature {
   221  		return nil, fmt.Errorf("%s GPT signature invalid (%x), needs to be %x", which, g.Signature, Signature)
   222  	}
   223  	if g.Revision != Revision {
   224  		return nil, fmt.Errorf("%s GPT revision (%x) is not supported value (%x)", which, g.Revision, Revision)
   225  	}
   226  	if g.HeaderSize != HeaderSize {
   227  		return nil, fmt.Errorf("%s GPT HeaderSize (%x) is not supported value (%x)", which, g.HeaderSize, HeaderSize)
   228  	}
   229  	if g.NPart > MaxNPart {
   230  		return nil, fmt.Errorf("%s GPT MaxNPart (%x) is above maximum of %x", which, g.NPart, MaxNPart)
   231  	}
   232  
   233  	// Read in all the partition data and check the hash.
   234  	// Since the partition hash is included in the header CRC,
   235  	// it's sensible to check it first.
   236  	s := int64(g.PartSize)
   237  	partBlocks := make([]byte, int64(g.NPart)*s)
   238  	n, err := r.ReadAt(partBlocks, int64(g.PartStart)*BlockSize)
   239  	if n != len(partBlocks) || err != nil {
   240  		return nil, fmt.Errorf("%s Reading partitions: Wanted %d bytes, got %d: %v", which, n, len(partBlocks), err)
   241  	}
   242  	if h := crc32.ChecksumIEEE(partBlocks); h != g.PartCRC {
   243  		return g, fmt.Errorf("%s Partition CRC: Header %v, computed checksum is %08x, header has %08x", which, g, h, g.PartCRC)
   244  	}
   245  
   246  	hdr := make([]byte, g.HeaderSize)
   247  	n, err = r.ReadAt(hdr, off)
   248  	if n != len(hdr) || err != nil {
   249  		return nil, fmt.Errorf("%s Reading Header: Wanted %d bytes, got %d: %v", which, n, len(hdr), err)
   250  	}
   251  	// Zap the checksum in the header to 0.
   252  	copy(hdr[16:], []byte{0, 0, 0, 0})
   253  	if h := crc32.ChecksumIEEE(hdr); h != g.CRC {
   254  		return g, fmt.Errorf("%s Header CRC: computed checksum is %08x, header has %08x", which, h, g.CRC)
   255  	}
   256  
   257  	// Now read in the partition table entries.
   258  	g.Parts = make([]Part, g.NPart)
   259  
   260  	for i := range g.Parts {
   261  		if err := binary.Read(io.NewSectionReader(r, int64(g.PartStart*BlockSize)+int64(i)*s, s), binary.LittleEndian, &g.Parts[i]); err != nil {
   262  			return nil, fmt.Errorf("%s GPT partition %d failed: %v", which, i, err)
   263  		}
   264  	}
   265  
   266  	return g, nil
   267  }
   268  
   269  // Write writes the MBR and primary and backup GPTs to w.
   270  func Write(w io.WriterAt, p *PartitionTable) error {
   271  	if _, err := w.WriteAt(p.MasterBootRecord[:], 0); err != nil {
   272  		return err
   273  	}
   274  	if err := writeGPT(w, p.Primary); err != nil {
   275  		return err
   276  	}
   277  
   278  	if err := writeGPT(w, p.Backup); err != nil {
   279  		return err
   280  	}
   281  	return nil
   282  }
   283  
   284  // Write writes the GPT to w. It generates the partition and header CRC before writing.
   285  func writeGPT(w io.WriterAt, g *GPT) error {
   286  	// The maximum extent is NPart * PartSize
   287  	h := make([]byte, uint64(g.NPart*g.PartSize))
   288  	s := int64(g.PartSize)
   289  	for i := int64(0); i < int64(g.NPart); i++ {
   290  		var b bytes.Buffer
   291  		if err := binary.Write(&b, binary.LittleEndian, &g.Parts[i]); err != nil {
   292  			return err
   293  		}
   294  		copy(h[i*s:], b.Bytes())
   295  	}
   296  
   297  	ps := int64(g.PartStart * BlockSize)
   298  	if _, err := w.WriteAt(h, ps); err != nil {
   299  		return fmt.Errorf("writing %d bytes of partition table at %v: %v", len(h), ps, err)
   300  	}
   301  
   302  	g.PartCRC = crc32.ChecksumIEEE(h[:])
   303  	g.CRC = 0
   304  	var b bytes.Buffer
   305  	if err := binary.Write(&b, binary.LittleEndian, &g.Header); err != nil {
   306  		return err
   307  	}
   308  
   309  	var block [BlockSize]byte
   310  	copy(block[:], b.Bytes())
   311  	g.CRC = crc32.ChecksumIEEE(block[0:g.HeaderSize])
   312  
   313  	b.Reset()
   314  	if err := binary.Write(&b, binary.LittleEndian, g.CRC); err != nil {
   315  		return err
   316  	}
   317  	copy(block[16:], b.Bytes())
   318  
   319  	_, err := w.WriteAt(block[:], int64(g.CurrentLBA*BlockSize))
   320  	return err
   321  }
   322  
   323  // New reads in the MBR, primary and backup GPT from a disk and returns a pointer to them.
   324  // There are some checks it can apply. It can return with a
   325  // one or more headers AND an error. Sorry. Experience with some real USB sticks
   326  // is showing that we need to return data even if there are some things wrong.
   327  func New(r io.ReaderAt) (*PartitionTable, error) {
   328  	p := &PartitionTable{}
   329  	mbr := &MBR{}
   330  	n, err := r.ReadAt(mbr[:], 0)
   331  	if n != BlockSize || err != nil {
   332  		return p, err
   333  	}
   334  	p.MasterBootRecord = mbr
   335  	g, err := Table(r, HeaderOff)
   336  	// If we can't read the table it's kinda hard to find the backup.
   337  	// Bit of a flaw in the design, eh?
   338  	// "We can't recover the backup from the error with the primary because there
   339  	// was an error in the primary"
   340  	// uh, what?
   341  	if err != nil {
   342  		return p, err
   343  	}
   344  	p.Primary = g
   345  
   346  	b, err := Table(r, int64(g.BackupLBA*BlockSize))
   347  	if err != nil {
   348  		// you go to war with the army you have
   349  		return p, err
   350  	}
   351  
   352  	if err := EqualHeader(g.Header, b.Header); err != nil {
   353  		return p, fmt.Errorf("primary GPT and backup GPT header differ: %v", err)
   354  	}
   355  
   356  	if g.CRC == b.CRC {
   357  		return p, fmt.Errorf("primary (%v) and backup (%v) header CRC (%x) are the same and should differ", g.Header, b.Header, g.CRC)
   358  	}
   359  
   360  	p.Backup = b
   361  	return p, EqualParts(g, b)
   362  }
   363  
   364  // GetBlockSize returns the block size of device.
   365  func GetBlockSize(device string) (int, error) {
   366  	// TODO: scan device to determine block size.
   367  	return BlockSize, nil
   368  }