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