pkg/vimg/partitions.go
package vimg
/**
* SPDX-License-Identifier: Apache-2.0
* Copyright 2020 vorteil.io Pty Ltd
*/
import (
"bytes"
"context"
"encoding/binary"
"hash/crc32"
"io"
"github.com/vorteil/vorteil/pkg/vio"
)
// Various build constants.
const (
SectorSize = 512
GPTSignature = 0x5452415020494645 // "EFI PART" (little-endian)
GPTHeaderSize = 92
MaximumGPTEntries = 128
GPTEntrySize = 128
GPTEntriesSectors = MaximumGPTEntries * GPTEntrySize / SectorSize
PrimaryGPTHeaderLBA = 1
PrimaryGPTHeaderOffset = SectorSize * PrimaryGPTHeaderLBA
PrimaryGPTEntriesLBA = PrimaryGPTHeaderLBA + 1
PrimaryGPTEntriesOffset = SectorSize * PrimaryGPTEntriesLBA
P0FirstLBA = PrimaryGPTEntriesLBA + GPTEntriesSectors
P0Offset = P0FirstLBA * SectorSize
)
var (
// OSPartitionName is the hardcoded name for the Vorteil OS partition in the GPT.
OSPartitionName = []byte{0x76, 0x0, 0x6f, 0x0, 0x72, 0x0, 0x74, 0x0, 0x65, 0x0,
0x69, 0x0, 0x6c, 0x0, 0x2d, 0x0, 0x6f, 0x0, 0x73, 0x0} // "vorteil-os" in utf16
// RootPartitionName is the hardcoded name for the Vorteil root file-system partition in the GPT.
RootPartitionName = []byte{0x76, 0x0, 0x6f, 0x0, 0x72, 0x0, 0x74, 0x0, 0x65, 0x0, 0x69, 0x0,
0x6c, 0x0, 0x2d, 0x0, 0x72, 0x0, 0x6f, 0x0, 0x6f, 0x0, 0x74, 0x0} // "vorteil-root" in utf16
// Part2UUID for second partition. used to define rooot partition in kernel args
Part2UUID = []byte{
0x7d, 0x44, 0x48, 0x40,
0x9d, 0xc0, 0x11, 0xd1,
0xb2, 0x45, 0x5f, 0xfd,
0xce, 0x74, 0xfa, 0xd2,
}
// Part2UUIDString string value of Part2UUID
Part2UUIDString = "4048447D-C09D-D111-B245-5FFDCE74FAD2"
)
func (b *Builder) writeGPT(ctx context.Context, w io.WriteSeeker) error {
err := b.writeMBR(ctx, w)
if err != nil {
return err
}
err = b.writePrimaryGPTHeader(ctx, w)
if err != nil {
return err
}
err = b.writePrimaryGPTEntries(ctx, w)
if err != nil {
return err
}
return nil
}
func (b *Builder) writeSecondaryGPT(ctx context.Context, w io.WriteSeeker) error {
err := b.writeSecondaryGPTEntries(ctx, w)
if err != nil {
return err
}
err = b.writeSecondaryGPTHeader(ctx, w)
if err != nil {
return err
}
return nil
}
func (b *Builder) writePartitionsContents(ctx context.Context, w io.WriteSeeker) error {
err := b.writeOS(ctx, w)
if err != nil {
return err
}
err = b.writeRoot(ctx, w)
if err != nil {
return err
}
return nil
}
func (b *Builder) writePartitions(ctx context.Context, w io.WriteSeeker) error {
err := b.writeGPT(ctx, w)
if err != nil {
return err
}
err = b.writePartitionsContents(ctx, w)
if err != nil {
return err
}
err = b.writeSecondaryGPT(ctx, w)
if err != nil {
return err
}
return nil
}
// ProtectiveMBR is the structure of a protective master boot record as it appears on disk.
type ProtectiveMBR struct {
Bootloader [446]byte
Status byte
_ byte // first head
_ byte // first sector
_ byte // cylinder first
PartitionType byte
_ byte // last head
_ byte // last sector
_ byte // last cylinder
FirstLBA uint32
TotalSectors uint32
_ [48]byte
MagicNumber [2]byte
}
func (b *Builder) writeMBR(ctx context.Context, w io.WriteSeeker) error {
err := ctx.Err()
if err != nil {
return err
}
_, err = w.Seek(0, io.SeekStart)
if err != nil {
return err
}
mbr := ProtectiveMBR{
Status: 0x7F,
PartitionType: 0xEE,
FirstLBA: 1,
MagicNumber: [2]byte{0x55, 0xAA},
TotalSectors: uint32(b.size/SectorSize) - 1,
}
copy(mbr.Bootloader[:], Bootloader)
err = binary.Write(w, binary.LittleEndian, &mbr)
if err != nil {
return err
}
return nil
}
// GPTHeader is the structure of a GUID Partition Table Header as it appears on disk.
type GPTHeader struct {
Signature uint64
Revision [4]byte
HeaderSize uint32
CRC uint32
_ uint32
CurrentLBA uint64
BackupLBA uint64
FirstUsableLBA uint64
LastUsableLBA uint64
GUID [16]byte
StartLBAParts uint64
NoOfParts uint32
SizePartEntry uint32
CRCParts uint32
_ [420]byte
}
// GPTEntry is the structure of a GUID Partition Table entry as it appears on disk.
type GPTEntry struct {
TypeGUID [16]byte
PartitionGUID [16]byte
FirstLBA uint64
LastLBA uint64
_ uint64 // attributes
Name [72]byte
}
func (b *Builder) writePrimaryGPTHeader(ctx context.Context, w io.WriteSeeker) error {
err := ctx.Err()
if err != nil {
return err
}
_, err = w.Seek(PrimaryGPTHeaderOffset, io.SeekStart)
if err != nil {
return err
}
hdr := GPTHeader{
Signature: GPTSignature,
Revision: [4]byte{0, 0, 1, 0},
HeaderSize: GPTHeaderSize,
CurrentLBA: PrimaryGPTHeaderLBA,
BackupLBA: uint64(b.secondaryGPTHeaderLBA),
FirstUsableLBA: P0FirstLBA,
LastUsableLBA: uint64(b.lastUsableLBA),
StartLBAParts: PrimaryGPTEntriesLBA,
NoOfParts: MaximumGPTEntries,
SizePartEntry: GPTEntrySize,
CRCParts: b.gptEntriesCRC,
}
copy(hdr.GUID[:], b.diskUID)
buf := new(bytes.Buffer)
_ = binary.Write(buf, binary.LittleEndian, hdr)
crc := crc32.NewIEEE()
_, _ = io.CopyN(crc, bytes.NewReader(buf.Bytes()), GPTHeaderSize)
hdr.CRC = crc.Sum32()
err = binary.Write(w, binary.LittleEndian, hdr)
if err != nil {
return err
}
return nil
}
func (b *Builder) writePrimaryGPTEntries(ctx context.Context, w io.WriteSeeker) error {
err := ctx.Err()
if err != nil {
return err
}
_, err = w.Seek(PrimaryGPTEntriesOffset, io.SeekStart)
if err != nil {
return err
}
_, err = io.Copy(w, bytes.NewReader(b.gptEntries))
if err != nil {
return err
}
return nil
}
func (b *Builder) writeSecondaryGPTHeader(ctx context.Context, w io.WriteSeeker) error {
err := ctx.Err()
if err != nil {
return err
}
_, err = w.Seek(b.secondaryGPTHeaderOffset, io.SeekStart)
if err != nil {
return err
}
hdr := GPTHeader{
Signature: GPTSignature,
Revision: [4]byte{0, 0, 1, 0},
HeaderSize: GPTHeaderSize,
CurrentLBA: uint64(b.secondaryGPTHeaderLBA),
BackupLBA: PrimaryGPTHeaderLBA,
FirstUsableLBA: P0FirstLBA,
LastUsableLBA: uint64(b.lastUsableLBA),
StartLBAParts: uint64(b.secondaryGPTEntriesLBA),
NoOfParts: MaximumGPTEntries,
SizePartEntry: GPTEntrySize,
CRCParts: b.gptEntriesCRC,
}
copy(hdr.GUID[:], b.diskUID)
buf := new(bytes.Buffer)
_ = binary.Write(buf, binary.LittleEndian, hdr)
crc := crc32.NewIEEE()
_, _ = io.CopyN(crc, bytes.NewReader(buf.Bytes()), GPTHeaderSize)
hdr.CRC = crc.Sum32()
err = binary.Write(w, binary.LittleEndian, hdr)
if err != nil {
return err
}
return nil
}
func (b *Builder) writeSecondaryGPTEntries(ctx context.Context, w io.WriteSeeker) error {
err := ctx.Err()
if err != nil {
return err
}
_, err = w.Seek(b.secondaryGPTEntriesOffset, io.SeekStart)
if err != nil {
return err
}
_, err = io.Copy(w, bytes.NewReader(b.gptEntries))
if err != nil {
return err
}
return nil
}
func (b *Builder) generateUID() ([]byte, error) {
buf := make([]byte, 16)
_, err := io.ReadFull(b.rng, buf)
if err != nil {
return nil, err
}
// NOTE: I wrote this a long time ago and cannot explain why I do these
// bitwise operations...
buf[6] = buf[6]&^0xf0 | 0x40
buf[8] = buf[8]&^0xc0 | 0x80
return buf, nil
}
func (b *Builder) generateGPTEntries() error {
var err error
b.diskUID, err = b.generateUID()
if err != nil {
return err
}
uid0, err := b.generateUID()
if err != nil {
return err
}
p0 := GPTEntry{
FirstLBA: uint64(b.osFirstLBA),
LastLBA: uint64(b.osLastLBA),
}
copy(p0.PartitionGUID[:], uid0)
copy(p0.Name[:], OSPartitionName)
p1 := GPTEntry{
TypeGUID: [16]byte{0xE3, 0xBC, 0x68, 0x4F, 0xCD, 0xE8,
0xB1, 0x4D, 0x96, 0xE7, 0xFB, 0xCA, 0xF9, 0x84, 0xB7, 0x09}, // Linux x86-64 root filesystem partition
FirstLBA: uint64(b.rootFirstLBA),
LastLBA: uint64(b.rootLastLBA),
}
copy(p1.PartitionGUID[:], Part2UUID)
copy(p1.Name[:], RootPartitionName)
entriesBuffer := new(bytes.Buffer)
_ = binary.Write(entriesBuffer, binary.LittleEndian, p0)
_ = binary.Write(entriesBuffer, binary.LittleEndian, p1)
b.gptEntries = entriesBuffer.Bytes()
crc := crc32.NewIEEE()
_, _ = io.Copy(crc, bytes.NewReader(b.gptEntries))
_, _ = io.CopyN(crc, vio.Zeroes, MaximumGPTEntries*GPTEntrySize-int64(len(b.gptEntries)))
b.gptEntriesCRC = crc.Sum32()
return nil
}