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