refactor: extract blockdevice library

Library `blockdevice` was extracted as `talos-systems/go-blockdevice`,
this PR finalizes the move by removing Talos copy of it.

Some functions around `mkfs`/`growfs` were extracted as `makefs`
package, as they depend on `cmd` package.

Signed-off-by: Andrey Smirnov <smirnov.andrey@gmail.com>
This commit is contained in:
Andrey Smirnov 2020-10-05 20:35:41 +03:00 committed by talos-bot
parent 16eb47a1a3
commit 018086d1fa
48 changed files with 106 additions and 3201 deletions

View File

@ -16,11 +16,12 @@ import (
"github.com/talos-systems/go-procfs/procfs"
"golang.org/x/sys/unix"
"github.com/talos-systems/go-blockdevice/blockdevice/probe"
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime"
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime/v1alpha1/bootloader"
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime/v1alpha1/bootloader/grub"
"github.com/talos-systems/talos/internal/pkg/mount"
"github.com/talos-systems/talos/pkg/blockdevice/probe"
"github.com/talos-systems/talos/pkg/machinery/constants"
"github.com/talos-systems/talos/pkg/version"
)

View File

@ -15,14 +15,14 @@ import (
"github.com/talos-systems/go-retry/retry"
"github.com/talos-systems/go-blockdevice/blockdevice"
"github.com/talos-systems/go-blockdevice/blockdevice/table"
"github.com/talos-systems/go-blockdevice/blockdevice/table/gpt/partition"
"github.com/talos-systems/go-blockdevice/blockdevice/util"
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime"
"github.com/talos-systems/talos/pkg/blockdevice"
"github.com/talos-systems/talos/pkg/blockdevice/filesystem/vfat"
"github.com/talos-systems/talos/pkg/blockdevice/filesystem/xfs"
"github.com/talos-systems/talos/pkg/blockdevice/table"
"github.com/talos-systems/talos/pkg/blockdevice/table/gpt/partition"
"github.com/talos-systems/talos/pkg/blockdevice/util"
"github.com/talos-systems/talos/pkg/machinery/constants"
"github.com/talos-systems/talos/pkg/makefs"
)
// Manifest represents the instructions for preparing all block devices
@ -264,38 +264,38 @@ func (t *Target) Format() error {
switch t.Label {
case constants.EFIPartitionLabel:
log.Printf("formatting partition %q as %q with label %q\n", t.PartitionName, "fat", t.Label)
return vfat.MakeFS(t.PartitionName, vfat.WithLabel(t.Label))
return makefs.VFAT(t.PartitionName, makefs.WithLabel(t.Label))
case constants.BIOSGrubPartitionLabel:
return nil
case constants.BootPartitionLabel:
log.Printf("formatting partition %q as %q with label %q\n", t.PartitionName, "xfs", t.Label)
opts := []xfs.Option{xfs.WithForce(t.Force)}
opts := []makefs.Option{makefs.WithForce(t.Force)}
if t.Label != "" {
opts = append(opts, xfs.WithLabel(t.Label))
opts = append(opts, makefs.WithLabel(t.Label))
}
return xfs.MakeFS(t.PartitionName, opts...)
return makefs.XFS(t.PartitionName, opts...)
case constants.MetaPartitionLabel:
return nil
case constants.StatePartitionLabel:
log.Printf("formatting partition %q as %q with label %q\n", t.PartitionName, "xfs", t.Label)
opts := []xfs.Option{xfs.WithForce(t.Force)}
opts := []makefs.Option{makefs.WithForce(t.Force)}
if t.Label != "" {
opts = append(opts, xfs.WithLabel(t.Label))
opts = append(opts, makefs.WithLabel(t.Label))
}
return xfs.MakeFS(t.PartitionName, opts...)
return makefs.XFS(t.PartitionName, opts...)
case constants.EphemeralPartitionLabel:
log.Printf("formatting partition %q as %q with label %q\n", t.PartitionName, "xfs", t.Label)
opts := []xfs.Option{xfs.WithForce(t.Force)}
opts := []makefs.Option{makefs.WithForce(t.Force)}
if t.Label != "" {
opts = append(opts, xfs.WithLabel(t.Label))
opts = append(opts, makefs.WithLabel(t.Label))
}
return xfs.MakeFS(t.PartitionName, opts...)
return makefs.XFS(t.PartitionName, opts...)
default:
return nil
}

View File

@ -8,7 +8,8 @@ import (
"errors"
"fmt"
"github.com/talos-systems/talos/pkg/blockdevice/probe"
"github.com/talos-systems/go-blockdevice/blockdevice/probe"
"github.com/talos-systems/talos/pkg/machinery/constants"
)

6
go.mod
View File

@ -32,7 +32,7 @@ require (
github.com/gizak/termui/v3 v3.1.0
github.com/gogo/googleapis v1.4.0 // indirect
github.com/golang/protobuf v1.4.2
github.com/google/uuid v1.1.1
github.com/google/uuid v1.1.2
github.com/grpc-ecosystem/go-grpc-middleware v1.2.0
github.com/hashicorp/go-getter v1.4.1
github.com/hashicorp/go-multierror v1.1.0
@ -58,6 +58,7 @@ require (
github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2
github.com/talos-systems/bootkube-plugin v0.0.0-20200915135634-229d57e818f3
github.com/talos-systems/crypto v0.2.0
github.com/talos-systems/go-blockdevice v0.1.0
github.com/talos-systems/go-loadbalancer v0.1.0
github.com/talos-systems/go-procfs v0.0.0-20200219015357-57c7311fdd45
github.com/talos-systems/go-retry v0.1.1-0.20200922131245-752f081252cf
@ -74,8 +75,7 @@ require (
golang.org/x/net v0.0.0-20200707034311-ab3426394381
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d // indirect
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208
golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1
golang.org/x/text v0.3.3
golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a
golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e
golang.org/x/tools v0.0.0-20200716134326-a8f9df4c9543 // indirect
google.golang.org/grpc v1.29.0

6
go.sum
View File

@ -403,6 +403,8 @@ github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm4
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
@ -752,6 +754,8 @@ github.com/talos-systems/bootkube-plugin v0.0.0-20200915135634-229d57e818f3 h1:L
github.com/talos-systems/bootkube-plugin v0.0.0-20200915135634-229d57e818f3/go.mod h1:AbdJAgHK5rJNDPUN3msPTfQJSR9b4DKb5xNN07uG8/Y=
github.com/talos-systems/crypto v0.2.0 h1:UwT8uhJ0eDlklY0vYwo1+LGoFgiqkPqjQnae6j8UNYE=
github.com/talos-systems/crypto v0.2.0/go.mod h1:KwqG+jANKU1FNQIapmioHQ5fkovY1DJkAqMenjYBGh0=
github.com/talos-systems/go-blockdevice v0.1.0 h1:KEUqVnsFzLaVSWaLHoilEjJ8HTMG/LZGFMtanxDgHyo=
github.com/talos-systems/go-blockdevice v0.1.0/go.mod h1:z7Wgf5zZUFRiASnjKMoMwYQUr841NK03Pn/RZ4DkF/M=
github.com/talos-systems/go-loadbalancer v0.1.0 h1:MQFONvSjoleU8RrKq1O1Z8CyTCJGd4SLqdAHDlR6o9s=
github.com/talos-systems/go-loadbalancer v0.1.0/go.mod h1:D5Qjfz+29WVjONWECZvOkmaLsBb3f5YeWME0u/5HmIc=
github.com/talos-systems/go-procfs v0.0.0-20200219015357-57c7311fdd45 h1:FND/LgzFHTBdJBOeZVzdO6B47kxQZvSIzb9AMIXYotg=
@ -980,6 +984,8 @@ golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200622214017-ed371f2e16b4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1 h1:sIky/MyNRSHTrdxfsiUSS4WIAMvInbeXljJz+jDjeYE=
golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a h1:i47hUS795cOydZI4AwJQCKXOr4BvxzvikwDoDtHhP2Y=
golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=

View File

@ -5,7 +5,8 @@
package runtime
import (
"github.com/talos-systems/talos/pkg/blockdevice/probe"
"github.com/talos-systems/go-blockdevice/blockdevice/probe"
"github.com/talos-systems/talos/pkg/machinery/config"
)

View File

@ -10,8 +10,9 @@ import (
"io"
"os"
"github.com/talos-systems/go-blockdevice/blockdevice/probe"
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime/v1alpha1/bootloader/grub"
"github.com/talos-systems/talos/pkg/blockdevice/probe"
"github.com/talos-systems/talos/pkg/machinery/constants"
)

View File

@ -18,9 +18,10 @@ import (
"strings"
"text/template"
"github.com/talos-systems/go-blockdevice/blockdevice/probe"
"github.com/talos-systems/go-blockdevice/blockdevice/util"
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime"
"github.com/talos-systems/talos/pkg/blockdevice/probe"
"github.com/talos-systems/talos/pkg/blockdevice/util"
"github.com/talos-systems/talos/pkg/machinery/constants"
)

View File

@ -17,8 +17,9 @@ import (
"github.com/talos-systems/go-procfs/procfs"
"github.com/talos-systems/go-smbios/smbios"
"github.com/talos-systems/go-blockdevice/blockdevice/probe"
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime"
"github.com/talos-systems/talos/pkg/blockdevice/probe"
"github.com/talos-systems/talos/pkg/download"
"github.com/talos-systems/talos/pkg/machinery/constants"
)

View File

@ -30,6 +30,10 @@ import (
"github.com/talos-systems/go-retry/retry"
"github.com/talos-systems/go-blockdevice/blockdevice"
"github.com/talos-systems/go-blockdevice/blockdevice/table"
"github.com/talos-systems/go-blockdevice/blockdevice/util"
installer "github.com/talos-systems/talos/cmd/installer/pkg/install"
"github.com/talos-systems/talos/internal/app/machined/internal/install"
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime"
@ -46,9 +50,6 @@ import (
"github.com/talos-systems/talos/internal/pkg/kmsg"
"github.com/talos-systems/talos/internal/pkg/kubeconfig"
"github.com/talos-systems/talos/internal/pkg/mount"
"github.com/talos-systems/talos/pkg/blockdevice"
"github.com/talos-systems/talos/pkg/blockdevice/table"
"github.com/talos-systems/talos/pkg/blockdevice/util"
"github.com/talos-systems/talos/pkg/cmd"
"github.com/talos-systems/talos/pkg/conditions"
"github.com/talos-systems/talos/pkg/kubernetes"

View File

@ -8,9 +8,10 @@ import (
"errors"
"os"
"github.com/talos-systems/go-blockdevice/blockdevice/probe"
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime"
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime/v1alpha1/platform"
"github.com/talos-systems/talos/pkg/blockdevice/probe"
"github.com/talos-systems/talos/pkg/machinery/constants"
)

View File

@ -16,11 +16,12 @@ import (
"github.com/talos-systems/go-retry/retry"
"golang.org/x/sys/unix"
"github.com/talos-systems/talos/pkg/blockdevice"
"github.com/talos-systems/talos/pkg/blockdevice/filesystem/xfs"
gptpartition "github.com/talos-systems/talos/pkg/blockdevice/table/gpt/partition"
"github.com/talos-systems/talos/pkg/blockdevice/util"
"github.com/talos-systems/go-blockdevice/blockdevice"
gptpartition "github.com/talos-systems/go-blockdevice/blockdevice/table/gpt/partition"
"github.com/talos-systems/go-blockdevice/blockdevice/util"
"github.com/talos-systems/talos/pkg/machinery/constants"
"github.com/talos-systems/talos/pkg/makefs"
)
// RetryFunc defines the requirements for retrying a mount point operation.
@ -334,7 +335,7 @@ func (p *Point) ResizePartition() (err error) {
// GrowFilesystem grows a partition's filesystem to the maximum size allowed.
// NB: An XFS partition MUST be mounted, or this will fail.
func (p *Point) GrowFilesystem() (err error) {
if err = xfs.GrowFS(p.Target()); err != nil {
if err = makefs.XFSGrow(p.Target()); err != nil {
return fmt.Errorf("xfs_growfs: %w", err)
}

View File

@ -10,7 +10,8 @@ import (
"golang.org/x/sys/unix"
"github.com/talos-systems/talos/pkg/blockdevice/probe"
"github.com/talos-systems/go-blockdevice/blockdevice/probe"
"github.com/talos-systems/talos/pkg/machinery/constants"
)

View File

@ -1,27 +0,0 @@
// 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 blkpg
import (
"fmt"
"os"
"github.com/talos-systems/talos/pkg/blockdevice/table"
)
// InformKernelOfAdd invokes the BLKPG_ADD_PARTITION ioctl.
func InformKernelOfAdd(f *os.File, partition table.Partition) error {
return fmt.Errorf("not implemented")
}
// InformKernelOfResize invokes the BLKPG_RESIZE_PARTITION ioctl.
func InformKernelOfResize(f *os.File, partition table.Partition) error {
return fmt.Errorf("not implemented")
}
// InformKernelOfDelete invokes the BLKPG_DEL_PARTITION ioctl.
func InformKernelOfDelete(f *os.File, partition table.Partition) error {
return fmt.Errorf("not implemented")
}

View File

@ -1,100 +0,0 @@
// 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 blkpg
import (
"fmt"
"os"
"syscall"
"time"
"unsafe"
"github.com/talos-systems/go-retry/retry"
"golang.org/x/sys/unix"
"github.com/talos-systems/talos/pkg/blockdevice/lba"
"github.com/talos-systems/talos/pkg/blockdevice/table"
)
// InformKernelOfAdd invokes the BLKPG_ADD_PARTITION ioctl.
func InformKernelOfAdd(f *os.File, partition table.Partition) error {
return inform(f, partition, unix.BLKPG_ADD_PARTITION)
}
// InformKernelOfResize invokes the BLKPG_RESIZE_PARTITION ioctl.
func InformKernelOfResize(f *os.File, partition table.Partition) error {
return inform(f, partition, unix.BLKPG_RESIZE_PARTITION)
}
// InformKernelOfDelete invokes the BLKPG_DEL_PARTITION ioctl.
func InformKernelOfDelete(f *os.File, partition table.Partition) error {
return inform(f, partition, unix.BLKPG_DEL_PARTITION)
}
func inform(f *os.File, partition table.Partition, op int32) (err error) {
var (
start int64
length int64
)
switch op {
case unix.BLKPG_DEL_PARTITION:
start = 0
length = 0
default:
var l *lba.LogicalBlockAddresser
if l, err = lba.New(f); err != nil {
return err
}
blocksize := int64(l.LogicalBlockSize)
start = partition.Start() * blocksize
length = partition.Length() * blocksize
}
data := &unix.BlkpgPartition{
Start: start,
Length: length,
Pno: partition.No(),
}
arg := &unix.BlkpgIoctlArg{
Op: op,
Datalen: int32(unsafe.Sizeof(*data)),
Data: (*byte)(unsafe.Pointer(data)),
}
err = retry.Constant(10*time.Second, retry.WithUnits(500*time.Millisecond)).Retry(func() error {
_, _, errno := syscall.Syscall(
syscall.SYS_IOCTL,
f.Fd(),
unix.BLKPG,
uintptr(unsafe.Pointer(arg)),
)
if errno != 0 {
switch errno { //nolint: exhaustive
case unix.EBUSY:
return retry.ExpectedError(err)
default:
return retry.UnexpectedError(err)
}
}
if err = f.Sync(); err != nil {
return retry.UnexpectedError(err)
}
return nil
})
if err != nil {
return fmt.Errorf("failed to inform kernel: %w", err)
}
return nil
}

View File

@ -1,12 +0,0 @@
// 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 "errors"
// ErrMissingPartitionTable indicates that the the block device does not have a
// partition table.
var ErrMissingPartitionTable = errors.New("missing partition table")

View File

@ -1,60 +0,0 @@
// 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
import (
"fmt"
"os"
"github.com/talos-systems/talos/pkg/blockdevice/table"
)
// 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.
func Open(devname string, setters ...Option) (bd *BlockDevice, err error) {
return nil, fmt.Errorf("not implemented")
}
// Close closes the block devices's open file.
func (bd *BlockDevice) Close() error {
return fmt.Errorf("not implemented")
}
// PartitionTable returns the block device partition table.
func (bd *BlockDevice) PartitionTable() (table.PartitionTable, error) {
return nil, fmt.Errorf("not implemented")
}
// RereadPartitionTable invokes the BLKRRPART ioctl to have the kernel read the
// partition table.
//
// NB: Rereading the partition table requires that all partitions be
// unmounted or it will fail with EBUSY.
func (bd *BlockDevice) RereadPartitionTable() error {
return fmt.Errorf("not implemented")
}
// Device returns the backing file for the block device.
func (bd *BlockDevice) Device() *os.File {
return nil
}
// Size returns the size of the block device in bytes.
func (bd *BlockDevice) Size() (uint64, error) {
return 0, fmt.Errorf("not implemented")
}
// Reset will reset a block device given a device name.
// Simply deletes partition table on device.
func (bd *BlockDevice) Reset() error {
return fmt.Errorf("not implemented")
}

View File

@ -1,181 +0,0 @@
// 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
import (
"bytes"
"fmt"
"os"
"syscall"
"time"
"unsafe"
"github.com/talos-systems/go-retry/retry"
"golang.org/x/sys/unix"
"github.com/talos-systems/talos/pkg/blockdevice/table"
"github.com/talos-systems/talos/pkg/blockdevice/table/gpt"
)
// 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.
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|unix.O_CLOEXEC, os.ModeDevice); err != nil {
return nil, err
}
bd.f = f
defer func() {
if err != nil {
// nolint: errcheck
f.Close()
}
}()
if opts.CreateGPT {
var g *gpt.GPT
if g, err = gpt.NewGPT(devname, f); err != nil {
return nil, err
}
var pt table.PartitionTable
if pt, err = g.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}) {
var g *gpt.GPT
if g, err = gpt.NewGPT(devname, f); err != nil {
return nil, err
}
bd.table = g
}
}
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() (table.PartitionTable, error) {
if bd.table == nil {
return nil, ErrMissingPartitionTable
}
return bd.table, bd.table.Read()
}
// RereadPartitionTable invokes the BLKRRPART ioctl to have the kernel read the
// partition table.
//
// NB: Rereading the partition table requires that all partitions be
// unmounted or it will fail with EBUSY.
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 fmt.Errorf("flush block device buffers: %v", ret)
}
var (
err error
ret syscall.Errno
)
// Reread the partition table.
err = retry.Constant(5*time.Second, retry.WithUnits(50*time.Millisecond)).Retry(func() error {
if _, _, ret = unix.Syscall(unix.SYS_IOCTL, bd.f.Fd(), unix.BLKRRPART, 0); ret == 0 {
return nil
}
switch ret { //nolint: exhaustive
case syscall.EBUSY:
return retry.ExpectedError(err)
default:
return retry.UnexpectedError(err)
}
})
if err != nil {
return fmt.Errorf("failed to re-read partition table: %w", err)
}
return err
}
// 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
}
// Reset will reset a block device given a device name.
// Simply deletes partition table on device.
func (bd *BlockDevice) Reset() (err error) {
var pt table.PartitionTable
if pt, err = bd.PartitionTable(); err != nil {
return err
}
for _, p := range pt.Partitions() {
if err = pt.Delete(p); err != nil {
return fmt.Errorf("failed to delete partition: %w", err)
}
}
if err = pt.Write(); err != nil {
return err
}
return nil
}

View File

@ -1,14 +0,0 @@
// 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_test
import "testing"
func TestEmpty(t *testing.T) {
// added for accurate coverage estimation
//
// please remove it once any unit-test is added
// for this package
}

View File

@ -1,12 +0,0 @@
// 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 filesystem
// SuperBlocker describes the requirements for file system super blocks.
type SuperBlocker interface {
Is() bool
Offset() int64
Type() string
}

View File

@ -1,5 +0,0 @@
// 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 iso9660

View File

@ -1,23 +0,0 @@
// 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 iso9660
// Options is the functional options struct.
type Options struct {
}
// Option is the functional option func.
type Option func(*Options)
// NewDefaultOptions initializes a Options struct with default values.
func NewDefaultOptions(setters ...Option) *Options {
opts := &Options{}
for _, setter := range setters {
setter(opts)
}
return opts
}

View File

@ -1,46 +0,0 @@
// 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 iso9660
import "bytes"
const (
// Magic is the ISO 9660 magic signature.
Magic = "CD001"
)
// SuperBlock represents the ISO 9660 super block.
type SuperBlock struct {
FType uint8
ID [5]uint8
Version uint8
Flags uint8
SystemID [32]uint8
VolumeID [32]uint8
_ [8]uint8
SpaceSize [8]uint8
EscapeSequences [8]uint8
_ [222]uint8
PublisherID [128]uint8
_ [128]uint8
ApplicationID [128]uint8
_ [111]uint8
}
// Is implements the SuperBlocker interface.
func (sb *SuperBlock) Is() bool {
trimmed := bytes.Trim(sb.ID[:], " ")
return bytes.Equal(trimmed, []byte(Magic))
}
// Offset implements the SuperBlocker interface.
func (sb *SuperBlock) Offset() int64 {
return 0x8000
}
// Type implements the SuperBlocker interface.
func (sb *SuperBlock) Type() string {
return "iso9660"
}

View File

@ -1,308 +0,0 @@
// 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 vfat
import (
"encoding/binary"
"fmt"
"io"
"os"
"strings"
)
// FileSystem provides simple way to read files from FAT32 filesystems.
//
// This code is far from being production quality, it might break if filesystem
// is corrupted or is not actually FAT32.
type FileSystem struct {
sb *SuperBlock
f io.ReaderAt
sectorSize uint16
clusterSize uint32
fatSize uint64
fatOffset uint32
dataStart uint64
fat map[uint32]uint32
}
// NewFileSystem initializes Filesystem, reads FAT.
func NewFileSystem(f io.ReaderAt, sb *SuperBlock) (*FileSystem, error) {
fs := &FileSystem{
sb: sb,
f: f,
}
fs.sectorSize = binary.LittleEndian.Uint16(sb.SectorSize[:])
fs.clusterSize = uint32(sb.ClusterSize) * uint32(fs.sectorSize)
fs.fatSize = uint64(fs.sectorSize) * uint64(binary.LittleEndian.Uint32(sb.Fat32Length[:]))
fs.fatOffset = uint32(binary.LittleEndian.Uint16(sb.Reserved[:])) * uint32(fs.sectorSize)
fs.dataStart = uint64(fs.fatOffset) + 2*fs.fatSize
rawFat := make([]byte, fs.fatSize)
if err := readAtFull(fs.f, int64(fs.fatOffset), rawFat); err != nil {
return nil, fmt.Errorf("error reading FAT: %w", err)
}
fs.fat = make(map[uint32]uint32)
for i := uint32(2); i < uint32(fs.fatSize/4); i++ {
c := binary.LittleEndian.Uint32(rawFat[i*4 : i*4+4])
if c != 0 {
fs.fat[i] = c
}
}
return fs, nil
}
// Open the file as read-only stream on the filesystem.
func (fs *FileSystem) Open(path string) (*File, error) {
components := strings.Split(path, string(os.PathSeparator))
// start with rootDirectory
dir := &directory{
fs: fs,
firstCluster: binary.LittleEndian.Uint32(fs.sb.RootCluster[:]),
}
for i, component := range components {
if component == "" {
continue
}
entries, err := dir.scan()
if err != nil {
return nil, err
}
found := false
for _, entry := range entries {
if entry.filename == component {
found = true
if i == len(components)-1 {
// should be a file
if entry.isDirectory {
return nil, fmt.Errorf("expected file entry, but directory found")
}
return &File{
fs: fs,
chain: fs.fatChain(entry.firstCluster),
size: entry.size,
}, nil
}
if !entry.isDirectory {
return nil, fmt.Errorf("expected directory, but file found")
}
dir = &directory{
fs: fs,
firstCluster: entry.firstCluster,
}
break
}
}
if !found {
return nil, os.ErrNotExist
}
}
return nil, os.ErrNotExist
}
// fatChain results list of clusters for a file (or directory) based on first
// cluster number and FAT.
func (fs *FileSystem) fatChain(firstCluster uint32) (chain []uint32) {
chain = []uint32{firstCluster}
for {
next := fs.fat[firstCluster]
if next == 0 || next&0xFFFFFF8 == 0xFFFFFF8 {
return
}
chain = append(chain, next)
firstCluster = next
}
}
type directory struct {
fs *FileSystem
firstCluster uint32
}
type directoryEntry struct {
filename string
isDirectory bool
size uint32
firstCluster uint32
}
// scan a directory building list of entries.
//
// Only LFN are supported, entries without LFN are ignored.
func (d *directory) scan() ([]directoryEntry, error) {
// read whole directory into memory
chain := d.fs.fatChain(d.firstCluster)
raw := make([]byte, uint32(len(chain))*d.fs.clusterSize)
for i, cluster := range chain {
if err := readAtFull(d.fs.f, int64(d.fs.dataStart)+int64(cluster-2)*int64(d.fs.clusterSize), raw[uint32(i)*d.fs.clusterSize:uint32(i+1)*d.fs.clusterSize]); err != nil {
return nil, fmt.Errorf("error reading directory contents: %w", err)
}
}
var (
entries []directoryEntry
lfn string
)
for i := 0; i < len(raw); i += 32 {
entry := raw[i : i+32]
if entry[0] == 0 {
return entries, nil
}
if entry[0] == 0xe5 {
continue
}
if entry[11] == 0x0f {
if entry[0]&0x40 == 0x40 {
lfn = ""
}
lfn = parseLfn(entry) + lfn
continue
}
if lfn == "" {
// no lfn, skip directory entry
continue
}
entries = append(entries, directoryEntry{
filename: lfn,
isDirectory: entry[11]&0x10 == 0x10,
size: binary.LittleEndian.Uint32(entry[28:32]),
firstCluster: binary.LittleEndian.Uint32(append(entry[26:28], entry[20:22]...)),
})
}
return entries, nil
}
// File represents a VFAT backed file.
type File struct {
fs *FileSystem
chain []uint32
offset uint32
size uint32
}
func (f *File) Read(p []byte) (n int, err error) {
remaining := len(p)
if uint32(remaining) > f.size-f.offset {
remaining = int(f.size - f.offset)
if remaining == 0 {
err = io.EOF
return
}
}
for remaining > 0 {
clusterIdx := f.offset / f.fs.clusterSize
clusterOffset := f.offset % f.fs.clusterSize
if clusterIdx > uint32(len(f.chain)) {
err = fmt.Errorf("FAT chain overrun")
return
}
cluster := f.chain[clusterIdx]
readLen := f.fs.clusterSize - clusterOffset
if readLen > uint32(remaining) {
readLen = uint32(remaining)
}
if err = readAtFull(f.fs.f, int64(f.fs.dataStart)+int64(cluster-2)*int64(f.fs.clusterSize)+int64(clusterOffset), p[:readLen]); err != nil {
return
}
remaining -= int(readLen)
n += int(readLen)
f.offset += readLen
p = p[readLen:]
}
return n, err
}
// Seek sets the offset for the next Read or Write on file to offset, interpreted
// according to whence: 0 means relative to the origin of the file, 1 means
// relative to the current offset, and 2 means relative to the end.
// It returns the new offset and an error, if any.
func (f *File) Seek(offset int64, whence int) (int64, error) {
switch whence {
case 0:
f.offset = uint32(offset)
case 1:
f.offset = uint32(int64(f.offset) + offset)
case 2:
f.offset = uint32(int64(f.size) + offset)
default:
return 0, fmt.Errorf("unknown whence: %d", whence)
}
return int64(f.offset), nil
}
func readAtFull(r io.ReaderAt, off int64, buf []byte) error {
remaining := len(buf)
for remaining > 0 {
n, err := r.ReadAt(buf, off)
if err != nil {
return err
}
remaining -= n
off += int64(n)
buf = buf[n:]
}
return nil
}
func parseLfn(entry []byte) string {
raw := append(entry[1:11], append(entry[14:26], entry[28:32]...)...)
parsed := []rune{}
for i := 0; i < len(raw); i += 2 {
val := binary.LittleEndian.Uint16(raw[i : i+2])
if val == 0 {
break
}
parsed = append(parsed, rune(val))
}
return string(parsed)
}

View File

@ -1,33 +0,0 @@
// 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 vfat
// Options is the functional options struct.
type Options struct {
Label string
}
// WithLabel sets the filesystem label.
func WithLabel(o string) Option {
return func(args *Options) {
args.Label = o
}
}
// Option is the functional option func.
type Option func(*Options)
// NewDefaultOptions initializes a Options struct with default values.
func NewDefaultOptions(setters ...Option) *Options {
opts := &Options{
Label: "",
}
for _, setter := range setters {
setter(opts)
}
return opts
}

View File

@ -1,61 +0,0 @@
// 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 vfat
import (
"bytes"
)
const (
// Magic is the VFAT magic signature.
Magic = "FAT32"
)
// SuperBlock represents the vfat super block.
type SuperBlock struct {
Ignored [3]uint8
Sysid [8]uint8
SectorSize [2]uint8
ClusterSize uint8
Reserved [2]uint8
Fats uint8
DirEntries [2]uint8
Sectors [2]uint8
Media uint8
FatLength uint16
SecsTrack uint16
Heads uint16
Hidden uint32
TotalSect uint32
Fat32Length [4]uint8
Flags uint16
Version [2]uint8
RootCluster [4]uint8
FsinfoSector uint16
BackupBoot uint16
Reserved2 [6]uint16
Unknown [3]uint8
Serno [4]uint8
Label [11]uint8
Magic [8]uint8
Dummy2 [0x1fe - 0x5a]uint8
Pmagic [2]uint8
}
// Is implements the SuperBlocker interface.
func (sb *SuperBlock) Is() bool {
trimmed := bytes.Trim(sb.Magic[:], " ")
return bytes.Equal(trimmed, []byte(Magic))
}
// Offset implements the SuperBlocker interface.
func (sb *SuperBlock) Offset() int64 {
return 0x0
}
// Type implements the SuperBlocker interface.
func (sb *SuperBlock) Type() string {
return "vfat"
}

View File

@ -1,42 +0,0 @@
// 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 xfs
// Options is the functional options struct.
type Options struct {
Label string
Force bool
}
// Option is the functional option func.
type Option func(*Options)
// WithLabel sets the filesystem label.
func WithLabel(o string) Option {
return func(args *Options) {
args.Label = o
}
}
// WithForce forces the creation of the filesystem.
func WithForce(o bool) Option {
return func(args *Options) {
args.Force = o
}
}
// NewDefaultOptions initializes a Options struct with default values.
func NewDefaultOptions(setters ...Option) *Options {
opts := &Options{
Label: "",
Force: false,
}
for _, setter := range setters {
setter(opts)
}
return opts
}

View File

@ -1,61 +0,0 @@
// 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 xfs
const (
// Magic is the XFS magic number.
Magic = 0x58465342
)
// SuperBlock represents the xfs super block.
type SuperBlock struct {
Magic uint32
Blocksize uint32
Dblocks uint64
Rblocks uint64
Rextents uint64
UUID [16]uint8
Logstart uint64
Rootino uint64
Rbmino uint64
Rsumino uint64
Rextsize uint32
Agblocks uint32
Agcount uint32
Rbmblocks uint32
Logblocks uint32
Versionnum uint16
Sectsize uint16
Inodesize uint16
Inopblock uint16
Fname [12]uint8
Blocklog uint8
Sectlog uint8
Inodelog uint8
Inopblog uint8
Agblklog uint8
Rextslog uint8
Inprogress uint8
ImaxPct uint8
Icount uint64
Ifree uint64
Fdblocks uint64
Frextents uint64
}
// Is implements the SuperBlocker interface.
func (sb *SuperBlock) Is() bool {
return sb.Magic == Magic
}
// Offset implements the SuperBlocker interface.
func (sb *SuperBlock) Offset() int64 {
return 0x0
}
// Type implements the SuperBlocker interface.
func (sb *SuperBlock) Type() string {
return "xfs"
}

View File

@ -1,43 +0,0 @@
// 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 lba provides a library for working with Logical Block Addresses.
package lba
import (
"fmt"
"os"
)
// Range represents a range of Logical Block Addresses.
type Range struct {
Start uint64
End uint64
}
// LogicalBlockAddresser represents Logical Block Addressing.
type LogicalBlockAddresser struct {
PhysicalBlockSize uint64
LogicalBlockSize uint64
}
// New initializes and returns a LogicalBlockAddresser.
func New(f *os.File) (lba *LogicalBlockAddresser, err error) {
return nil, fmt.Errorf("not implemented")
}
// Make returns a slice from a source slice in the the specified range inclusively.
func (lba *LogicalBlockAddresser) Make(size uint64) []byte {
return nil
}
// Copy copies from src to dst in the specified range.
func (lba *LogicalBlockAddresser) Copy(dst, src []byte, rng Range) (int, error) {
return 0, fmt.Errorf("not implemented")
}
// From returns a slice from a source slice in the the specified range inclusively.
func (lba *LogicalBlockAddresser) From(src []byte, rng Range) ([]byte, error) {
return nil, fmt.Errorf("not implemented")
}

View File

@ -1,89 +0,0 @@
// 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 lba provides a library for working with Logical Block Addresses.
package lba
import (
"errors"
"fmt"
"os"
"unsafe"
"golang.org/x/sys/unix"
)
// Range represents a range of Logical Block Addresses.
type Range struct {
Start uint64
End uint64
}
// LogicalBlockAddresser represents Logical Block Addressing.
type LogicalBlockAddresser struct {
PhysicalBlockSize uint64
LogicalBlockSize uint64
}
// New initializes and returns a LogicalBlockAddresser.
func New(f *os.File) (lba *LogicalBlockAddresser, err error) {
st, err := f.Stat()
if err != nil {
return nil, fmt.Errorf("stat disk error: %w", err)
}
var psize uint64
if _, _, errno := unix.Syscall(unix.SYS_IOCTL, f.Fd(), unix.BLKPBSZGET, uintptr(unsafe.Pointer(&psize))); errno != 0 {
if st.Mode().IsRegular() {
// not a device, assume default block size
psize = 512
} else {
return nil, errors.New("BLKPBSZGET failed")
}
}
var lsize uint64
if _, _, errno := unix.Syscall(unix.SYS_IOCTL, f.Fd(), unix.BLKSSZGET, uintptr(unsafe.Pointer(&lsize))); errno != 0 {
if st.Mode().IsRegular() {
// not a device, assume default block size
lsize = 512
} else {
return nil, errors.New("BLKSSZGET failed")
}
}
lba = &LogicalBlockAddresser{
PhysicalBlockSize: psize,
LogicalBlockSize: lsize,
}
return lba, nil
}
// Make returns a slice from a source slice in the the specified range inclusively.
func (lba *LogicalBlockAddresser) Make(size uint64) []byte {
return make([]byte, lba.LogicalBlockSize*size)
}
// Copy copies from src to dst in the specified range.
func (lba *LogicalBlockAddresser) Copy(dst, src []byte, rng Range) (int, error) {
size := lba.LogicalBlockSize
n := copy(dst[size*rng.Start:size*rng.End], src)
if n != len(src) {
return -1, fmt.Errorf("expected to write %d elements, wrote %d", len(src), n)
}
return n, nil
}
// From returns a slice from a source slice in the the specified range inclusively.
func (lba *LogicalBlockAddresser) From(src []byte, rng Range) ([]byte, error) {
size := lba.LogicalBlockSize
if uint64(len(src)) < size+size*rng.End {
return nil, fmt.Errorf("cannot read LBA range (start: %d, end %d), source too small", rng.Start, rng.End)
}
return src[size*rng.Start : size+size*rng.End], nil
}

View File

@ -1,14 +0,0 @@
// 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 lba_test
import "testing"
func TestEmpty(t *testing.T) {
// added for accurate coverage estimation
//
// please remove it once any unit-test is added
// for this package
}

View File

@ -1,33 +0,0 @@
// 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
// 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
}

View File

@ -1,328 +0,0 @@
// 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 probe
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"time"
"github.com/talos-systems/go-retry/retry"
"golang.org/x/sys/unix"
"github.com/talos-systems/talos/pkg/blockdevice"
"github.com/talos-systems/talos/pkg/blockdevice/filesystem"
"github.com/talos-systems/talos/pkg/blockdevice/filesystem/iso9660"
"github.com/talos-systems/talos/pkg/blockdevice/filesystem/vfat"
"github.com/talos-systems/talos/pkg/blockdevice/filesystem/xfs"
gptpartition "github.com/talos-systems/talos/pkg/blockdevice/table/gpt/partition"
"github.com/talos-systems/talos/pkg/blockdevice/util"
)
// ProbedBlockDevice represents a probed block device.
type ProbedBlockDevice struct {
*blockdevice.BlockDevice
SuperBlock filesystem.SuperBlocker
Path string
}
// All probes a block device's file system for the given label.
func All() (all []*ProbedBlockDevice, err error) {
var infos []os.FileInfo
if infos, err = ioutil.ReadDir("/sys/block"); err != nil {
return nil, err
}
for _, info := range infos {
devpath := "/dev/" + info.Name()
var probed []*ProbedBlockDevice
probed, err = probeFilesystem(devpath)
if err != nil {
return nil, err
}
all = append(all, probed...)
}
return all, nil
}
// FileSystem probes the provided path's file system.
func FileSystem(path string) (sb filesystem.SuperBlocker, err error) {
var f *os.File
// Sleep for up to 5s to wait for kernel to create the necessary device files.
// If we dont sleep this becomes racy in that the device file does not exist
// and it will fail to open.
err = retry.Constant(5*time.Second, retry.WithUnits((50 * time.Millisecond))).Retry(func() error {
if f, err = os.OpenFile(path, os.O_RDONLY|unix.O_CLOEXEC, os.ModeDevice); err != nil {
if os.IsNotExist(err) {
return retry.ExpectedError(err)
}
return retry.UnexpectedError(err)
}
return nil
})
if err != nil {
return nil, err
}
// nolint: errcheck
defer f.Close()
superblocks := []filesystem.SuperBlocker{
&iso9660.SuperBlock{},
&vfat.SuperBlock{},
&xfs.SuperBlock{},
}
for _, sb := range superblocks {
if _, err = f.Seek(sb.Offset(), io.SeekStart); err != nil {
return nil, err
}
err = binary.Read(f, binary.BigEndian, sb)
if err != nil {
return nil, err
}
if sb.Is() {
return sb, nil
}
}
return nil, nil
}
// GetDevWithFileSystemLabel probes all known block device's file systems for
// the given label.
func GetDevWithFileSystemLabel(value string) (probe *ProbedBlockDevice, err error) {
var probed []*ProbedBlockDevice
if probed, err = All(); err != nil {
return nil, err
}
return filterByLabel(probed, value)
}
// DevForFileSystemLabel probes a block device's file systems for the
// given label.
func DevForFileSystemLabel(devpath, value string) (probe *ProbedBlockDevice, err error) {
var probed []*ProbedBlockDevice
probed, err = probeFilesystem(devpath)
if err != nil {
return nil, err
}
probe, err = filterByLabel(probed, value)
if err != nil {
return nil, err
}
return probe, err
}
func probe(devpath string) (devpaths []string) {
devpaths = []string{}
// Start by opening the block device.
// If a partition table was not found, it is still possible that a
// file system exists without a partition table.
bd, err := blockdevice.Open(devpath)
if err != nil {
// nolint: errcheck
if sb, _ := FileSystem(devpath); sb != nil {
devpaths = append(devpaths, devpath)
}
return devpaths
}
// nolint: errcheck
defer bd.Close()
// A partition table was not found, and we have already checked for
// a file system on the block device. Let's check if the block device
// has partitions.
pt, err := bd.PartitionTable()
if err != nil {
return devpaths
}
// A partition table was found, now probe each partition's file system.
name := filepath.Base(devpath)
for _, p := range pt.Partitions() {
partpath, err := util.PartPath(name, int(p.No()))
if err != nil {
return devpaths
}
// nolint: errcheck
if sb, _ := FileSystem(partpath); sb != nil {
devpaths = append(devpaths, partpath)
}
}
return devpaths
}
// GetBlockDeviceWithPartitonName probes all known block device's partition
// table for a parition with the specified name.
func GetBlockDeviceWithPartitonName(name string) (bd *blockdevice.BlockDevice, err error) {
var infos []os.FileInfo
if infos, err = ioutil.ReadDir("/sys/block"); err != nil {
return nil, err
}
for _, info := range infos {
devpath := "/dev/" + info.Name()
if bd, err = blockdevice.Open(devpath); err != nil {
continue
}
pt, err := bd.PartitionTable()
if err != nil {
// nolint: errcheck
bd.Close()
if errors.Is(err, blockdevice.ErrMissingPartitionTable) {
continue
}
return nil, fmt.Errorf("failed to open partition table: %w", err)
}
for _, p := range pt.Partitions() {
if part, ok := p.(*gptpartition.Partition); ok {
if part.Name == name {
return bd, nil
}
}
}
// nolint: errcheck
bd.Close()
}
return nil, os.ErrNotExist
}
// GetPartitionWithName probes all known block device's partition
// table for a parition with the specified name.
//
//nolint: gocyclo
func GetPartitionWithName(name string) (f *os.File, err error) {
var infos []os.FileInfo
if infos, err = ioutil.ReadDir("/sys/block"); err != nil {
return nil, err
}
for _, info := range infos {
devpath := "/dev/" + info.Name()
var bd *blockdevice.BlockDevice
if bd, err = blockdevice.Open(devpath); err != nil {
continue
}
// nolint: errcheck
defer bd.Close()
pt, err := bd.PartitionTable()
if err != nil {
if errors.Is(err, blockdevice.ErrMissingPartitionTable) {
continue
}
return nil, fmt.Errorf("failed to open partition table: %w", err)
}
for _, p := range pt.Partitions() {
if part, ok := p.(*gptpartition.Partition); ok {
if part.Name == name {
partpath, err := util.PartPath(info.Name(), int(part.No()))
if err != nil {
return nil, err
}
f, err = os.OpenFile(partpath, os.O_RDWR|unix.O_CLOEXEC, os.ModeDevice)
if err != nil {
return nil, err
}
return f, nil
}
}
}
}
return nil, os.ErrNotExist
}
func probeFilesystem(devpath string) (probed []*ProbedBlockDevice, err error) {
for _, path := range probe(devpath) {
var (
bd *blockdevice.BlockDevice
sb filesystem.SuperBlocker
)
// We ignore the error here because there is the
// possibility that opening the block device fails for
// good reason (e.g. no partition table, read-only
// filesystem), but the block device does have a
// filesystem. This is currently a limitation in our
// blockdevice package. We should make that package
// better and update the code here.
// nolint: errcheck
bd, _ = blockdevice.Open(devpath)
if sb, err = FileSystem(path); err != nil {
return nil, fmt.Errorf("unexpected error when reading super block: %w", err)
}
probed = append(probed, &ProbedBlockDevice{BlockDevice: bd, SuperBlock: sb, Path: path})
}
return probed, nil
}
func filterByLabel(probed []*ProbedBlockDevice, value string) (probe *ProbedBlockDevice, err error) {
for _, probe = range probed {
switch sb := probe.SuperBlock.(type) {
case *iso9660.SuperBlock:
trimmed := bytes.Trim(sb.VolumeID[:], " \x00")
if bytes.Equal(trimmed, []byte(value)) {
return probe, nil
}
case *vfat.SuperBlock:
trimmed := bytes.Trim(sb.Label[:], " \x00")
if bytes.Equal(trimmed, []byte(value)) {
return probe, nil
}
case *xfs.SuperBlock:
trimmed := bytes.Trim(sb.Fname[:], " \x00")
if bytes.Equal(trimmed, []byte(value)) {
return probe, nil
}
}
}
return nil, os.ErrNotExist
}

View File

@ -1,14 +0,0 @@
// 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 probe_test
import "testing"
func TestEmpty(t *testing.T) {
// added for accurate coverage estimation
//
// please remove it once any unit-test is added
// for this package
}

View File

@ -1,461 +0,0 @@
// 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 gpt provides a library for working with GPT partitions.
package gpt
import (
"encoding/binary"
"fmt"
"os"
"github.com/google/uuid"
"github.com/talos-systems/talos/pkg/blockdevice/blkpg"
"github.com/talos-systems/talos/pkg/blockdevice/lba"
"github.com/talos-systems/talos/pkg/blockdevice/table"
"github.com/talos-systems/talos/pkg/blockdevice/table/gpt/header"
"github.com/talos-systems/talos/pkg/blockdevice/table/gpt/partition"
"github.com/talos-systems/talos/pkg/serde"
)
// GPT represents the GUID partition table.
type GPT struct {
table table.Table
header *header.Header
partitions []table.Partition
lba *lba.LogicalBlockAddresser
devname string
f *os.File
}
// NewGPT initializes and returns a GUID partition table.
func NewGPT(devname string, f *os.File, setters ...interface{}) (gpt *GPT, err error) {
_ = NewDefaultOptions(setters...)
lba, err := lba.New(f)
if err != nil {
return nil, err
}
gpt = &GPT{
lba: lba,
devname: devname,
f: f,
}
return gpt, nil
}
// Bytes returns the partition table as a byte slice.
func (gpt *GPT) Bytes() []byte {
return gpt.table
}
// Type returns the partition type.
func (gpt *GPT) Type() table.Type {
return table.GPT
}
// Header returns the header.
func (gpt *GPT) Header() table.Header {
return gpt.header
}
// Partitions returns the partitions.
func (gpt *GPT) Partitions() []table.Partition {
return gpt.partitions
}
// Read performs reads the partition table.
func (gpt *GPT) Read() error {
primaryTable, err := gpt.readPrimary()
if err != nil {
return err
}
serializedHeader, err := gpt.deserializeHeader(primaryTable)
if err != nil {
return err
}
serializedPartitions, err := gpt.deserializePartitions(serializedHeader)
if err != nil {
return err
}
gpt.table = primaryTable
gpt.header = serializedHeader
gpt.partitions = serializedPartitions
return nil
}
// Write writes the partition table to disk.
func (gpt *GPT) Write() error {
partitions, err := gpt.serializePartitions()
if err != nil {
return err
}
if err := gpt.writePrimary(partitions); err != nil {
return fmt.Errorf("failed to write primary table: %w", err)
}
if err := gpt.writeSecondary(partitions); err != nil {
return fmt.Errorf("failed to write secondary table: %w", err)
}
if err := gpt.f.Sync(); err != nil {
return err
}
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 := gpt.newPMBR(h)
gpt.header = h
gpt.partitions = []table.Partition{}
written, err := gpt.f.WriteAt(pmbr[446:], 446)
if err != nil {
return nil, fmt.Errorf("failed to write the protective MBR: %w", err)
}
if written != len(pmbr[446:]) {
return nil, fmt.Errorf("expected a write %d bytes, got %d", written, len(pmbr[446:]))
}
// Reset and seek to the beginning.
_, err = gpt.f.Seek(0, 0)
if 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.LogicalBlockSize) - 1)
h.FirstUsableLBA = 34
h.LastUsableLBA = h.BackupLBA - 33
guuid, err := uuid.NewUUID()
if err != nil {
return nil, fmt.Errorf("failed to generate UUID for new partition table: %w", err)
}
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 {
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
}
// Write the primary table.
func (gpt *GPT) writePrimary(partitions []byte) error {
header, err := gpt.serializeHeader(partitions)
if err != nil {
return err
}
table, err := gpt.newTable(header, partitions, lba.Range{Start: 0, End: 1}, lba.Range{Start: 1, End: 33})
if err != nil {
return err
}
written, err := gpt.f.WriteAt(table, int64(gpt.lba.LogicalBlockSize))
if err != nil {
return err
}
if written != len(table) {
return fmt.Errorf("expected a primary table write of %d bytes, got %d", len(table), written)
}
return nil
}
// Write the secondary table.
func (gpt *GPT) writeSecondary(partitions []byte) error {
header, err := gpt.serializeHeader(partitions, header.WithHeaderPrimary(false))
if err != nil {
return err
}
table, err := gpt.newTable(header, partitions, lba.Range{Start: 32, End: 33}, lba.Range{Start: 0, End: 32})
if err != nil {
return err
}
offset := int64((gpt.header.LastUsableLBA + 1))
written, err := gpt.f.WriteAt(table, offset*int64(gpt.lba.LogicalBlockSize))
if err != nil {
return err
}
if written != len(table) {
return fmt.Errorf("expected a secondary table write of %d bytes, got %d", len(table), written)
}
return nil
}
// Repair repairs the partition table.
func (gpt *GPT) Repair() error {
// Seek to the end to get the size.
size, err := gpt.f.Seek(0, 2)
if err != nil {
return err
}
// Reset and seek to the beginning.
_, err = gpt.f.Seek(0, 0)
if err != nil {
return err
}
gpt.header.BackupLBA = uint64(size/int64(gpt.lba.LogicalBlockSize) - 1)
gpt.header.LastUsableLBA = gpt.header.BackupLBA - 33
return nil
}
// Add adds a partition.
func (gpt *GPT) Add(size uint64, setters ...interface{}) (table.Partition, error) {
opts := partition.NewDefaultOptions(setters...)
var start, end uint64
if len(gpt.partitions) == 0 {
start = gpt.header.FirstUsableLBA
} else {
previous := gpt.partitions[len(gpt.partitions)-1]
start = previous.(*partition.Partition).LastLBA + 1
}
if opts.MaximumSize {
end = gpt.header.LastUsableLBA
if end <= start {
return nil, fmt.Errorf("requested partition with maximum size, but no space available")
}
} else {
end = start + size/gpt.lba.LogicalBlockSize
if end > gpt.header.LastUsableLBA {
// Convert the total available LBAs to units of bytes.
available := (gpt.header.LastUsableLBA - start) * gpt.lba.LogicalBlockSize
return nil, fmt.Errorf("requested partition size %d, available is %d (%d too many bytes)", size, available, size-available)
}
}
uuid, err := uuid.NewUUID()
if err != nil {
return nil, err
}
partition := &partition.Partition{
Type: opts.Type,
ID: uuid,
FirstLBA: start,
LastLBA: end,
Flags: opts.Flags,
Name: opts.Name,
Number: int32(len(gpt.partitions) + 1),
}
gpt.partitions = append(gpt.partitions, partition)
if err := blkpg.InformKernelOfAdd(gpt.f, partition); err != nil {
return nil, err
}
return partition, nil
}
// Resize resizes a partition.
// 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(andrewrynhard): This should be a parameter.
partition.LastLBA = gpt.header.LastUsableLBA
index := partition.Number - 1
if len(gpt.partitions) < int(index) {
return fmt.Errorf("unknown partition %d, only %d available", partition.Number, len(gpt.partitions))
}
gpt.partitions[index] = partition
return blkpg.InformKernelOfResize(gpt.f, partition)
}
// Delete deletes a partition.
func (gpt *GPT) Delete(partition table.Partition) error {
i := partition.No() - 1
gpt.partitions[i] = nil
return blkpg.InformKernelOfDelete(gpt.f, partition)
}
func (gpt *GPT) readPrimary() ([]byte, error) {
// LBA 34 is the first usable sector on the disk.
table := gpt.lba.Make(34)
read, err := gpt.f.ReadAt(table, 0)
if err != nil {
return nil, err
}
if read != len(table) {
return nil, fmt.Errorf("expected a read of %d bytes, got %d", len(table), read)
}
return table, nil
}
func (gpt *GPT) newTable(header, partitions []byte, headerRange, paritionsRange lba.Range) ([]byte, error) {
table := gpt.lba.Make(33)
if _, err := gpt.lba.Copy(table, header, headerRange); err != nil {
return nil, fmt.Errorf("failed to copy header data: %w", err)
}
if _, err := gpt.lba.Copy(table, partitions, paritionsRange); err != nil {
return nil, fmt.Errorf("failed to copy partition data: %w", err)
}
return table, nil
}
func (gpt *GPT) serializeHeader(partitions []byte, setters ...interface{}) ([]byte, error) {
data := gpt.lba.Make(1)
setters = append(setters, header.WithHeaderArrayBytes(partitions))
opts := header.NewDefaultOptions(setters...)
if err := serde.Ser(gpt.header, data, 0, opts); err != nil {
return nil, fmt.Errorf("failed to serialize the header: %w", err)
}
return data, nil
}
func (gpt *GPT) deserializeHeader(table []byte) (*header.Header, error) {
// GPT header is in LBA 1.
data, err := gpt.lba.From(table, lba.Range{Start: 1, End: 1})
if err != nil {
return nil, err
}
hdr := header.NewHeader(data, gpt.lba)
opts := header.NewDefaultOptions(header.WithHeaderTable(table))
if err := serde.De(hdr, hdr.Bytes(), 0, opts); err != nil {
return nil, fmt.Errorf("failed to deserialize the header: %w", err)
}
return hdr, nil
}
func (gpt *GPT) serializePartitions() ([]byte, error) {
// 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 {
if p == nil {
continue
}
i := uint32(j)
partition, ok := p.(*partition.Partition)
if !ok {
return nil, fmt.Errorf("partition is not a GUID partition table partition")
}
if err := serde.Ser(partition, data, i*gpt.header.PartitionEntrySize, nil); err != nil {
return nil, fmt.Errorf("failed to serialize the partitions: %w", err)
}
}
return data, nil
}
func (gpt *GPT) deserializePartitions(header *header.Header) ([]table.Partition, error) {
partitions := make([]table.Partition, 0, header.NumberOfPartitionEntries)
for i := uint32(0); i < header.NumberOfPartitionEntries; i++ {
offset := i * header.PartitionEntrySize
data := header.ArrayBytes()[offset : offset+header.PartitionEntrySize]
prt := partition.NewPartition(data)
if err := serde.De(prt, header.ArrayBytes(), offset, nil); err != nil {
return nil, fmt.Errorf("failed to deserialize the partitions: %w", err)
}
// The first LBA of the partition cannot start before the first usable
// LBA specified in the header.
if prt.FirstLBA >= header.FirstUsableLBA {
prt.Number = int32(i) + 1
partitions = append(partitions, prt)
}
}
return partitions, nil
}

