From 88e919472650b615b028659d877d43b276bf6ff9 Mon Sep 17 00:00:00 2001 From: Steven Presti Date: Tue, 17 Sep 2024 17:01:46 -0400 Subject: [PATCH] device_managers: add abstraction for sgdisk and sfdisk Add an interface to abstract implementation details for partition management, allowing tooling to pivot between implementations. TBD: argument/tag reading for triggering the pivot Fixes: https://issues.redhat.com/browse/COS-2930 --- internal/device_managers/deviceManagers.go | 28 +++ internal/device_managers/sfdisk/sfdisk.go | 157 ++++++-------- .../sfdisk/sfdisk_test.go} | 24 ++- internal/device_managers/sgdisk/sgdisk.go | 114 ++++++++-- internal/exec/stages/disks/partitions.go | 194 +++--------------- 5 files changed, 228 insertions(+), 289 deletions(-) create mode 100644 internal/device_managers/deviceManagers.go rename internal/{exec/stages/disks/partitions_test.go => device_managers/sfdisk/sfdisk_test.go} (83%) diff --git a/internal/device_managers/deviceManagers.go b/internal/device_managers/deviceManagers.go new file mode 100644 index 000000000..27300e972 --- /dev/null +++ b/internal/device_managers/deviceManagers.go @@ -0,0 +1,28 @@ +package device_managers + +import ( + "github.com/coreos/ignition/v2/config/v3_5_experimental/types" +) + +type DeviceManager interface { + CreatePartition(p Partition) + DeletePartition(num int) + Info(num int) + WipeTable(wipe bool) + Pretend() (string, error) + Commit() error + ParseOutput(string, []int) (map[int]Output, error) +} + +type Partition struct { + types.Partition + StartSector *int64 + SizeInSectors *int64 + StartMiB string + SizeMiB string +} + +type Output struct { + Start int64 + Size int64 +} diff --git a/internal/device_managers/sfdisk/sfdisk.go b/internal/device_managers/sfdisk/sfdisk.go index 37c16d121..45c3342f4 100644 --- a/internal/device_managers/sfdisk/sfdisk.go +++ b/internal/device_managers/sfdisk/sfdisk.go @@ -19,9 +19,13 @@ import ( "fmt" "io" "os/exec" + "regexp" + "strconv" + "strings" + sharedErrors "github.com/coreos/ignition/v2/config/shared/errors" "github.com/coreos/ignition/v2/config/util" - "github.com/coreos/ignition/v2/config/v3_5_experimental/types" + "github.com/coreos/ignition/v2/internal/device_managers" "github.com/coreos/ignition/v2/internal/distro" "github.com/coreos/ignition/v2/internal/log" ) @@ -30,30 +34,18 @@ type Operation struct { logger *log.Logger dev string wipe bool - parts []Partition + parts []device_managers.Partition deletions []int infos []int } -// We ignore types.Partition.StartMiB/SizeMiB in favor of -// StartSector/SizeInSectors. The caller is expected to do the conversion. -type Partition struct { - types.Partition - StartSector *int64 - SizeInSectors *int64 - - // shadow StartMiB/SizeMiB so they're not accidentally used - StartMiB string - SizeMiB string -} - // Begin begins an sfdisk operation func Begin(logger *log.Logger, dev string) *Operation { return &Operation{logger: logger, dev: dev} } -// CreatePartition adds the supplied partition to the list of partitions to be created as part of an operation. -func (op *Operation) CreatePartition(p Partition) { +// CreatePartition adds the supplied partition to the list of partitions to be created as part of an operation +func (op *Operation) CreatePartition(p device_managers.Partition) { op.parts = append(op.parts, p) } @@ -81,7 +73,7 @@ func (op *Operation) Pretend() (string, error) { return "", err } - script := op.sfdiskBuildOptions() + script := op.buildOptions() cmd := exec.Command("sh", "-c", fmt.Sprintf("echo -e \"%s\" | sudo %s --no-act %s", script, distro.SfdiskCmd(), op.dev)) stdout, err := cmd.StdoutPipe() @@ -114,8 +106,8 @@ func (op *Operation) Pretend() (string, error) { } // Commit commits an partitioning operation. -func (op *Operation) SfdiskCommit() error { - script := op.sfdiskBuildOptions() +func (op *Operation) Commit() error { + script := op.buildOptions() if len(script) == 0 { return nil } @@ -139,7 +131,60 @@ func (op *Operation) SfdiskCommit() error { return nil } -func (op Operation) sfdiskBuildOptions() string { +// ParseOutput takes the output from sfdisk. Similarly to sgdisk +// it then uses regex to parse the output into understood values like 'start' 'size' and attempts +// to catch any failures and wrap them to return to the caller. +func (op *Operation) ParseOutput(sfdiskOutput string, partitionNumbers []int) (map[int]device_managers.Output, error) { + if len(partitionNumbers) == 0 { + return nil, nil + } + + // Prepare the data, and a regex for matching on partitions + partitionRegex := regexp.MustCompile(`^/dev/\S+\s+\S*\s+(\d+)\s+(\d+)\s+\d+\s+\S+\s+\S+\s+\S+.*$`) + output := map[int]device_managers.Output{} + current := device_managers.Output{} + i := 0 + lines := strings.Split(sfdiskOutput, "\n") + for _, line := range lines { + matches := partitionRegex.FindStringSubmatch(line) + + // Sanity check number of partition entries + if i > len(partitionNumbers) { + return nil, sharedErrors.ErrBadSfdiskPretend + } + + // Verify that we are not reading a 'failed' or 'error' + errorRegex := regexp.MustCompile(`(?i)(failed|error)`) + if errorRegex.MatchString(line) { + return nil, fmt.Errorf("%w: sfdisk returned :%v", sharedErrors.ErrBadSfdiskPretend, line) + } + + // When we get a match it should be + // Whole line at [0] + // Start at [1] + // Size at [2] + if len(matches) > 2 { + start, err := strconv.Atoi(matches[1]) + if err != nil { + return nil, err + } + end, err := strconv.Atoi(matches[2]) + if err != nil { + return nil, err + } + + current.Start = int64(start) + // Add one due to overlap + current.Size = int64(end - start + 1) + output[partitionNumbers[i]] = current + i++ + } + } + + return output, nil +} + +func (op Operation) buildOptions() string { var script bytes.Buffer for _, p := range op.parts { @@ -202,75 +247,3 @@ func (op *Operation) handleInfo() error { } return nil } - -// Copy old functionality from sgdisk to switch between the two during testing. -// Will be removed. -func (op *Operation) SgdiskCommit() error { - opts := op.sgdiskBuildOptions() - if len(opts) == 0 { - return nil - } - op.logger.Info("running sgdisk with options: %v", opts) - cmd := exec.Command(distro.SgdiskCmd(), opts...) - - if _, err := op.logger.LogCmd(cmd, "deleting %d partitions and creating %d partitions on %q", len(op.deletions), len(op.parts), op.dev); err != nil { - return fmt.Errorf("create partitions failed: %v", err) - } - - return nil -} - -// Copy old functionality from sgdisk to switch between the two during testing. -// Will be removed. -func (op Operation) sgdiskBuildOptions() []string { - opts := []string{} - - if op.wipe { - opts = append(opts, "--zap-all") - } - - // Do all deletions before creations - for _, partition := range op.deletions { - opts = append(opts, fmt.Sprintf("--delete=%d", partition)) - } - - for _, p := range op.parts { - opts = append(opts, fmt.Sprintf("--new=%d:%s:+%s", p.Number, partitionGetStart(p), partitionGetSize(p))) - if p.Label != nil { - opts = append(opts, fmt.Sprintf("--change-name=%d:%s", p.Number, *p.Label)) - } - if util.NotEmpty(p.TypeGUID) { - opts = append(opts, fmt.Sprintf("--typecode=%d:%s", p.Number, *p.TypeGUID)) - } - if util.NotEmpty(p.GUID) { - opts = append(opts, fmt.Sprintf("--partition-guid=%d:%s", p.Number, *p.GUID)) - } - } - - for _, partition := range op.infos { - opts = append(opts, fmt.Sprintf("--info=%d", partition)) - } - - if len(opts) == 0 { - return nil - } - - opts = append(opts, op.dev) - return opts -} - -// Copy old functionality from sgdisk to switch between the two during testing. -// Will be removed. -func partitionGetStart(p Partition) string { - if p.StartSector != nil { - return fmt.Sprintf("%d", *p.StartSector) - } - return "0" -} - -func partitionGetSize(p Partition) string { - if p.SizeInSectors != nil { - return fmt.Sprintf("%d", *p.SizeInSectors) - } - return "0" -} diff --git a/internal/exec/stages/disks/partitions_test.go b/internal/device_managers/sfdisk/sfdisk_test.go similarity index 83% rename from internal/exec/stages/disks/partitions_test.go rename to internal/device_managers/sfdisk/sfdisk_test.go index 683cb044e..4d0339914 100644 --- a/internal/exec/stages/disks/partitions_test.go +++ b/internal/device_managers/sfdisk/sfdisk_test.go @@ -1,4 +1,4 @@ -package disks +package sfdisk_test import ( "errors" @@ -6,6 +6,8 @@ import ( "testing" internalErrors "github.com/coreos/ignition/v2/config/shared/errors" + "github.com/coreos/ignition/v2/internal/device_managers" + "github.com/coreos/ignition/v2/internal/device_managers/sfdisk" ) func TestPartitionParse(t *testing.T) { @@ -14,7 +16,7 @@ func TestPartitionParse(t *testing.T) { name string sfdiskOut string partitionNumbers []int - expectedOutput map[int]sfdiskOutput + expectedOutput map[int]device_managers.Output expectedError error }{ { @@ -37,8 +39,8 @@ Device Boot Start End Sectors Size Id Type /dev/vda1 2048 2057 10 5K 83 Linux The partition table is unchanged (--no-act).`, partitionNumbers: []int{1}, - expectedOutput: map[int]sfdiskOutput{ - 1: {start: 2048, size: 10}, + expectedOutput: map[int]device_managers.Output{ + 1: {Start: 2048, Size: 10}, }, expectedError: nil, }, @@ -64,9 +66,9 @@ Device Boot Start End Sectors Size Id Type /dev/vda2 4096 4105 10 5K 83 Linux The partition table is unchanged (--no-act).`, partitionNumbers: []int{1, 2}, - expectedOutput: map[int]sfdiskOutput{ - 1: {start: 2048, size: 10}, - 2: {start: 4096, size: 10}, + expectedOutput: map[int]device_managers.Output{ + 1: {Start: 2048, Size: 10}, + 2: {Start: 4096, Size: 10}, }, expectedError: nil, }, @@ -84,16 +86,16 @@ Failed to add #1 partition: Numerical result out of range Leaving. `, partitionNumbers: []int{1}, - expectedOutput: map[int]sfdiskOutput{ - 1: {start: 0, size: 0}, + expectedOutput: map[int]device_managers.Output{ + 1: {Start: 0, Size: 0}, }, expectedError: internalErrors.ErrBadSfdiskPretend, }, } - + op := sfdisk.Begin(nil, "") for i, tt := range tests { t.Run(tt.name, func(t *testing.T) { - output, err := parseSfdiskPretend(tt.sfdiskOut, tt.partitionNumbers) + output, err := op.ParseOutput(tt.sfdiskOut, tt.partitionNumbers) if tt.expectedError != nil { if !errors.Is(err, tt.expectedError) { t.Errorf("#%d: bad error: result = %v, expected = %v", i, err, tt.expectedError) diff --git a/internal/device_managers/sgdisk/sgdisk.go b/internal/device_managers/sgdisk/sgdisk.go index 299158098..f838bc69c 100644 --- a/internal/device_managers/sgdisk/sgdisk.go +++ b/internal/device_managers/sgdisk/sgdisk.go @@ -15,44 +15,40 @@ package sgdisk import ( + "errors" "fmt" "io" "os/exec" + "regexp" + "strconv" + "strings" "github.com/coreos/ignition/v2/config/util" - "github.com/coreos/ignition/v2/config/v3_5_experimental/types" + "github.com/coreos/ignition/v2/internal/device_managers" "github.com/coreos/ignition/v2/internal/distro" "github.com/coreos/ignition/v2/internal/log" ) +var ( + ErrBadSgdiskOutput = errors.New("sgdisk had unexpected output") +) + type Operation struct { logger *log.Logger dev string wipe bool - parts []Partition + parts []device_managers.Partition deletions []int infos []int } -// We ignore types.Partition.StartMiB/SizeMiB in favor of -// StartSector/SizeInSectors. The caller is expected to do the conversion. -type Partition struct { - types.Partition - StartSector *int64 - SizeInSectors *int64 - - // shadow StartMiB/SizeMiB so they're not accidentally used - StartMiB string - SizeMiB string -} - // Begin begins an sgdisk operation func Begin(logger *log.Logger, dev string) *Operation { return &Operation{logger: logger, dev: dev} } // CreatePartition adds the supplied partition to the list of partitions to be created as part of an operation. -func (op *Operation) CreatePartition(p Partition) { +func (op *Operation) CreatePartition(p device_managers.Partition) { op.parts = append(op.parts, p) } @@ -104,7 +100,7 @@ func (op *Operation) Pretend() (string, error) { } if err := cmd.Wait(); err != nil { - return "", fmt.Errorf("Failed to pretend to create partitions. Err: %v. Stderr: %v", err, string(errors)) + return "", fmt.Errorf("failed to pretend to create partitions. Err: %v. Stderr: %v", err, string(errors)) } return string(output), nil @@ -125,6 +121,88 @@ func (op *Operation) Commit() error { return nil } +// ParseOutput parses the output of running sgdisk pretend with --info specified for each partition +// number specified in partitionNumbers. E.g. if paritionNumbers is [1,4,5], it is expected that the sgdisk +// output was from running `sgdisk --pretend --info=1 --info=4 --info=5`. It assumes the the +// partition labels are well behaved (i.e. contain no control characters). It returns a list of partitions +// matching the partition numbers specified, but with the start and size information as determined by sgdisk. +// The partition numbers need to passed in because sgdisk includes them in its output. +func (op *Operation) ParseOutput(sgdiskOutput string, partitionNumbers []int) (map[int]device_managers.Output, error) { + if len(partitionNumbers) == 0 { + return nil, nil + } + startRegex := regexp.MustCompile(`^First sector: (\d*) \(.*\)$`) + endRegex := regexp.MustCompile(`^Last sector: (\d*) \(.*\)$`) + const ( + START = iota + END = iota + FAIL_ON_START_END = iota + ) + + output := map[int]device_managers.Output{} + state := START + current := device_managers.Output{} + i := 0 + + lines := strings.Split(sgdiskOutput, "\n") + for _, line := range lines { + switch state { + case START: + start, err := parseLine(startRegex, line) + if err != nil { + return nil, err + } + if start != -1 { + current.Start = start + state = END + } + case END: + end, err := parseLine(endRegex, line) + if err != nil { + return nil, err + } + if end != -1 { + current.Size = 1 + end - current.Start + output[partitionNumbers[i]] = current + i++ + if i == len(partitionNumbers) { + state = FAIL_ON_START_END + } else { + current = device_managers.Output{} + state = START + } + } + case FAIL_ON_START_END: + if len(startRegex.FindStringSubmatch(line)) != 0 || + len(endRegex.FindStringSubmatch(line)) != 0 { + return nil, ErrBadSgdiskOutput + } + } + } + + if state != FAIL_ON_START_END { + // We stopped parsing in the middle of a info block. Something is wrong + return nil, ErrBadSgdiskOutput + } + + return output, nil +} + +// parseLine takes a regexp that captures an int64 and a string to match on. On success it returns +// the captured int64 and nil. If the regexp does not match it returns -1 and nil. If it encountered +// an error it returns 0 and the error. +func parseLine(r *regexp.Regexp, line string) (int64, error) { + matches := r.FindStringSubmatch(line) + switch len(matches) { + case 0: + return -1, nil + case 2: + return strconv.ParseInt(matches[1], 10, 64) + default: + return 0, ErrBadSgdiskOutput + } +} + func (op Operation) buildOptions() []string { opts := []string{} @@ -162,14 +240,14 @@ func (op Operation) buildOptions() []string { return opts } -func partitionGetStart(p Partition) string { +func partitionGetStart(p device_managers.Partition) string { if p.StartSector != nil { return fmt.Sprintf("%d", *p.StartSector) } return "0" } -func partitionGetSize(p Partition) string { +func partitionGetSize(p device_managers.Partition) string { if p.SizeInSectors != nil { return fmt.Sprintf("%d", *p.SizeInSectors) } diff --git a/internal/exec/stages/disks/partitions.go b/internal/exec/stages/disks/partitions.go index 7f9fd2fca..f36db703b 100644 --- a/internal/exec/stages/disks/partitions.go +++ b/internal/exec/stages/disks/partitions.go @@ -20,28 +20,32 @@ package disks import ( "bufio" - "errors" "fmt" "os" "os/exec" "path/filepath" - "regexp" "sort" "strconv" "strings" - sharedErrors "github.com/coreos/ignition/v2/config/shared/errors" cutil "github.com/coreos/ignition/v2/config/util" "github.com/coreos/ignition/v2/config/v3_5_experimental/types" + "github.com/coreos/ignition/v2/internal/device_managers" + "github.com/coreos/ignition/v2/internal/device_managers/sfdisk" + "github.com/coreos/ignition/v2/internal/device_managers/sgdisk" "github.com/coreos/ignition/v2/internal/distro" "github.com/coreos/ignition/v2/internal/exec/util" - "github.com/coreos/ignition/v2/internal/sgdisk" + "github.com/coreos/ignition/v2/internal/log" iutil "github.com/coreos/ignition/v2/internal/util" ) -var ( - ErrBadSgdiskOutput = errors.New("sgdisk had unexpected output") -) +func getDeviceManager(logger *log.Logger, dev string) device_managers.DeviceManager { + // To be replaced with build tag support or something similar. + if false { + return sgdisk.Begin(logger, dev) + } + return sfdisk.Begin(logger, dev) +} // createPartitions creates the partitions described in config.Storage.Disks. func (s stage) createPartitions(config types.Config) error { @@ -76,7 +80,7 @@ func (s stage) createPartitions(config types.Config) error { // partitionMatches determines if the existing partition matches the spec given. See doc/operator notes for what // what it means for an existing partition to match the spec. spec must have non-zero Start and Size. -func partitionMatches(existing util.PartitionInfo, spec sgdisk.Partition) error { +func partitionMatches(existing util.PartitionInfo, spec device_managers.Partition) error { if err := partitionMatchesCommon(existing, spec); err != nil { return err } @@ -88,13 +92,13 @@ func partitionMatches(existing util.PartitionInfo, spec sgdisk.Partition) error // partitionMatchesResize returns if the existing partition should be resized by evaluating if // `resize`field is true and partition matches in all respects except size. -func partitionMatchesResize(existing util.PartitionInfo, spec sgdisk.Partition) bool { +func partitionMatchesResize(existing util.PartitionInfo, spec device_managers.Partition) bool { return cutil.IsTrue(spec.Resize) && partitionMatchesCommon(existing, spec) == nil } // partitionMatchesCommon handles the common tests (excluding the partition size) to determine // if the existing partition matches the spec given. -func partitionMatchesCommon(existing util.PartitionInfo, spec sgdisk.Partition) error { +func partitionMatchesCommon(existing util.PartitionInfo, spec device_managers.Partition) error { if spec.Number != existing.Number { return fmt.Errorf("partition numbers did not match (specified %d, got %d). This should not happen, please file a bug.", spec.Number, existing.Number) } @@ -114,7 +118,7 @@ func partitionMatchesCommon(existing util.PartitionInfo, spec sgdisk.Partition) } // partitionShouldBeInspected returns if the partition has zeroes that need to be resolved to sectors. -func partitionShouldBeInspected(part sgdisk.Partition) bool { +func partitionShouldBeInspected(part device_managers.Partition) bool { if part.Number == 0 { return false } @@ -134,17 +138,17 @@ func convertMiBToSectors(mib *int, sectorSize int) *int64 { // getRealStartAndSize returns a map of partition numbers to a struct that contains what their real start // and end sector should be. It runs sgdisk --pretend to determine what the partitions would look like if // everything specified were to be (re)created. -func (s stage) getRealStartAndSize(dev types.Disk, devAlias string, diskInfo util.DiskInfo) ([]sgdisk.Partition, error) { - partitions := []sgdisk.Partition{} +func (s stage) getRealStartAndSize(dev types.Disk, devAlias string, diskInfo util.DiskInfo) ([]device_managers.Partition, error) { + partitions := []device_managers.Partition{} for _, cpart := range dev.Partitions { - partitions = append(partitions, sgdisk.Partition{ + partitions = append(partitions, device_managers.Partition{ Partition: cpart, StartSector: convertMiBToSectors(cpart.StartMiB, diskInfo.LogicalSectorSize), SizeInSectors: convertMiBToSectors(cpart.SizeMiB, diskInfo.LogicalSectorSize), }) } - op := sgdisk.Begin(s.Logger, devAlias) + op := getDeviceManager(s.Logger, devAlias) for _, part := range partitions { if info, exists := diskInfo.GetPartition(part.Number); exists { // delete all existing partitions @@ -177,20 +181,19 @@ func (s stage) getRealStartAndSize(dev types.Disk, devAlias string, diskInfo uti if err != nil { return nil, err } - - realDimensions, err := parseSfdiskPretend(output, partitionsToInspect) + realDimensions, err := op.ParseOutput(output, partitionsToInspect) if err != nil { return nil, err } - result := []sgdisk.Partition{} + result := []device_managers.Partition{} for _, part := range partitions { if dims, ok := realDimensions[part.Number]; ok { if part.StartSector != nil { - part.StartSector = &dims.start + part.StartSector = &dims.Start } if part.SizeInSectors != nil { - part.SizeInSectors = &dims.size + part.SizeInSectors = &dims.Size } } result = append(result, part) @@ -198,154 +201,9 @@ func (s stage) getRealStartAndSize(dev types.Disk, devAlias string, diskInfo uti return result, nil } -type sgdiskOutput struct { - start int64 - size int64 -} - -// parseLine takes a regexp that captures an int64 and a string to match on. On success it returns -// the captured int64 and nil. If the regexp does not match it returns -1 and nil. If it encountered -// an error it returns 0 and the error. -func parseLine(r *regexp.Regexp, line string) (int64, error) { - matches := r.FindStringSubmatch(line) - switch len(matches) { - case 0: - return -1, nil - case 2: - return strconv.ParseInt(matches[1], 10, 64) - default: - return 0, ErrBadSgdiskOutput - } -} - -// parseSgdiskPretend parses the output of running sgdisk pretend with --info specified for each partition -// number specified in partitionNumbers. E.g. if paritionNumbers is [1,4,5], it is expected that the sgdisk -// output was from running `sgdisk --pretend --info=1 --info=4 --info=5`. It assumes the the -// partition labels are well behaved (i.e. contain no control characters). It returns a list of partitions -// matching the partition numbers specified, but with the start and size information as determined by sgdisk. -// The partition numbers need to passed in because sgdisk includes them in its output. -func parseSgdiskPretend(sgdiskOut string, partitionNumbers []int) (map[int]sgdiskOutput, error) { - if len(partitionNumbers) == 0 { - return nil, nil - } - startRegex := regexp.MustCompile(`^First sector: (\d*) \(.*\)$`) - endRegex := regexp.MustCompile(`^Last sector: (\d*) \(.*\)$`) - const ( - START = iota - END = iota - FAIL_ON_START_END = iota - ) - - output := map[int]sgdiskOutput{} - state := START - current := sgdiskOutput{} - i := 0 - - lines := strings.Split(sgdiskOut, "\n") - for _, line := range lines { - switch state { - case START: - start, err := parseLine(startRegex, line) - if err != nil { - return nil, err - } - if start != -1 { - current.start = start - state = END - } - case END: - end, err := parseLine(endRegex, line) - if err != nil { - return nil, err - } - if end != -1 { - current.size = 1 + end - current.start - output[partitionNumbers[i]] = current - i++ - if i == len(partitionNumbers) { - state = FAIL_ON_START_END - } else { - current = sgdiskOutput{} - state = START - } - } - case FAIL_ON_START_END: - if len(startRegex.FindStringSubmatch(line)) != 0 || - len(endRegex.FindStringSubmatch(line)) != 0 { - return nil, ErrBadSgdiskOutput - } - } - } - - if state != FAIL_ON_START_END { - // We stopped parsing in the middle of a info block. Something is wrong - return nil, ErrBadSgdiskOutput - } - - return output, nil -} - -type sfdiskOutput struct { - start int64 - size int64 -} - -// ParsePretend takes the output from sfdisk running with the argument --no-act. Similar to sgdisk -// it then uses regex to parse the output into understood values like 'start' 'size' and attempts -// to catch any failures and wrap them to return to the caller. -func parseSfdiskPretend(sfdiskOut string, partitionNumbers []int) (map[int]sfdiskOutput, error) { - if len(partitionNumbers) == 0 { - return nil, nil - } - - // Prepare the data, and a regex for matching on partitions - partitionRegex := regexp.MustCompile(`^/dev/\S+\s+\S*\s+(\d+)\s+(\d+)\s+\d+\s+\S+\s+\S+\s+\S+.*$`) - output := map[int]sfdiskOutput{} - current := sfdiskOutput{} - i := 0 - lines := strings.Split(sfdiskOut, "\n") - for _, line := range lines { - matches := partitionRegex.FindStringSubmatch(line) - - // Sanity check number of partition entries - if i > len(partitionNumbers) { - return nil, sharedErrors.ErrBadSfdiskPretend - } - - // Verify that we are not reading a 'failed' or 'error' - errorRegex := regexp.MustCompile(`(?i)(failed|error)`) - if errorRegex.MatchString(line) { - return nil, fmt.Errorf("%w: sfdisk returned :%v", sharedErrors.ErrBadSfdiskPretend, line) - } - - // When we get a match it should be - // Whole line at [0] - // Start at [1] - // Size at [2] - if len(matches) > 1 { - start, err := strconv.Atoi(matches[1]) - if err != nil { - return nil, err - } - end, err := strconv.Atoi(matches[2]) - if err != nil { - return nil, err - } - - current.start = int64(start) - // Add one due to overlap - current.size = int64(end - start + 1) - output[partitionNumbers[i]] = current - i++ - } - } - - return output, nil -} - // partitionShouldExist returns whether a bool is indicating if a partition should exist or not. // nil (unspecified in json) is treated the same as true. -func partitionShouldExist(part sgdisk.Partition) bool { +func partitionShouldExist(part device_managers.Partition) bool { return !cutil.IsFalse(part.ShouldExist) } @@ -497,7 +355,7 @@ func (s stage) partitionDisk(dev types.Disk, devAlias string) error { return fmt.Errorf("refusing to operate on directly active disk %q", devAlias) } if cutil.IsTrue(dev.WipeTable) { - op := sgdisk.Begin(s.Logger, devAlias) + op := getDeviceManager(s.Logger, devAlias) s.Logger.Info("wiping partition table requested on %q", devAlias) if len(activeParts) > 0 { return fmt.Errorf("refusing to wipe active disk %q", devAlias) @@ -516,7 +374,7 @@ func (s stage) partitionDisk(dev types.Disk, devAlias string) error { // Ensure all partitions with number 0 are last sort.Stable(PartitionList(dev.Partitions)) - op := sgdisk.Begin(s.Logger, devAlias) + op := getDeviceManager(s.Logger, devAlias) diskInfo, err := s.getPartitionMap(devAlias) if err != nil {