leapp-repository/SOURCES/0048-mount_unit_gen-bind-mount-sysroot-boot-to-boot.patch
2025-12-01 09:14:24 +00:00

210 lines
9.4 KiB
Diff

From 0a203330cee4fba6a28c65f1c6e0e450cc45771e Mon Sep 17 00:00:00 2001
From: Michal Hecko <mhecko@redhat.com>
Date: Thu, 30 Oct 2025 11:52:39 +0100
Subject: [PATCH 48/55] mount_unit_gen: bind mount /sysroot/boot to /boot
Our changes towards using systemd-fstab-generator in the upgrade
initramfs caused that we are mounting almost all partitions, including
/boot (the actual mount target is /sysroot/boot) early in the boot
process. When upgrading with FIPS, the dracut fips module tries to mount
the device where the boot partition resides to check the integrity of
the kernel, however, it fails as the boot block device is already
mounted by us. This patch therefore introduces a static unit that
bind-mounts What=/sysroot/boot to Where=/boot, making the contents of
/boot available to the fips module. The bind-mounting service is
introduced only if the source system has /boot on a separate partition.
This is determined by checking whether anything shuld be mounted at
/boot according to fstab.
Jira-ref: RHEL-123886
---
.../initramfs/mount_units_generator/actor.py | 3 +-
.../files/bundled_units/boot.mount | 11 ++++
.../libraries/mount_unit_generator.py | 38 ++++++++++-
.../tests/test_mount_unit_generation.py | 63 ++++++++++++++++++-
4 files changed, 111 insertions(+), 4 deletions(-)
create mode 100644 repos/system_upgrade/common/actors/initramfs/mount_units_generator/files/bundled_units/boot.mount
diff --git a/repos/system_upgrade/common/actors/initramfs/mount_units_generator/actor.py b/repos/system_upgrade/common/actors/initramfs/mount_units_generator/actor.py
index dd667513..23c618b6 100644
--- a/repos/system_upgrade/common/actors/initramfs/mount_units_generator/actor.py
+++ b/repos/system_upgrade/common/actors/initramfs/mount_units_generator/actor.py
@@ -1,6 +1,6 @@
from leapp.actors import Actor
from leapp.libraries.actor import mount_unit_generator as mount_unit_generator_lib
-from leapp.models import LiveModeConfig, TargetUserSpaceInfo, UpgradeInitramfsTasks
+from leapp.models import LiveModeConfig, StorageInfo, TargetUserSpaceInfo, UpgradeInitramfsTasks
from leapp.tags import InterimPreparationPhaseTag, IPUWorkflowTag
@@ -15,6 +15,7 @@ class MountUnitGenerator(Actor):
consumes = (
LiveModeConfig,
TargetUserSpaceInfo,
+ StorageInfo,
)
produces = (
UpgradeInitramfsTasks,
diff --git a/repos/system_upgrade/common/actors/initramfs/mount_units_generator/files/bundled_units/boot.mount b/repos/system_upgrade/common/actors/initramfs/mount_units_generator/files/bundled_units/boot.mount
new file mode 100644
index 00000000..869c5e4c
--- /dev/null
+++ b/repos/system_upgrade/common/actors/initramfs/mount_units_generator/files/bundled_units/boot.mount
@@ -0,0 +1,11 @@
+[Unit]
+DefaultDependencies=no
+Before=local-fs.target
+After=sysroot-boot.target
+Requires=sysroot-boot.target
+
+[Mount]
+What=/sysroot/boot
+Where=/boot
+Type=none
+Options=bind
diff --git a/repos/system_upgrade/common/actors/initramfs/mount_units_generator/libraries/mount_unit_generator.py b/repos/system_upgrade/common/actors/initramfs/mount_units_generator/libraries/mount_unit_generator.py
index 943bddd4..e3070986 100644
--- a/repos/system_upgrade/common/actors/initramfs/mount_units_generator/libraries/mount_unit_generator.py
+++ b/repos/system_upgrade/common/actors/initramfs/mount_units_generator/libraries/mount_unit_generator.py
@@ -5,7 +5,9 @@ import tempfile
from leapp.exceptions import StopActorExecutionError
from leapp.libraries.common import mounting
from leapp.libraries.stdlib import api, CalledProcessError, run
-from leapp.models import LiveModeConfig, TargetUserSpaceInfo, UpgradeInitramfsTasks
+from leapp.models import LiveModeConfig, StorageInfo, TargetUserSpaceInfo, UpgradeInitramfsTasks
+
+BIND_MOUNT_SYSROOT_BOOT_UNIT = 'boot.mount'
def run_systemd_fstab_generator(output_directory):
@@ -294,6 +296,39 @@ def request_units_inclusion_in_initramfs(files_to_include):
api.produce(tasks)
+def does_system_have_separate_boot_partition():
+ storage_info = next(api.consume(StorageInfo), None)
+ if not storage_info:
+ err_msg = 'Actor did not receive required information about system storage (StorageInfo)'
+ raise StopActorExecutionError(err_msg)
+
+ for fstab_entry in storage_info.fstab:
+ if fstab_entry.fs_file == '/boot':
+ return True
+
+ return False
+
+
+def inject_bundled_units(workspace):
+ """
+ Copy static units that are bundled within this actor into the workspace.
+ """
+ bundled_units_dir = api.get_actor_folder_path('bundled_units')
+ for unit in os.listdir(bundled_units_dir):
+ if unit == BIND_MOUNT_SYSROOT_BOOT_UNIT:
+ has_separate_boot = does_system_have_separate_boot_partition()
+ if not has_separate_boot:
+ # We perform bind-mounting because of dracut's fips module.
+ # When /boot is not a separate partition, we don't need to bind mount it --
+ # the fips module itself will create a symlink.
+ continue
+
+ unit_path = os.path.join(bundled_units_dir, unit)
+ unit_dst = os.path.join(workspace, unit)
+ api.current_logger().debug('Copying static unit bundled within leapp {} to {}'.format(unit, unit_dst))
+ shutil.copyfile(unit_path, unit_dst)
+
+
def setup_storage_initialization():
livemode_config = next(api.consume(LiveModeConfig), None)
if livemode_config and livemode_config.is_enabled:
@@ -306,6 +341,7 @@ def setup_storage_initialization():
run_systemd_fstab_generator(workspace_path)
remove_units_for_targets_that_are_already_mounted_by_dracut(workspace_path)
prefix_all_mount_units_with_sysroot(workspace_path)
+ inject_bundled_units(workspace_path)
fix_symlinks_in_targets(workspace_path)
mount_unit_files = copy_units_into_system_location(upgrade_container_ctx, workspace_path)
request_units_inclusion_in_initramfs(mount_unit_files)
diff --git a/repos/system_upgrade/common/actors/initramfs/mount_units_generator/tests/test_mount_unit_generation.py b/repos/system_upgrade/common/actors/initramfs/mount_units_generator/tests/test_mount_unit_generation.py
index 8849ada9..eb90a75d 100644
--- a/repos/system_upgrade/common/actors/initramfs/mount_units_generator/tests/test_mount_unit_generation.py
+++ b/repos/system_upgrade/common/actors/initramfs/mount_units_generator/tests/test_mount_unit_generation.py
@@ -5,9 +5,9 @@ import pytest
from leapp.exceptions import StopActorExecutionError
from leapp.libraries.actor import mount_unit_generator
-from leapp.libraries.common.testutils import logger_mocked
+from leapp.libraries.common.testutils import CurrentActorMocked, logger_mocked
from leapp.libraries.stdlib import api, CalledProcessError
-from leapp.models import TargetUserSpaceInfo, UpgradeInitramfsTasks
+from leapp.models import FstabEntry, StorageInfo, TargetUserSpaceInfo, UpgradeInitramfsTasks
def test_run_systemd_fstab_generator_successful_generation(monkeypatch):
@@ -267,3 +267,62 @@ def test_copy_units_mixed_content(monkeypatch):
]
assert sorted(files) == sorted(expected_files)
assert mount_unit_generator._delete_file.removal_called
+
+
+class CurrentActorMockedWithActorFolder(CurrentActorMocked):
+ def __init__(self, actor_folder_path, *args, **kwargs):
+ self.actor_folder_path = actor_folder_path
+ super().__init__(*args, **kwargs)
+
+ def get_actor_folder_path(self, subfolder):
+ return os.path.join(self.actor_folder_path, subfolder)
+
+
+@pytest.mark.parametrize('has_separate_boot', (True, False))
+def test_injection_of_sysroot_boot_bindmount_unit(monkeypatch, has_separate_boot):
+ fstab_entries = [
+ FstabEntry(fs_spec='UUID=123', fs_file='/root', fs_vfstype='xfs',
+ fs_mntops='defaults', fs_freq='0', fs_passno='0')
+ ]
+
+ if has_separate_boot:
+ boot_fstab_entry = FstabEntry(fs_spec='UUID=123', fs_file='/root', fs_vfstype='xfs',
+ fs_mntops='defaults', fs_freq='0', fs_passno='0')
+ fstab_entries.append(boot_fstab_entry)
+
+ storage_info = StorageInfo(fstab=fstab_entries)
+
+ actor_mock = CurrentActorMockedWithActorFolder(actor_folder_path='/actor', msgs=[storage_info])
+ monkeypatch.setattr(api, 'current_actor', actor_mock)
+
+ workspace_path = '/workspace'
+ was_copyfile_for_sysroot_boot_called = False
+
+ def copyfile_mocked(source, dest, *args, **kwargs):
+ if not os.path.basename(source) == mount_unit_generator.BIND_MOUNT_SYSROOT_BOOT_UNIT:
+ return
+
+ assert has_separate_boot
+ assert dest == os.path.join(workspace_path, mount_unit_generator.BIND_MOUNT_SYSROOT_BOOT_UNIT)
+
+ nonlocal was_copyfile_for_sysroot_boot_called
+ was_copyfile_for_sysroot_boot_called = True
+
+ monkeypatch.setattr(shutil, 'copyfile', copyfile_mocked)
+
+ def listdir_mocked(path):
+ assert path == actor_mock.get_actor_folder_path('bundled_units')
+ return [
+ mount_unit_generator.BIND_MOUNT_SYSROOT_BOOT_UNIT,
+ 'other.mount'
+ ]
+
+ monkeypatch.setattr(os, 'listdir', listdir_mocked)
+ monkeypatch.setattr(mount_unit_generator,
+ 'does_system_have_separate_boot_partition',
+ lambda: has_separate_boot)
+
+ mount_unit_generator.inject_bundled_units(workspace_path)
+
+ if has_separate_boot:
+ assert was_copyfile_for_sysroot_boot_called
--
2.51.1