View File

@ -1,433 +0,0 @@
// 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 header provides a library for working with GPT headers.
package header
import (
"bytes"
"encoding/binary"
"fmt"
"hash/crc32"
"github.com/google/uuid"
"github.com/talos-systems/talos/pkg/blockdevice/lba"
"github.com/talos-systems/talos/pkg/endianness"
"github.com/talos-systems/talos/pkg/serde"
)
const (
// HeaderSize is the GUID partition table header size in bytes.
HeaderSize = 92
)
// Header represents a GUID partition table.
type Header struct {
data []byte
array []byte
Signature string // 0
Revision uint32 // 8
Size uint32 // 12
CRC uint32 // 16
Reserved uint32 // 20
CurrentLBA uint64 // 24
BackupLBA uint64 // 32
FirstUsableLBA uint64 // 40
LastUsableLBA uint64 // 48
GUUID uuid.UUID // 56
PartitionEntriesStartLBA uint64 // 72
NumberOfPartitionEntries uint32 // 80
PartitionEntrySize uint32 // 84
PartitionsArrayCRC uint32 // 88
TrailingBytes []byte // 92
*lba.LogicalBlockAddresser
}
// NewHeader inializes and returns a GUID partition table header.
func NewHeader(data []byte, lba *lba.LogicalBlockAddresser) *Header {
return &Header{
data: data,
LogicalBlockAddresser: lba,
}
}
// Bytes implements the table.Header interface.
func (hdr *Header) Bytes() []byte {
return hdr.data
}
// ArrayBytes returns the GUID partition table partitions entries array as a byte slice.
func (hdr *Header) ArrayBytes() []byte {
return hdr.array
}
// Fields impements the serde.Serde interface.
// nolint: gocyclo
func (hdr *Header) Fields() []*serde.Field {
return []*serde.Field{
// 8 bytes Signature ("EFI PART", 45h 46h 49h 20h 50h 41h 52h 54h or 0x5452415020494645ULL on little-endian machines)
{
Offset: 0,
Length: 8,
// Contents: []byte{0x45, 0x46, 0x49, 0x20, 0x50, 0x41, 0x52, 0x54},
SerializerFunc: func(offset, length uint32, new []byte, opts interface{}) ([]byte, error) {
return []byte{0x45, 0x46, 0x49, 0x20, 0x50, 0x41, 0x52, 0x54}, nil
},
DeserializerFunc: func(contents []byte, opts interface{}) error {
signature := string(contents)
if signature != "EFI PART" {
return fmt.Errorf("expected signature of \"EFI PART\", got %q", signature)
}
hdr.Signature = string(contents)
return nil
},
},
// 4 bytes Revision (for GPT version 1.0 (through at least UEFI version 2.7 (May 2017)), the value is 00h 00h 01h 00h)
{
Offset: 8,
Length: 4,
// Contents: []byte{0x00, 0x00, 0x01, 0x00},
SerializerFunc: func(offset, length uint32, new []byte, opts interface{}) ([]byte, error) {
data := make([]byte, length)
binary.LittleEndian.PutUint32(data, hdr.Revision)
return data, nil
},
DeserializerFunc: func(contents []byte, opts interface{}) error {
expected := []byte{0x00, 0x00, 0x01, 0x00}
if !bytes.Equal(contents, expected) {
return fmt.Errorf("expected revision of %v, got %v", expected, contents)
}
hdr.Revision = binary.LittleEndian.Uint32(contents)
return nil
},
},
// 4 bytes Header size in little endian (in bytes, usually 5Ch 00h 00h 00h or 92 bytes)
{
Offset: 12,
Length: 4,
// Contents: []byte{0x5c, 0x00, 0x00, 0x00},
SerializerFunc: func(offset, length uint32, new []byte, opts interface{}) ([]byte, error) {
data := make([]byte, length)
binary.LittleEndian.PutUint32(data, hdr.Size)
return data, nil
},
DeserializerFunc: func(contents []byte, opts interface{}) error {
hdr.Size = binary.LittleEndian.Uint32(contents)
if hdr.Size != HeaderSize {
return fmt.Errorf("expected GPT header size of %d, got %d", HeaderSize, hdr.Size)
}
return nil
},
},
// 4 bytes Reserved; must be zero
{
Offset: 20,
Length: 4,
// Contents: []byte{0x00, 0x00, 0x00, 0x00},
SerializerFunc: func(offset, length uint32, new []byte, opts interface{}) ([]byte, error) {
return []byte{0x00, 0x00, 0x00, 0x00}, nil
},
DeserializerFunc: func(contents []byte, opts interface{}) error {
expected := []byte{0x00, 0x00, 0x00, 0x00}
if !bytes.Equal(contents, expected) {
return fmt.Errorf("expected reserved field to be %v, got %v", expected, contents)
}
hdr.Reserved = binary.LittleEndian.Uint32(contents)
return nil
},
},
// 8 bytes Current LBA (location of this header copy)
// nolint: dupl
{
Offset: 24,
Length: 8,
// Contents: []byte{0x00, 0x00, 0x00, 0x00},
SerializerFunc: func(offset, length uint32, new []byte, opts interface{}) ([]byte, error) {
data := make([]byte, length)
o, ok := opts.(*Options)
if !ok {
return nil, fmt.Errorf("option is not a GPT header option")
}
if o.Primary {
binary.LittleEndian.PutUint64(data, hdr.CurrentLBA)
} else {
binary.LittleEndian.PutUint64(data, hdr.BackupLBA)
}
return data, nil
},
DeserializerFunc: func(contents []byte, opts interface{}) error {
hdr.CurrentLBA = binary.LittleEndian.Uint64(contents)
return nil
},
},
// 8 bytes Backup LBA (location of the other header copy)
// nolint: dupl
{
Offset: 32,
Length: 8,
// Contents: []byte{0x00, 0x00, 0x00, 0x00},
SerializerFunc: func(offset, length uint32, new []byte, opts interface{}) ([]byte, error) {
data := make([]byte, length)
o, ok := opts.(*Options)
if !ok {
return nil, fmt.Errorf("option is not a GPT header option")
}
if o.Primary {
binary.LittleEndian.PutUint64(data, hdr.BackupLBA)
} else {
binary.LittleEndian.PutUint64(data, hdr.CurrentLBA)
}
return data, nil
},
DeserializerFunc: func(contents []byte, opts interface{}) error {
hdr.BackupLBA = binary.LittleEndian.Uint64(contents)
return nil
},
},
// 8 bytes First usable LBA for partitions (primary partition table last LBA + 1)
{
Offset: 40,
Length: 8,
// Contents: []byte{0x00, 0x00, 0x00, 0x00},
SerializerFunc: func(offset, length uint32, new []byte, opts interface{}) ([]byte, error) {
data := make([]byte, length)
binary.LittleEndian.PutUint64(data, hdr.FirstUsableLBA)
return data, nil
},
DeserializerFunc: func(contents []byte, opts interface{}) error {
hdr.FirstUsableLBA = binary.LittleEndian.Uint64(contents)
return nil
},
},
// 8 bytes Last usable LBA (secondary partition table first LBA - 1)
{
Offset: 48,
Length: 8,
// Contents: []byte{0x00, 0x00, 0x00, 0x00},
SerializerFunc: func(offset, length uint32, new []byte, opts interface{}) ([]byte, error) {
data := make([]byte, length)
binary.LittleEndian.PutUint64(data, hdr.LastUsableLBA)
return data, nil
},
DeserializerFunc: func(contents []byte, opts interface{}) error {
hdr.LastUsableLBA = binary.LittleEndian.Uint64(contents)
return nil
},
},
// 16 bytes Disk GUID (also referred as UUID on UNIXes)
{
Offset: 56,
Length: 16,
// Contents: []byte{0x00},
SerializerFunc: func(offset, length uint32, new []byte, opts interface{}) ([]byte, error) {
b, err := hdr.GUUID.MarshalBinary()
if err != nil {
return nil, err
}
return endianness.ToMiddleEndian(b)
},
DeserializerFunc: func(contents []byte, opts interface{}) error {
u, err := endianness.FromMiddleEndian(contents)
if err != nil {
return err
}
guid, err := uuid.FromBytes(u)
if err != nil {
return fmt.Errorf("invalid GUUID: %w", err)
}
hdr.GUUID = guid
return nil
},
},
// 8 bytes Starting LBA of array of partition entries (always 2 in primary copy)
{
Offset: 72,
Length: 8,
// Contents: []byte{0x00},
SerializerFunc: func(offset, length uint32, new []byte, opts interface{}) ([]byte, error) {
data := make([]byte, length)
binary.LittleEndian.PutUint64(data, hdr.PartitionEntriesStartLBA)
return data, nil
},
DeserializerFunc: func(contents []byte, opts interface{}) error {
// TODO: Should we verify it is 2 in the case of primary?
o, ok := opts.(*Options)
if !ok {
return fmt.Errorf("option is not a GPT header option")
}
hdr.PartitionEntriesStartLBA = binary.LittleEndian.Uint64(contents)
array, err := hdr.From(o.Table, lba.Range{Start: hdr.PartitionEntriesStartLBA, End: uint64(33)})
if err != nil {
return fmt.Errorf("failed to read starting LBA from header: %w", err)
}
hdr.array = array
return nil
},
},
// 4 bytes Number of partition entries in array
{
Offset: 80,
Length: 4,
// Contents: []byte{0x00},
SerializerFunc: func(offset, length uint32, new []byte, opts interface{}) ([]byte, error) {
data := make([]byte, length)
binary.LittleEndian.PutUint32(data, hdr.NumberOfPartitionEntries)
return data, nil
},
DeserializerFunc: func(contents []byte, opts interface{}) error {
hdr.NumberOfPartitionEntries = binary.LittleEndian.Uint32(contents)
return nil
},
},
// 4 bytes Size of a single partition entry (usually 80h or 128)
{
Offset: 84,
Length: 4,
// Contents: []byte{0x00},
SerializerFunc: func(offset, length uint32, new []byte, opts interface{}) ([]byte, error) {
data := make([]byte, length)
binary.LittleEndian.PutUint32(data, hdr.PartitionEntrySize)
return data, nil
},
DeserializerFunc: func(contents []byte, opts interface{}) error {
length := binary.LittleEndian.Uint32(contents)
// This field should be set to a value of: 128 x 2n where n is an integer greater than or equal to zero.
if length%128 != 0 {
return fmt.Errorf("expected partition entry size to be a multiple of %d, got %d", 128, length)
}
hdr.PartitionEntrySize = binary.LittleEndian.Uint32(contents)
return nil
},
},
// 4 bytes CRC32/zlib of partition array in little endian
{
Offset: 88,
Length: 4,
// Contents: []byte{0x00},
SerializerFunc: func(offset, length uint32, new []byte, opts interface{}) ([]byte, error) {
o, ok := opts.(*Options)
if !ok {
return nil, fmt.Errorf("option is not a GPT header option")
}
expected := hdr.NumberOfPartitionEntries * hdr.PartitionEntrySize
if len(o.Array) != int(expected) {
return nil, fmt.Errorf("expected array length of %d, got %d", expected, len(o.Array))
}
crc := crc32.ChecksumIEEE(o.Array)
data := make([]byte, length)
binary.LittleEndian.PutUint32(data, crc)
// We should update this here and now to ensure that the field is still valid.
hdr.PartitionsArrayCRC = crc
return data, nil
},
DeserializerFunc: func(contents []byte, opts interface{}) error {
crc := binary.LittleEndian.Uint32(contents)
// Note that hdr.array is expected to be set in offset 72 SerializerFunc.
checksum := crc32.ChecksumIEEE(hdr.array)
if crc != checksum {
return fmt.Errorf("expected partition checksum of %v, got %v", checksum, crc)
}
hdr.PartitionsArrayCRC = crc
return nil
},
},
// Reserved; must be zeroes for the rest of the block (420 bytes for a sector size of 512 bytes; but can be more with larger sector sizes)
{
Offset: HeaderSize,
Length: 420,
// Contents: []byte{0x00},
SerializerFunc: func(offset, length uint32, new []byte, opts interface{}) ([]byte, error) {
data := make([]byte, 420)
return data, nil
},
DeserializerFunc: func(contents []byte, opts interface{}) error {
expected := make([]byte, 420)
if !bytes.Equal(contents, expected) {
return fmt.Errorf("expected %d trailing bytes of zeroes", 420)
}
hdr.TrailingBytes = contents
return nil
},
},
// 4 bytes CRC32/zlib of header (offset +0 up to header size) in little endian, with this field zeroed during calculation
{
Offset: 16,
Length: 4,
// Contents: []byte{0x00, 0x00, 0x00, 0x00},
SerializerFunc: func(offset, length uint32, new []byte, opts interface{}) ([]byte, error) {
// Copy the header into a temporary slice and to avoid modifying the original.
header := make([]byte, HeaderSize)
copy(header, new)
// Zero the CRC field during the calculation.
copy(header[16:20], []byte{0x00, 0x00, 0x00, 0x00})
crc := crc32.ChecksumIEEE(header)
data := make([]byte, length)
binary.LittleEndian.PutUint32(data, crc)
// We should update this here and now to ensure that the field is still valid.
hdr.CRC = crc
return data, nil
},
DeserializerFunc: func(contents []byte, opts interface{}) error {
crc := binary.LittleEndian.Uint32(contents)
// Copy the header into a temporary slice and to avoid modifying the original.
header := make([]byte, HeaderSize)
copy(header, hdr.data)
// Zero the CRC field during the calculation.
copy(header[16:20], []byte{0x00, 0x00, 0x00, 0x00})
checksum := crc32.ChecksumIEEE(header)
if crc != checksum {
return fmt.Errorf("expected header checksum of %d, got %d", crc, checksum)
}
hdr.CRC = crc
return nil
},
},
}
}

