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:
parent
16eb47a1a3
commit
018086d1fa
@ -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"
|
||||
)
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
6
go.mod
@ -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
6
go.sum
@ -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=
|
||||
|
@ -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"
|
||||
)
|
||||
|
||||
|
@ -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"
|
||||
)
|
||||
|
||||
|
@ -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"
|
||||
)
|
||||
|
||||
|
@ -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"
|
||||
)
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
)
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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"
|
||||
)
|
||||
|
||||
|
@ -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")
|
||||
}
|
@ -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
|
||||
}
|
@ -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")
|
@ -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")
|
||||
}
|
@ -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
|
||||
}
|
@ -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
|
||||
}
|
@ -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
|
||||
}
|
@ -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
|
@ -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
|
||||
}
|
@ -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"
|
||||
}
|
@ -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)
|
||||
}
|
@ -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
|
||||
}
|
@ -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"
|
||||
}
|
@ -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
|
||||
}
|
@ -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"
|
||||
}
|
@ -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")
|
||||
}
|
@ -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
|
||||
}
|
@ -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
|
||||
}
|
@ -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
|
||||
}
|
@ -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
|
||||
}
|
@ -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
|
||||
}
|
@ -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
|
||||
}
|
@ -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
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
@ -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
|
||||
}
|
@ -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
|
||||
}
|
@ -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
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -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
40
pkg/makefs/makefs.go
Normal 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
|
||||
}
|
@ -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{}
|
@ -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")
|
||||
}
|
@ -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"
|
||||
)
|
||||
|
@ -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"
|
||||
|
Loading…
x
Reference in New Issue
Block a user