diff --git a/changelog/bugfixes/2026-06-08-ignition-part-0.md b/changelog/bugfixes/2026-06-08-ignition-part-0.md new file mode 100644 index 00000000000..b0d23f65659 --- /dev/null +++ b/changelog/bugfixes/2026-06-08-ignition-part-0.md @@ -0,0 +1 @@ +- Fixed using Ignition to create new partitions with number 0 to get the next available slot. ([ignition#2234](https://github.com/coreos/ignition/pull/2234)) diff --git a/sdk_container/src/third_party/coreos-overlay/sys-apps/ignition/files/0019-stages-disks-Fix-handling-of-device-mapper-when-part.patch b/sdk_container/src/third_party/coreos-overlay/sys-apps/ignition/files/0019-stages-disks-Fix-handling-of-device-mapper-when-part.patch new file mode 100644 index 00000000000..28900e2ea1c --- /dev/null +++ b/sdk_container/src/third_party/coreos-overlay/sys-apps/ignition/files/0019-stages-disks-Fix-handling-of-device-mapper-when-part.patch @@ -0,0 +1,136 @@ +From 1a2908e042ce2fb35cf87890b0cceb25be399383 Mon Sep 17 00:00:00 2001 +From: James Le Cuirot +Date: Thu, 11 Jun 2026 23:03:00 +0100 +Subject: [PATCH 19/21] stages/disks: Fix handling of device mapper when + partitioning + +It was handled correctly when checking whether the whole disk was in use +but not when checking individual partitions. + +Signed-off-by: James Le Cuirot +--- a/internal/exec/stages/disks/partitions.go ++++ b/internal/exec/stages/disks/partitions.go +@@ -323,11 +323,15 @@ func (p PartitionList) Swap(i, j int) { + p[i], p[j] = p[j], p[i] + } + +-func isBlockDevMapper(blockDevResolved string) bool { ++// blockDevDMName returns the device mapper name for the given block device, ++// or an empty string if it is not a device mapper device. ++func blockDevDMName(blockDevResolved string) string { + blockDevNode := filepath.Base(blockDevResolved) +- dmName := fmt.Sprintf("/sys/class/block/%s/dm/name", blockDevNode) +- _, err := os.Stat(dmName) +- return err == nil ++ dmNameBytes, err := os.ReadFile(fmt.Sprintf("/sys/class/block/%s/dm/name", blockDevNode)) ++ if err != nil { ++ return "" ++ } ++ return strings.TrimSpace(string(dmNameBytes)) + } + + // Expects a /dev/xyz path +@@ -368,8 +372,23 @@ func blockDevMounted(blockDevResolved string) (bool, error) { + } + + // Expects a /dev/xyz path +-func blockDevPartitions(blockDevResolved string) ([]string, error) { +- _, blockDevNode := filepath.Split(blockDevResolved) ++func blockDevPartitions(blockDevResolved string, dmName string) ([]string, error) { ++ blockDevNode := filepath.Base(blockDevResolved) ++ ++ if dmName != "" { ++ // For device mapper (e.g. multipath), partition devices are ++ // separate DM nodes. Find them via dm-name symlinks. ++ matches, _ := filepath.Glob(fmt.Sprintf("/dev/disk/by-id/dm-name-%sp[0-9]*", dmName)) ++ var partitions []string ++ for _, m := range matches { ++ resolved, err := filepath.EvalSymlinks(m) ++ if err != nil { ++ return nil, fmt.Errorf("failed to resolve %q: %v", m, err) ++ } ++ partitions = append(partitions, resolved) ++ } ++ return partitions, nil ++ } + + // This also works for extended MBR partitions + sysDir := fmt.Sprintf("/sys/class/block/%s/", blockDevNode) +@@ -388,12 +407,11 @@ func blockDevPartitions(blockDevResolved string) ([]string, error) { + } + + // Expects a /dev/xyz path +-func blockDevInUse(blockDevResolved string, skipPartitionCheck bool) (bool, []string, error) { ++func blockDevInUse(blockDevResolved string, dmName string, skipPartitionCheck bool) (bool, []string, error) { + // Note: This ignores swap and LVM usage + inUse := false +- isDevMapper := isBlockDevMapper(blockDevResolved) + held := false +- if !isDevMapper { ++ if dmName == "" { + var err error + held, err = blockDevHeld(blockDevResolved) + if err != nil { +@@ -408,13 +426,13 @@ func blockDevInUse(blockDevResolved string, skipPartitionCheck bool) (bool, []st + if skipPartitionCheck { + return inUse, nil, nil + } +- partitions, err := blockDevPartitions(blockDevResolved) ++ partitions, err := blockDevPartitions(blockDevResolved, dmName) + if err != nil { + return false, nil, fmt.Errorf("failed to retrieve partitions of %q: %v", blockDevResolved, err) + } + var activePartitions []string + for _, partition := range partitions { +- partInUse, _, err := blockDevInUse(partition, true) ++ partInUse, _, err := blockDevInUse(partition, dmName, true) + if err != nil { + return false, nil, fmt.Errorf("failed to check if partition %q is in use: %v", partition, err) + } +@@ -435,6 +453,18 @@ func partitionNumberPrefix(blockDevResolved string) string { + return "" + } + ++// partitionDevPath returns the expected device path for the given partition ++// number on the given disk. For device mapper devices (e.g. multipath), the ++// partition devices are separate DM nodes, so we locate them via ++// /dev/disk/by-id/dm-name-p symlinks. The returned path may or may ++// not exist on disk. ++func partitionDevPath(blockDevResolved string, dmName string, prefix string, partNum int) string { ++ if dmName == "" { ++ return fmt.Sprintf("%s%s%d", blockDevResolved, prefix, partNum) ++ } ++ return fmt.Sprintf("/dev/disk/by-id/dm-name-%sp%d", dmName, partNum) ++} ++ + // partitionDisk partitions devAlias according to the spec given by dev + func (s stage) partitionDisk(dev types.Disk, devAlias string) error { + blockDevResolved, err := filepath.EvalSymlinks(devAlias) +@@ -442,7 +472,9 @@ func (s stage) partitionDisk(dev types.Disk, devAlias string) error { + return fmt.Errorf("failed to resolve %q: %v", devAlias, err) + } + +- inUse, activeParts, err := blockDevInUse(blockDevResolved, false) ++ dmName := blockDevDMName(blockDevResolved) ++ ++ inUse, activeParts, err := blockDevInUse(blockDevResolved, dmName, false) + if err != nil { + return fmt.Errorf("failed usage check on %q: %v", devAlias, err) + } +@@ -499,7 +531,12 @@ func (s stage) partitionDisk(dev types.Disk, devAlias string) error { + } + matches := exists && matchErr == nil + wipeEntry := cutil.IsTrue(part.WipePartitionEntry) +- partInUse := iutil.StrSliceContains(activeParts, fmt.Sprintf("%s%s%d", blockDevResolved, prefix, part.Number)) ++ ++ partDevForCheck := partitionDevPath(blockDevResolved, dmName, prefix, part.Number) ++ if resolved, err := filepath.EvalSymlinks(partDevForCheck); err == nil { ++ partDevForCheck = resolved ++ } ++ partInUse := iutil.StrSliceContains(activeParts, partDevForCheck) + + var modification bool + +-- +2.54.0 + 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 deleted file mode 100644 index 16c9ad20ef7..00000000000 --- a/sdk_container/src/third_party/coreos-overlay/sys-apps/ignition/files/0019-stages-disks-Make-getRealStartAndSize-return-a-map-l.patch +++ /dev/null @@ -1,108 +0,0 @@ -From a14bde1c582771b17dfe378efcc213c854cabc45 Mon Sep 17 00:00:00 2001 -From: James Le Cuirot -Date: Mon, 11 May 2026 12:25:38 +0100 -Subject: [PATCH 19/20] 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 dd6acdac..327c7efd 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 deleted file mode 100644 index d360bbbe2cc..00000000000 --- a/sdk_container/src/third_party/coreos-overlay/sys-apps/ignition/files/0020-stages-disks-Allow-partx-to-fail-then-check-the-stat.patch +++ /dev/null @@ -1,101 +0,0 @@ -From e9d0f43924886744830a0599880916aef643c66d Mon Sep 17 00:00:00 2001 -From: James Le Cuirot -Date: Fri, 8 May 2026 17:58:38 +0100 -Subject: [PATCH 20/20] 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 327c7efd..86e4a4b8 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/files/0020-stages-disks-Fix-giving-partition-number-0-to-get-th.patch b/sdk_container/src/third_party/coreos-overlay/sys-apps/ignition/files/0020-stages-disks-Fix-giving-partition-number-0-to-get-th.patch new file mode 100644 index 00000000000..e9116dc389d --- /dev/null +++ b/sdk_container/src/third_party/coreos-overlay/sys-apps/ignition/files/0020-stages-disks-Fix-giving-partition-number-0-to-get-th.patch @@ -0,0 +1,128 @@ +From ec53df4431c91342caeed84e35e590ccd191e593 Mon Sep 17 00:00:00 2001 +From: James Le Cuirot +Date: Tue, 2 Jun 2026 15:12:37 +0100 +Subject: [PATCH 20/21] stages/disks: Fix giving partition number 0 to get the + next available + +This was broken since partx was used in commit c2cc56cd. Passing 0 to +partx causes it to try and add all the partitions, which will almost +always fail because the kernel will usually already know about at least +some of them. + +This changes getRealStartAndSize() to also determine and return the +resulting partition numbers so that subsequent operations use these +instead of 0. + +sgdisk does support --new=0, but it has no way to report which partition +number it actually used. + +Signed-off-by: James Le Cuirot +--- a/internal/exec/stages/disks/partitions.go ++++ b/internal/exec/stages/disks/partitions.go +@@ -112,15 +112,6 @@ func partitionMatchesCommon(existing util.PartitionInfo, spec sgdisk.Partition) + return nil + } + +-// partitionShouldBeInspected returns if the partition has zeroes that need to be resolved to sectors. +-func partitionShouldBeInspected(part sgdisk.Partition) bool { +- if part.Number == 0 { +- return false +- } +- return (part.StartSector != nil && *part.StartSector == 0) || +- (part.SizeInSectors != nil && *part.SizeInSectors == 0) +-} +- + func convertMiBToSectors(mib *int, sectorSize int) *int64 { + if mib != nil { + v := int64(*mib) * (1024 * 1024 / int64(sectorSize)) +@@ -130,10 +121,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. ++// getRealStartAndSize returns a copy of the given partition configuration with the real partition ++// numbers, start sectors, and end sectors filled in. 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) { ++ used := map[int]bool{} ++ ++ // Determine which partition numbers are already used. ++ for _, part := range diskInfo.Partitions { ++ used[part.Number] = true ++ } ++ + partitions := []sgdisk.Partition{} + for _, cpart := range dev.Partitions { + partitions = append(partitions, sgdisk.Partition{ +@@ -156,6 +154,10 @@ func (s stage) getRealStartAndSize(dev types.Disk, devAlias string, diskInfo uti + part.SizeInSectors = &info.SizeInSectors + } + } ++ if part.Number > 0 { ++ // Mark the partition number as used or not. ++ used[part.Number] = partitionShouldExist(part) ++ } + if partitionShouldExist(part) { + // Clear the label. sgdisk doesn't escape control characters. This makes parsing easier + part.Label = nil +@@ -163,12 +165,25 @@ func (s stage) getRealStartAndSize(dev types.Disk, devAlias string, diskInfo uti + } + } + +- // We only care to examine partitions that have start or size 0. ++ free := 1 + partitionsToInspect := []int{} +- for _, part := range partitions { +- if partitionShouldBeInspected(part) { +- op.Info(part.Number) +- partitionsToInspect = append(partitionsToInspect, part.Number) ++ for i := range partitions { ++ part := &partitions[i] ++ if partitionShouldExist(*part) { ++ // Find the next free partition number and use it. ++ if part.Number == 0 { ++ for used[free] { ++ free++ ++ } ++ part.Number = free ++ free++ ++ } ++ // We only care to examine partitions that have start or size 0. ++ if part.StartSector == nil || *part.StartSector == 0 || ++ part.SizeInSectors == nil || *part.SizeInSectors == 0 { ++ op.Info(part.Number) ++ partitionsToInspect = append(partitionsToInspect, part.Number) ++ } + } + } + +@@ -182,19 +197,14 @@ func (s stage) getRealStartAndSize(dev types.Disk, devAlias string, diskInfo uti + return nil, err + } + +- result := []sgdisk.Partition{} +- for _, part := range partitions { ++ for i := range partitions { ++ part := &partitions[i] + if dims, ok := realDimensions[part.Number]; ok { +- if part.StartSector != nil { +- part.StartSector = &dims.start +- } +- if part.SizeInSectors != nil { +- part.SizeInSectors = &dims.size +- } ++ part.StartSector = &dims.start ++ part.SizeInSectors = &dims.size + } +- result = append(result, part) + } +- return result, nil ++ return partitions, nil + } + + type sgdiskOutput struct { +-- +2.54.0 + diff --git a/sdk_container/src/third_party/coreos-overlay/sys-apps/ignition/files/0021-stages-disks-Allow-partx-to-fail-then-check-the-stat.patch b/sdk_container/src/third_party/coreos-overlay/sys-apps/ignition/files/0021-stages-disks-Allow-partx-to-fail-then-check-the-stat.patch new file mode 100644 index 00000000000..089a31b72fe --- /dev/null +++ b/sdk_container/src/third_party/coreos-overlay/sys-apps/ignition/files/0021-stages-disks-Allow-partx-to-fail-then-check-the-stat.patch @@ -0,0 +1,246 @@ +From 55f30de3bb7cd9fb8fbea9f2c11d7411744f36b8 Mon Sep 17 00:00:00 2001 +From: James Le Cuirot +Date: Fri, 8 May 2026 17:58:38 +0100 +Subject: [PATCH 21/21] 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. + +Now that partx can safely fail, we can run it unconditionally instead of +just when partitions on the disk were active. It seems best to run it +just in case. + +Signed-off-by: James Le Cuirot +--- a/internal/distro/distro.go ++++ b/internal/distro/distro.go +@@ -36,6 +36,7 @@ var ( + // Helper programs + groupaddCmd = "groupadd" + groupdelCmd = "groupdel" ++ dmsetupCmd = "dmsetup" + mdadmCmd = "mdadm" + mountCmd = "mount" + partxCmd = "partx" +@@ -94,6 +95,7 @@ func OEMLookasideDir() string { return fromEnv("OEM_LOOKASIDE_DIR", oemLookasi + + func GroupaddCmd() string { return groupaddCmd } + func GroupdelCmd() string { return groupdelCmd } ++func DmsetupCmd() string { return dmsetupCmd } + func MdadmCmd() string { return mdadmCmd } + func MountCmd() string { return mountCmd } + func PartxCmd() string { return partxCmd } +--- a/internal/exec/stages/disks/partitions.go ++++ b/internal/exec/stages/disks/partitions.go +@@ -22,10 +22,12 @@ import ( + "bufio" + "errors" + "fmt" ++ "iter" + "os" + "os/exec" + "path/filepath" + "regexp" ++ "slices" + "sort" + "strconv" + "strings" +@@ -475,6 +477,32 @@ func partitionDevPath(blockDevResolved string, dmName string, prefix string, par + return fmt.Sprintf("/dev/disk/by-id/dm-name-%sp%d", dmName, partNum) + } + ++// dmPartitionStartAndSize returns the start sector and size (in 512-byte ++// sectors) of a device mapper partition device by parsing `dmsetup table`. ++// The table for a linear DM partition looks like: ++// ++// 0 linear ++func dmPartitionStartAndSize(partDev string) (int64, int64, error) { ++ out, err := exec.Command(distro.DmsetupCmd(), "table", partDev).Output() ++ if err != nil { ++ return 0, 0, fmt.Errorf("dmsetup table failed for %q: %v", partDev, err) ++ } ++ // Parse: "0 linear " ++ fields := strings.Fields(strings.TrimSpace(string(out))) ++ if len(fields) < 5 || fields[2] != "linear" { ++ return 0, 0, fmt.Errorf("unexpected dmsetup table output for %q: %q", partDev, string(out)) ++ } ++ size, err := strconv.ParseInt(fields[1], 10, 64) ++ if err != nil { ++ return 0, 0, fmt.Errorf("failed to parse size from dmsetup table for %q: %v", partDev, err) ++ } ++ start, err := strconv.ParseInt(fields[4], 10, 64) ++ if err != nil { ++ return 0, 0, fmt.Errorf("failed to parse start from dmsetup table for %q: %v", partDev, err) ++ } ++ return start, size, nil ++} ++ + // partitionDisk partitions devAlias according to the spec given by dev + func (s stage) partitionDisk(dev types.Disk, devAlias string) error { + blockDevResolved, err := filepath.EvalSymlinks(devAlias) +@@ -528,9 +556,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) +@@ -557,13 +585,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: +@@ -577,7 +605,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) + } +@@ -586,7 +614,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) +@@ -604,26 +632,22 @@ func (s stage) partitionDisk(dev types.Disk, devAlias string) error { + // In contrast to similar tools, sgdisk does not trigger the update of the + // 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 { +- for _, partNr := range partitions { +- cmd := exec.Command(distro.PartxCmd(), "--"+op, "--nr", strconv.FormatUint(partNr, 10), 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) +- } ++ runPartxCommand := func(op string, partitions iter.Seq[int]) { ++ for partNr := range partitions { ++ // Don't use LogCmd here because we don't want to treat failure as ++ // critical and this command will never produce anything on Stdout. ++ cmd := exec.Command(distro.PartxCmd(), "--"+op, "--nr", fmt.Sprint(partNr), blockDevResolved) ++ s.Info("triggering partition %d %s on %q", partNr, op, devAlias) ++ s.Debug("executing: %q", cmd.Args) ++ _, err := cmd.Output() ++ if err, ok := err.(*exec.ExitError); ok { ++ s.Notice("%v: Cmd: %q Stderr: %q", err, cmd.Args, err.Stderr) + } +- 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", slices.Values(partxDelete)) ++ runPartxCommand("update", slices.Values(partxUpdate)) ++ runPartxCommand("add", slices.Values(partxAdd)) + + // It's best to wait here for the /dev/ABC entries to be + // (re)created, not only for other parts of the initramfs but +@@ -633,5 +657,70 @@ 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 _, part := range resolvedPartitions { ++ partDev := partitionDevPath(blockDevResolved, dmName, prefix, part.Number) ++ ++ if slices.Contains(partxDelete, part.Number) { ++ _, 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) ++ } ++ } else if slices.Contains(partxAdd, part.Number) || slices.Contains(partxUpdate, part.Number) { ++ var kernelStart, kernelSize int64 ++ ++ // 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 ++ ++ if dmName != "" { ++ // DM partition devices don't have a "start" entry in ++ // sysfs. Use dmsetup table to get start and size. ++ kernelStart, kernelSize, err = dmPartitionStartAndSize(partDev) ++ if err != nil { ++ return fmt.Errorf("failed to get DM table for %q: %v", partDev, err) ++ } ++ } else { ++ partDevResolved, err := filepath.EvalSymlinks(partDev) ++ if err != nil { ++ return fmt.Errorf("failed to resolve %q: %v", partDev, err) ++ } ++ sysBlockDir := fmt.Sprintf("/sys/class/block/%s/", filepath.Base(partDevResolved)) ++ ++ 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) ++ } ++ ++ 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.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) ++ } ++ } ++ 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) ++ } ++ } ++ } ++ } ++ + return nil + } +-- +2.54.0 + diff --git a/sdk_container/src/third_party/coreos-overlay/sys-apps/ignition/ignition-2.24.0-r3.ebuild b/sdk_container/src/third_party/coreos-overlay/sys-apps/ignition/ignition-2.24.0-r4.ebuild similarity index 100% rename from sdk_container/src/third_party/coreos-overlay/sys-apps/ignition/ignition-2.24.0-r3.ebuild rename to sdk_container/src/third_party/coreos-overlay/sys-apps/ignition/ignition-2.24.0-r4.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 a5c521a1a6b..3c1b8c0d829 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 @@ -34,8 +34,9 @@ else "${FILESDIR}/0016-docs-Add-re-added-platforms-to-docs-to-pass-tests.patch" "${FILESDIR}/0017-usr-share-oem-oem.patch" "${FILESDIR}/0018-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" + "${FILESDIR}/0019-stages-disks-Fix-handling-of-device-mapper-when-part.patch" + "${FILESDIR}/0020-stages-disks-Fix-giving-partition-number-0-to-get-th.patch" + "${FILESDIR}/0021-stages-disks-Allow-partx-to-fail-then-check-the-stat.patch" ) fi