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