View File

@ -1,53 +0,0 @@
// 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 header
// Options is the functional options struct.
type Options struct {
Primary bool
Table []byte
Array []byte
}
// Option is the functional option func.
type Option func(*Options)
// WithHeaderPrimary sets the primary option.
func WithHeaderPrimary(o bool) Option {
return func(args *Options) {
args.Primary = o
}
}
// WithHeaderTable sets the partition type.
func WithHeaderTable(o []byte) Option {
return func(args *Options) {
args.Table = o
}
}
// WithHeaderArrayBytes sets the partition type.
func WithHeaderArrayBytes(o []byte) Option {
return func(args *Options) {
args.Array = o
}
}
// NewDefaultOptions initializes a Options struct with default values.
func NewDefaultOptions(setters ...interface{}) *Options {
opts := &Options{
Primary: true,
Table: []byte{},
Array: []byte{},
}
for _, setter := range setters {
if s, ok := setter.(Option); ok {
s(opts)
}
}
return opts
}

View File

@ -1,35 +0,0 @@
// 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 gpt
// Options is the functional options struct.
type Options struct {
PrimaryGPT bool
}
// Option is the functional option func.
type Option func(*Options)
// WithPrimaryGPT sets the contents of offset 24 in the GPT header to the location of the primary header.
func WithPrimaryGPT(o bool) Option {
return func(args *Options) {
args.PrimaryGPT = o
}
}
// NewDefaultOptions initializes a Options struct with default values.
func NewDefaultOptions(setters ...interface{}) *Options {
opts := &Options{
PrimaryGPT: true,
}
for _, setter := range setters {
if s, ok := setter.(Option); ok {
s(opts)
}
}
return opts
}

