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"
|
"github.com/talos-systems/go-procfs/procfs"
|
||||||
"golang.org/x/sys/unix"
|
"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"
|
||||||
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime/v1alpha1/bootloader"
|
"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/app/machined/pkg/runtime/v1alpha1/bootloader/grub"
|
||||||
"github.com/talos-systems/talos/internal/pkg/mount"
|
"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/machinery/constants"
|
||||||
"github.com/talos-systems/talos/pkg/version"
|
"github.com/talos-systems/talos/pkg/version"
|
||||||
)
|
)
|
||||||
|
@ -15,14 +15,14 @@ import (
|
|||||||
|
|
||||||
"github.com/talos-systems/go-retry/retry"
|
"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/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/machinery/constants"
|
||||||
|
"github.com/talos-systems/talos/pkg/makefs"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Manifest represents the instructions for preparing all block devices
|
// Manifest represents the instructions for preparing all block devices
|
||||||
@ -264,38 +264,38 @@ func (t *Target) Format() error {
|
|||||||
switch t.Label {
|
switch t.Label {
|
||||||
case constants.EFIPartitionLabel:
|
case constants.EFIPartitionLabel:
|
||||||
log.Printf("formatting partition %q as %q with label %q\n", t.PartitionName, "fat", t.Label)
|
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:
|
case constants.BIOSGrubPartitionLabel:
|
||||||
return nil
|
return nil
|
||||||
case constants.BootPartitionLabel:
|
case constants.BootPartitionLabel:
|
||||||
log.Printf("formatting partition %q as %q with label %q\n", t.PartitionName, "xfs", t.Label)
|
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 != "" {
|
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:
|
case constants.MetaPartitionLabel:
|
||||||
return nil
|
return nil
|
||||||
case constants.StatePartitionLabel:
|
case constants.StatePartitionLabel:
|
||||||
log.Printf("formatting partition %q as %q with label %q\n", t.PartitionName, "xfs", t.Label)
|
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 != "" {
|
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:
|
case constants.EphemeralPartitionLabel:
|
||||||
log.Printf("formatting partition %q as %q with label %q\n", t.PartitionName, "xfs", t.Label)
|
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 != "" {
|
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:
|
default:
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,8 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"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"
|
"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/gizak/termui/v3 v3.1.0
|
||||||
github.com/gogo/googleapis v1.4.0 // indirect
|
github.com/gogo/googleapis v1.4.0 // indirect
|
||||||
github.com/golang/protobuf v1.4.2
|
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/grpc-ecosystem/go-grpc-middleware v1.2.0
|
||||||
github.com/hashicorp/go-getter v1.4.1
|
github.com/hashicorp/go-getter v1.4.1
|
||||||
github.com/hashicorp/go-multierror v1.1.0
|
github.com/hashicorp/go-multierror v1.1.0
|
||||||
@ -58,6 +58,7 @@ require (
|
|||||||
github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2
|
github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2
|
||||||
github.com/talos-systems/bootkube-plugin v0.0.0-20200915135634-229d57e818f3
|
github.com/talos-systems/bootkube-plugin v0.0.0-20200915135634-229d57e818f3
|
||||||
github.com/talos-systems/crypto v0.2.0
|
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-loadbalancer v0.1.0
|
||||||
github.com/talos-systems/go-procfs v0.0.0-20200219015357-57c7311fdd45
|
github.com/talos-systems/go-procfs v0.0.0-20200219015357-57c7311fdd45
|
||||||
github.com/talos-systems/go-retry v0.1.1-0.20200922131245-752f081252cf
|
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/net v0.0.0-20200707034311-ab3426394381
|
||||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d // indirect
|
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d // indirect
|
||||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208
|
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208
|
||||||
golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1
|
golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a
|
||||||
golang.org/x/text v0.3.3
|
|
||||||
golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e
|
golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e
|
||||||
golang.org/x/tools v0.0.0-20200716134326-a8f9df4c9543 // indirect
|
golang.org/x/tools v0.0.0-20200716134326-a8f9df4c9543 // indirect
|
||||||
google.golang.org/grpc v1.29.0
|
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.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 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
|
||||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
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.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 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM=
|
||||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
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/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 h1:UwT8uhJ0eDlklY0vYwo1+LGoFgiqkPqjQnae6j8UNYE=
|
||||||
github.com/talos-systems/crypto v0.2.0/go.mod h1:KwqG+jANKU1FNQIapmioHQ5fkovY1DJkAqMenjYBGh0=
|
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 h1:MQFONvSjoleU8RrKq1O1Z8CyTCJGd4SLqdAHDlR6o9s=
|
||||||
github.com/talos-systems/go-loadbalancer v0.1.0/go.mod h1:D5Qjfz+29WVjONWECZvOkmaLsBb3f5YeWME0u/5HmIc=
|
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=
|
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-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 h1:sIky/MyNRSHTrdxfsiUSS4WIAMvInbeXljJz+jDjeYE=
|
||||||
golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
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.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.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/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
|
package runtime
|
||||||
|
|
||||||
import (
|
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"
|
"github.com/talos-systems/talos/pkg/machinery/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -10,8 +10,9 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"os"
|
"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/internal/app/machined/pkg/runtime/v1alpha1/bootloader/grub"
|
||||||
"github.com/talos-systems/talos/pkg/blockdevice/probe"
|
|
||||||
"github.com/talos-systems/talos/pkg/machinery/constants"
|
"github.com/talos-systems/talos/pkg/machinery/constants"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -18,9 +18,10 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"text/template"
|
"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/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"
|
"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-procfs/procfs"
|
||||||
"github.com/talos-systems/go-smbios/smbios"
|
"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/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/download"
|
||||||
"github.com/talos-systems/talos/pkg/machinery/constants"
|
"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-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"
|
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/internal/install"
|
||||||
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime"
|
"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/kmsg"
|
||||||
"github.com/talos-systems/talos/internal/pkg/kubeconfig"
|
"github.com/talos-systems/talos/internal/pkg/kubeconfig"
|
||||||
"github.com/talos-systems/talos/internal/pkg/mount"
|
"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/cmd"
|
||||||
"github.com/talos-systems/talos/pkg/conditions"
|
"github.com/talos-systems/talos/pkg/conditions"
|
||||||
"github.com/talos-systems/talos/pkg/kubernetes"
|
"github.com/talos-systems/talos/pkg/kubernetes"
|
||||||
|
@ -8,9 +8,10 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"os"
|
"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"
|
||||||
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime/v1alpha1/platform"
|
"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"
|
"github.com/talos-systems/talos/pkg/machinery/constants"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -16,11 +16,12 @@ import (
|
|||||||
"github.com/talos-systems/go-retry/retry"
|
"github.com/talos-systems/go-retry/retry"
|
||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
|
|
||||||
"github.com/talos-systems/talos/pkg/blockdevice"
|
"github.com/talos-systems/go-blockdevice/blockdevice"
|
||||||
"github.com/talos-systems/talos/pkg/blockdevice/filesystem/xfs"
|
gptpartition "github.com/talos-systems/go-blockdevice/blockdevice/table/gpt/partition"
|
||||||
gptpartition "github.com/talos-systems/talos/pkg/blockdevice/table/gpt/partition"
|
"github.com/talos-systems/go-blockdevice/blockdevice/util"
|
||||||
"github.com/talos-systems/talos/pkg/blockdevice/util"
|
|
||||||
"github.com/talos-systems/talos/pkg/machinery/constants"
|
"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.
|
// 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.
|
// GrowFilesystem grows a partition's filesystem to the maximum size allowed.
|
||||||
// NB: An XFS partition MUST be mounted, or this will fail.
|
// NB: An XFS partition MUST be mounted, or this will fail.
|
||||||
func (p *Point) GrowFilesystem() (err error) {
|
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)
|
return fmt.Errorf("xfs_growfs: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,7 +10,8 @@ import (
|
|||||||
|
|
||||||
"golang.org/x/sys/unix"
|
"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"
|
"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
|
// 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/.
|
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
package vfat
|
package makefs
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/talos-systems/talos/pkg/cmd"
|
"github.com/talos-systems/talos/pkg/cmd"
|
||||||
)
|
)
|
||||||
|
|
||||||
// MakeFS creates a VFAT filesystem on the specified partition.
|
// VFAT creates a VFAT filesystem on the specified partition.
|
||||||
func MakeFS(partname string, setters ...Option) error {
|
func VFAT(partname string, setters ...Option) error {
|
||||||
opts := NewDefaultOptions(setters...)
|
opts := NewDefaultOptions(setters...)
|
||||||
|
|
||||||
args := []string{}
|
args := []string{}
|
@ -2,8 +2,7 @@
|
|||||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
// 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/.
|
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
// Package xfs provides an interface to xfsprogs.
|
package makefs
|
||||||
package xfs
|
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
@ -11,16 +10,16 @@ import (
|
|||||||
"github.com/talos-systems/talos/pkg/cmd"
|
"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.
|
// MUST be mounted, or this will fail.
|
||||||
func GrowFS(partname string) error {
|
func XFSGrow(partname string) error {
|
||||||
_, err := cmd.Run("xfs_growfs", "-d", partname)
|
_, err := cmd.Run("xfs_growfs", "-d", partname)
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// MakeFS creates a XFS filesystem on the specified partition.
|
// XFS creates a XFS filesystem on the specified partition.
|
||||||
func MakeFS(partname string, setters ...Option) error {
|
func XFS(partname string, setters ...Option) error {
|
||||||
if partname == "" {
|
if partname == "" {
|
||||||
return fmt.Errorf("missing path to disk")
|
return fmt.Errorf("missing path to disk")
|
||||||
}
|
}
|
@ -15,9 +15,10 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
|
||||||
"github.com/talos-systems/talos/pkg/blockdevice/filesystem/vfat"
|
"github.com/talos-systems/go-blockdevice/blockdevice/filesystem/vfat"
|
||||||
"github.com/talos-systems/talos/pkg/blockdevice/table"
|
"github.com/talos-systems/go-blockdevice/blockdevice/table"
|
||||||
"github.com/talos-systems/talos/pkg/blockdevice/table/gpt"
|
"github.com/talos-systems/go-blockdevice/blockdevice/table/gpt"
|
||||||
|
|
||||||
"github.com/talos-systems/talos/pkg/machinery/constants"
|
"github.com/talos-systems/talos/pkg/machinery/constants"
|
||||||
"github.com/talos-systems/talos/pkg/provision/internal/vmlinuz"
|
"github.com/talos-systems/talos/pkg/provision/internal/vmlinuz"
|
||||||
)
|
)
|
||||||
|
@ -20,7 +20,8 @@ import (
|
|||||||
"github.com/containernetworking/plugins/pkg/testutils"
|
"github.com/containernetworking/plugins/pkg/testutils"
|
||||||
"github.com/google/uuid"
|
"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"
|
||||||
"github.com/talos-systems/talos/pkg/provision/internal/cniutils"
|
"github.com/talos-systems/talos/pkg/provision/internal/cniutils"
|
||||||
"github.com/talos-systems/talos/pkg/provision/providers/vm"
|
"github.com/talos-systems/talos/pkg/provision/providers/vm"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user