From ee58bf71f38451d0bcd00e8ed4ac50bfb622b043 Mon Sep 17 00:00:00 2001 From: Vojtech Trefny Date: Fri, 30 May 2025 13:40:40 +0200 Subject: [PATCH] Wipe end partition before creating it as well as the start Resolves: RHEL-93967 --- ...ore-creating-it-as-well-as-the-start.patch | 385 ++++++++++++++++++ python-blivet.spec | 7 +- 2 files changed, 391 insertions(+), 1 deletion(-) create mode 100644 0020-Wipe-end-partition-before-creating-it-as-well-as-the-start.patch diff --git a/0020-Wipe-end-partition-before-creating-it-as-well-as-the-start.patch b/0020-Wipe-end-partition-before-creating-it-as-well-as-the-start.patch new file mode 100644 index 0000000..1f02612 --- /dev/null +++ b/0020-Wipe-end-partition-before-creating-it-as-well-as-the-start.patch @@ -0,0 +1,385 @@ +From 025cc54c04132a056ba863ecdb1d05f3465632a5 Mon Sep 17 00:00:00 2001 +From: Vojtech Trefny +Date: Tue, 27 May 2025 15:21:23 +0200 +Subject: [PATCH 1/3] Add some basic partitioning storage tests + +This supplements the existing tests which use sparse files. These +new test cases actually run do_it() and check the result after +reset. More test cases will follow. + +Related: RHEL-93967 +--- + .../devices_test/partition_test.py | 148 ++++++++++++++++++ + 1 file changed, 148 insertions(+) + +diff --git a/tests/storage_tests/devices_test/partition_test.py b/tests/storage_tests/devices_test/partition_test.py +index 73da87b43..30e6a0151 100644 +--- a/tests/storage_tests/devices_test/partition_test.py ++++ b/tests/storage_tests/devices_test/partition_test.py +@@ -5,6 +5,7 @@ + + from unittest.mock import patch + ++import blivet + from blivet.devices import DiskFile + from blivet.devices import PartitionDevice + from blivet.devicelibs.gpt import gpt_part_uuid_for_mountpoint +@@ -13,6 +14,8 @@ + from blivet.size import Size + from blivet.util import sparsetmpfile + ++from ..storagetestcase import StorageTestCase ++ + + class PartitionDeviceTestCase(unittest.TestCase): + +@@ -266,3 +269,148 @@ def test_dev_part_type_gpt_autodiscover(self): + flags.gpt_discoverable_partitions = True + self.assertEqual(device.part_type_uuid, + gpt_part_uuid_for_mountpoint("/home")) ++ ++ ++class PartitionTestCase(StorageTestCase): ++ ++ def setUp(self): ++ super().setUp() ++ ++ disks = [os.path.basename(vdev) for vdev in self.vdevs] ++ self.storage = blivet.Blivet() ++ self.storage.exclusive_disks = disks ++ self.storage.reset() ++ ++ # make sure only the targetcli disks are in the devicetree ++ for disk in self.storage.disks: ++ self.assertTrue(disk.path in self.vdevs) ++ self.assertIsNone(disk.format.type) ++ self.assertFalse(disk.children) ++ ++ def _clean_up(self): ++ self.storage.reset() ++ for disk in self.storage.disks: ++ if disk.path not in self.vdevs: ++ raise RuntimeError("Disk %s found in devicetree but not in disks created for tests" % disk.name) ++ self.storage.recursive_remove(disk) ++ ++ self.storage.do_it() ++ ++ def test_msdos_basic(self): ++ disk = self.storage.devicetree.get_device_by_path(self.vdevs[0]) ++ self.assertIsNotNone(disk) ++ ++ self.storage.format_device(disk, blivet.formats.get_format("disklabel", label_type="msdos")) ++ ++ for i in range(4): ++ part = self.storage.new_partition(size=Size("100 MiB"), parents=[disk], ++ primary=True) ++ self.storage.create_device(part) ++ ++ blivet.partitioning.do_partitioning(self.storage) ++ ++ self.storage.do_it() ++ self.storage.reset() ++ ++ disk = self.storage.devicetree.get_device_by_path(self.vdevs[0]) ++ self.assertIsNotNone(disk) ++ self.assertEqual(disk.format.type, "disklabel") ++ self.assertEqual(disk.format.label_type, "msdos") ++ self.assertIsNotNone(disk.format.parted_disk) ++ self.assertIsNotNone(disk.format.parted_device) ++ self.assertEqual(len(disk.format.partitions), 4) ++ self.assertEqual(len(disk.format.primary_partitions), 4) ++ self.assertEqual(len(disk.children), 4) ++ ++ for i in range(4): ++ part = self.storage.devicetree.get_device_by_path(self.vdevs[0] + str(i + 1)) ++ self.assertIsNotNone(part) ++ self.assertEqual(part.type, "partition") ++ self.assertEqual(part.disk, disk) ++ self.assertEqual(part.size, Size("100 MiB")) ++ self.assertTrue(part.is_primary) ++ self.assertFalse(part.is_extended) ++ self.assertFalse(part.is_logical) ++ self.assertIsNotNone(part.parted_partition) ++ ++ def test_msdos_extended(self): ++ disk = self.storage.devicetree.get_device_by_path(self.vdevs[0]) ++ self.assertIsNotNone(disk) ++ ++ self.storage.format_device(disk, blivet.formats.get_format("disklabel", label_type="msdos")) ++ ++ part = self.storage.new_partition(size=Size("100 MiB"), parents=[disk]) ++ self.storage.create_device(part) ++ ++ part = self.storage.new_partition(size=Size("1 GiB"), parents=[disk], ++ part_type=parted.PARTITION_EXTENDED) ++ self.storage.create_device(part) ++ ++ blivet.partitioning.do_partitioning(self.storage) ++ ++ for i in range(4): ++ part = self.storage.new_partition(size=Size("100 MiB"), parents=[disk], ++ part_type=parted.PARTITION_LOGICAL) ++ self.storage.create_device(part) ++ ++ blivet.partitioning.do_partitioning(self.storage) ++ ++ self.storage.do_it() ++ self.storage.reset() ++ ++ disk = self.storage.devicetree.get_device_by_path(self.vdevs[0]) ++ self.assertIsNotNone(disk) ++ self.assertEqual(disk.format.type, "disklabel") ++ self.assertEqual(disk.format.label_type, "msdos") ++ self.assertIsNotNone(disk.format.parted_disk) ++ self.assertIsNotNone(disk.format.parted_device) ++ self.assertEqual(len(disk.format.partitions), 6) ++ self.assertEqual(len(disk.format.primary_partitions), 1) ++ self.assertEqual(len(disk.children), 6) ++ ++ for i in range(4, 8): ++ part = self.storage.devicetree.get_device_by_path(self.vdevs[0] + str(i + 1)) ++ self.assertIsNotNone(part) ++ self.assertEqual(part.type, "partition") ++ self.assertEqual(part.disk, disk) ++ self.assertEqual(part.size, Size("100 MiB")) ++ self.assertFalse(part.is_primary) ++ self.assertFalse(part.is_extended) ++ self.assertTrue(part.is_logical) ++ self.assertIsNotNone(part.parted_partition) ++ ++ def test_gpt_basic(self): ++ disk = self.storage.devicetree.get_device_by_path(self.vdevs[0]) ++ self.assertIsNotNone(disk) ++ ++ self.storage.format_device(disk, blivet.formats.get_format("disklabel", label_type="gpt")) ++ ++ for i in range(4): ++ part = self.storage.new_partition(size=Size("100 MiB"), parents=[disk],) ++ self.storage.create_device(part) ++ ++ blivet.partitioning.do_partitioning(self.storage) ++ ++ self.storage.do_it() ++ self.storage.reset() ++ ++ disk = self.storage.devicetree.get_device_by_path(self.vdevs[0]) ++ self.assertIsNotNone(disk) ++ self.assertEqual(disk.format.type, "disklabel") ++ self.assertEqual(disk.format.label_type, "gpt") ++ self.assertIsNotNone(disk.format.parted_disk) ++ self.assertIsNotNone(disk.format.parted_device) ++ self.assertEqual(len(disk.format.partitions), 4) ++ self.assertEqual(len(disk.format.primary_partitions), 4) ++ self.assertEqual(len(disk.children), 4) ++ ++ for i in range(4): ++ part = self.storage.devicetree.get_device_by_path(self.vdevs[0] + str(i + 1)) ++ self.assertIsNotNone(part) ++ self.assertEqual(part.type, "partition") ++ self.assertEqual(part.disk, disk) ++ self.assertEqual(part.size, Size("100 MiB")) ++ self.assertTrue(part.is_primary) ++ self.assertFalse(part.is_extended) ++ self.assertFalse(part.is_logical) ++ self.assertIsNotNone(part.parted_partition) + +From ab6261adbdfedc26c6b0712a42a3dd9169cabb38 Mon Sep 17 00:00:00 2001 +From: Vojtech Trefny +Date: Tue, 27 May 2025 14:10:49 +0200 +Subject: [PATCH 2/3] Wipe end partition before creating it as well as the + start + +We are currently overwritting start of the newly created partition +with zeroes to remove any filesystem metadata that might occupy +the space. This extends this functionality to end of the partition +to remove 1.0 MD metadata that might be there. + +Resolves: RHEL-93967 +--- + blivet/devices/partition.py | 20 +++++++++++++++++++- + 1 file changed, 19 insertions(+), 1 deletion(-) + +diff --git a/blivet/devices/partition.py b/blivet/devices/partition.py +index 89470d9fb..fc9a97be7 100644 +--- a/blivet/devices/partition.py ++++ b/blivet/devices/partition.py +@@ -659,7 +659,7 @@ def _wipe(self): + """ Wipe the partition metadata. + + Assumes that the partition metadata is located at the start +- of the partition and occupies no more than 1 MiB. ++ and end of the partition and occupies no more than 1 MiB. + + Erases in block increments. Erases the smallest number of blocks + such that at least 1 MiB is erased or the whole partition is +@@ -692,6 +692,24 @@ def _wipe(self): + # things to settle. + udev.settle() + ++ if count >= part_len: ++ # very small partition, we wiped it completely already ++ return ++ ++ # now do the end of the partition as well (RAID 1.0 metadata) ++ end = self.parted_partition.geometry.end ++ cmd = ["dd", "if=/dev/zero", "of=%s" % device, "bs=%d" % bs, ++ "seek=%d" % (end - count), "count=%d" % count] ++ try: ++ util.run_program(cmd) ++ except OSError as e: ++ log.error(str(e)) ++ finally: ++ # If a udev device is created with the watch option, then ++ # a change uevent is synthesized and we need to wait for ++ # things to settle. ++ udev.settle() ++ + def _create(self): + """ Create the device. """ + log_method_call(self, self.name, status=self.status) + +From 936cccdf67e3ee612399bd3f0f8b383ca118ce9b Mon Sep 17 00:00:00 2001 +From: Vojtech Trefny +Date: Wed, 28 May 2025 11:01:14 +0200 +Subject: [PATCH 3/3] tests: Add tests for wiping stale metadata from new + partitions + +Related: RHEL-93967 +--- + .../devices_test/partition_test.py | 119 ++++++++++++++++++ + 1 file changed, 119 insertions(+) + +diff --git a/tests/storage_tests/devices_test/partition_test.py b/tests/storage_tests/devices_test/partition_test.py +index 30e6a0151..87e4b1155 100644 +--- a/tests/storage_tests/devices_test/partition_test.py ++++ b/tests/storage_tests/devices_test/partition_test.py +@@ -1,6 +1,7 @@ + import os + import unittest + from uuid import UUID ++import blivet.deviceaction + import parted + + from unittest.mock import patch +@@ -414,3 +415,121 @@ def test_gpt_basic(self): + self.assertFalse(part.is_extended) + self.assertFalse(part.is_logical) + self.assertIsNotNone(part.parted_partition) ++ ++ def _partition_wipe_check(self): ++ part1 = self.storage.devicetree.get_device_by_path(self.vdevs[0] + "1") ++ self.assertIsNotNone(part1) ++ self.assertIsNone(part1.format.type) ++ ++ out = blivet.util.capture_output(["blkid", "-p", "-sTYPE", "-ovalue", self.vdevs[0] + "1"]) ++ self.assertEqual(out.strip(), "") ++ ++ part2 = self.storage.devicetree.get_device_by_path(self.vdevs[0] + "2") ++ self.assertIsNotNone(part2) ++ self.assertEqual(part2.format.type, "ext4") ++ ++ try: ++ part2.format.do_check() ++ except blivet.errors.FSError as e: ++ self.fail("Partition wipe corrupted filesystem on an adjacent partition: %s" % str(e)) ++ ++ out = blivet.util.capture_output(["blkid", "-p", "-sTYPE", "-ovalue", self.vdevs[0] + "2"]) ++ self.assertEqual(out.strip(), "ext4") ++ ++ def test_partition_wipe_ext(self): ++ """ Check that any stray filesystem metadata are removed before creating a partition """ ++ disk = self.storage.devicetree.get_device_by_path(self.vdevs[0]) ++ self.assertIsNotNone(disk) ++ ++ self.storage.format_device(disk, blivet.formats.get_format("disklabel", label_type="gpt")) ++ ++ # create two partitions with ext4 ++ part1 = self.storage.new_partition(size=Size("100 MiB"), parents=[disk], ++ fmt=blivet.formats.get_format("ext4")) ++ self.storage.create_device(part1) ++ ++ part2 = self.storage.new_partition(size=Size("1 MiB"), parents=[disk], grow=True, ++ fmt=blivet.formats.get_format("ext4")) ++ self.storage.create_device(part2) ++ ++ blivet.partitioning.do_partitioning(self.storage) ++ ++ self.storage.do_it() ++ self.storage.reset() ++ ++ # remove the first partition (only the partition without removing the format) ++ part1 = self.storage.devicetree.get_device_by_path(self.vdevs[0] + "1") ++ ac = blivet.deviceaction.ActionDestroyDevice(part1) ++ self.storage.devicetree.actions.add(ac) ++ ++ self.storage.do_it() ++ self.storage.reset() ++ ++ # create the first partition again (without ext4) ++ disk = self.storage.devicetree.get_device_by_path(self.vdevs[0]) ++ part1 = self.storage.new_partition(size=Size("100 MiB"), parents=[disk]) ++ self.storage.create_device(part1) ++ ++ blivet.partitioning.do_partitioning(self.storage) ++ ++ # XXX PartitionDevice._post_create calls wipefs on the partition, we want to check that ++ # the _pre_create dd wipe works so we need to skip the _post_create wipefs call ++ part1._post_create = lambda: None ++ ++ self.storage.do_it() ++ self.storage.reset() ++ ++ # make sure the ext4 signature is not present on part1 (and untouched on part2) ++ self._partition_wipe_check() ++ ++ def test_partition_wipe_mdraid(self): ++ """ Check that any stray RAID metadata are removed before creating a partition """ ++ disk = self.storage.devicetree.get_device_by_path(self.vdevs[0]) ++ self.assertIsNotNone(disk) ++ ++ self.storage.format_device(disk, blivet.formats.get_format("disklabel", label_type="gpt")) ++ ++ # create two partitions, one empty, one with ext4 ++ part1 = self.storage.new_partition(size=Size("100 MiB"), parents=[disk]) ++ self.storage.create_device(part1) ++ ++ part2 = self.storage.new_partition(size=Size("1 MiB"), parents=[disk], grow=True, ++ fmt=blivet.formats.get_format("ext4")) ++ self.storage.create_device(part2) ++ ++ blivet.partitioning.do_partitioning(self.storage) ++ ++ self.storage.do_it() ++ self.storage.reset() ++ ++ # create MD RAID with metadata 1.0 on the first partition ++ ret = blivet.util.run_program(["mdadm", "--create", "blivetMDTest", "--level=linear", ++ "--metadata=1.0", "--raid-devices=1", "--force", part1.path]) ++ self.assertEqual(ret, 0, "Failed to create RAID array for partition wipe test") ++ ret = blivet.util.run_program(["mdadm", "--stop", "/dev/md/blivetMDTest"]) ++ self.assertEqual(ret, 0, "Failed to create RAID array for partition wipe test") ++ ++ # now remove the partition without removing the array first ++ part1 = self.storage.devicetree.get_device_by_path(self.vdevs[0] + "1") ++ ac = blivet.deviceaction.ActionDestroyDevice(part1) ++ self.storage.devicetree.actions.add(ac) ++ ++ self.storage.do_it() ++ self.storage.reset() ++ ++ # create the first partition again (without format) ++ disk = self.storage.devicetree.get_device_by_path(self.vdevs[0]) ++ part1 = self.storage.new_partition(size=Size("100 MiB"), parents=[disk]) ++ self.storage.create_device(part1) ++ ++ blivet.partitioning.do_partitioning(self.storage) ++ ++ # XXX PartitionDevice._post_create calls wipefs on the partition, we want to check that ++ # the _pre_create dd wipe works so we need to skip the _post_create wipefs call ++ part1._post_create = lambda: None ++ ++ self.storage.do_it() ++ self.storage.reset() ++ ++ # make sure the mdmember signature is not present on part1 (and ext4 is untouched on part2) ++ self._partition_wipe_check() diff --git a/python-blivet.spec b/python-blivet.spec index fc393ba..dbbd216 100644 --- a/python-blivet.spec +++ b/python-blivet.spec @@ -5,7 +5,7 @@ Version: 3.10.0 #%%global prerelease .b2 # prerelease, if defined, should be something like .a1, .b1, .b2.dev1, or .c2 -Release: 19%{?prerelease}%{?dist} +Release: 20%{?prerelease}%{?dist} Epoch: 1 License: LGPL-2.1-or-later %global realname blivet @@ -34,6 +34,7 @@ Patch14: 0016-Make-sure-selinux_test-doesnt-try-to-create-mountpoints.patch Patch15: 0017-LVMPV-format-size-fix.patch Patch16: 0018-Include-additional-information-in-PartitioningError.patch Patch17: 0019-Make-ActionDestroyFormat-optional.patch +Patch18: 0020-Wipe-end-partition-before-creating-it-as-well-as-the-start.patch # Versions of required components (done so we make sure the buildrequires # match the requires versions of things). @@ -128,6 +129,10 @@ make DESTDIR=%{buildroot} install %{python3_sitelib}/* %changelog +* Fri May 30 2025 Vojtech Trefny - 3.10.0-20 +- Wipe end partition before creating it as well as the start + Resolves: RHEL-93967 + * Tue May 20 2025 Vojtech Trefny - 3.10.0-19 - Make ActionDestroyFormat optional when the device is also scheduled to be removed Resolves: RHEL-84685