blob: c3a908cda7a77cb4ddd15981baddad42be4ab8bf [file] [log] [blame]
// Copyright 2017 The Fuchsia 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 is an implementation of GUID Partition Table read and write. It
// is based on the UEFI specification v2.6.
package gpt
import (
"bytes"
"crypto/rand"
"encoding/binary"
"encoding/hex"
"errors"
"fmt"
"hash/crc32"
"io"
"math"
"os"
"strings"
"unicode/utf16"
"thinfs/mbr"
)
// ErrInvalidGPT aggregates zero or more errors that occur during validation
type ErrInvalidGPT []error
func (e ErrInvalidGPT) Error() string {
errStrings := make([]string, len(e))
for i, err := range e {
errStrings[i] = err.Error()
}
return "gpt: invalid GPT: " + strings.Join(errStrings, " and ")
}
var (
// ErrInvalidSignature indicates that the GPT contained an invalid signature
ErrInvalidSignature = errors.New("gpt: invalid signature")
// ErrInvalidAddress indicates that an LBA address in a header or partition
// table points to an invalid location, either overlapping or out of range.
ErrInvalidAddress = errors.New("gpt: invalid address")
// ErrHeaderCRC indicates that a header contained an invalid CRC
ErrHeaderCRC = errors.New("gpt: bad header CRC")
// ErrUnsupportedPlatform is returned by functions that are not implemented on
// the host plaform
ErrUnsupportedPlatform = errors.New("gpt: unsupported platform")
)
// GUID is a 128 bit globally unique identifier
type GUID struct {
TimeLow uint32
TimeMid uint16
TimeHi uint16
SeqHi byte
SeqLo byte
Node [6]byte
}
// NewGUID constructs a GUID from a string in hexidecimal form, with arbitrary
// splits containing hyphens.
func NewGUID(s string) (GUID, error) {
h := strings.Join(strings.Split(s, "-"), "")
b := make([]byte, 16)
_, err := hex.Decode(b, []byte(h))
if err != nil {
return GUID{}, err
}
var g GUID
return g, binary.Read(bytes.NewReader(b), binary.BigEndian, &g)
}
// NewRandomGUID generates a new entirely random GUID
func NewRandomGUID() GUID {
// TODO(raggi): strictly speaking this isn't conformant to a paticular GUID
// version, but it is also reasonable for the use case
var g GUID
err := binary.Read(rand.Reader, binary.LittleEndian, &g)
if err != nil {
panic(err)
}
return g
}
func (g GUID) String() string {
var buf bytes.Buffer
binary.Write(&buf, binary.BigEndian, g)
b := buf.Bytes()
return fmt.Sprintf("%X-%X-%X-%X-%X", b[0:4], b[4:6], b[6:8], b[8:10], b[10:16])
}
// IsZero returns true if all fields in the GUID are zero
func (g GUID) IsZero() bool {
if g.TimeLow != 0 || g.TimeMid != 0 || g.TimeHi != 0 || g.SeqHi != 0 || g.SeqLo != 0 {
return false
}
for _, b := range g.Node {
if b != 0 {
return false
}
}
return true
}
// Name looks up the guid in the GUIDS map, and returns the name if found,
// otherwise it returns the standard string representation.
func (g GUID) Name() string {
for name, ng := range GUIDS {
if ng == g {
return name
}
}
return g.String()
}
// Signature is the representation of a GPT signature, normally `EFI PART`
type Signature [8]byte
func (s Signature) String() string {
return string(s[:])
}
// EFISignature is the default EFI Signature for GPT `EFI PART`
var EFISignature = Signature{'E', 'F', 'I', ' ', 'P', 'A', 'R', 'T'}
// Revision is the 2 byte representation of a GPT revision number
type Revision [4]byte
func (r Revision) String() string {
return fmt.Sprintf("%d.%d", binary.LittleEndian.Uint16(r[2:]), binary.LittleEndian.Uint16(r[0:2]))
}
// EFIRevision is the 1.0 EFI revision
var EFIRevision = Revision{0, 0, 1, 0}
// HeaderSize is the byte size of a GPT Header
const HeaderSize = 92
// Header is the Go struct represtation of a GPT header
type Header struct {
Signature Signature
Revision Revision
HeaderSize uint32
HeaderCRC32 uint32
Reserved [4]byte
MyLBA uint64
AlternateLBA uint64
FirstUsableLBA uint64
LastUsableLBA uint64
DiskGUID GUID
PartitionEntryLBA uint64
NumberOfPartitionEntries uint32
SizeOfPartitionEntry uint32
PartitionEntryArrayCRC32 uint32
}
// ReadFrom reads from the given reader into the reciever header. If an error
// occurs, the returned bytes read may be incorrect.
func (h *Header) ReadFrom(r io.Reader) (int64, error) {
return HeaderSize, binary.Read(r, binary.LittleEndian, h)
}
// WriteTo writes the header to the given writer. If an error ocurrs, the
// returned bytes written may be incorrect.
func (h *Header) WriteTo(w io.Writer) (int64, error) {
return HeaderSize, binary.Write(w, binary.LittleEndian, h)
}
func (h Header) String() string {
var b bytes.Buffer
fmt.Fprintf(&b, "Signature: %s\n", h.Signature)
fmt.Fprintf(&b, "Revision: %s\n", h.Revision)
fmt.Fprintf(&b, "HeaderSize: %d\n", h.HeaderSize)
fmt.Fprintf(&b, "HeaderCRC32: %d\n", h.HeaderCRC32)
fmt.Fprintf(&b, "MyLBA: %d\n", h.MyLBA)
fmt.Fprintf(&b, "AlternateLBA: %d\n", h.AlternateLBA)
fmt.Fprintf(&b, "FirstUsableLBA: %d\n", h.FirstUsableLBA)
fmt.Fprintf(&b, "LastUsableLBA: %d\n", h.LastUsableLBA)
fmt.Fprintf(&b, "DiskGUID: %s\n", h.DiskGUID)
fmt.Fprintf(&b, "PartitionEntryLBA: %d\n", h.PartitionEntryLBA)
fmt.Fprintf(&b, "NumberOfPartitionEntries: %d\n", h.NumberOfPartitionEntries)
fmt.Fprintf(&b, "SizeOfPartitionEntry: %d\n", h.SizeOfPartitionEntry)
fmt.Fprintf(&b, "PartitionEntryArrayCRC32: %d\n", h.PartitionEntryArrayCRC32)
return b.String()
}
// MinPartitionEntryArraySize is the minimum allowed size of a GPT partition array
const MinPartitionEntryArraySize = 16384
// PartitionName is a 32 character string in utf-16
type PartitionName [72]byte
// NewPartitionName constructs a partition name from the given string (encodes
// it in utf16 and truncates it to 32 characters.
func NewPartitionName(s string) PartitionName {
var b bytes.Buffer
var pn PartitionName
binary.Write(&b, binary.LittleEndian, utf16.Encode([]rune(s)))
copy(pn[:], b.Bytes())
return pn
}
func (pn PartitionName) String() string {
var chars []uint16
for i := 0; i < len(pn); i += 2 {
if pn[i] == 0 && pn[i+1] == 0 {
break
}
chars = append(chars, binary.LittleEndian.Uint16(pn[i:i+2]))
}
return string(utf16.Decode(chars))
}
// PartitionEntrySize is the size of the partition entry structure
const PartitionEntrySize = 128
// PartitionEntry is the Go structure representation of a partition in GPT
type PartitionEntry struct {
PartitionTypeGUID GUID
UniquePartitionGUID GUID
StartingLBA uint64
EndingLBA uint64
Attributes uint64
PartitionName PartitionName
}
// ReadFrom reads from the given reader into the reciever PartitionEntry. If an
// error occurs, the returned bytes read may be incorrect.
func (p *PartitionEntry) ReadFrom(r io.Reader) (int64, error) {
return PartitionEntrySize, binary.Read(r, binary.LittleEndian, p)
}
// WriteTo writes the PartitionEntry to the given writer.
func (p *PartitionEntry) WriteTo(w io.Writer) (int64, error) {
return PartitionEntrySize, binary.Write(w, binary.LittleEndian, p)
}
// IsZero returns true if the partition entry is unused
func (p *PartitionEntry) IsZero() bool {
if p.UniquePartitionGUID == GUIDUnused {
return true
}
return false
}
func (p PartitionEntry) String() string {
var b bytes.Buffer
fmt.Fprintf(&b, "PartitionTypeGUID: %s\n", p.PartitionTypeGUID.Name())
fmt.Fprintf(&b, "UniquePartitionGUID: %s\n", p.UniquePartitionGUID)
fmt.Fprintf(&b, "StartingLBA: %d\n", p.StartingLBA)
fmt.Fprintf(&b, "EndingLBA: %d\n", p.EndingLBA)
fmt.Fprintf(&b, "Attributes: %d\n", p.Attributes)
fmt.Fprintf(&b, "PartitionName: %s\n", p.PartitionName)
return b.String()
}
// Partition Attributes
const (
RequiredPartition = 0
NoBlockIOProtocol = 1
LegacyBIOSBootable = 2
MicrosoftReadOnly = 60
MicrosoftShadowCopy = 61
MicrosoftHidden = 62
MicrosoftNoDriveLetter = 63
)
func mustNewGUID(s string) GUID {
g, err := NewGUID(s)
if err != nil {
panic(err)
}
return g
}
// PartitionGUIDs:
var (
GUIDUnused = mustNewGUID("00000000-0000-0000-0000-000000000000")
GUIDMBR = mustNewGUID("024DEE41-33E7-11D3-9D69-0008C781F39F")
GUIDEFI = mustNewGUID("C12A7328-F81F-11D2-BA4B-00A0C93EC93B")
GUIDBIOS = mustNewGUID("21686148-6449-6E6F-744E-656564454649")
GUIDIntelFastFlash = mustNewGUID("D3BFE2DE-3DAF-11DF-BA40-E3A556D89593")
GUIDSonyBoot = mustNewGUID("F4019732-066E-4E12-8273-346C5641494F")
GUIDLenovoBoot = mustNewGUID("BFBFAFE7-A34F-448A-9A5B-6213EB736C22")
GUIDAppleHFSPlus = mustNewGUID("48465300-0000-11AA-AA11-00306543ECAC")
GUIDAppleUFS = mustNewGUID("55465300-0000-11AA-AA11-00306543ECAC")
GUIDAppleBoot = mustNewGUID("426F6F74-0000-11AA-AA11-00306543ECAC")
GUIDAppleRaid = mustNewGUID("52414944-0000-11AA-AA11-00306543ECAC")
GUIDAppleOfflineRAID = mustNewGUID("52414944-5F4F-11AA-AA11-00306543ECAC")
GUIDAppleLabel = mustNewGUID("4C616265-6C00-11AA-AA11-00306543ECAC")
GUIDFuchsiaSystem = mustNewGUID("606B000B-B7C7-4653-A7D5-B737332C899D")
GUIDFuchsiaData = mustNewGUID("08185F0C-892D-428A-A789-DBEEC8F55E6A")
GUIDFuchsiaBlob = mustNewGUID("2967380E-134C-4CBB-B6DA-17E7CE1CA45D")
GUIDFuchsiaFVM = mustNewGUID("41D0E340-57E3-954E-8C1E-17ECAC44CFF5")
GUIDFuchsiaZirconA = mustNewGUID("DE30CC86-1F4A-4A31-93C4-66F147D33E05")
GUIDFuchsiaZirconB = mustNewGUID("23CC04DF-C278-4CE7-8471-897D1A4BCDF7")
GUIDFuchsiaZirconR = mustNewGUID("A0E5CF57-2DEF-46BE-A80C-A2067C37CD49")
)
// GUIDS contains a map of known GUIDS to their names.
var GUIDS = map[string]GUID{
// TODO(raggi): make / find / use a generator to manage this list and the
// above
"unused": GUIDUnused,
"mbr": GUIDMBR,
"efi": GUIDEFI,
"bios": GUIDBIOS,
"intel-fast-flash": GUIDIntelFastFlash,
"sony-boot": GUIDSonyBoot,
"lenovo-boot": GUIDLenovoBoot,
"apple-hfs-plus": GUIDAppleHFSPlus,
"apple-ufs": GUIDAppleUFS,
"apple-boot": GUIDAppleBoot,
"apple-raid": GUIDAppleRaid,
"apple-offline-raid": GUIDAppleOfflineRAID,
"apple-label": GUIDAppleLabel,
"fuchsia-system": GUIDFuchsiaSystem,
"fuchsia-data": GUIDFuchsiaData,
"fuchsia-blob": GUIDFuchsiaBlob,
"zircon-a": GUIDFuchsiaZirconA,
"zircon-b": GUIDFuchsiaZirconB,
"zircon-r": GUIDFuchsiaZirconR,
}
// ReadHeader reads a single GPT header from r.
func ReadHeader(r io.Reader) (Header, error) {
var h Header
_, err := h.ReadFrom(r)
return h, err
}
// ReadPartitionEntry reads a single GPT PartitionEntry from r.
func ReadPartitionEntry(r io.Reader) (PartitionEntry, error) {
var p PartitionEntry
return p, binary.Read(r, binary.LittleEndian, &p)
}
// PartitionArray is an array of PartitionEntry
type PartitionArray []PartitionEntry
// WriteTo implements io.WriterTo for PartitionArray. Note that it writes only
// the partition entries stored in the receiver array. Callers are responsible
// for any necessary zero-ing of non-present zero partition entries.
func (pa PartitionArray) WriteTo(w io.Writer) (int64, error) {
var total int64
for _, pe := range pa {
n, err := pe.WriteTo(w)
total += n
if err != nil {
return total, err
}
}
return total, nil
}
// PartitionTable is a header followed by an array of partiton entries
type PartitionTable struct {
Header
Partitions PartitionArray
}
// ComputePartitionArrayCRC32 calculates the CRC32 of the contained partition
// array. It does not write the result back to the partition table.
func (pt PartitionTable) ComputePartitionArrayCRC32() uint32 {
partitionTableCRC32 := crc32.NewIEEE()
for _, p := range pt.Partitions {
if _, err := p.WriteTo(partitionTableCRC32); err != nil {
panic(err)
}
}
// write any left over empty partition entries
var pe PartitionEntry
for i := uint32(len(pt.Partitions)); i < pt.NumberOfPartitionEntries; i++ {
if _, err := pe.WriteTo(partitionTableCRC32); err != nil {
panic(err)
}
}
return partitionTableCRC32.Sum32()
}
// ComputeHeaderCRC32 calculates the CRC32 of the header. Users may need to call
// ComputePartitionArrayCRC32 before calling this method. It does not write the
// result back to the partition table.
func (pt PartitionTable) ComputeHeaderCRC32() uint32 {
headerCRC32 := crc32.NewIEEE()
pt.HeaderCRC32 = 0
if _, err := pt.WriteTo(headerCRC32); err != nil {
panic(err)
}
return headerCRC32.Sum32()
}
func (pt PartitionTable) String() string {
var b bytes.Buffer
fmt.Fprintf(&b, "%s\n", pt.Header)
for i, p := range pt.Partitions {
if p.IsZero() {
continue
}
fmt.Fprintf(&b, "Partition %d:\n%s\n", i, p)
}
return b.String()
}
// PartitionArrayPad calculates the amount of space that must be zero-written
// between the last partition contained in the PartitionArray and the end of the
// on-disk partition array size.
func (pt PartitionTable) PartitionArrayPad() int64 {
return int64(pt.NumberOfPartitionEntries*pt.SizeOfPartitionEntry) - int64(len(pt.Partitions)*int(pt.SizeOfPartitionEntry))
}
// GPT is a wrapper around a disks MBR, Primary and Backup PartitionTable and
// block size metadata.
type GPT struct {
MBR mbr.MBR
Primary PartitionTable
Backup PartitionTable
logical, physical, optimal, size uint64 // block, transfer and sizes in bytes
}
// ReadGPT reads a GPT from r using the given logical block size.
func ReadGPT(r io.ReadSeeker, blockSize uint64, diskSize uint64) (GPT, error) {
var g GPT
if _, err := g.MBR.ReadFrom(r); err != nil {
return g, err
}
if _, err := r.Seek(int64(blockSize), io.SeekStart); err != nil {
return g, err
}
if _, err := g.Primary.ReadFrom(r); err != nil {
return g, err
}
if g.Primary.ComputeHeaderCRC32() == g.Primary.HeaderCRC32 {
ptLoc := int64(g.Primary.PartitionEntryLBA * uint64(blockSize))
if _, err := r.Seek(ptLoc, io.SeekStart); err != nil {
return g, err
}
// Avoid divide by zero, recover any possibly readable data..
if g.Primary.SizeOfPartitionEntry == 0 {
g.Primary.SizeOfPartitionEntry = PartitionEntrySize
}
// the minimium number of partitions is defined by the minimum allowed size
numPartitions := MinPartitionEntryArraySize / g.Primary.SizeOfPartitionEntry
if numPartitions < g.Primary.NumberOfPartitionEntries {
numPartitions = g.Primary.NumberOfPartitionEntries
}
g.Primary.Partitions = make([]PartitionEntry, numPartitions)
pePad := int64(g.Primary.SizeOfPartitionEntry - PartitionEntrySize)
for i := range g.Primary.Partitions {
if _, err := g.Primary.Partitions[i].ReadFrom(r); err != nil {
return g, err
}
if _, err := r.Seek(pePad, io.SeekCurrent); err != nil {
return g, err
}
}
}
lastBlock := int64(diskSize - uint64(blockSize))
if _, err := r.Seek(lastBlock, io.SeekStart); err != nil {
return g, err
}
if _, err := g.Backup.ReadFrom(r); err != nil {
return g, err
}
if g.Backup.ComputeHeaderCRC32() == g.Backup.HeaderCRC32 {
ptLoc := int64(g.Backup.PartitionEntryLBA * uint64(blockSize))
if _, err := r.Seek(ptLoc, io.SeekStart); err != nil {
return g, err
}
// Avoid divide by zero, recover any possibly readable data..
if g.Backup.SizeOfPartitionEntry == 0 {
g.Backup.SizeOfPartitionEntry = PartitionEntrySize
}
numPartitions := MinPartitionEntryArraySize / g.Backup.SizeOfPartitionEntry
if numPartitions < g.Backup.NumberOfPartitionEntries {
numPartitions = g.Backup.NumberOfPartitionEntries
}
g.Backup.Partitions = make([]PartitionEntry, numPartitions)
pePad := int64(g.Backup.SizeOfPartitionEntry - PartitionEntrySize)
for i := range g.Backup.Partitions {
if _, err := g.Backup.Partitions[i].ReadFrom(r); err != nil {
return g, err
}
if _, err := r.Seek(pePad, io.SeekCurrent); err != nil {
return g, err
}
}
}
return g, nil
}
// nullReader is a convenience for cross-platform io.copy of 0 bytes to a target
// writer.
type nullReader struct{}
func (nr nullReader) Read(b []byte) (int, error) {
for i := range b {
b[i] = 0
}
return len(b), nil
}
func seekOrWrite(w io.Writer, n int64) (int64, error) {
if seeker, canSeek := w.(io.Seeker); canSeek {
_, err := seeker.Seek(n, io.SeekCurrent)
return 0, err
}
return io.CopyN(w, nullReader{}, n)
}
// WriteTo implements io.WriterTo for writing out the GPT to a target writer. It
// expects that update was called with correct values beforehand, but it will,
// if otherwise un-set try to detect reasonable values for logical and physical
// block sizes. If the target writer does not support detection of these sizes,
// and they were not supplied to Update, the write will make conventional
// assumptions (logical block size: 512, physical block size: 4096). Note: If
// the given writer does not implement io.Seeker, then the write will zero out
// all non-partition data regions. If the argument implements io.Seeker, then
// all non-partition table data will remain untouched.
func (g *GPT) WriteTo(w io.Writer) (int64, error) {
if f, ok := w.(*os.File); ok {
if g.logical == 0 {
g.logical, _ = GetLogicalBlockSize(f)
}
if g.physical == 0 {
g.physical, _ = GetPhysicalBlockSize(f)
}
// TODO(raggi): try to find out optimal transfer sizes
}
if g.logical == 0 {
g.logical = FallbackLogicalBlockSize
}
if g.physical == 0 {
g.physical = FallbackPhysicalBlockSize
}
var total int64
if seeker, canSeek := w.(io.Seeker); canSeek {
if _, err := seeker.Seek(0, io.SeekStart); err != nil {
return total, err
}
}
// MBR...
n, err := g.MBR.WriteTo(w)
total += n
if err != nil {
return total, err
}
// write zero's up to the second block
n, err = io.CopyN(w, nullReader{}, total-int64(g.logical))
total += n
if err != nil {
return total, err
}
// Primary...
n, err = g.Primary.Header.WriteTo(w)
total += n
if err != nil {
return total, err
}
if total%int64(g.logical) != 0 {
n, err = io.CopyN(w, nullReader{}, int64(g.logical)-total%int64(g.logical))
total += n
if err != nil {
return total, err
}
}
n, err = g.Primary.Partitions.WriteTo(w)
total += n
if err != nil {
return total, err
}
n, err = io.CopyN(w, nullReader{}, g.Primary.PartitionArrayPad())
total += n
if err != nil {
return total, err
}
// Backup...
n, err = seekOrWrite(w, int64(g.Backup.PartitionEntryLBA*g.logical)-total)
total += n
if err != nil {
return total, err
}
n, err = g.Backup.Partitions.WriteTo(w)
total += n
if err != nil {
return total, err
}
n, err = io.CopyN(w, nullReader{}, g.Primary.PartitionArrayPad())
total += n
if err != nil {
return total, err
}
if total%int64(g.logical) != 0 {
n, err = io.CopyN(w, nullReader{}, int64(g.logical)-total%int64(g.logical))
total += n
if err != nil {
return total, err
}
}
n, err = g.Backup.Header.WriteTo(w)
total += n
if err != nil {
return total, err
}
if total%int64(g.logical) != 0 {
n, err = io.CopyN(w, nullReader{}, int64(g.logical)-total%int64(g.logical))
total += n
if err != nil {
return total, err
}
}
return total, err
}
func (g GPT) String() string {
var b bytes.Buffer
fmt.Fprintf(&b, "MBR:\n%s\n\n", g.MBR)
if g.Primary.Signature == EFISignature {
fmt.Fprintf(&b, "GPT Primary:\n%s\n\n", g.Primary)
}
if g.Backup.Signature == EFISignature {
fmt.Fprintf(&b, "GPT Backup:\n%s\n\n", g.Backup)
}
fmt.Fprintf(&b, "GPT Valid: %v\n", g.Validate() == nil)
return b.String()
}
// Update uses the provided geometry information, combined with the values
// already present in g to update the geometry and CRC fields for the tables and
// partitions in g. If the changes make laying out the partitions impossible, an
// error is returned. Subsequent calls to WriteTo will use the given values if
// non-zero. Note that partition re-alignment will only move partition starts
// forward, it will not preserve size, so callers must set partition starting
// positions appropriately using NextAlignedLBA to avoid this.
func (g *GPT) Update(blockSize, physicalBlockSize, optimalTransferLengthGranularity, diskSize uint64) error {
g.logical = blockSize
g.physical = physicalBlockSize
g.optimal = optimalTransferLengthGranularity
g.size = diskSize
// Various fields in the GPT have known constant values, they're setup first
g.Primary.Signature = EFISignature
g.Primary.Revision = EFIRevision
g.Primary.HeaderSize = HeaderSize
g.Primary.Reserved = [4]byte{}
g.Primary.SizeOfPartitionEntry = PartitionEntrySize
// The primary partition array always starts at logical block 2
g.Primary.PartitionEntryLBA = 2
// The number of non-zero partition entries
paSize := uint32(len(g.Primary.Partitions)) * g.Primary.SizeOfPartitionEntry
if paSize < MinPartitionEntryArraySize {
paSize = MinPartitionEntryArraySize
}
g.Primary.NumberOfPartitionEntries = uint32(paSize / g.Primary.SizeOfPartitionEntry)
// The first usable block is located after the end of the partition array
ptBlockSize := uint64(paSize) / blockSize
diskBlockSize := diskSize / blockSize
headerBlockSize := uint64(g.Primary.HeaderSize)
if headerBlockSize < blockSize {
headerBlockSize = blockSize
}
headerBlockSize = headerBlockSize / blockSize
g.Primary.MyLBA = 1
g.Primary.FirstUsableLBA = 2 + ptBlockSize
g.Primary.LastUsableLBA = diskBlockSize - ptBlockSize - uint64(headerBlockSize) - 1
g.Primary.AlternateLBA = diskBlockSize - 1
if g.Primary.DiskGUID.IsZero() {
g.Primary.DiskGUID = NewRandomGUID()
}
nextUsableLBA := g.Primary.FirstUsableLBA
for i := range g.Primary.Partitions {
var p = &g.Primary.Partitions[i]
if p.IsZero() {
continue
}
if p.StartingLBA == 0 {
p.StartingLBA = nextUsableLBA
}
p.StartingLBA = NextAlignedLBA(p.StartingLBA, blockSize, physicalBlockSize, optimalTransferLengthGranularity)
if p.EndingLBA <= p.StartingLBA {
return fmt.Errorf("partition %s has invalid geometry after alignment", p)
}
if p.EndingLBA > diskSize/blockSize {
return fmt.Errorf("partition %s extends beyond disk geometry", p)
}
nextUsableLBA = p.EndingLBA + 1
}
// Copy the updated primary data and partition entries to backup
g.Backup = g.Primary
// The backup header lives at the last block of the disk
g.Backup.MyLBA, g.Backup.AlternateLBA = g.Primary.AlternateLBA, g.Primary.MyLBA
g.Backup.PartitionEntryLBA = g.Backup.LastUsableLBA + 1
// Update the CRCs
for _, pt := range []*PartitionTable{&g.Primary, &g.Backup} {
pt.PartitionEntryArrayCRC32 = pt.ComputePartitionArrayCRC32()
pt.HeaderCRC32 = pt.ComputeHeaderCRC32()
}
return nil
}
// NextAlignedLBA takes a starting LBA, the logical, physical and optimal block
// sizes, and returns the next LBA that conforms the UEFI alignment
// specifications. Unknown values may be passed as 0, but logcal must have a
// reasonable value.
func NextAlignedLBA(nextLBA, logical, physical, optimal uint64) uint64 {
alignTo := logical
if physical > alignTo {
alignTo = physical
}
if optimal > alignTo {
alignTo = optimal
}
alignTo = alignTo / logical
if d := nextLBA % alignTo; d != 0 {
nextLBA = nextLBA + (alignTo - d)
}
return nextLBA
}
// AlignedRange takes a starting lba, size (in bytes), logical, physical and
// optimal block sizes and computes a well-aligned maximal range of block
// addresses starting after nextLBA, and ending to be at most one block larger
// than the requested size.
func AlignedRange(nextLBA, size, logical, physical, optimal uint64) (uint64, uint64) {
start := NextAlignedLBA(nextLBA, logical, physical, optimal)
end := NextAlignedLBA(start+upDiv(size, logical)+1, logical, physical, optimal) - 1
return start, end
}
// upDiv divides two uints, always rounding up.
func upDiv(a, b uint64) uint64 {
return uint64(math.Ceil(float64(a) / float64(b)))
}
// Validate runs a set of validation operations on the entire GPT, and if errors
// are found, returns them inside ErrInvalidGPT
func (g *GPT) Validate() error {
// TODO(raggi): the errors array might be better off as a struct that
// separates errors in primary from errors in backup, and then move the
// contents of this method onto PartitionTable and call once for each.
var errors ErrInvalidGPT
if g.Primary.Signature != EFISignature || g.Backup.Signature != EFISignature {
errors = append(errors, ErrInvalidSignature)
}
if g.Primary.MyLBA != 1 || g.Backup.AlternateLBA != 1 {
errors = append(errors, ErrInvalidAddress)
}
if g.Primary.AlternateLBA != g.Backup.MyLBA || g.Backup.AlternateLBA != g.Primary.MyLBA {
errors = append(errors, ErrInvalidAddress)
}
// Note that this also covers the partition table CRC32, as that is part of
// the calculation.
if g.Primary.ComputeHeaderCRC32() != g.Primary.HeaderCRC32 ||
g.Backup.ComputeHeaderCRC32() != g.Backup.HeaderCRC32 {
errors = append(errors, ErrHeaderCRC)
}
// TODO(raggi): although it is not required by EFI for booting, and thus not
// in the spec section about validation, there are rules about not producing
// overlapping partitions and over-extended partitions that are not checked
// here. They should be checked somewhere, but that would require a knowledge
// of the disk size and other such things.
if errors == nil {
return nil
}
if len(errors) == 0 {
return nil
}
return errors
}
// FallbackPhysicalBlockSize of 4096 is returned (with error) from
// GetPhysicalBlockSize as a sensible default assumption. Devices are tending
// toward this as a common physical sector size, and there isn't much to lose
// from this alignment as opposed to 512, the prior common value. By contrast,
// write performance can be significantly negatively affected by an alignment
// less than this on such disks.
const FallbackPhysicalBlockSize = 4096
// FallbackLogicalBlockSize of 512 is returned (with error) from
// GetLogicalBlockSize as a sensible default assumption.
const FallbackLogicalBlockSize = 512
// regularSize tries to stat the file, if the stat succeeds and the file is a
// regular file, it returns the files size, and true, otherwise 0 and false.
func regularSize(f *os.File) (int64, bool) {
info, err := f.Stat()
if err != nil {
return 0, false
}
if info.Mode().IsRegular() {
return info.Size(), true
}
return 0, false
}