View File

@ -1,73 +0,0 @@
// 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 partition
import (
"github.com/google/uuid"
)
// Options is the functional options struct.
type Options struct {
Type uuid.UUID
Name string
MaximumSize bool
Flags uint64
}
// Option is the functional option func.
type Option func(*Options)
// WithPartitionType sets the partition type.
func WithPartitionType(id string) Option {
return func(args *Options) {
// TODO: An Option should return an error.
// nolint: errcheck
guuid, _ := uuid.Parse(id)
args.Type = guuid
}
}
// WithPartitionName sets the partition name.
func WithPartitionName(o string) Option {
return func(args *Options) {
args.Name = o
}
}
// WithMaximumSize indicates if the partition should be created with the maximum size possible.
func WithMaximumSize(o bool) Option {
return func(args *Options) {
args.MaximumSize = o
}
}
// WithLegacyBIOSBootableAttribute marks the partition as bootable.
func WithLegacyBIOSBootableAttribute(o bool) Option {
return func(args *Options) {
if o {
args.Flags |= (1 << 2)
}
}
}
// NewDefaultOptions initializes a Options struct with default values.
func NewDefaultOptions(setters ...interface{}) *Options {
// TODO: An Option should return an error.
// nolint: errcheck
guuid, _ := uuid.Parse("0FC63DAF-8483-4772-8E79-3D69D8477DE4")
opts := &Options{
Type: guuid,
Name: "",
}
for _, setter := range setters {
if s, ok := setter.(Option); ok {
s(opts)
}
}
return opts
}

