diff --git a/src/initramfs/pkg/blockdevice/blockdevice.go b/src/initramfs/pkg/blockdevice/blockdevice.go index f3df3869c..cc640d8d1 100644 --- a/src/initramfs/pkg/blockdevice/blockdevice.go +++ b/src/initramfs/pkg/blockdevice/blockdevice.go @@ -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) diff --git a/src/initramfs/pkg/blockdevice/bockdevice_options.go b/src/initramfs/pkg/blockdevice/bockdevice_options.go new file mode 100644 index 000000000..3a0cbc214 --- /dev/null +++ b/src/initramfs/pkg/blockdevice/bockdevice_options.go @@ -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 +} diff --git a/src/initramfs/pkg/blockdevice/table/gpt/gpt.go b/src/initramfs/pkg/blockdevice/table/gpt/gpt.go index da1438df0..065feb6eb 100644 --- a/src/initramfs/pkg/blockdevice/table/gpt/gpt.go +++ b/src/initramfs/pkg/blockdevice/table/gpt/gpt.go @@ -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 { diff --git a/src/initramfs/pkg/blockdevice/table/table.go b/src/initramfs/pkg/blockdevice/table/table.go index dc85e2b68..acac328aa 100644 --- a/src/initramfs/pkg/blockdevice/table/table.go +++ b/src/initramfs/pkg/blockdevice/table/table.go @@ -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 }