From 7abbefe026f8b4610f0a97c4a5a377a8e680741f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Budai?= Date: Fri, 20 Dec 2024 14:40:35 +0100 Subject: [PATCH] disk: add support for custom partition GUIDs This commit takes the blueprint customization added in the last commit, and makes it actually useful. --- pkg/disk/disk.go | 21 ++++++++++ pkg/disk/partition_table.go | 68 ++++++++++++++++++++++---------- pkg/disk/partition_table_test.go | 48 +++++++++++++++++++++- 3 files changed, 115 insertions(+), 22 deletions(-) diff --git a/pkg/disk/disk.go b/pkg/disk/disk.go index 9844e701dd..345aaf0bf1 100644 --- a/pkg/disk/disk.go +++ b/pkg/disk/disk.go @@ -24,6 +24,7 @@ import ( "io" "math/rand" "reflect" + "regexp" "strings" "slices" @@ -120,6 +121,26 @@ func getPartitionTypeIDfor(ptType PartitionTableType, partTypeName string) (stri return id, nil } +// exactly 2 hex digits +var validDosPartitionType = regexp.MustCompile(`^[0-9a-fA-F]{2}$`) + +func validatePartitionTypeID(ptType PartitionTableType, partTypeName string) error { + switch ptType { + case PT_DOS: + if !validDosPartitionType.MatchString(partTypeName) { + return fmt.Errorf("invalid dos partition type ID: %s", partTypeName) + } + case PT_GPT: + if _, err := uuid.Parse(partTypeName); err != nil { + return fmt.Errorf("invalid gpt partition type GUID: %s", partTypeName) + } + default: + return fmt.Errorf("unknown or unsupported partition table enum: %d", ptType) + } + + return nil +} + // FSType is the filesystem type enum. // // There should always be one value for each filesystem type supported by diff --git a/pkg/disk/partition_table.go b/pkg/disk/partition_table.go index 9983bdb64e..e7dd33a874 100644 --- a/pkg/disk/partition_table.go +++ b/pkg/disk/partition_table.go @@ -1313,24 +1313,35 @@ func addPlainPartition(pt *PartitionTable, partition blueprint.PartitionCustomiz return fmt.Errorf("error creating partition with mountpoint %q: %w", partition.Mountpoint, err) } - // all user-defined partitions are data partitions except boot and swap - var typeName string - switch { - case partition.Mountpoint == "/boot": - typeName = "boot" - case fstype == "swap": - typeName = "swap" - default: - typeName = "data" - } + partType := partition.GUID - partType, err := getPartitionTypeIDfor(pt.Type, typeName) - if err != nil { - return fmt.Errorf("error getting partition type ID for %q: %w", partition.Mountpoint, err) + if partType != "" { + if err := validatePartitionTypeID(pt.Type, partType); err != nil { + return fmt.Errorf("error validating partition type ID for %q: %w", partition.Mountpoint, err) + } + } else { + // if the partition type is not specified, determine it based on the + // mountpoint and the partition type + + // all user-defined partitions are data partitions except boot and swap + var typeName string + switch { + case partition.Mountpoint == "/boot": + typeName = "boot" + case fstype == "swap": + typeName = "swap" + default: + typeName = "data" + } + + partType, err = getPartitionTypeIDfor(pt.Type, typeName) + if err != nil { + return fmt.Errorf("error getting partition type ID for %q: %w", partition.Mountpoint, err) + } } var payload PayloadEntity - switch typeName { + switch fstype { case "swap": payload = &Swap{ Label: partition.Label, @@ -1408,10 +1419,19 @@ func addLVMPartition(pt *PartitionTable, partition blueprint.PartitionCustomizat } // create partition for volume group - partType, err := getPartitionTypeIDfor(pt.Type, "lvm") - if err != nil { - return fmt.Errorf("error creating lvm partition %q: %w", vgname, err) + partType := partition.GUID + if partType != "" { + if err := validatePartitionTypeID(pt.Type, partType); err != nil { + return fmt.Errorf("error validating partition type ID for %q: %w", vgname, err) + } + } else { + var err error + partType, err = getPartitionTypeIDfor(pt.Type, "lvm") + if err != nil { + return fmt.Errorf("error creating lvm partition %q: %w", vgname, err) + } } + newpart := Partition{ Type: partType, Size: partition.MinSize, @@ -1437,9 +1457,17 @@ func addBtrfsPartition(pt *PartitionTable, partition blueprint.PartitionCustomiz } // create partition for btrfs volume - partType, err := getPartitionTypeIDfor(pt.Type, "data") - if err != nil { - return fmt.Errorf("error creating btrfs partition: %w", err) + partType := partition.GUID + if partType != "" { + if err := validatePartitionTypeID(pt.Type, partType); err != nil { + return fmt.Errorf("error validating partition type ID for btrfs: %w", err) + } + } else { + var err error + partType, err = getPartitionTypeIDfor(pt.Type, "data") + if err != nil { + return fmt.Errorf("error creating btrfs partition: %w", err) + } } newpart := Partition{ Type: partType, diff --git a/pkg/disk/partition_table_test.go b/pkg/disk/partition_table_test.go index c198984764..66f547aa9f 100644 --- a/pkg/disk/partition_table_test.go +++ b/pkg/disk/partition_table_test.go @@ -1187,6 +1187,7 @@ func TestNewCustomPartitionTable(t *testing.T) { Partitions: []blueprint.PartitionCustomization{ { MinSize: 20 * datasizes.MiB, + GUID: "42", // overrides the inferred type FilesystemTypedCustomization: blueprint.FilesystemTypedCustomization{ Mountpoint: "/data", Label: "data", @@ -1230,7 +1231,7 @@ func TestNewCustomPartitionTable(t *testing.T) { { Start: 202 * datasizes.MiB, Size: 20 * datasizes.MiB, - Type: disk.FilesystemLinuxDOSID, + Type: "42", Bootable: false, UUID: "", // partitions on dos PTs don't have UUIDs Payload: &disk.Filesystem{ @@ -1267,6 +1268,7 @@ func TestNewCustomPartitionTable(t *testing.T) { Partitions: []blueprint.PartitionCustomization{ { MinSize: 20 * datasizes.MiB, + GUID: "01234567-89ab-cdef-0123-456789abcdef", // overrides the inferred type FilesystemTypedCustomization: blueprint.FilesystemTypedCustomization{ Mountpoint: "/data", Label: "data", @@ -1310,7 +1312,7 @@ func TestNewCustomPartitionTable(t *testing.T) { { Start: 202 * datasizes.MiB, Size: 20 * datasizes.MiB, - Type: disk.FilesystemDataGUID, + Type: "01234567-89ab-cdef-0123-456789abcdef", Bootable: false, UUID: "a178892e-e285-4ce1-9114-55780875d64e", Payload: &disk.Filesystem{ @@ -2640,6 +2642,48 @@ func TestNewCustomPartitionTableErrors(t *testing.T) { }, errmsg: `error generating partition table: invalid partition table: "dos" partition table type only supports up to 4 partitions: got 5 after creating the partition table with all necessary partitions`, }, + "bad-guid-dos": { + customizations: &blueprint.DiskCustomization{ + Type: "dos", + Partitions: []blueprint.PartitionCustomization{ + { + MinSize: 20 * datasizes.MiB, + GUID: "01234567-89ab-cdef-0123-456789abcdef", // dos cannot use UUIDs + FilesystemTypedCustomization: blueprint.FilesystemTypedCustomization{ + Mountpoint: "/data", + Label: "data", + FSType: "ext4", + }, + }, + }, + }, + options: &disk.CustomPartitionTableOptions{ + DefaultFSType: disk.FS_XFS, + BootMode: platform.BOOT_HYBRID, + }, + errmsg: `error generating partition table: error validating partition type ID for "/data": invalid dos partition type ID: 01234567-89ab-cdef-0123-456789abcdef`, + }, + "bad-guid-gpt": { + customizations: &blueprint.DiskCustomization{ + Type: "gpt", + Partitions: []blueprint.PartitionCustomization{ + { + MinSize: 20 * datasizes.MiB, + GUID: "EF", // gpt requires a 36-character GUID + FilesystemTypedCustomization: blueprint.FilesystemTypedCustomization{ + Mountpoint: "/data", + Label: "data", + FSType: "ext4", + }, + }, + }, + }, + options: &disk.CustomPartitionTableOptions{ + DefaultFSType: disk.FS_XFS, + BootMode: platform.BOOT_HYBRID, + }, + errmsg: `error generating partition table: error validating partition type ID for "/data": invalid gpt partition type GUID: EF`, + }, } // we don't care about the rng for error tests