diff --git a/sdk_container/src/third_party/coreos-overlay/sys-apps/ignition/files/0022-disks-Refuse-to-modify-disks-partitions-in-use.patch b/sdk_container/src/third_party/coreos-overlay/sys-apps/ignition/files/0022-disks-Refuse-to-modify-disks-partitions-in-use.patch new file mode 100644 index 00000000000..4b5685ed2a3 --- /dev/null +++ b/sdk_container/src/third_party/coreos-overlay/sys-apps/ignition/files/0022-disks-Refuse-to-modify-disks-partitions-in-use.patch @@ -0,0 +1,235 @@ +From dde5942cd6b1157e80ad2e16c55d2337a3979aaf Mon Sep 17 00:00:00 2001 +From: Kai Lueke +Date: Mon, 20 Nov 2023 15:47:24 +0100 +Subject: [PATCH 1/3] disks: Refuse to modify disks/partitions in use + +When a partition or the whole disk is in use, sgdisk should not execute +the destructive operation. +Add a check that errors out when a disk in use or a partition in use is +to be destroyed. +--- + internal/exec/stages/disks/partitions.go | 152 +++++++++++++++++++++++ + 1 file changed, 152 insertions(+) + +diff --git a/internal/exec/stages/disks/partitions.go b/internal/exec/stages/disks/partitions.go +index 747f08dc..7c1bd272 100644 +--- a/internal/exec/stages/disks/partitions.go ++++ b/internal/exec/stages/disks/partitions.go +@@ -19,8 +19,12 @@ + package disks + + import ( ++ "bufio" + "errors" + "fmt" ++ "io/ioutil" ++ "os" ++ "path/filepath" + "regexp" + "sort" + "strconv" +@@ -317,11 +321,134 @@ func (p PartitionList) Swap(i, j int) { + p[i], p[j] = p[j], p[i] + } + ++func blockDevHeld(blockDev string) (bool, error) { ++ blockDevResolved, err := filepath.EvalSymlinks(blockDev) ++ if err != nil { ++ return false, fmt.Errorf("failed to resolve %q: %v", blockDev, err) ++ } ++ _, blockDevNode := filepath.Split(blockDevResolved) ++ ++ holdersDir := fmt.Sprintf("/sys/class/block/%s/holders/", blockDevNode) ++ entries, err := ioutil.ReadDir(holdersDir) ++ if err != nil { ++ return false, fmt.Errorf("failed to retrieve holders of %q: %v", blockDev, err) ++ } ++ return len(entries) > 0, nil ++} ++ ++func blockDevMounted(blockDev string) (bool, error) { ++ blockDevResolved, err := filepath.EvalSymlinks(blockDev) ++ if err != nil { ++ return false, fmt.Errorf("failed to resolve %q: %v", blockDev, err) ++ } ++ ++ mounts, err := os.Open("/proc/mounts") ++ if err != nil { ++ return false, fmt.Errorf("failed to open mounts: %v", err) ++ } ++ scanner := bufio.NewScanner(mounts) ++ for scanner.Scan() { ++ mountSource := strings.Split(scanner.Text(), " ")[0] ++ if strings.Contains(mountSource, "/") { ++ mountSourceResolved, err := filepath.EvalSymlinks(mountSource) ++ if err != nil { ++ return false, fmt.Errorf("failed to resolve %q: %v", mountSource, err) ++ } ++ if mountSourceResolved == blockDevResolved { ++ return true, nil ++ } ++ } ++ } ++ if err := scanner.Err(); err != nil { ++ return false, fmt.Errorf("failed to check mounts for %q: %v", blockDev, err) ++ } ++ return false, nil ++} ++ ++func blockDevPartitions(blockDev string) ([]string, error) { ++ blockDevResolved, err := filepath.EvalSymlinks(blockDev) ++ if err != nil { ++ return nil, fmt.Errorf("failed to resolve %q: %v", blockDev, err) ++ } ++ _, blockDevNode := filepath.Split(blockDevResolved) ++ ++ // This also works for extended MBR partitions ++ sysDir := fmt.Sprintf("/sys/class/block/%s/", blockDevNode) ++ entries, err := ioutil.ReadDir(sysDir) ++ if err != nil { ++ return nil, fmt.Errorf("failed to retrieve sysfs entries of %q: %v", blockDev, err) ++ } ++ var partitions []string ++ for _, entry := range entries { ++ if strings.HasPrefix(entry.Name(), blockDevNode) { ++ partitions = append(partitions, "/dev/"+entry.Name()) ++ } ++ } ++ ++ return partitions, nil ++} ++ ++func blockDevInUse(blockDev string) (bool, []string, error) { ++ // Note: This ignores swap and LVM usage ++ inUse := false ++ held, err := blockDevHeld(blockDev) ++ if err != nil { ++ return false, nil, fmt.Errorf("failed to check if %q is held: %v", blockDev, err) ++ } ++ mounted, err := blockDevMounted(blockDev) ++ if err != nil { ++ return false, nil, fmt.Errorf("failed to check if %q is mounted: %v", blockDev, err) ++ } ++ inUse = held || mounted ++ partitions, err := blockDevPartitions(blockDev) ++ if err != nil { ++ return false, nil, fmt.Errorf("failed to retrieve partitions of %q: %v", blockDev, err) ++ } ++ var activePartitions []string ++ for _, partition := range partitions { ++ held, err := blockDevHeld(partition) ++ if err != nil { ++ return false, nil, fmt.Errorf("failed to check if %q is held: %v", partition, err) ++ } ++ mounted, err := blockDevMounted(partition) ++ if err != nil { ++ return false, nil, fmt.Errorf("failed to check if %q is mounted: %v", partition, err) ++ } ++ if held || mounted { ++ activePartitions = append(activePartitions, partition) ++ inUse = true ++ } ++ } ++ return inUse, activePartitions, nil ++} ++ ++func partitionNumberPrefix(blockDev string) (string, error) { ++ blockDevResolved, err := filepath.EvalSymlinks(blockDev) ++ if err != nil { ++ return "", fmt.Errorf("failed to resolve %q: %v", blockDev, err) ++ } ++ lastChar := blockDevResolved[len(blockDevResolved)-1] ++ if '0' <= lastChar && lastChar <= '9' { ++ return "p", nil ++ } ++ return "", nil ++} ++ + // partitionDisk partitions devAlias according to the spec given by dev + func (s stage) partitionDisk(dev types.Disk, devAlias string) error { ++ inUse, activeParts, err := blockDevInUse(devAlias) ++ if err != nil { ++ return fmt.Errorf("failed usage check on %q: %v", devAlias, err) ++ } ++ if inUse && len(activeParts) == 0 { ++ return fmt.Errorf("refusing to operate on directly active disk %q", devAlias) ++ } + if cutil.IsTrue(dev.WipeTable) { + op := sgdisk.Begin(s.Logger, devAlias) + s.Logger.Info("wiping partition table requested on %q", devAlias) ++ if len(activeParts) > 0 { ++ return fmt.Errorf("refusing to wipe active disk %q", devAlias) ++ } + op.WipeTable(true) + if err := op.Commit(); err != nil { + return err +@@ -338,6 +465,11 @@ func (s stage) partitionDisk(dev types.Disk, devAlias string) error { + return err + } + ++ blockDevResolved, err := filepath.EvalSymlinks(devAlias) ++ if err != nil { ++ return fmt.Errorf("failed to resolve %q: %v", devAlias, err) ++ } ++ + // get a list of parititions that have size and start 0 replaced with the real sizes + // that would be used if all specified partitions were to be created anew. + // Also calculate sectors for all of the start/size values. +@@ -356,16 +488,30 @@ func (s stage) partitionDisk(dev types.Disk, devAlias string) error { + matches := exists && matchErr == nil + wipeEntry := cutil.IsTrue(part.WipePartitionEntry) + ++ var modification bool ++ var partInUse bool ++ for _, activePart := range activeParts { ++ prefix, err := partitionNumberPrefix(blockDevResolved) ++ if err != nil { ++ return err ++ } ++ if activePart == fmt.Sprintf("%s%s%d", blockDevResolved, prefix, part.Number) { ++ partInUse = true ++ } ++ } ++ + // This is a translation of the matrix in the operator notes. + switch { + case !exists && !shouldExist: + s.Logger.Info("partition %d specified as nonexistant and no partition was found. Success.", part.Number) + case !exists && shouldExist: + op.CreatePartition(part) ++ modification = true + 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 + case exists && shouldExist && matches: + s.Logger.Info("partition %d found with correct specifications", part.Number) + case exists && shouldExist && !wipeEntry && !matches: +@@ -378,6 +524,7 @@ func (s stage) partitionDisk(dev types.Disk, devAlias string) error { + part.Label = &info.Label + part.StartSector = &info.StartSector + op.CreatePartition(part) ++ modification = true + } else { + return fmt.Errorf("Partition %d didn't match: %v", part.Number, matchErr) + } +@@ -385,10 +532,15 @@ func (s stage) partitionDisk(dev types.Disk, devAlias string) error { + s.Logger.Info("partition %d did not meet specifications, wiping partition entry and recreating", part.Number) + op.DeletePartition(part.Number) + op.CreatePartition(part) ++ modification = true + 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) + } ++ ++ if partInUse && modification { ++ return fmt.Errorf("refusing to modfiy active partition %d on %q", part.Number, devAlias) ++ } + } + + if err := op.Commit(); err != nil { +-- +2.42.0 + diff --git a/sdk_container/src/third_party/coreos-overlay/sys-apps/ignition/files/0022-sgdisk-Run-partprobe-after-partition-changes.patch b/sdk_container/src/third_party/coreos-overlay/sys-apps/ignition/files/0022-sgdisk-Run-partprobe-after-partition-changes.patch deleted file mode 100644 index 06901f2a20b..00000000000 --- a/sdk_container/src/third_party/coreos-overlay/sys-apps/ignition/files/0022-sgdisk-Run-partprobe-after-partition-changes.patch +++ /dev/null @@ -1,67 +0,0 @@ -From e5c4e6bd9f3bad3b27e338e4da2f3b0b53ab1599 Mon Sep 17 00:00:00 2001 -From: Kai Lueke -Date: Fri, 29 Sep 2023 18:06:09 +0200 -Subject: [PATCH] sgdisk: Run partprobe after partition changes - -The sgdisk tool does not update the kernel partition table in contrast -to other similar tools. Often udev can detect the changes but not always -as experienced when adding a new partition on Flatcar's boot disk. -Instead of implicitly relying on some other component to re-read the -kernel partition table, trigger the re-read with partprobe. ---- - dracut/30ignition/module-setup.sh | 1 + - internal/distro/distro.go | 2 ++ - internal/sgdisk/sgdisk.go | 5 +++++ - 3 files changed, 8 insertions(+) - -diff --git a/dracut/30ignition/module-setup.sh b/dracut/30ignition/module-setup.sh -index ad7e80fd..3cdcb631 100755 ---- a/dracut/30ignition/module-setup.sh -+++ b/dracut/30ignition/module-setup.sh -@@ -33,6 +33,7 @@ install() { - mkfs.xfs \ - mkswap \ - sgdisk \ -+ partprobe \ - useradd \ - userdel \ - usermod \ -diff --git a/internal/distro/distro.go b/internal/distro/distro.go -index 61ca87ae..c1c13b62 100644 ---- a/internal/distro/distro.go -+++ b/internal/distro/distro.go -@@ -37,6 +37,7 @@ var ( - mdadmCmd = "mdadm" - mountCmd = "mount" - sgdiskCmd = "sgdisk" -+ partprobeCmd = "partprobe" - modprobeCmd = "modprobe" - udevadmCmd = "udevadm" - usermodCmd = "usermod" -@@ -90,6 +91,7 @@ func GroupdelCmd() string { return groupdelCmd } - func MdadmCmd() string { return mdadmCmd } - func MountCmd() string { return mountCmd } - func SgdiskCmd() string { return sgdiskCmd } -+func PartprobeCmd() string { return partprobeCmd } - func ModprobeCmd() string { return modprobeCmd } - func UdevadmCmd() string { return udevadmCmd } - func UsermodCmd() string { return usermodCmd } -diff --git a/internal/sgdisk/sgdisk.go b/internal/sgdisk/sgdisk.go -index 29915809..e70a3881 100644 ---- a/internal/sgdisk/sgdisk.go -+++ b/internal/sgdisk/sgdisk.go -@@ -121,6 +121,11 @@ func (op *Operation) Commit() error { - if _, err := op.logger.LogCmd(cmd, "deleting %d partitions and creating %d partitions on %q", len(op.deletions), len(op.parts), op.dev); err != nil { - return fmt.Errorf("create partitions failed: %v", err) - } -+ // In contrast to similar tools, sgdisk does not trigger the update of the kernel partition table -+ cmd = exec.Command(distro.PartprobeCmd(), op.dev) -+ if _, err := op.logger.LogCmd(cmd, "re-reading of %d deleted partitions and %d created partitions on %q", len(op.deletions), len(op.parts), op.dev); err != nil { -+ return fmt.Errorf("re-reading partitions failed: %v", err) -+ } - - return nil - } --- -2.41.0 - diff --git a/sdk_container/src/third_party/coreos-overlay/sys-apps/ignition/files/0023-sgdisk-Run-partx-after-partition-changes.patch b/sdk_container/src/third_party/coreos-overlay/sys-apps/ignition/files/0023-sgdisk-Run-partx-after-partition-changes.patch new file mode 100644 index 00000000000..522f71a0412 --- /dev/null +++ b/sdk_container/src/third_party/coreos-overlay/sys-apps/ignition/files/0023-sgdisk-Run-partx-after-partition-changes.patch @@ -0,0 +1,156 @@ +From d26cedc99224c695a21b464e73ac112045090834 Mon Sep 17 00:00:00 2001 +From: Kai Lueke +Date: Fri, 29 Sep 2023 18:06:09 +0200 +Subject: [PATCH] sgdisk: Run partx after partition changes + +The sgdisk tool does not update the kernel partition table with BLKPG in +contrast to other similar tools but only uses BLKRRPART which fails as +soon as one partition of the disk is mounted. +Update the kernel partition table with partx when we know that a +partition of the disk is in use. +--- + docs/release-notes.md | 1 + + dracut/30ignition/module-setup.sh | 1 + + internal/distro/distro.go | 2 ++ + internal/exec/stages/disks/partitions.go | 34 ++++++++++++++++++++++++ + 4 files changed, 38 insertions(+) + +diff --git a/docs/release-notes.md b/docs/release-notes.md +index db49c304..ce97d2d8 100644 +--- a/docs/release-notes.md ++++ b/docs/release-notes.md +@@ -14,6 +14,7 @@ nav_order: 9 + + ### Changes + ++- The Dracut module now installs partx + + ### Bug fixes + +diff --git a/dracut/30ignition/module-setup.sh b/dracut/30ignition/module-setup.sh +index d7a5cfcd..86fd892e 100755 +--- a/dracut/30ignition/module-setup.sh ++++ b/dracut/30ignition/module-setup.sh +@@ -33,6 +33,7 @@ install() { + mkfs.xfs \ + mkswap \ + sgdisk \ ++ partx \ + useradd \ + userdel \ + usermod \ +diff --git a/internal/distro/distro.go b/internal/distro/distro.go +index 9e96166e..f295a572 100644 +--- a/internal/distro/distro.go ++++ b/internal/distro/distro.go +@@ -44,6 +44,7 @@ var ( + mdadmCmd = "mdadm" + mountCmd = "mount" + sgdiskCmd = "sgdisk" ++ partxCmd = "partx" + modprobeCmd = "modprobe" + udevadmCmd = "udevadm" + usermodCmd = "usermod" +@@ -100,6 +101,7 @@ func GroupdelCmd() string { return groupdelCmd } + func MdadmCmd() string { return mdadmCmd } + func MountCmd() string { return mountCmd } + func SgdiskCmd() string { return sgdiskCmd } ++func PartxCmd() string { return partxCmd } + func ModprobeCmd() string { return modprobeCmd } + func UdevadmCmd() string { return udevadmCmd } + func UsermodCmd() string { return usermodCmd } +diff --git a/internal/exec/stages/disks/partitions.go b/internal/exec/stages/disks/partitions.go +index 7c1bd272..5dd261a4 100644 +--- a/internal/exec/stages/disks/partitions.go ++++ b/internal/exec/stages/disks/partitions.go +@@ -24,6 +24,7 @@ import ( + "fmt" + "io/ioutil" + "os" ++ "os/exec" + "path/filepath" + "regexp" + "sort" +@@ -32,6 +33,7 @@ import ( + + cutil "github.com/flatcar/ignition/v2/config/util" + "github.com/flatcar/ignition/v2/config/v3_5_experimental/types" ++ "github.com/flatcar/ignition/v2/internal/distro" + "github.com/flatcar/ignition/v2/internal/exec/util" + "github.com/flatcar/ignition/v2/internal/sgdisk" + ) +@@ -478,6 +480,10 @@ func (s stage) partitionDisk(dev types.Disk, devAlias string) error { + return err + } + ++ var partxAdd []uint64 ++ var partxDelete []uint64 ++ var partxUpdate []uint64 ++ + for _, part := range resolvedPartitions { + shouldExist := partitionShouldExist(part) + info, exists := diskInfo.GetPartition(part.Number) +@@ -507,11 +513,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)) + 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)) + case exists && shouldExist && matches: + s.Logger.Info("partition %d found with correct specifications", part.Number) + case exists && shouldExist && !wipeEntry && !matches: +@@ -525,6 +533,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)) + } else { + return fmt.Errorf("Partition %d didn't match: %v", part.Number, matchErr) + } +@@ -533,6 +542,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)) + 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) +@@ -547,6 +557,30 @@ func (s stage) partitionDisk(dev types.Disk, devAlias string) error { + return fmt.Errorf("commit failure: %v", err) + } + ++ // 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 { ++ for _, partNr := range partxDelete { ++ cmd := exec.Command(distro.PartxCmd(), "--delete", "--nr", strconv.FormatUint(partNr, 10), blockDevResolved) ++ if _, err := s.Logger.LogCmd(cmd, "triggering partition %d deletion on %q", partNr, devAlias); err != nil { ++ return fmt.Errorf("deleting partition failed: %v", err) ++ } ++ } ++ for _, partNr := range partxUpdate { ++ cmd := exec.Command(distro.PartxCmd(), "--update", "--nr", strconv.FormatUint(partNr, 10), blockDevResolved) ++ if _, err := s.Logger.LogCmd(cmd, "triggering partition %d re-read on %q", partNr, devAlias); err != nil { ++ return fmt.Errorf("updating partition failed: %v", err) ++ } ++ } ++ for _, partNr := range partxAdd { ++ cmd := exec.Command(distro.PartxCmd(), "--add", "--nr", strconv.FormatUint(partNr, 10), blockDevResolved) ++ if _, err := s.Logger.LogCmd(cmd, "triggering partition %d addition on %q", partNr, devAlias); err != nil { ++ return fmt.Errorf("adding partition failed: %v", err) ++ } ++ } ++ } ++ + // It's best to wait here for the /dev/ABC entries to be + // (re)created, not only for other parts of the initramfs but + // also because s.waitOnDevices() can still race with udev's +-- +2.43.0 + diff --git a/sdk_container/src/third_party/coreos-overlay/sys-apps/ignition/ignition-2.15.0-r4.ebuild b/sdk_container/src/third_party/coreos-overlay/sys-apps/ignition/ignition-2.15.0-r5.ebuild similarity index 100% rename from sdk_container/src/third_party/coreos-overlay/sys-apps/ignition/ignition-2.15.0-r4.ebuild rename to sdk_container/src/third_party/coreos-overlay/sys-apps/ignition/ignition-2.15.0-r5.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 6c53d9c7eca..6807ae0a70e 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 @@ -63,7 +63,8 @@ PATCHES=( "${FILESDIR}/0019-docs-Add-re-added-platforms-to-docs-to-pass-tests.patch" "${FILESDIR}/0020-usr-share-oem-oem.patch" "${FILESDIR}/0021-internal-exec-stages-mount-Mount-oem.patch" - "${FILESDIR}/0022-sgdisk-Run-partprobe-after-partition-changes.patch" + "${FILESDIR}/0022-disks-Refuse-to-modify-disks-partitions-in-use.patch" + "${FILESDIR}/0023-sgdisk-Run-partx-after-partition-changes.patch" ) src_compile() {