Skip to content

Commit 1ca010f

Browse files
committed
sfdisk: add sfdisk module with 'pretend' support
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
1 parent 64c7271 commit 1ca010f

File tree

4 files changed

+444
-1
lines changed

4 files changed

+444
-1
lines changed

config/shared/errors/errors.go

+2
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,8 @@ var (
8787
ErrPathConflictsSystemd = errors.New("path conflicts with systemd unit or dropin")
8888
ErrCexWithClevis = errors.New("cannot use cex with clevis")
8989
ErrCexWithKeyFile = errors.New("cannot use key file with cex")
90+
ErrBadSfdiskPretend = errors.New("sfdisk had unexpected output while pretending partition configuration on device")
91+
ErrBadSfdiskCommit = errors.New("sfdisk had unexpected output while committing partition configuration to device")
9092

9193
// Systemd section errors
9294
ErrInvalidSystemdExt = errors.New("invalid systemd unit extension")

internal/exec/stages/disks/partitions.go

+60-1
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import (
3030
"strconv"
3131
"strings"
3232

33+
sharedErrors "github.com/coreos/ignition/v2/config/shared/errors"
3334
cutil "github.com/coreos/ignition/v2/config/util"
3435
"github.com/coreos/ignition/v2/config/v3_5_experimental/types"
3536
"github.com/coreos/ignition/v2/internal/distro"
@@ -177,7 +178,7 @@ func (s stage) getRealStartAndSize(dev types.Disk, devAlias string, diskInfo uti
177178
return nil, err
178179
}
179180

180-
realDimensions, err := parseSgdiskPretend(output, partitionsToInspect)
181+
realDimensions, err := parseSfdiskPretend(output, partitionsToInspect)
181182
if err != nil {
182183
return nil, err
183184
}
@@ -284,6 +285,64 @@ func parseSgdiskPretend(sgdiskOut string, partitionNumbers []int) (map[int]sgdis
284285
return output, nil
285286
}
286287

288+
type sfdiskOutput struct {
289+
start int64
290+
size int64
291+
}
292+
293+
// ParsePretend takes the output from sfdisk running with the argument --no-act. Similar to sgdisk
294+
// it then uses regex to parse the output into understood values like 'start' 'size' and attempts
295+
// to catch any failures and wrap them to return to the caller.
296+
func parseSfdiskPretend(sfdiskOut string, partitionNumbers []int) (map[int]sfdiskOutput, error) {
297+
if len(partitionNumbers) == 0 {
298+
return nil, nil
299+
}
300+
301+
// Prepare the data, and a regex for matching on partitions
302+
partitionRegex := regexp.MustCompile(`^/dev/\S+\s+\S*\s+(\d+)\s+(\d+)\s+\d+\s+\S+\s+\S+\s+\S+.*$`)
303+
output := map[int]sfdiskOutput{}
304+
current := sfdiskOutput{}
305+
i := 0
306+
lines := strings.Split(sfdiskOut, "\n")
307+
for _, line := range lines {
308+
matches := partitionRegex.FindStringSubmatch(line)
309+
310+
// Sanity check number of partition entries
311+
if i > len(partitionNumbers) {
312+
return nil, sharedErrors.ErrBadSfdiskPretend
313+
}
314+
315+
// Verify that we are not reading a 'failed' or 'error'
316+
errorRegex := regexp.MustCompile(`(?i)(failed|error)`)
317+
if errorRegex.MatchString(line) {
318+
return nil, fmt.Errorf("%w: sfdisk returned :%v", sharedErrors.ErrBadSfdiskPretend, line)
319+
}
320+
321+
// When we get a match it should be
322+
// Whole line at [0]
323+
// Start at [1]
324+
// Size at [2]
325+
if len(matches) > 1 {
326+
start, err := strconv.Atoi(matches[1])
327+
if err != nil {
328+
return nil, err
329+
}
330+
end, err := strconv.Atoi(matches[2])
331+
if err != nil {
332+
return nil, err
333+
}
334+
335+
current.start = int64(start)
336+
// Add one due to overlap
337+
current.size = int64(end - start + 1)
338+
output[partitionNumbers[i]] = current
339+
i++
340+
}
341+
}
342+
343+
return output, nil
344+
}
345+
287346
// partitionShouldExist returns whether a bool is indicating if a partition should exist or not.
288347
// nil (unspecified in json) is treated the same as true.
289348
func partitionShouldExist(part sgdisk.Partition) bool {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
package disks
2+
3+
import (
4+
"errors"
5+
"reflect"
6+
"testing"
7+
8+
internalErrors "github.com/coreos/ignition/v2/config/shared/errors"
9+
)
10+
11+
func TestPartitionParse(t *testing.T) {
12+
// Define test cases
13+
tests := []struct {
14+
name string
15+
sfdiskOut string
16+
partitionNumbers []int
17+
expectedOutput map[int]sfdiskOutput
18+
expectedError error
19+
}{
20+
{
21+
name: "valid input with single partition",
22+
sfdiskOut: `
23+
Disk /dev/vda: 2 GiB, 2147483648 bytes, 4194304 sectors
24+
Units: sectors of 1 * 512 = 512 bytes
25+
Sector size (logical/physical): 512 bytes / 512 bytes
26+
I/O size (minimum/optimal): 512 bytes / 512 bytes
27+
28+
>>> Created a new DOS (MBR) disklabel with disk identifier 0x501fc254.
29+
/dev/vda1: Created a new partition 1 of type 'Linux' and of size 5 KiB.
30+
/dev/vda2: Done.
31+
32+
New situation:
33+
Disklabel type: dos
34+
Disk identifier: 0x501fc254
35+
36+
Device Boot Start End Sectors Size Id Type
37+
/dev/vda1 2048 2057 10 5K 83 Linux
38+
The partition table is unchanged (--no-act).`,
39+
partitionNumbers: []int{1},
40+
expectedOutput: map[int]sfdiskOutput{
41+
1: {start: 2048, size: 10},
42+
},
43+
expectedError: nil,
44+
},
45+
{
46+
name: "valid input with two partitions",
47+
sfdiskOut: `
48+
Disk /dev/vda: 2 GiB, 2147483648 bytes, 4194304 sectors
49+
Units: sectors of 1 * 512 = 512 bytes
50+
Sector size (logical/physical): 512 bytes / 512 bytes
51+
I/O size (minimum/optimal): 512 bytes / 512 bytes
52+
53+
>>> Created a new DOS (MBR) disklabel with disk identifier 0x8d8dd38c.
54+
/dev/vda1: Created a new partition 1 of type 'Linux' and of size 5 KiB.
55+
/dev/vda2: Created a new partition 2 of type 'Linux' and of size 5 KiB.
56+
/dev/vda3: Done.
57+
58+
New situation:
59+
Disklabel type: dos
60+
Disk identifier: 0x8d8dd38c
61+
62+
Device Boot Start End Sectors Size Id Type
63+
/dev/vda1 2048 2057 10 5K 83 Linux
64+
/dev/vda2 4096 4105 10 5K 83 Linux
65+
The partition table is unchanged (--no-act).`,
66+
partitionNumbers: []int{1, 2},
67+
expectedOutput: map[int]sfdiskOutput{
68+
1: {start: 2048, size: 10},
69+
2: {start: 4096, size: 10},
70+
},
71+
expectedError: nil,
72+
},
73+
{
74+
name: "invalid input with 1 partition starting on sector 0",
75+
sfdiskOut: `
76+
Disk /dev/vda: 2 GiB, 2147483648 bytes, 4194304 sectors
77+
Units: sectors of 1 * 512 = 512 bytes
78+
Sector size (logical/physical): 512 bytes / 512 bytes
79+
I/O size (minimum/optimal): 512 bytes / 512 bytes
80+
81+
>>> Created a new DOS (MBR) disklabel with disk identifier 0xdebbe997.
82+
/dev/vda1: Start sector 0 out of range.
83+
Failed to add #1 partition: Numerical result out of range
84+
Leaving.
85+
`,
86+
partitionNumbers: []int{1},
87+
expectedOutput: map[int]sfdiskOutput{
88+
1: {start: 0, size: 0},
89+
},
90+
expectedError: internalErrors.ErrBadSfdiskPretend,
91+
},
92+
}
93+
94+
for i, tt := range tests {
95+
t.Run(tt.name, func(t *testing.T) {
96+
output, err := parseSfdiskPretend(tt.sfdiskOut, tt.partitionNumbers)
97+
if tt.expectedError != nil {
98+
if !errors.Is(err, tt.expectedError) {
99+
t.Errorf("#%d: bad error: result = %v, expected = %v", i, err, tt.expectedError)
100+
}
101+
} else if !reflect.DeepEqual(output, tt.expectedOutput) {
102+
t.Errorf("#%d: result = %v, expected = %v", i, output, tt.expectedOutput)
103+
}
104+
})
105+
}
106+
}

0 commit comments

Comments
 (0)