View File

@ -1,212 +0,0 @@
// 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 partition provides a library for working with GPT partitions.
package partition
import (
"bytes"
"encoding/binary"
"fmt"
"github.com/google/uuid"
"golang.org/x/text/encoding/unicode"
"github.com/talos-systems/talos/pkg/endianness"
"github.com/talos-systems/talos/pkg/serde"
)
// Partition represents a partition entry in a GUID partition table.
type Partition struct {
data []byte
Type uuid.UUID // 0
ID uuid.UUID // 16
FirstLBA uint64 // 32
LastLBA uint64 // 40
Flags uint64 // 48
Name string // 56
TrailingBytes []byte // 128
Number int32
}
// NewPartition initializes and returns a new partition.
func NewPartition(data []byte) *Partition {
return &Partition{
data: data,
}
}
// Bytes returns the partition as a byte slice.
func (prt *Partition) Bytes() []byte {
return prt.data
}
// Start returns the partition's starting LBA..
func (prt *Partition) Start() int64 {
return int64(prt.FirstLBA)
}
// Length returns the partition's length in LBA.
func (prt *Partition) Length() int64 {
// TODO(andrewrynhard): For reasons I don't understand right now, we need
// to add 1 in order to align with what partx thinks is the length of the
// partition.
return int64(prt.LastLBA - prt.FirstLBA + 1)
}
// No returns the partition's number.
func (prt *Partition) No() int32 {
return prt.Number
}
// Fields implements the serder.Serde interface.
func (prt *Partition) Fields() []*serde.Field {
return []*serde.Field{
// 16 bytes Partition type GUID
// nolint: dupl
{
Offset: 0,
Length: 16,
SerializerFunc: func(offset, length uint32, new []byte, opts interface{}) ([]byte, error) {
b, err := prt.Type.MarshalBinary()
if err != nil {
return nil, err
}
return endianness.ToMiddleEndian(b)
},
DeserializerFunc: func(contents []byte, opts interface{}) error {
u, err := endianness.FromMiddleEndian(contents)
if err != nil {
return err
}
guid, err := uuid.FromBytes(u)
if err != nil {
return fmt.Errorf("invalid GUUID: %w", err)
}
// TODO: Provide a method for getting the human readable name of the type.
// See https://en.wikipedia.org/wiki/GUID_Partition_Table.
prt.Type = guid
return nil
},
},
// 16 bytes Unique partition GUID
// nolint: dupl
{
Offset: 16,
Length: 16,
SerializerFunc: func(offset, length uint32, new []byte, opts interface{}) ([]byte, error) {
b, err := prt.ID.MarshalBinary()
if err != nil {
return nil, err
}
return endianness.ToMiddleEndian(b)
},
DeserializerFunc: func(contents []byte, opts interface{}) error {
u, err := endianness.FromMiddleEndian(contents)
if err != nil {
return err
}
guid, err := uuid.FromBytes(u)
if err != nil {
return fmt.Errorf("invalid GUUID: %w", err)
}
prt.ID = guid
return nil
},
},
// 8 bytes First LBA (little endian)
{
Offset: 32,
Length: 8,
SerializerFunc: func(offset, length uint32, new []byte, opts interface{}) ([]byte, error) {
data := make([]byte, length)
binary.LittleEndian.PutUint64(data, prt.FirstLBA)
return data, nil
},
DeserializerFunc: func(contents []byte, opts interface{}) error {
prt.FirstLBA = binary.LittleEndian.Uint64(contents)
return nil
},
},
// 8 bytes Last LBA (inclusive, usually odd)
{
Offset: 40,
Length: 8,
SerializerFunc: func(offset, length uint32, new []byte, opts interface{}) ([]byte, error) {
data := make([]byte, length)
binary.LittleEndian.PutUint64(data, prt.LastLBA)
return data, nil
},
DeserializerFunc: func(contents []byte, opts interface{}) error {
prt.LastLBA = binary.LittleEndian.Uint64(contents)
return nil
},
},
// 8 bytes Attribute flags (e.g. bit 60 denotes read-only)
// Known attributes are:
// 0: system partition
// 1: hide from EFI
// 2: legacy BIOS bootable
// 60: read-only
// 62: hidden
// 63: do not automount
{
Offset: 48,
Length: 8,
SerializerFunc: func(offset, length uint32, new []byte, opts interface{}) ([]byte, error) {
data := make([]byte, length)
binary.LittleEndian.PutUint64(data, prt.Flags)
return data, nil
},
DeserializerFunc: func(contents []byte, opts interface{}) error {
prt.Flags = binary.LittleEndian.Uint64(contents)
return nil
},
},
// 72 bytes Partition name (36 UTF-16LE code units)
{
Offset: 56,
Length: 72,
SerializerFunc: func(offset, length uint32, new []byte, opts interface{}) ([]byte, error) {
utf16 := unicode.UTF16(unicode.LittleEndian, unicode.IgnoreBOM)
name, err := utf16.NewEncoder().Bytes([]byte(prt.Name))
if err != nil {
return nil, err
}
// TODO: Should we error if the name exceeds 72 bytes?
data := make([]byte, 72)
copy(data, name)
return data, nil
},
DeserializerFunc: func(contents []byte, opts interface{}) error {
utf16 := unicode.UTF16(unicode.LittleEndian, unicode.IgnoreBOM)
decoded, err := utf16.NewDecoder().Bytes(contents)
if err != nil {
return err
}
prt.Name = string(bytes.Trim(decoded, "\x00"))
return nil
},
},
}
}

