Skip to content

Commit

Permalink
sfdisk: add sfdisk module with 'pretend' support
Browse files Browse the repository at this point in the history
This is a copy of sgdisk, with mapping around sfdisk tooling.
Update the buildOptions to understand sfdisk requirements and
focus on adding support for the pretend function.

Fixes: https://issues.redhat.com/browse/COS-2837
  • Loading branch information
prestist committed Oct 9, 2024
1 parent 2a7c85a commit 836db96
Show file tree
Hide file tree
Showing 4 changed files with 444 additions and 1 deletion.
2 changes: 2 additions & 0 deletions config/shared/errors/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@ var (
ErrPathConflictsSystemd = errors.New("path conflicts with systemd unit or dropin")
ErrCexWithClevis = errors.New("cannot use cex with clevis")
ErrCexWithKeyFile = errors.New("cannot use key file with cex")
ErrBadSfdiskPretend = errors.New("sfdisk had unexpected output while pretending partition configuration on device")
ErrBadSfdiskCommit = errors.New("sfdisk had unexpected output while committing partition configuration to device")

// Systemd section errors
ErrInvalidSystemdExt = errors.New("invalid systemd unit extension")
Expand Down
61 changes: 60 additions & 1 deletion internal/exec/stages/disks/partitions.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import (
"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/distro"
Expand Down Expand Up @@ -177,7 +178,7 @@ func (s stage) getRealStartAndSize(dev types.Disk, devAlias string, diskInfo uti
return nil, err
}

realDimensions, err := parseSgdiskPretend(output, partitionsToInspect)
realDimensions, err := parseSfdiskPretend(output, partitionsToInspect)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -284,6 +285,64 @@ func parseSgdiskPretend(sgdiskOut string, partitionNumbers []int) (map[int]sgdis
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 {
Expand Down
106 changes: 106 additions & 0 deletions internal/exec/stages/disks/partitions_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package disks

import (
"errors"
"reflect"
"testing"

internalErrors "github.com/coreos/ignition/v2/config/shared/errors"
)

func TestPartitionParse(t *testing.T) {
// Define test cases
tests := []struct {
name string
sfdiskOut string
partitionNumbers []int
expectedOutput map[int]sfdiskOutput
expectedError error
}{
{
name: "valid input with single partition",
sfdiskOut: `
Disk /dev/vda: 2 GiB, 2147483648 bytes, 4194304 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes

>>> Created a new DOS (MBR) disklabel with disk identifier 0x501fc254.
/dev/vda1: Created a new partition 1 of type 'Linux' and of size 5 KiB.
/dev/vda2: Done.

New situation:
Disklabel type: dos
Disk identifier: 0x501fc254

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},
},
expectedError: nil,
},
{
name: "valid input with two partitions",
sfdiskOut: `
Disk /dev/vda: 2 GiB, 2147483648 bytes, 4194304 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes

>>> Created a new DOS (MBR) disklabel with disk identifier 0x8d8dd38c.
/dev/vda1: Created a new partition 1 of type 'Linux' and of size 5 KiB.
/dev/vda2: Created a new partition 2 of type 'Linux' and of size 5 KiB.
/dev/vda3: Done.

New situation:
Disklabel type: dos
Disk identifier: 0x8d8dd38c

Device Boot Start End Sectors Size Id Type
/dev/vda1 2048 2057 10 5K 83 Linux
/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},
},
expectedError: nil,
},
{
name: "invalid input with 1 partition starting on sector 0",
sfdiskOut: `
Disk /dev/vda: 2 GiB, 2147483648 bytes, 4194304 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes

>>> Created a new DOS (MBR) disklabel with disk identifier 0xdebbe997.
/dev/vda1: Start sector 0 out of range.
Failed to add #1 partition: Numerical result out of range
Leaving.
`,
partitionNumbers: []int{1},
expectedOutput: map[int]sfdiskOutput{
1: {start: 0, size: 0},
},
expectedError: internalErrors.ErrBadSfdiskPretend,
},
}

for i, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
output, err := parseSfdiskPretend(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)
}
} else if !reflect.DeepEqual(output, tt.expectedOutput) {
t.Errorf("#%d: result = %v, expected = %v", i, output, tt.expectedOutput)
}
})
}
}
Loading

0 comments on commit 836db96

Please sign in to comment.