Skip to content

Commit

Permalink
pyanaconda: payload: split rsync command for /boot/efi to handle FAT …
Browse files Browse the repository at this point in the history
…filesystem limitations

The previous rsync command attempted to preserve attributes (permissions, ownership, symlinks)
that are not supported by FAT. This commit splits the rsync process for `/boot/efi` to avoid
these incompatible options and ensure proper handling of FAT-specific filesystems.

Resolves: rhbz#2329379
  • Loading branch information
KKoukiou committed Nov 29, 2024
1 parent d19089a commit 5918feb
Show file tree
Hide file tree
Showing 2 changed files with 63 additions and 23 deletions.
29 changes: 25 additions & 4 deletions pyanaconda/modules/payloads/payload/live_image/installation.py
Original file line number Diff line number Diff line change
Expand Up @@ -424,7 +424,7 @@ def run(self):
"--exclude", "/run/",
"--exclude", "/boot/*rescue*",
"--exclude", "/boot/loader/",
"--exclude", "/boot/efi/loader/",
"--exclude", "/boot/efi/",
"--exclude", "/etc/machine-id",
"--exclude", "/etc/machine-info",
os.path.normpath(self._mount_point) + "/",
Expand All @@ -434,13 +434,33 @@ def run(self):
try:
self.report_progress(_("Installing software..."))
for line in execReadlines(cmd, args):
self._parse_rsync_update(line)
self._parse_rsync_update(line, True)

except (OSError, RuntimeError) as e:
msg = "Failed to install image: {}".format(e)
raise PayloadInstallationError(msg) from None

def _parse_rsync_update(self, line):
if os.path.exists(os.path.join(self._mount_point, "boot/efi")):
# Handle /boot/efi separately due to FAT filesystem limitations
# FAT cannot support permissions, ownership, symlinks, hard links, xattrs, ACLs, or modification times
args = [
"-rx",
"--stats", # show statistics at end of process
"--info=flist2,name,progress2", # show progress after each file
"--no-inc-recursive", # force calculating total work in advance
"--exclude", "/boot/efi/loader/",
os.path.normpath(self._mount_point) + "/boot/efi/",
os.path.join(self._sysroot, "boot/efi")
]

try:
execWithRedirect(cmd, args)
except (OSError, RuntimeError) as e:
msg = "Failed to install /boot/efi from image: {}".format(e)
raise PayloadInstallationError(msg) from None


def _parse_rsync_update(self, line, report_progress=True):
"""Try to extract progress from rsync output.
This is called for every line. There are two things done here:
Expand Down Expand Up @@ -484,7 +504,8 @@ def _parse_rsync_update(self, line):
return
self._rsync_progress = str_pct
log.debug("rsync progress: %s", self._rsync_progress)
self.report_progress(_("Installing software {}").format(self._rsync_progress))
if report_progress:
self.report_progress(_("Installing software {}").format(self._rsync_progress))
except IndexError:
pass

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,36 +91,55 @@ def _make_reader(self, rc):
return reader

@patch("pyanaconda.modules.payloads.payload.live_image.installation.os.sync")
@patch("pyanaconda.modules.payloads.payload.live_image.installation.execWithRedirect")
@patch("pyanaconda.modules.payloads.payload.live_image.installation.execReadlines")
def test_install_image_task(self, exec_readlines, os_sync):
def test_install_image_task(self, exec_readlines, exec_with_redirect, os_sync):
"""Test installation from an image task."""
exec_readlines.return_value = self._make_reader(0)
exec_with_redirect.return_value = 0

with tempfile.TemporaryDirectory() as mount_point:
task = InstallFromImageTask(
sysroot="/mnt/root",
mount_point=mount_point
)

task.run()

exec_readlines.assert_called_once_with("rsync", [
"-pogAXtlHrDx",
"--stats",
"--info=flist2,name,progress2",
"--no-inc-recursive",
"--exclude", "/dev/",
"--exclude", "/proc/",
"--exclude", "/tmp/*",
"--exclude", "/sys/",
"--exclude", "/run/",
"--exclude", "/boot/*rescue*",
"--exclude", "/boot/loader/",
"--exclude", "/boot/efi/loader/",
"--exclude", "/etc/machine-id",
"--exclude", "/etc/machine-info",
mount_point + "/",
"/mnt/root"
])
exec_readlines.assert_called_once_with("rsync", [
"-pogAXtlHrDx",
"--stats",
"--info=flist2,name,progress2",
"--no-inc-recursive",
"--exclude", "/dev/",
"--exclude", "/proc/",
"--exclude", "/tmp/*",
"--exclude", "/sys/",
"--exclude", "/run/",
"--exclude", "/boot/*rescue*",
"--exclude", "/boot/loader/",
"--exclude", "/boot/efi/",
"--exclude", "/etc/machine-id",
"--exclude", "/etc/machine-info",
mount_point + "/",
"/mnt/root"
])

exec_with_redirect.assert_not_called()

# Create /boot/efi directory in mount point.
os.makedirs(join_paths(mount_point, "boot/efi"))
task.run()

exec_with_redirect.assert_called_once_with("rsync", [
"-rx",
"--stats",
"--info=flist2,name,progress2",
"--no-inc-recursive",
"--exclude", "/boot/efi/loader/",
mount_point + "/boot/efi/",
"/mnt/root/boot/efi"
])

@patch("pyanaconda.modules.payloads.payload.live_image.installation.os.sync")
@patch("pyanaconda.modules.payloads.payload.live_image.installation.execReadlines")
Expand Down

0 comments on commit 5918feb

Please sign in to comment.