View File

@ -1,73 +0,0 @@
// 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 table provides a library for working with block device partition tables.
package table
import "github.com/talos-systems/talos/pkg/serde"
// Table represents a partition table.
type Table = []byte
// PartitionTable describes a partition table.
type PartitionTable interface {
// Bytes returns the partition table as a byte slice.
Bytes() Table
// Read reades the partition table.
Read() error
// Write writes the partition table/.
Write() error
// Type returns the partition table type.
Type() Type
// Header returns the partition table header.
Header() Header
// Partitions returns a slice o partition table partitions.
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
}
// Type represents a partition table type.
type Type int
const (
// MBR is the Master Boot Record artition table.
MBR Type = iota
// GPT is the GUID partition table.
GPT
)
// Header describes a partition table header.
type Header interface {
// Bytes returns the partition table header as a byte slice.
Bytes() []byte
serde.Serde
}
// Partition describes a partition.
type Partition interface {
// Bytes returns the partition table partitions as a byte slice.
Bytes() []byte
// Start returns the partition's starting LBA.
Start() int64
// Length returns the partition's length in LBA.
Length() int64
// No returns the partition's number.
No() int32
serde.Serde
}
// Partitioner describes actions that can be taken on a partition.
type Partitioner interface {
// Add adds a partition to the partition table.
Add(uint64, ...interface{}) (Partition, error)
// Resize resizes a partition table.
Resize(Partition) error
// Delete deletes a partition table.
Delete(Partition) error
}

