talos/internal/pkg/blockdevice/blockdevice.go
Andrew Rynhard 00eb0658aa
feat: add support for ISO based installations (#606)
Signed-off-by: Andrew Rynhard <andrew@andrewrynhard.com>
2019-05-02 21:30:06 -07:00

130 lines
3.3 KiB
Go

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// Package blockdevice provides a library for working with block devices.
package blockdevice
import (
"bytes"
"os"
"unsafe"
"github.com/pkg/errors"
"github.com/talos-systems/talos/internal/pkg/blockdevice/table"
"github.com/talos-systems/talos/internal/pkg/blockdevice/table/gpt"
"golang.org/x/sys/unix"
)
// BlockDevice represents a block device.
type BlockDevice struct {
table table.PartitionTable
f *os.File
}
// Open initializes and returns a block device.
// 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) (bd *BlockDevice, err error) {
opts := NewDefaultOptions(setters...)
bd = &BlockDevice{}
var f *os.File
if f, err = os.OpenFile(devname, os.O_RDWR, os.ModeDevice); err != nil {
return nil, err
}
bd.f = f
defer func() {
if err != nil {
// nolint: errcheck
f.Close()
}
}()
if opts.CreateGPT {
gpt := gpt.NewGPT(devname, f)
var pt table.PartitionTable
if pt, err = gpt.New(); err != nil {
return nil, err
}
if err = pt.Write(); err != nil {
return nil, err
}
bd.table = pt
} 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)
}
}
return bd, nil
}
// Close closes the block devices's open file.
func (bd *BlockDevice) Close() error {
return bd.f.Close()
}
// PartitionTable returns the block device partition table.
func (bd *BlockDevice) PartitionTable(read bool) (table.PartitionTable, error) {
if bd.table == nil {
return nil, errors.New("missing partition table")
}
if !read {
return bd.table, nil
}
return bd.table, bd.table.Read()
}
// RereadPartitionTable invokes the BLKRRPART ioctl to have the kernel read the
// partition table.
func (bd *BlockDevice) RereadPartitionTable() error {
// Flush the file buffers.
// NOTE(andrewrynhard): I'm not entirely sure we need this, but
// figured it wouldn't hurt.
if err := bd.f.Sync(); err != nil {
return err
}
// Flush the block device buffers.
if _, _, ret := unix.Syscall(unix.SYS_IOCTL, bd.f.Fd(), unix.BLKFLSBUF, 0); ret != 0 {
return errors.Errorf("flush block device buffers: %v", ret)
}
// Reread the partition table.
if _, _, ret := unix.Syscall(unix.SYS_IOCTL, bd.f.Fd(), unix.BLKRRPART, 0); ret != 0 {
return errors.Errorf("re-read partition table: %v", ret)
}
return nil
}
// Device returns the backing file for the block device
func (bd *BlockDevice) Device() *os.File {
return bd.f
}
// Size returns the size of the block device in bytes.
func (bd *BlockDevice) Size() (uint64, error) {
var devsize uint64
if _, _, errno := unix.Syscall(unix.SYS_IOCTL, bd.f.Fd(), unix.BLKGETSIZE64, uintptr(unsafe.Pointer(&devsize))); errno != 0 {
return 0, errno
}
return devsize, nil
}