feat(initramfs): API for creating new partition tables (#227)
This commit is contained in:
parent
aa08f15659
commit
374343a883
@ -2,11 +2,14 @@
|
||||
package blockdevice
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/autonomy/talos/src/initramfs/pkg/blockdevice/table"
|
||||
"github.com/autonomy/talos/src/initramfs/pkg/blockdevice/table/gpt"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
@ -18,18 +21,41 @@ type BlockDevice struct {
|
||||
}
|
||||
|
||||
// Open initializes and returns a block device.
|
||||
func Open(devname string) (*BlockDevice, error) {
|
||||
// TODO(andrewrynhard): Use BLKGETSIZE ioctl to get the size.
|
||||
// TODO(andrewrynhard): Use BLKPBSZGET ioctl to get the physical sector size.
|
||||
// TODO(andrewrynhard): Use BLKSSZGET ioctl to get the logical sector size
|
||||
// and pass them into gpt as options.
|
||||
func Open(devname string, setters ...Option) (*BlockDevice, error) {
|
||||
opts := NewDefaultOptions(setters...)
|
||||
|
||||
bd := &BlockDevice{}
|
||||
|
||||
f, err := os.OpenFile(devname, os.O_RDWR, os.ModeDevice)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// TODO: Dynamically detect MBR/GPT.
|
||||
// TODO: Use BLKGETSIZE ioctl to get the size.
|
||||
// TODO: Use BLKPBSZGET ioctl to get the physical sector size.
|
||||
// TODO: Use BLKSSZGET ioctl to get the logical sector size.
|
||||
// and pass them into gpt as options.
|
||||
bd := &BlockDevice{
|
||||
table: gpt.NewGPT(devname, f),
|
||||
|
||||
if opts.CreateGPT {
|
||||
gpt := gpt.NewGPT(devname, f)
|
||||
table, e := gpt.New()
|
||||
if e != nil {
|
||||
return nil, e
|
||||
}
|
||||
bd.table = table
|
||||
} else {
|
||||
buf := make([]byte, 1)
|
||||
// PMBR protective entry starts at 446. The partition type is at offset
|
||||
// 4 from the start of the PMBR protective entry.
|
||||
_, err = f.ReadAt(buf, 450)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// For GPT, the partition type should be 0xee (EFI GPT).
|
||||
if bytes.Equal(buf, []byte{0xee}) {
|
||||
bd.table = gpt.NewGPT(devname, f)
|
||||
} else {
|
||||
return nil, errors.New("failed to find GUID partition table")
|
||||
}
|
||||
}
|
||||
|
||||
return bd, nil
|
||||
@ -46,10 +72,10 @@ func (bd *BlockDevice) PartitionTable() (table.PartitionTable, error) {
|
||||
return nil, fmt.Errorf("missing partition table")
|
||||
}
|
||||
|
||||
return bd.table, nil
|
||||
return bd.table, bd.table.Read()
|
||||
}
|
||||
|
||||
// RereadPartitionTable invokes the BLKRRPART ioctl have the kernel read the
|
||||
// RereadPartitionTable invokes the BLKRRPART ioctl to have the kernel read the
|
||||
// partition table.
|
||||
func (bd *BlockDevice) RereadPartitionTable(devname string) error {
|
||||
f, err := os.Open(devname)
|
||||
|
29
src/initramfs/pkg/blockdevice/bockdevice_options.go
Normal file
29
src/initramfs/pkg/blockdevice/bockdevice_options.go
Normal file
@ -0,0 +1,29 @@
|
||||
package blockdevice
|
||||
|
||||
// Options is the functional options struct.
|
||||
type Options struct {
|
||||
CreateGPT bool
|
||||
}
|
||||
|
||||
// Option is the functional option func.
|
||||
type Option func(*Options)
|
||||
|
||||
// WithNewGPT opens the blockdevice with a new GPT.
|
||||
func WithNewGPT(o bool) Option {
|
||||
return func(args *Options) {
|
||||
args.CreateGPT = o
|
||||
}
|
||||
}
|
||||
|
||||
// NewDefaultOptions initializes a Options struct with default values.
|
||||
func NewDefaultOptions(setters ...Option) *Options {
|
||||
opts := &Options{
|
||||
CreateGPT: false,
|
||||
}
|
||||
|
||||
for _, setter := range setters {
|
||||
setter(opts)
|
||||
}
|
||||
|
||||
return opts
|
||||
}
|
@ -2,6 +2,7 @@
|
||||
package gpt
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"os"
|
||||
"syscall"
|
||||
@ -13,6 +14,7 @@ import (
|
||||
"github.com/autonomy/talos/src/initramfs/pkg/blockdevice/table/gpt/header"
|
||||
"github.com/autonomy/talos/src/initramfs/pkg/blockdevice/table/gpt/partition"
|
||||
"github.com/google/uuid"
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
@ -105,6 +107,90 @@ func (gpt *GPT) Write() error {
|
||||
return gpt.Read()
|
||||
}
|
||||
|
||||
// New creates a new partition table and writes it to disk.
|
||||
func (gpt *GPT) New() (table.PartitionTable, error) {
|
||||
// Seek to the end to get the size.
|
||||
size, err := gpt.f.Seek(0, 2)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Reset and seek to the beginning.
|
||||
_, err = gpt.f.Seek(0, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
h, err := gpt.newHeader(size)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pmbr, err := gpt.newPMBR(h)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
gpt.header = h
|
||||
gpt.partitions = []table.Partition{}
|
||||
|
||||
written, err := gpt.f.WriteAt(pmbr[446:], 446)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to write the protective MBR")
|
||||
}
|
||||
if written != len(pmbr[446:]) {
|
||||
return nil, errors.Errorf("expected a write %d bytes, got %d", written, len(pmbr[446:]))
|
||||
}
|
||||
|
||||
if err := gpt.Write(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return gpt, nil
|
||||
}
|
||||
|
||||
func (gpt *GPT) newHeader(size int64) (*header.Header, error) {
|
||||
h := &header.Header{}
|
||||
h.Signature = "EFI PART"
|
||||
h.Revision = binary.LittleEndian.Uint32([]byte{0x00, 0x00, 0x01, 0x00})
|
||||
h.Size = header.HeaderSize
|
||||
h.Reserved = binary.LittleEndian.Uint32([]byte{0x00, 0x00, 0x00, 0x00})
|
||||
h.CurrentLBA = 1
|
||||
h.BackupLBA = uint64(size/int64(gpt.lba.PhysicalBlockSize) - 1)
|
||||
h.FirstUsableLBA = 34
|
||||
h.LastUsableLBA = h.BackupLBA - 33
|
||||
guuid, err := uuid.NewUUID()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to generate UUID for new partition table")
|
||||
}
|
||||
h.GUUID = guuid
|
||||
h.PartitionEntriesStartLBA = 2
|
||||
h.NumberOfPartitionEntries = 128
|
||||
h.PartitionEntrySize = 128
|
||||
|
||||
return h, nil
|
||||
}
|
||||
|
||||
// See:
|
||||
// - https://en.wikipedia.org/wiki/GUID_Partition_Table#Protective_MBR_(LBA_0)
|
||||
// - https://www.syslinux.org/wiki/index.php?title=Doc/gpt
|
||||
// - https://en.wikipedia.org/wiki/Master_boot_record
|
||||
func (gpt *GPT) newPMBR(h *header.Header) ([]byte, error) {
|
||||
pmbr := make([]byte, 512)
|
||||
|
||||
// Boot signature.
|
||||
copy(pmbr[510:], []byte{0x55, 0xaa})
|
||||
// PMBR protective entry.
|
||||
b := pmbr[446 : 446+16]
|
||||
b[0] = 0x00
|
||||
// Partition type: EFI data partition.
|
||||
b[4] = 0xee
|
||||
// Partition start LBA.
|
||||
binary.LittleEndian.PutUint32(b[8:12], 1)
|
||||
// Partition length in sectors.
|
||||
binary.LittleEndian.PutUint32(b[12:16], uint32(h.BackupLBA))
|
||||
|
||||
return pmbr, nil
|
||||
}
|
||||
|
||||
// Write the primary table.
|
||||
func (gpt *GPT) writePrimary(partitions []byte) error {
|
||||
header, err := gpt.deserializeHeader(partitions)
|
||||
@ -187,7 +273,7 @@ func (gpt *GPT) Add(size uint64, setters ...interface{}) (table.Partition, error
|
||||
end = start + size/uint64(gpt.PhysicalBlockSize())
|
||||
|
||||
if end > gpt.header.LastUsableLBA {
|
||||
// TODO: This calculation is wrong, fix it.
|
||||
// TODO(andrewrynhard): This calculation is wrong, fix it.
|
||||
available := (gpt.header.LastUsableLBA - start) * uint64(gpt.PhysicalBlockSize())
|
||||
return nil, fmt.Errorf("requested partition size %d is too big, largest available is %d", size, available)
|
||||
}
|
||||
@ -202,7 +288,7 @@ func (gpt *GPT) Add(size uint64, setters ...interface{}) (table.Partition, error
|
||||
ID: uuid,
|
||||
FirstLBA: start,
|
||||
LastLBA: end,
|
||||
// TODO: Flags should be an option.
|
||||
// TODO(andrewrynhard): Flags should be an option.
|
||||
Flags: 0,
|
||||
Name: opts.Name,
|
||||
Number: int32(len(gpt.partitions) + 1),
|
||||
@ -222,14 +308,14 @@ func (gpt *GPT) Add(size uint64, setters ...interface{}) (table.Partition, error
|
||||
}
|
||||
|
||||
// Resize resizes a partition.
|
||||
// TODO: Verify that we can indeed grow this partition safely.
|
||||
// TODO(andrewrynhard): Verify that we can indeed grow this partition safely.
|
||||
func (gpt *GPT) Resize(p table.Partition) error {
|
||||
partition, ok := p.(*partition.Partition)
|
||||
if !ok {
|
||||
return fmt.Errorf("partition is not a GUID partition table partition")
|
||||
}
|
||||
|
||||
// TODO: This should be a parameter.
|
||||
// TODO(andrewrynhard): This should be a parameter.
|
||||
partition.LastLBA = gpt.header.LastUsableLBA
|
||||
|
||||
index := partition.Number - 1
|
||||
@ -256,7 +342,6 @@ func (gpt *GPT) PhysicalBlockSize() int {
|
||||
return gpt.lba.PhysicalBlockSize
|
||||
}
|
||||
|
||||
// TODO: Rename this func, it doesn't deserialize anything.
|
||||
func (gpt *GPT) readPrimary() ([]byte, error) {
|
||||
// LBA 34 is the first usable sector on the disk.
|
||||
table := gpt.lba.Make(34)
|
||||
@ -338,7 +423,7 @@ func (gpt *GPT) serializePartitions(header *header.Header) ([]table.Partition, e
|
||||
}
|
||||
|
||||
func (gpt *GPT) deserializePartitions() ([]byte, error) {
|
||||
// TODO: Should this be a method on the Header struct?
|
||||
// TODO(andrewrynhard): Should this be a method on the Header struct?
|
||||
data := make([]byte, gpt.header.NumberOfPartitionEntries*gpt.header.PartitionEntrySize)
|
||||
|
||||
for j, p := range gpt.partitions {
|
||||
|
@ -22,6 +22,8 @@ type PartitionTable interface {
|
||||
Partitions() []Partition
|
||||
// Repair repairs a partition table.
|
||||
Repair() error
|
||||
// New creates a new partition table.
|
||||
New() (PartitionTable, error)
|
||||
// Partitioner must be implemented by a partition table.
|
||||
Partitioner
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user