View File

@ -1,102 +0,0 @@
// 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 util
import (
"fmt"
"os"
"path/filepath"
"strings"
)
// PartNo returns the partition number.
func PartNo(partname string) (partno string, err error) {
partname = strings.TrimPrefix(partname, "/dev/")
switch p := partname; {
case strings.HasPrefix(p, "nvme"):
fallthrough
case strings.HasPrefix(p, "loop"):
idx := strings.LastIndex(partname, "p")
return partname[idx+1:], nil
case strings.HasPrefix(p, "sd"):
fallthrough
case strings.HasPrefix(p, "hd"):
fallthrough
case strings.HasPrefix(p, "vd"):
fallthrough
case strings.HasPrefix(p, "xvd"):
return strings.TrimLeft(partname, "/abcdefghijklmnopqrstuvwxyz"), nil
default:
return "", fmt.Errorf("could not determine partition number from partition name: %s", partname)
}
}
// DevnameFromPartname returns the device name from a partition name.
func DevnameFromPartname(partname string) (devname string, err error) {
partname = strings.TrimPrefix(partname, "/dev/")
var partno string
if partno, err = PartNo(partname); err != nil {
return "", err
}
switch p := partname; {
case strings.HasPrefix(p, "nvme"):
fallthrough
case strings.HasPrefix(p, "loop"):
return strings.TrimSuffix(p, "p"+partno), nil
case strings.HasPrefix(p, "sd"):
fallthrough
case strings.HasPrefix(p, "hd"):
fallthrough
case strings.HasPrefix(p, "vd"):
fallthrough
case strings.HasPrefix(p, "xvd"):
return strings.TrimSuffix(p, partno), nil
default:
return "", fmt.Errorf("could not determine dev name from partition name: %s", p)
}
}
// PartName returns a valid partition name given a device and parition number.
func PartName(d string, n int) string {
partname := strings.TrimPrefix(d, "/dev/")
switch p := partname; {
case strings.HasPrefix(p, "nvme"):
fallthrough
case strings.HasPrefix(p, "loop"):
partname = fmt.Sprintf("%sp%d", p, n)
default:
partname = fmt.Sprintf("%s%d", p, n)
}
return partname
}
// PartPath returns the canonical path to a partition name (e.g. /dev/sda).
func PartPath(d string, n int) (string, error) {
switch {
case strings.HasPrefix(d, "/dev/disk/by-id"):
name, err := os.Readlink(d)
if err != nil {
return "", err
}
return filepath.Join("/dev", PartName(filepath.Base(name), n)), nil
case strings.HasPrefix(d, "/dev/disk/by-label"):
fallthrough
case strings.HasPrefix(d, "/dev/disk/by-partlabel"):
fallthrough
case strings.HasPrefix(d, "/dev/disk/by-partuuid"):
fallthrough
case strings.HasPrefix(d, "/dev/disk/by-uuid"):
return "", fmt.Errorf("disk name is already a partition")
default:
return filepath.Join("/dev", PartName(d, n)), nil
}
}

