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 }