From 3ff790c37f1a019377c82e4304bdc5ae23e5a655 Mon Sep 17 00:00:00 2001 From: James Le Cuirot Date: Mon, 11 May 2026 14:59:13 +0100 Subject: [PATCH] sys-apps/ignition: Patch to address partitioning race conditions Signed-off-by: James Le Cuirot --- ...e-getRealStartAndSize-return-a-map-l.patch | 108 ++++++++++++++++++ ...ow-partx-to-fail-then-check-the-stat.patch | 101 ++++++++++++++++ ....0-r2.ebuild => ignition-2.24.0-r3.ebuild} | 0 .../sys-apps/ignition/ignition-9999.ebuild | 2 + 4 files changed, 211 insertions(+) create mode 100644 sdk_container/src/third_party/coreos-overlay/sys-apps/ignition/files/0019-stages-disks-Make-getRealStartAndSize-return-a-map-l.patch create mode 100644 sdk_container/src/third_party/coreos-overlay/sys-apps/ignition/files/0020-stages-disks-Allow-partx-to-fail-then-check-the-stat.patch rename sdk_container/src/third_party/coreos-overlay/sys-apps/ignition/{ignition-2.24.0-r2.ebuild => ignition-2.24.0-r3.ebuild} (100%) diff --git a/sdk_container/src/third_party/coreos-overlay/sys-apps/ignition/files/0019-stages-disks-Make-getRealStartAndSize-return-a-map-l.patch b/sdk_container/src/third_party/coreos-overlay/sys-apps/ignition/files/0019-stages-disks-Make-getRealStartAndSize-return-a-map-l.patch new file mode 100644 index 0000000000..40ae664891 --- /dev/null +++ b/sdk_container/src/third_party/coreos-overlay/sys-apps/ignition/files/0019-stages-disks-Make-getRealStartAndSize-return-a-map-l.patch @@ -0,0 +1,108 @@ +From 31f898a9eef2fdf83683b0f5d9551d45e669223d Mon Sep 17 00:00:00 2001 +From: James Le Cuirot +Date: Mon, 11 May 2026 12:25:38 +0100 +Subject: [PATCH 1/2] stages/disks: Make getRealStartAndSize return a map like + it says it does + +This is useful for the code I'm about to add. Use int rather than uint64 +because that's what sgdisk.Partition.Number uses. That should be more +than big enough! + +Signed-off-by: James Le Cuirot +--- + internal/exec/stages/disks/partitions.go | 24 ++++++++++++------------ + 1 file changed, 12 insertions(+), 12 deletions(-) + +diff --git a/internal/exec/stages/disks/partitions.go b/internal/exec/stages/disks/partitions.go +index 9a7ae9ca..469ba77f 100644 +--- a/internal/exec/stages/disks/partitions.go ++++ b/internal/exec/stages/disks/partitions.go +@@ -133,7 +133,7 @@ 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) { ++func (s stage) getRealStartAndSize(dev types.Disk, devAlias string, diskInfo util.DiskInfo) (map[int]sgdisk.Partition, error) { + partitions := []sgdisk.Partition{} + for _, cpart := range dev.Partitions { + partitions = append(partitions, sgdisk.Partition{ +@@ -182,7 +182,7 @@ func (s stage) getRealStartAndSize(dev types.Disk, devAlias string, diskInfo uti + return nil, err + } + +- result := []sgdisk.Partition{} ++ result := map[int]sgdisk.Partition{} + for _, part := range partitions { + if dims, ok := realDimensions[part.Number]; ok { + if part.StartSector != nil { +@@ -192,7 +192,7 @@ func (s stage) getRealStartAndSize(dev types.Disk, devAlias string, diskInfo uti + part.SizeInSectors = &dims.size + } + } +- result = append(result, part) ++ result[part.Number] = part + } + return result, nil + } +@@ -486,9 +486,9 @@ func (s stage) partitionDisk(dev types.Disk, devAlias string) error { + return err + } + +- var partxAdd []uint64 +- var partxDelete []uint64 +- var partxUpdate []uint64 ++ var partxAdd []int ++ var partxDelete []int ++ var partxUpdate []int + + for _, part := range resolvedPartitions { + shouldExist := partitionShouldExist(part) +@@ -510,13 +510,13 @@ func (s stage) partitionDisk(dev types.Disk, devAlias string) error { + case !exists && shouldExist: + op.CreatePartition(part) + modification = true +- partxAdd = append(partxAdd, uint64(part.Number)) ++ partxAdd = append(partxAdd, part.Number) + case exists && !shouldExist && !wipeEntry: + return fmt.Errorf("partition %d exists but is specified as nonexistant and wipePartitionEntry is false", part.Number) + case exists && !shouldExist && wipeEntry: + op.DeletePartition(part.Number) + modification = true +- partxDelete = append(partxDelete, uint64(part.Number)) ++ partxDelete = append(partxDelete, part.Number) + case exists && shouldExist && matches: + s.Info("partition %d found with correct specifications", part.Number) + case exists && shouldExist && !wipeEntry && !matches: +@@ -530,7 +530,7 @@ func (s stage) partitionDisk(dev types.Disk, devAlias string) error { + part.StartSector = &info.StartSector + op.CreatePartition(part) + modification = true +- partxUpdate = append(partxUpdate, uint64(part.Number)) ++ partxUpdate = append(partxUpdate, part.Number) + } else { + return fmt.Errorf("partition %d didn't match: %v", part.Number, matchErr) + } +@@ -539,7 +539,7 @@ func (s stage) partitionDisk(dev types.Disk, devAlias string) error { + op.DeletePartition(part.Number) + op.CreatePartition(part) + modification = true +- partxUpdate = append(partxUpdate, uint64(part.Number)) ++ partxUpdate = append(partxUpdate, part.Number) + default: + // unfortunatey, golang doesn't check that all cases are handled exhaustively + return fmt.Errorf("unreachable code reached when processing partition %d. golang--", part.Number) +@@ -558,9 +558,9 @@ func (s stage) partitionDisk(dev types.Disk, devAlias string) error { + // kernel partition table with BLKPG but only uses BLKRRPART which fails + // as soon as one partition of the disk is mounted + if len(activeParts) > 0 { +- runPartxCommand := func(op string, partitions []uint64) error { ++ runPartxCommand := func(op string, partitions []int) error { + for _, partNr := range partitions { +- cmd := exec.Command(distro.PartxCmd(), "--"+op, "--nr", strconv.FormatUint(partNr, 10), blockDevResolved) ++ cmd := exec.Command(distro.PartxCmd(), "--"+op, "--nr", fmt.Sprint(partNr), blockDevResolved) + if _, err := s.LogCmd(cmd, "triggering partition %d %s on %q", partNr, op, devAlias); err != nil { + return fmt.Errorf("partition %s failed: %v", op, err) + } +-- +2.53.0 + diff --git a/sdk_container/src/third_party/coreos-overlay/sys-apps/ignition/files/0020-stages-disks-Allow-partx-to-fail-then-check-the-stat.patch b/sdk_container/src/third_party/coreos-overlay/sys-apps/ignition/files/0020-stages-disks-Allow-partx-to-fail-then-check-the-stat.patch new file mode 100644 index 0000000000..fd364eb8cc --- /dev/null +++ b/sdk_container/src/third_party/coreos-overlay/sys-apps/ignition/files/0020-stages-disks-Allow-partx-to-fail-then-check-the-stat.patch @@ -0,0 +1,101 @@ +From 305f9601c8968a993eaefc5ed251fa8ead26140a Mon Sep 17 00:00:00 2001 +From: James Le Cuirot +Date: Fri, 8 May 2026 17:58:38 +0100 +Subject: [PATCH 2/2] stages/disks: Allow partx to fail then check the state + later + +`partx --add` will fail if the kernel is already aware of the new +partition. It was always theoretically possible that udev might trigger +early, and that appears to be happening now. + +Allow partx to fail and then check that added/updated partitions have +the right start sector and size and that deleted partitions are absent +once udev has settled. + +Signed-off-by: James Le Cuirot +--- + internal/exec/stages/disks/partitions.go | 62 ++++++++++++++++++++---- + 1 file changed, 53 insertions(+), 9 deletions(-) + +diff --git a/internal/exec/stages/disks/partitions.go b/internal/exec/stages/disks/partitions.go +index 469ba77f..8b7d859b 100644 +--- a/internal/exec/stages/disks/partitions.go ++++ b/internal/exec/stages/disks/partitions.go +@@ -567,15 +567,9 @@ func (s stage) partitionDisk(dev types.Disk, devAlias string) error { + } + return nil + } +- if err := runPartxCommand("delete", partxDelete); err != nil { +- return err +- } +- if err := runPartxCommand("update", partxUpdate); err != nil { +- return err +- } +- if err := runPartxCommand("add", partxAdd); err != nil { +- return err +- } ++ runPartxCommand("delete", partxDelete) ++ runPartxCommand("update", partxUpdate) ++ runPartxCommand("add", partxAdd) + } + + // It's best to wait here for the /dev/ABC entries to be +@@ -586,5 +580,55 @@ func (s stage) partitionDisk(dev types.Disk, devAlias string) error { + return fmt.Errorf("failed to wait for udev on %q after partitioning: %v", devAlias, err) + } + ++ for _, partNum := range append(append([]int{}, partxAdd...), partxUpdate...) { ++ part := resolvedPartitions[partNum] ++ partDev := fmt.Sprintf("%s%s%d", blockDevResolved, prefix, partNum) ++ sysBlockDir := fmt.Sprintf("/sys/class/block/%s/", filepath.Base(partDev)) ++ ++ // sysfs always reports in 512-byte sectors; convert our expected ++ // values from logical sectors to 512-byte sectors for comparison ++ logicalTo512 := int64(diskInfo.LogicalSectorSize) / 512 ++ ++ startStr, err := os.ReadFile(sysBlockDir + "start") ++ if err != nil { ++ return fmt.Errorf("failed to read start of %q from sysfs: %v", partDev, err) ++ } ++ kernelStart, err := strconv.ParseInt(strings.TrimSpace(string(startStr)), 10, 64) ++ if err != nil { ++ return fmt.Errorf("failed to parse start of %q from sysfs: %v", partDev, err) ++ } ++ if part.StartSector != nil { ++ expectedStart := *part.StartSector * logicalTo512 ++ if kernelStart != expectedStart { ++ return fmt.Errorf("kernel partition start for %q does not match expected (%d != %d 512-byte sectors)", partDev, kernelStart, expectedStart) ++ } ++ } ++ ++ sizeStr, err := os.ReadFile(sysBlockDir + "size") ++ if err != nil { ++ return fmt.Errorf("failed to read size of %q from sysfs: %v", partDev, err) ++ } ++ kernelSize, err := strconv.ParseInt(strings.TrimSpace(string(sizeStr)), 10, 64) ++ if err != nil { ++ return fmt.Errorf("failed to parse size of %q from sysfs: %v", partDev, err) ++ } ++ if part.SizeInSectors != nil { ++ expectedSize := *part.SizeInSectors * logicalTo512 ++ if kernelSize != expectedSize { ++ return fmt.Errorf("kernel partition size for %q does not match expected (%d != %d 512-byte sectors)", partDev, kernelSize, expectedSize) ++ } ++ } ++ } ++ ++ for _, partNum := range partxDelete { ++ partDev := fmt.Sprintf("%s%s%d", blockDevResolved, prefix, partNum) ++ _, err := os.Stat(partDev) ++ if err == nil { ++ return fmt.Errorf("%q unexpectedly exists after partitioning", partDev) ++ } else if !os.IsNotExist(err) { ++ return fmt.Errorf("failed to stat %q after partitioning: %v", partDev, err) ++ } ++ } ++ + return nil + } +-- +2.53.0 + diff --git a/sdk_container/src/third_party/coreos-overlay/sys-apps/ignition/ignition-2.24.0-r2.ebuild b/sdk_container/src/third_party/coreos-overlay/sys-apps/ignition/ignition-2.24.0-r3.ebuild similarity index 100% rename from sdk_container/src/third_party/coreos-overlay/sys-apps/ignition/ignition-2.24.0-r2.ebuild rename to sdk_container/src/third_party/coreos-overlay/sys-apps/ignition/ignition-2.24.0-r3.ebuild diff --git a/sdk_container/src/third_party/coreos-overlay/sys-apps/ignition/ignition-9999.ebuild b/sdk_container/src/third_party/coreos-overlay/sys-apps/ignition/ignition-9999.ebuild index 63750b1496..6b798d9a87 100644 --- a/sdk_container/src/third_party/coreos-overlay/sys-apps/ignition/ignition-9999.ebuild +++ b/sdk_container/src/third_party/coreos-overlay/sys-apps/ignition/ignition-9999.ebuild @@ -35,6 +35,8 @@ else "${FILESDIR}/0017-docs-Add-re-added-platforms-to-docs-to-pass-tests.patch" "${FILESDIR}/0018-usr-share-oem-oem.patch" "${FILESDIR}/0019-internal-exec-stages-mount-Mount-oem.patch" + "${FILESDIR}/0019-stages-disks-Make-getRealStartAndSize-return-a-map-l.patch" + "${FILESDIR}/0020-stages-disks-Allow-partx-to-fail-then-check-the-stat.patch" ) fi