View File

@ -1,204 +0,0 @@
// 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/.
//nolint: scopelint
package util_test
import (
"testing"
"github.com/talos-systems/talos/pkg/blockdevice/util"
)
func Test_PartNo(t *testing.T) {
type args struct {
devname string
}
tests := []struct {
name string
args args
want string
}{
{
name: "hda1",
args: args{
devname: "hda1",
},
want: "1",
},
{
name: "hda10",
args: args{
devname: "hda10",
},
want: "10",
},
{
name: "sda1",
args: args{
devname: "sda1",
},
want: "1",
},
{
name: "sda10",
args: args{
devname: "sda10",
},
want: "10",
},
{
name: "nvme1n2p2",
args: args{
devname: "nvme1n2p2",
},
want: "2",
},
{
name: "nvme1n2p11",
args: args{
devname: "nvme1n2p11",
},
want: "11",
},
{
name: "vda1",
args: args{
devname: "vda1",
},
want: "1",
},
{
name: "vda10",
args: args{
devname: "vda10",
},
want: "10",
},
{
name: "xvda1",
args: args{
devname: "xvda1",
},
want: "1",
},
{
name: "xvda10",
args: args{
devname: "xvda10",
},
want: "10",
},
{
name: "loop0p1",
args: args{
devname: "loop0p1",
},
want: "1",
},
{
name: "loop7p11",
args: args{
devname: "loop7p11",
},
want: "11",
},
{
name: "loop4p4",
args: args{
devname: "loop4p4",
},
want: "4",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// nolint: errcheck
if got, _ := util.PartNo(tt.args.devname); got != tt.want {
t.Errorf("PartNo() = %v, want %v", got, tt.want)
}
})
}
}
func Test_DevnameFromPartname(t *testing.T) {
type args struct {
devname string
partno string
}
tests := []struct {
name string
args args
want string
}{
{
name: "hda1",
args: args{
devname: "hda1",
partno: "1",
},
want: "hda",
},
{
name: "sda1",
args: args{
devname: "sda1",
partno: "1",
},
want: "sda",
},
{
name: "vda1",
args: args{
devname: "vda1",
partno: "1",
},
want: "vda",
},
{
name: "nvme1n2p11",
args: args{
devname: "nvme1n2p11",
partno: "11",
},
want: "nvme1n2",
},
{
name: "loop0p1",
args: args{
devname: "loop0p1",
partno: "1",
},
want: "loop0",
},
{
name: "loop7p11",
args: args{
devname: "loop7p11",
partno: "11",
},
want: "loop7",
},
{
name: "loop4p1",
args: args{
devname: "loop4p1",
partno: "4",
},
want: "loop4",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// nolint: errcheck
if got, _ := util.DevnameFromPartname(tt.args.devname); got != tt.want {
t.Errorf("DevnameFromPartname() = %v, want %v", got, tt.want)
}
})
}
}

40
pkg/makefs/makefs.go Normal file
View File

@ -0,0 +1,40 @@
// 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 makefs provides function to format and grow filesystems.
package makefs
// Option to control makefs settings.
type Option func(*Options)
// Options for makefs.
type Options struct {
Label string
Force bool
}
// WithLabel sets the label for the filesystem to be created.
func WithLabel(label string) Option {
return func(o *Options) {
o.Label = label
}
}
// WithForce forces creation of a filesystem even if one already exists.
func WithForce(force bool) Option {
return func(o *Options) {
o.Force = force
}
}
// NewDefaultOptions builds options with specified setters applied.
func NewDefaultOptions(setters ...Option) Options {
var opt Options
for _, o := range setters {
o(&opt)
}
return opt
}

View File

@ -2,14 +2,14 @@
// 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 vfat
package makefs
import (
"github.com/talos-systems/talos/pkg/cmd"
)
// MakeFS creates a VFAT filesystem on the specified partition.
func MakeFS(partname string, setters ...Option) error {
// VFAT creates a VFAT filesystem on the specified partition.
func VFAT(partname string, setters ...Option) error {
opts := NewDefaultOptions(setters...)
args := []string{}

View File

@ -2,8 +2,7 @@
// 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 xfs provides an interface to xfsprogs.
package xfs
package makefs
import (
"fmt"
@ -11,16 +10,16 @@ import (
"github.com/talos-systems/talos/pkg/cmd"
)
// GrowFS expands a XFS filesystem to the maximum possible. The partition
// XFSGrow expands a XFS filesystem to the maximum possible. The partition
// MUST be mounted, or this will fail.
func GrowFS(partname string) error {
func XFSGrow(partname string) error {
_, err := cmd.Run("xfs_growfs", "-d", partname)
return err
}
// MakeFS creates a XFS filesystem on the specified partition.
func MakeFS(partname string, setters ...Option) error {
// XFS creates a XFS filesystem on the specified partition.
func XFS(partname string, setters ...Option) error {
if partname == "" {
return fmt.Errorf("missing path to disk")
}

View File

@ -15,9 +15,10 @@ import (
"path/filepath"
"regexp"
"github.com/talos-systems/talos/pkg/blockdevice/filesystem/vfat"
"github.com/talos-systems/talos/pkg/blockdevice/table"
"github.com/talos-systems/talos/pkg/blockdevice/table/gpt"
"github.com/talos-systems/go-blockdevice/blockdevice/filesystem/vfat"
"github.com/talos-systems/go-blockdevice/blockdevice/table"
"github.com/talos-systems/go-blockdevice/blockdevice/table/gpt"
"github.com/talos-systems/talos/pkg/machinery/constants"
"github.com/talos-systems/talos/pkg/provision/internal/vmlinuz"
)

View File

@ -20,7 +20,8 @@ import (
"github.com/containernetworking/plugins/pkg/testutils"
"github.com/google/uuid"
"github.com/talos-systems/talos/pkg/blockdevice/table/gpt"
"github.com/talos-systems/go-blockdevice/blockdevice/table/gpt"
"github.com/talos-systems/talos/pkg/provision"
"github.com/talos-systems/talos/pkg/provision/internal/cniutils"
"github.com/talos-systems/talos/pkg/provision/providers/vm"