From 68585a85ec1fd5bb2406d5374ea858c524692d6b Mon Sep 17 00:00:00 2001 From: Yasmin de Souza Date: Wed, 1 Apr 2026 11:26:48 -0300 Subject: [PATCH 01/12] Add copy-to-boot feature and add copy-to-boot cli command This is part of Fedora BootLoaderUpdatesPhase1: https://fedoraproject.org/wiki/Changes/BootLoaderUpdatesPhase1 --- src/bios.rs | 5 +++++ src/bootupd.rs | 16 ++++++++++++++++ src/cli/bootupctl.rs | 5 +++++ src/cli/bootupd.rs | 11 +++++++++++ src/component.rs | 3 +++ 5 files changed, 40 insertions(+) diff --git a/src/bios.rs b/src/bios.rs index f341f994..1012dd5e 100644 --- a/src/bios.rs +++ b/src/bios.rs @@ -277,4 +277,9 @@ impl Component for Bios { fn get_efi_vendor(&self, _: &Path) -> Result> { Ok(None) } + + /// Package mode copy is EFI-only + fn package_mode_copy_to_boot(&self, _root: &Path) -> Result<()> { + Ok(()) + } } diff --git a/src/bootupd.rs b/src/bootupd.rs index e01ce923..e6094659 100644 --- a/src/bootupd.rs +++ b/src/bootupd.rs @@ -802,6 +802,22 @@ pub(crate) fn client_run_migrate_static_grub_config() -> Result<()> { Ok(()) } +/// Copy bootloader files from /usr/lib/efi to boot/ESP for package mode installations. +pub(crate) fn copy_to_boot(root: &Path) -> Result<()> { + let all_components = get_components_impl(false); + if all_components.is_empty() { + anyhow::bail!("No components available for this platform."); + } + + for component in all_components.values() { + component + .package_mode_copy_to_boot(root) + .with_context(|| format!("Failed to copy component {} to boot", component.name()))?; + } + + Ok(()) +} + /// Writes a stripped GRUB config to `stripped_config_name`, removing lines between /// `### BEGIN /etc/grub.d/15_ostree ###` and `### END /etc/grub.d/15_ostree ###`. fn strip_grub_config_file( diff --git a/src/cli/bootupctl.rs b/src/cli/bootupctl.rs index 1c30b24e..34f0572b 100644 --- a/src/cli/bootupctl.rs +++ b/src/cli/bootupctl.rs @@ -73,6 +73,8 @@ pub enum CtlBackend { Generate(super::bootupd::GenerateOpts), #[clap(name = "install", hide = true)] Install(super::bootupd::InstallOpts), + #[clap(name = "copy-to-boot", hide = true)] + CopyToBoot, } #[derive(Debug, Parser)] @@ -109,6 +111,9 @@ impl CtlCommand { CtlVerb::Backend(CtlBackend::Install(opts)) => { super::bootupd::DCommand::run_install(opts) } + CtlVerb::Backend(CtlBackend::CopyToBoot) => { + super::bootupd::DCommand::run_copy_to_boot() + } CtlVerb::MigrateStaticGrubConfig => Self::run_migrate_static_grub_config(), } } diff --git a/src/cli/bootupd.rs b/src/cli/bootupd.rs index 10e0f256..cab6b580 100644 --- a/src/cli/bootupd.rs +++ b/src/cli/bootupd.rs @@ -38,6 +38,11 @@ pub enum DVerb { GenerateUpdateMetadata(GenerateOpts), #[clap(name = "install", about = "Install components")] Install(InstallOpts), + #[clap( + name = "copy-to-boot", + about = "Copy bootloader files from /usr/lib/efi to ESP (package mode), EFI only" + )] + CopyToBoot, } #[derive(Debug, Parser)] @@ -97,6 +102,7 @@ impl DCommand { match self.cmd { DVerb::Install(opts) => Self::run_install(opts), DVerb::GenerateUpdateMetadata(opts) => Self::run_generate_meta(opts), + DVerb::CopyToBoot => Self::run_copy_to_boot(), } } @@ -146,4 +152,9 @@ impl DCommand { .context("boot data installation failed")?; Ok(()) } + + pub(crate) fn run_copy_to_boot() -> Result<()> { + bootupd::copy_to_boot(std::path::Path::new("/")).context("copying to boot failed")?; + Ok(()) + } } diff --git a/src/component.rs b/src/component.rs index dabcea97..80a241a7 100644 --- a/src/component.rs +++ b/src/component.rs @@ -85,6 +85,9 @@ pub(crate) trait Component { /// Locating efi vendor dir fn get_efi_vendor(&self, sysroot: &Path) -> Result>; + + /// Copy from /usr/lib/efi to boot/ESP (package mode) + fn package_mode_copy_to_boot(&self, root: &Path) -> Result<()>; } /// Given a component name, create an implementation. From 547002d3b559e24f5db70e57877df5f50a2afa3b Mon Sep 17 00:00:00 2001 From: Yasmin de Souza Date: Wed, 1 Apr 2026 11:30:51 -0300 Subject: [PATCH 02/12] Check ESP space to avoid errors --- src/filetree.rs | 10 ++++++++++ src/util.rs | 13 ++++++++++++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/filetree.rs b/src/filetree.rs index a9eee816..b6eb8bc1 100644 --- a/src/filetree.rs +++ b/src/filetree.rs @@ -207,6 +207,16 @@ impl FileTree { Ok(Self { children }) } + /// Total size in bytes of all files in the tree (for space checks). + #[cfg(any( + target_arch = "x86_64", + target_arch = "aarch64", + target_arch = "riscv64" + ))] + pub(crate) fn total_size(&self) -> u64 { + self.children.values().map(|m| m.size).sum() + } + /// Determine the changes *from* self to the updated tree #[cfg(any( target_arch = "x86_64", diff --git a/src/util.rs b/src/util.rs index 2fda1778..4d36edca 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,9 +1,11 @@ use std::collections::HashSet; +use std::os::unix::io::AsRawFd; use std::path::Path; use std::process::Command; use anyhow::{bail, Context, Result}; use openat_ext::OpenatDirExt; +use rustix::fd::BorrowedFd; /// Parse an environment variable as UTF-8 #[allow(dead_code)] @@ -51,9 +53,18 @@ pub(crate) fn filenames(dir: &openat::Dir) -> Result> { Ok(ret) } +/// Return the available space in bytes on the filesystem containing the given directory. +/// Uses f_bavail * f_frsize from fstatvfs to avoid partial updates when the partition is full. +pub(crate) fn available_space_bytes(dir: &openat::Dir) -> Result { + let fd = unsafe { BorrowedFd::borrow_raw(dir.as_raw_fd()) }; + let st = rustix::fs::fstatvfs(fd)?; + Ok((st.f_bavail as u64) * (st.f_frsize as u64)) +} + pub(crate) fn ensure_writable_mount>(p: P) -> Result<()> { let p = p.as_ref(); - let stat = rustix::fs::statvfs(p)?; + let stat = + rustix::fs::statvfs(p).map_err(|e| std::io::Error::from_raw_os_error(e.raw_os_error()))?; if !stat.f_flag.contains(rustix::fs::StatVfsMountFlags::RDONLY) { return Ok(()); } From e4205a5a8fde11e6c99a297a87e1c78068f4a5f1 Mon Sep 17 00:00:00 2001 From: Yasmin de Souza Date: Wed, 1 Apr 2026 11:32:29 -0300 Subject: [PATCH 03/12] Implement copy_efi_components_to_esp and add unit tests --- src/efi.rs | 380 ++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 348 insertions(+), 32 deletions(-) diff --git a/src/efi.rs b/src/efi.rs index 1022f4af..c2170987 100644 --- a/src/efi.rs +++ b/src/efi.rs @@ -19,6 +19,10 @@ use chrono::prelude::*; use fn_error_context::context; use openat_ext::OpenatDirExt; use os_release::OsRelease; +use rustix::mount::{ + fsconfig_create, fsconfig_set_path, fsmount, fsopen, move_mount, FsMountFlags, FsOpenFlags, + MountAttrFlags, MoveMountFlags, UnmountFlags, +}; use rustix::{fd::AsFd, fd::BorrowedFd, fs::StatVfsMountFlags}; use walkdir::WalkDir; use widestring::U16CString; @@ -87,9 +91,40 @@ pub(crate) fn is_efi_booted() -> Result { .map_err(Into::into) } +fn mount_esp(esp_device: &Path, target: &Path) -> Result<()> { + use rustix::fs::CWD; + + let fs_fd = fsopen("vfat", FsOpenFlags::empty()).context("fsopen vfat")?; + fsconfig_set_path(fs_fd.as_fd(), "source", esp_device, CWD) + .context("fsconfig_set_path source")?; + fsconfig_create(fs_fd.as_fd()).context("fsconfig_create")?; + let mount_fd = fsmount( + fs_fd.as_fd(), + FsMountFlags::empty(), + MountAttrFlags::empty(), + ) + .context("fsmount")?; + let target_dir = std::fs::File::open(target).context("open target dir for move_mount")?; + let target_fd = unsafe { BorrowedFd::borrow_raw(target_dir.as_raw_fd()) }; + move_mount( + mount_fd.as_fd(), + "", + target_fd, + ".", + MoveMountFlags::MOVE_MOUNT_F_EMPTY_PATH, + ) + .context("move_mount")?; + Ok(()) +} + +struct Mount { + path: PathBuf, + owned: bool, +} + #[derive(Default)] pub(crate) struct Efi { - mountpoint: RefCell>, + mountpoint: RefCell>, } impl Efi { @@ -120,20 +155,21 @@ impl Efi { break; } } - - // Only borrow mutably if we found a mount point if let Some(mnt) = found_mount { log::debug!("Reusing existing mount point {mnt:?}"); - *self.mountpoint.borrow_mut() = Some(mnt.clone()); + *self.mountpoint.borrow_mut() = Some(Mount { + path: mnt.clone(), + owned: false, + }); Ok(Some(mnt)) } else { Ok(None) } } - // Mount the passed esp_device, return mount point pub(crate) fn mount_esp_device(&self, root: &Path, esp_device: &Path) -> Result { - let mut mountpoint = None; + // (mount path, whether bootupd performed the mount and must unmount) + let mut mountpoint: Option<(PathBuf, bool)> = None; for &mnt in ESP_MOUNTS.iter() { let mnt = root.join(mnt); @@ -147,31 +183,40 @@ impl Efi { if st.f_type == libc::MSDOS_SUPER_MAGIC { if is_mount_point(&mnt)? { log::debug!("ESP already mounted at {mnt:?}, reusing"); - mountpoint = Some(mnt); + mountpoint = Some((mnt, false)); break; } } + if mount_esp(esp_device, &mnt).is_ok() { + log::debug!("Mounted at {mnt:?}"); + mountpoint = Some((mnt, true)); + break; + } + log::trace!("Mount failed, falling back to mount(8)"); std::process::Command::new("mount") - .arg(&esp_device) + .arg(esp_device) .arg(&mnt) .run_inherited() .with_context(|| format!("Failed to mount {:?}", esp_device))?; log::debug!("Mounted at {mnt:?}"); - mountpoint = Some(mnt); + mountpoint = Some((mnt, true)); break; } - let mnt = mountpoint.ok_or_else(|| anyhow::anyhow!("No mount point found"))?; - *self.mountpoint.borrow_mut() = Some(mnt.clone()); + let (mnt, owned) = mountpoint.ok_or_else(|| anyhow::anyhow!("No mount point found"))?; + *self.mountpoint.borrow_mut() = Some(Mount { + path: mnt.clone(), + owned, + }); Ok(mnt) } // Firstly check if esp is already mounted, then mount the passed esp device pub(crate) fn ensure_mounted_esp(&self, root: &Path, esp_device: &Path) -> Result { - if let Some(mountpoint) = self.mountpoint.borrow().as_deref() { - return Ok(mountpoint.to_owned()); + if let Some(mount) = self.mountpoint.borrow().as_ref() { + return Ok(mount.path.clone()); } - let destdir = if let Some(destdir) = self.get_mounted_esp(Path::new(root))? { + let destdir = if let Some(destdir) = self.get_mounted_esp(root)? { destdir } else { self.mount_esp_device(root, esp_device)? @@ -180,12 +225,25 @@ impl Efi { } fn unmount(&self) -> Result<()> { - if let Some(mount) = self.mountpoint.borrow_mut().take() { - Command::new("umount") - .arg(&mount) - .run_inherited() - .with_context(|| format!("Failed to unmount {mount:?}"))?; - log::trace!("Unmounted"); + // Only unmount if we mounted it ourselves + let should_unmount = self + .mountpoint + .borrow() + .as_ref() + .map(|m| m.owned) + .unwrap_or(false); + if should_unmount { + if let Some(mount) = self.mountpoint.borrow_mut().take() { + if rustix::mount::unmount(&mount.path, UnmountFlags::empty()).is_ok() { + log::trace!("Unmounted (new mount API)"); + } else { + Command::new("umount") + .arg(&mount.path) + .run_inherited() + .with_context(|| format!("Failed to unmount {:?}", mount.path))?; + log::trace!("Unmounted"); + } + } } Ok(()) } @@ -236,6 +294,119 @@ impl Efi { let device_path = device.path(); create_efi_boot_entry(&device_path, esp_part_num.trim(), &loader, &product_name) } + /// Copy EFI components to ESP using the same "write alongside + atomic rename" pattern + /// as bootable container updates, so the system stays bootable if any step fails. + fn copy_efi_components_to_esp( + &self, + sysroot_dir: &openat::Dir, + esp_dir: &openat::Dir, + _esp_path: &Path, + efi_components: &[EFIComponent], + ) -> Result<()> { + // Build a merged source tree in a temp dir (same layout as desired ESP/EFI) + let temp_dir = tempfile::tempdir().context("Creating temp dir for EFI merge")?; + let temp_efi_path = temp_dir.path().join("EFI"); + std::fs::create_dir_all(&temp_efi_path) + .with_context(|| format!("Creating {}", temp_efi_path.display()))?; + let temp_efi_str = temp_efi_path + .to_str() + .context("Temp EFI path is not valid UTF-8")?; + + for efi_comp in efi_components { + log::info!( + "Merging EFI component {} version {} into update tree", + efi_comp.name, + efi_comp.version + ); + // Copy contents of component's EFI dir (e.g. fedora/) into temp_efi_path so merged + // layout is EFI/fedora/..., not EFI/EFI/fedora/... + let src_efi_contents = format!("{}/.", efi_comp.path); + filetree::copy_dir_with_args( + sysroot_dir, + src_efi_contents.as_str(), + temp_efi_str, + OPTIONS, + ) + .with_context(|| format!("Copying {} to merge dir", efi_comp.path))?; + } + + // Ensure ESP/EFI exists (e.g. first install) + esp_dir.ensure_dir_all(std::path::Path::new("EFI"), 0o755)?; + let esp_efi_dir = esp_dir.sub_dir("EFI").context("Opening ESP EFI dir")?; + + let source_dir = + openat::Dir::open(&temp_efi_path).context("Opening merged EFI source dir")?; + let source_filetree = + filetree::FileTree::new_from_dir(&source_dir).context("Building source filetree")?; + let current_filetree = + filetree::FileTree::new_from_dir(&esp_efi_dir).context("Building current filetree")?; + let mut diff = current_filetree + .diff(&source_filetree) + .context("Computing EFI diff")?; + diff.removals.clear(); + + // Check available space before writing to prevent partial updates when the partition is full + let required_bytes = current_filetree.total_size() + source_filetree.total_size(); + let available_bytes = util::available_space_bytes(&esp_efi_dir)?; + if available_bytes < required_bytes { + anyhow::bail!( + "ESP has insufficient free space for update: need {} MiB, have {} MiB", + required_bytes / (1024 * 1024), + available_bytes / (1024 * 1024) + ); + } + + // Same logic as bootable container: write to .btmp.* then atomic rename + filetree::apply_diff(&source_dir, &esp_efi_dir, &diff, None) + .context("Applying EFI update (write alongside + atomic rename)")?; + + // Sync the whole ESP filesystem + fsfreeze_thaw_cycle(esp_dir.open_file(".")?)?; + + Ok(()) + } + + /// Copy from /usr/lib/efi to boot/ESP. Caller provides sysroot (e.g. for recovery or tests). + fn package_mode_copy_to_boot_impl(&self, sysroot: &Path) -> Result<()> { + let sysroot_path = Utf8Path::from_path(sysroot) + .with_context(|| format!("Invalid UTF-8: {}", sysroot.display()))?; + let sysroot_dir = openat::Dir::open(sysroot).context("Opening sysroot for reading")?; + + let efi_comps = match get_efi_component_from_usr(sysroot_path, EFILIB)? { + Some(comps) if !comps.is_empty() => comps, + _ => anyhow::bail!("No EFI components found in /usr/lib/efi"), + }; + + // First try to use an already mounted ESP + let esp_path = if let Some(mounted_esp) = self.get_mounted_esp(sysroot)? { + mounted_esp + } else { + let sysroot_cap = Dir::open_ambient_dir(sysroot, cap_std::ambient_authority()) + .with_context(|| format!("Opening sysroot {}", sysroot.display()))?; + let device = bootc_internal_blockdev::list_dev_by_dir(&sysroot_cap) + .with_context(|| format!("Resolving block device for {}", sysroot.display()))?; + let Some(esp_devices) = device.find_colocated_esps()? else { + anyhow::bail!("No ESP found"); + }; + let esp = esp_devices + .first() + .ok_or_else(|| anyhow::anyhow!("No ESP partition found"))?; + self.ensure_mounted_esp(sysroot, Path::new(&esp.path()))? + }; + + let esp_dir = openat::Dir::open(&esp_path) + .with_context(|| format!("Opening ESP at {}", esp_path.display()))?; + validate_esp_fstype(&esp_dir)?; + + self.copy_efi_components_to_esp(&sysroot_dir, &esp_dir, &esp_path, &efi_comps)?; + + log::info!( + "Successfully copied {} EFI component(s) to ESP at {}", + efi_comps.len(), + esp_path.display() + ); + Ok(()) + } } #[context("Get product name")] @@ -473,23 +644,19 @@ impl Component for Efi { } else { None }; - let dest = destpath.to_str().with_context(|| { - format!( - "Include invalid UTF-8 characters in dest {}", - destpath.display() - ) - })?; let efi_path = if let Some(efi_components) = efi_comps { - for efi in efi_components { - filetree::copy_dir_with_args(&src_dir, efi.path.as_str(), dest, OPTIONS)?; - } + // Use shared helper to copy components from /usr/lib/efi + self.copy_efi_components_to_esp(&src_dir, destd, &destpath, &efi_components)?; EFILIB } else { let updates = component_updatedirname(self); let src = updates .to_str() - .context("Include invalid UTF-8 characters in path")?; + .with_context(|| format!("Invalid UTF-8: {}", updates.display()))?; + let dest = destpath + .to_str() + .with_context(|| format!("Invalid UTF-8: {}", destpath.display()))?; filetree::copy_dir_with_args(&src_dir, src, dest, OPTIONS)?; &src.to_owned() }; @@ -686,6 +853,11 @@ impl Component for Efi { anyhow::bail!("Failed to find {SHIM} in the image") } } + + /// Package mode copy: Simple copy from /usr/lib/efi to boot/ESP. + fn package_mode_copy_to_boot(&self, root: &Path) -> Result<()> { + self.package_mode_copy_to_boot_impl(root) + } } impl Drop for Efi { @@ -981,7 +1153,6 @@ Boot0003* test"; ); Ok(()) } - #[cfg(test)] fn fixture() -> Result { let tempdir = cap_std_ext::cap_tempfile::tempdir(cap_std::ambient_authority())?; tempdir.create_dir("etc")?; @@ -1011,7 +1182,7 @@ Boot0003* test"; { tmpd.atomic_write( "etc/system-release", - "Red Hat Enterprise Linux CoreOS release 4 + r"Red Hat Enterprise Linux CoreOS release 4 ", )?; let name = get_product_name(&tmpd)?; @@ -1057,4 +1228,149 @@ Boot0003* test"; assert_eq!(efi_comps, None); Ok(()) } + + #[test] + fn test_package_mode_copy_to_boot_discovery() -> Result<()> { + // Test that we can discover components from /usr/lib/efi + let tmpdir: &tempfile::TempDir = &tempfile::tempdir()?; + let tpath = tmpdir.path(); + let efi_path = tpath.join("usr/lib/efi"); + + // Create mock EFI components + std::fs::create_dir_all(efi_path.join("shim/15.8-3/EFI/fedora"))?; + std::fs::create_dir_all(efi_path.join("grub2/2.12-28/EFI/fedora"))?; + + // Write some test files + std::fs::write( + efi_path.join("shim/15.8-3/EFI/fedora/shimx64.efi"), + b"shim content", + )?; + std::fs::write( + efi_path.join("grub2/2.12-28/EFI/fedora/grubx64.efi"), + b"grub content", + )?; + + let utf8_tpath = + Utf8Path::from_path(tpath).ok_or_else(|| anyhow::anyhow!("Path is not valid UTF-8"))?; + + // Test component discovery + let efi_comps = match get_efi_component_from_usr(utf8_tpath, EFILIB)? { + Some(comps) if !comps.is_empty() => comps, + _ => { + anyhow::bail!("Should have found components"); + } + }; + + // Verify we found the expected components + assert_eq!(efi_comps.len(), 2); + let names: Vec<_> = efi_comps.iter().map(|c| c.name.as_str()).collect(); + assert!(names.contains(&"shim")); + assert!(names.contains(&"grub2")); + + // Verify paths are correct + for comp in &efi_comps { + assert!(comp.path.starts_with("usr/lib/efi")); + assert!(comp.path.ends_with("EFI")); + } + + Ok(()) + } + + #[test] + fn test_package_mode_shim_installation() -> Result<()> { + // Test that shim can be installed from /usr/lib/efi to ESP + let tmpdir: &tempfile::TempDir = &tempfile::tempdir()?; + let tpath = tmpdir.path(); + + // Create mock /usr/lib/efi structure with shim + let efi_path = tpath.join("usr/lib/efi"); + let shim_path = efi_path.join("shim/15.8-3/EFI/fedora"); + std::fs::create_dir_all(&shim_path)?; + + // Write shim binary + let shim_content = b"mock shim binary content"; + std::fs::write(shim_path.join(SHIM), shim_content)?; + + // Create additional shim files that might be present + std::fs::write(shim_path.join("MokManager.efi"), b"mok manager content")?; + std::fs::write(shim_path.join("fbx64.efi"), b"fallback content")?; + + // Create mock ESP directory structure (simulating /boot/efi in container) + let esp_path = tpath.join("boot/efi"); + std::fs::create_dir_all(&esp_path)?; + + // Create EFI directory in ESP + let esp_efi_path = esp_path.join("EFI"); + std::fs::create_dir_all(&esp_efi_path)?; + + // Set up sysroot directory + let sysroot_dir = openat::Dir::open(tpath)?; + + // Get EFI components from usr/lib/efi + let utf8_tpath = + Utf8Path::from_path(tpath).ok_or_else(|| anyhow::anyhow!("Path is not valid UTF-8"))?; + let efi_comps = get_efi_component_from_usr(utf8_tpath, EFILIB)?; + assert!(efi_comps.is_some(), "Should find shim component"); + let efi_comps = efi_comps.unwrap(); + assert_eq!(efi_comps.len(), 1, "Should find exactly one component"); + assert_eq!(efi_comps[0].name, "shim"); + assert_eq!(efi_comps[0].version, "15.8-3"); + + // Create Efi instance and copy components to ESP + let esp_dir = openat::Dir::open(&esp_path).context("Opening ESP dir for test")?; + let efi = Efi::default(); + efi.copy_efi_components_to_esp(&sysroot_dir, &esp_dir, &esp_path, &efi_comps)?; + + // Expected path: /boot/efi/EFI/fedora/shimx64.efi (or shimaa64.efi, etc.) + let copied_shim_path = esp_path.join("EFI/fedora").join(SHIM); + assert!( + copied_shim_path.exists(), + "Shim should be copied to ESP at {}", + copied_shim_path.display() + ); + + // Verify the shim file is actually a file, not a directory + assert!( + copied_shim_path.is_file(), + "Shim should be a file at {}", + copied_shim_path.display() + ); + + // Verify the content matches exactly + let copied_content = std::fs::read(&copied_shim_path)?; + assert_eq!( + copied_content, shim_content, + "Shim content should match exactly" + ); + + // Verify the directory structure is correct + assert!( + esp_path.join("EFI").exists(), + "EFI directory should exist in ESP at {}", + esp_path.join("EFI").display() + ); + assert!(esp_path.join("EFI").is_dir(), "EFI should be a directory"); + + assert!( + esp_path.join("EFI/fedora").exists(), + "Vendor directory (fedora) should exist in ESP at {}", + esp_path.join("EFI/fedora").display() + ); + assert!( + esp_path.join("EFI/fedora").is_dir(), + "EFI/fedora should be a directory" + ); + + // Verify the path structure matches expected package mode layout + // Source: /usr/lib/efi/shim/15.8-3/EFI/fedora/shimx64.efi + // Dest: /boot/efi/EFI/fedora/shimx64.efi + let expected_base = esp_path.join("EFI/fedora"); + assert_eq!( + copied_shim_path.parent(), + Some(expected_base.as_path()), + "Shim should be directly under EFI/fedora/, not in a subdirectory" + ); + + Ok(()) + } } From dd4fec28f3aeeb02c53cb17d605d200fe85d4e2c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 Mar 2026 01:42:48 +0000 Subject: [PATCH 04/12] build(deps): bump env_logger from 0.11.9 to 0.11.10 in the build group Bumps the build group with 1 update: [env_logger](https://github.com/rust-cli/env_logger). Updates `env_logger` from 0.11.9 to 0.11.10 - [Release notes](https://github.com/rust-cli/env_logger/releases) - [Changelog](https://github.com/rust-cli/env_logger/blob/main/CHANGELOG.md) - [Commits](https://github.com/rust-cli/env_logger/compare/v0.11.9...v0.11.10) --- updated-dependencies: - dependency-name: env_logger dependency-version: 0.11.10 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: build ... Signed-off-by: dependabot[bot] --- Cargo.lock | 34 +++++++++++++++++++++++++++++----- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3be9a944..c5684a36 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -33,7 +33,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" dependencies = [ "anstyle", - "anstyle-parse", + "anstyle-parse 0.2.7", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstream" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" +dependencies = [ + "anstyle", + "anstyle-parse 1.0.0", "anstyle-query", "anstyle-wincon", "colorchoice", @@ -56,6 +71,15 @@ dependencies = [ "utf8parse", ] +[[package]] +name = "anstyle-parse" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" +dependencies = [ + "utf8parse", +] + [[package]] name = "anstyle-query" version = "1.1.5" @@ -154,7 +178,7 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3292185c166091432ecbc6b203c0c73953d6b5965908368c51df800f1c549ed" dependencies = [ - "anstream", + "anstream 0.6.21", "anyhow", "chrono", "owo-colors", @@ -446,11 +470,11 @@ dependencies = [ [[package]] name = "env_logger" -version = "0.11.9" +version = "0.11.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2daee4ea451f429a58296525ddf28b45a3b64f1acf6587e2067437bb11e218d" +checksum = "0621c04f2196ac3f488dd583365b9c09be011a4ab8b9f37248ffcc8f6198b56a" dependencies = [ - "anstream", + "anstream 1.0.0", "anstyle", "env_filter", "jiff", From 340ecaca5125cc60064e9a2ec1a720fad3368113 Mon Sep 17 00:00:00 2001 From: Huijing Hei Date: Wed, 21 Jan 2026 20:03:36 +0800 Subject: [PATCH 05/12] efi: unmount only if bootupd did the mount Assisted-by: Claude Code (Claude Sonnet 4) --- src/efi.rs | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/efi.rs b/src/efi.rs index c2170987..0bbb22fc 100644 --- a/src/efi.rs +++ b/src/efi.rs @@ -124,7 +124,13 @@ struct Mount { #[derive(Default)] pub(crate) struct Efi { +<<<<<<< HEAD mountpoint: RefCell>, +======= + mountpoint: RefCell>, + /// Track whether we mounted the ESP ourselves (true) or found it pre-mounted (false) + did_mount: RefCell, +>>>>>>> 11a3df4 (efi: unmount only if bootupd did the mount) } impl Efi { @@ -157,10 +163,15 @@ impl Efi { } if let Some(mnt) = found_mount { log::debug!("Reusing existing mount point {mnt:?}"); +<<<<<<< HEAD *self.mountpoint.borrow_mut() = Some(Mount { path: mnt.clone(), owned: false, }); +======= + *self.mountpoint.borrow_mut() = Some(mnt.clone()); + *self.did_mount.borrow_mut() = false; // We didn't mount it +>>>>>>> 11a3df4 (efi: unmount only if bootupd did the mount) Ok(Some(mnt)) } else { Ok(None) @@ -203,11 +214,17 @@ impl Efi { mountpoint = Some((mnt, true)); break; } +<<<<<<< HEAD let (mnt, owned) = mountpoint.ok_or_else(|| anyhow::anyhow!("No mount point found"))?; *self.mountpoint.borrow_mut() = Some(Mount { path: mnt.clone(), owned, }); +======= + let mnt = mountpoint.ok_or_else(|| anyhow::anyhow!("No mount point found"))?; + *self.mountpoint.borrow_mut() = Some(mnt.clone()); + *self.did_mount.borrow_mut() = true; // We mounted it ourselves +>>>>>>> 11a3df4 (efi: unmount only if bootupd did the mount) Ok(mnt) } @@ -226,6 +243,7 @@ impl Efi { fn unmount(&self) -> Result<()> { // Only unmount if we mounted it ourselves +<<<<<<< HEAD let should_unmount = self .mountpoint .borrow() @@ -243,6 +261,16 @@ impl Efi { .with_context(|| format!("Failed to unmount {:?}", mount.path))?; log::trace!("Unmounted"); } +======= + if *self.did_mount.borrow() { + if let Some(mount) = self.mountpoint.borrow_mut().take() { + Command::new("umount") + .arg(&mount) + .run_inherited() + .with_context(|| format!("Failed to unmount {mount:?}"))?; + log::trace!("Unmounted"); + *self.did_mount.borrow_mut() = false; +>>>>>>> 11a3df4 (efi: unmount only if bootupd did the mount) } } Ok(()) From 86370d02b9263174f9644faa791df0644753a104 Mon Sep 17 00:00:00 2001 From: Yasmin de Souza Date: Tue, 10 Feb 2026 10:43:20 -0300 Subject: [PATCH 06/12] efi: refactor mount state and use new mount API for ESP Implementing code review changes --- Cargo.toml | 4 ++++ src/efi.rs | 28 ---------------------------- 2 files changed, 4 insertions(+), 28 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 8f64a2ba..3e9c7e8f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,7 +41,11 @@ openssl = "^0.10" os-release = "0.1.0" regex = "1.12.3" rpm-rs = { package = "rpm", version = "0.16.1", default-features = false, optional = true } +<<<<<<< HEAD rustix = { version = "1.1.4", features = ["process", "fs"] } +======= +rustix = { version = "1.1.3", features = ["process", "fs", "mount"] } +>>>>>>> 40f9692 (efi: refactor mount state and use new mount API for ESP) serde = { version = "^1.0", features = ["derive"] } serde_json = "^1.0" tempfile = "^3.26" diff --git a/src/efi.rs b/src/efi.rs index 0bbb22fc..c2170987 100644 --- a/src/efi.rs +++ b/src/efi.rs @@ -124,13 +124,7 @@ struct Mount { #[derive(Default)] pub(crate) struct Efi { -<<<<<<< HEAD mountpoint: RefCell>, -======= - mountpoint: RefCell>, - /// Track whether we mounted the ESP ourselves (true) or found it pre-mounted (false) - did_mount: RefCell, ->>>>>>> 11a3df4 (efi: unmount only if bootupd did the mount) } impl Efi { @@ -163,15 +157,10 @@ impl Efi { } if let Some(mnt) = found_mount { log::debug!("Reusing existing mount point {mnt:?}"); -<<<<<<< HEAD *self.mountpoint.borrow_mut() = Some(Mount { path: mnt.clone(), owned: false, }); -======= - *self.mountpoint.borrow_mut() = Some(mnt.clone()); - *self.did_mount.borrow_mut() = false; // We didn't mount it ->>>>>>> 11a3df4 (efi: unmount only if bootupd did the mount) Ok(Some(mnt)) } else { Ok(None) @@ -214,17 +203,11 @@ impl Efi { mountpoint = Some((mnt, true)); break; } -<<<<<<< HEAD let (mnt, owned) = mountpoint.ok_or_else(|| anyhow::anyhow!("No mount point found"))?; *self.mountpoint.borrow_mut() = Some(Mount { path: mnt.clone(), owned, }); -======= - let mnt = mountpoint.ok_or_else(|| anyhow::anyhow!("No mount point found"))?; - *self.mountpoint.borrow_mut() = Some(mnt.clone()); - *self.did_mount.borrow_mut() = true; // We mounted it ourselves ->>>>>>> 11a3df4 (efi: unmount only if bootupd did the mount) Ok(mnt) } @@ -243,7 +226,6 @@ impl Efi { fn unmount(&self) -> Result<()> { // Only unmount if we mounted it ourselves -<<<<<<< HEAD let should_unmount = self .mountpoint .borrow() @@ -261,16 +243,6 @@ impl Efi { .with_context(|| format!("Failed to unmount {:?}", mount.path))?; log::trace!("Unmounted"); } -======= - if *self.did_mount.borrow() { - if let Some(mount) = self.mountpoint.borrow_mut().take() { - Command::new("umount") - .arg(&mount) - .run_inherited() - .with_context(|| format!("Failed to unmount {mount:?}"))?; - log::trace!("Unmounted"); - *self.did_mount.borrow_mut() = false; ->>>>>>> 11a3df4 (efi: unmount only if bootupd did the mount) } } Ok(()) From be328c133780d3b1705d7dd540276e7c893bbb7c Mon Sep 17 00:00:00 2001 From: Huijing Hei Date: Mon, 2 Feb 2026 10:23:35 +0800 Subject: [PATCH 07/12] xtask: add `cargo xtask spec` to update spec --- xtask/src/main.rs | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/xtask/src/main.rs b/xtask/src/main.rs index d985b35c..923e4cf4 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -32,6 +32,7 @@ fn try_main() -> Result<()> { "vendor" => vendor, "package" => package, "package-srpm" => package_srpm, + "spec" => spec, _ => print_help, }; f(&sh)?; @@ -243,6 +244,42 @@ fn package_srpm(sh: &Shell) -> Result<()> { Ok(()) } +fn update_spec(sh: &Shell) -> Result { + let _targetdir = get_target_dir()?; + let p = Utf8Path::new("target"); + let pkg = impl_package(sh)?; + let srcpath = pkg.srcpath.file_name().unwrap(); + let v = pkg.version; + let src_vendorpath = pkg.vendorpath.file_name().unwrap(); + { + let specin = File::open(format!("contrib/packaging/{NAME}.spec")) + .map(BufReader::new) + .context("Opening spec")?; + let mut o = File::create(p.join(format!("{NAME}.spec"))).map(BufWriter::new)?; + for line in specin.lines() { + let line = line?; + if line.starts_with("Version:") { + writeln!(o, "# Replaced by cargo xtask spec")?; + writeln!(o, "Version: {v}")?; + } else if line.starts_with("Source0") { + writeln!(o, "Source0: {srcpath}")?; + } else if line.starts_with("Source1") { + writeln!(o, "Source1: {src_vendorpath}")?; + } else { + writeln!(o, "{line}")?; + } + } + } + let spec_path = p.join(format!("{NAME}.spec")); + Ok(spec_path) +} + +fn spec(sh: &Shell) -> Result<()> { + let s = update_spec(sh)?; + println!("Generated: {s}"); + Ok(()) +} + fn print_help(_sh: &Shell) -> Result<()> { eprintln!( "Tasks: From 87f5e955efa94ab6666d5f7f47546072eb2c278e Mon Sep 17 00:00:00 2001 From: Huijing Hei Date: Mon, 2 Feb 2026 10:25:52 +0800 Subject: [PATCH 08/12] packit: add initial support --- .packit.yaml | 55 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 .packit.yaml diff --git a/.packit.yaml b/.packit.yaml new file mode 100644 index 00000000..6a7c7d71 --- /dev/null +++ b/.packit.yaml @@ -0,0 +1,55 @@ +--- +# See the documentation for more information: +# https://packit.dev/docs/configuration/ + +# name in upstream package repository or registry +upstream_package_name: bootupd +upstream_tag_template: v{version} + +downstream_package_name: rust-bootupd + +specfile_path: contrib/packaging/bootupd.spec + +srpm_build_deps: + - cargo + - git + - libzstd-devel + - openssl-devel + - zstd + +actions: + # The last setp here is required by Packit to return the archive name + # https://packit.dev/docs/configuration/actions#create-archive + create-archive: + - bash -c "cargo install cargo-vendor-filterer" + - bash -c "cargo xtask spec" + - bash -c "cat target/bootupd.spec" + - bash -c "cp target/bootupd* contrib/packaging/" + - bash -c "ls -1 target/bootupd*.tar.zstd | grep -v 'vendor'" + # Do nothing with spec file. Two steps here are for debugging + fix-spec-file: + - bash -c "cat contrib/packaging/bootupd.spec" + - bash -c "ls -al contrib/packaging/" + +jobs: + - job: copr_build + trigger: pull_request + targets: + - fedora-rawhide-aarch64 + - fedora-rawhide-x86_64 + + - job: propose_downstream + trigger: release + dist_git_branches: + fedora-rawhide: + fast_forward_merge_into: [fedora-latest-stable] + + - job: koji_build + trigger: commit + dist_git_branches: + - fedora-all + + - job: bodhi_update + trigger: commit + dist_git_branches: + - fedora-all From 1333c728af8d24042b18d42dab19116119795fb1 Mon Sep 17 00:00:00 2001 From: Huijing Hei Date: Mon, 2 Feb 2026 10:37:34 +0800 Subject: [PATCH 09/12] test: add tmt test to verify `copy-to-boot` --- .fmf/version | 1 + .packit.yaml | 7 +++ tmt/plans/package.fmf | 25 +++++++++ tmt/tests/package/test-copy-to-boot.sh | 71 ++++++++++++++++++++++++++ tmt/tests/tests.fmf | 7 +++ 5 files changed, 111 insertions(+) create mode 100644 .fmf/version create mode 100644 tmt/plans/package.fmf create mode 100644 tmt/tests/package/test-copy-to-boot.sh create mode 100644 tmt/tests/tests.fmf diff --git a/.fmf/version b/.fmf/version new file mode 100644 index 00000000..d00491fd --- /dev/null +++ b/.fmf/version @@ -0,0 +1 @@ +1 diff --git a/.packit.yaml b/.packit.yaml index 6a7c7d71..5974a61e 100644 --- a/.packit.yaml +++ b/.packit.yaml @@ -38,6 +38,13 @@ jobs: - fedora-rawhide-aarch64 - fedora-rawhide-x86_64 + - job: tests + trigger: pull_request + targets: + - fedora-rawhide-aarch64 + - fedora-rawhide-x86_64 + tmt_plan: /tmt/plans/package + - job: propose_downstream trigger: release dist_git_branches: diff --git a/tmt/plans/package.fmf b/tmt/plans/package.fmf new file mode 100644 index 00000000..788af8ee --- /dev/null +++ b/tmt/plans/package.fmf @@ -0,0 +1,25 @@ +discover: + how: fmf +execute: + how: tmt +provision: + how: virtual + hardware: + boot: + method: uefi + image: $@{test_image} + user: root +prepare: + # Run on package mode VM especially on Fedora + - how: install + order: 20 + package: + - bootupd + + +/plan-test-copy-to-boot: + summary: Execute copy-to-boot test on package mode + discover: + how: fmf + test: + - /tmt/tests/tests/test-copy-to-boot diff --git a/tmt/tests/package/test-copy-to-boot.sh b/tmt/tests/package/test-copy-to-boot.sh new file mode 100644 index 00000000..8cd86862 --- /dev/null +++ b/tmt/tests/package/test-copy-to-boot.sh @@ -0,0 +1,71 @@ +# number: 10 +# tmt: +# summary: Test copy-to-boot on package mode +# duration: 10m +# +#!/bin/bash +set -eux + +echo "Testing copy-to-boot on package mode" + +rpm -q bootupd + +source /etc/os-release +if [ "$ID" == "fedora" ] && [ "$VERSION_ID" -lt 44 ]; then + echo "Skip testing on F43 and older" + exit 0 +fi + +suffix="" +get_grub_suffix() { + case "$(uname -m)" in + x86_64) + suffix="x64" + ;; + aarch64) + suffix="aa64" + ;; + *) + echo "Unsupported arch" + exit 1 + ;; + esac +} + +if [ "$TMT_REBOOT_COUNT" -eq 0 ]; then + echo 'Before first reboot' + # assume ESP is already mounted at /boot/efi + mountpoint /boot/efi + get_grub_suffix + grubefi="grub${suffix}.efi" + + grub_source_path=$(find /usr/lib/efi/ -name "${grubefi}") + if [ -z "${grub_source_path}" ]; then + echo "Error: Source GRUB binary ${grub_source_path} not found." + exit 1 + fi + + grub_target_path=/boot/efi/EFI/fedora/${grubefi} + if [ ! -f "${grub_target_path}" ]; then + echo "Error: Could not find target GRUB binary ${grub_target_path}." + exit 1 + fi + + # change grub.efi and it will be synced after copy-to-boot + echo test > "${grub_target_path}" + bootupctl backend copy-to-boot + + # get checksum from source /usr/lib/efi/grub2/xx/EFI/fedora/grub.efi + source_checksum=$(sha256sum "${grub_source_path}" | cut -d' ' -f1) + # get checksum from target /boot/efi/EFI/fedora/grub.efi + target_checksum=$(sha256sum "${grub_target_path}" | cut -d' ' -f1) + # confirm that the target grub.efi is updated + [ "${source_checksum}" == "${target_checksum}" ] + tmt-reboot +elif [ "$TMT_REBOOT_COUNT" -eq 1 ]; then + echo 'After the reboot' + # just confirm the reboot is successful + whoami +fi + +echo "Run copy-to-boot test successfully" diff --git a/tmt/tests/tests.fmf b/tmt/tests/tests.fmf new file mode 100644 index 00000000..b047c44f --- /dev/null +++ b/tmt/tests/tests.fmf @@ -0,0 +1,7 @@ +/test-copy-to-boot: + summary: Test copy-to-boot on package mode + duration: 10m + adjust: + - when: distro != fedora + enabled: false + test: bash package/test-copy-to-boot.sh From 7cce2cbb16cca23cbec51ff686257b864748508d Mon Sep 17 00:00:00 2001 From: Huijing Hei Date: Mon, 2 Feb 2026 11:03:16 +0800 Subject: [PATCH 10/12] xtask: revert to compress with zstd Revert part of https://github.com/coreos/bootupd/commit/7745244fa06a464b055573c9e02a5a64a3e9d6af to compress with zstd --- contrib/packaging/bootupd.spec | 2 +- xtask/src/main.rs | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/contrib/packaging/bootupd.spec b/contrib/packaging/bootupd.spec index a5b5f65d..ce94839e 100644 --- a/contrib/packaging/bootupd.spec +++ b/contrib/packaging/bootupd.spec @@ -9,7 +9,7 @@ Summary: Bootloader updater License: Apache-2.0 URL: https://github.com/coreos/bootupd -Source0: %{crates_source} +Source0: %{url}/releases/download/v%{version}/bootupd-%{version}.tar.zstd Source1: %{url}/releases/download/v%{version}/bootupd-%{version}-vendor.tar.zstd %if 0%{?fedora} || 0%{?rhel} >= 10 ExcludeArch: %{ix86} diff --git a/xtask/src/main.rs b/xtask/src/main.rs index 923e4cf4..a07d11ca 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -156,10 +156,9 @@ fn impl_package(sh: &Shell) -> Result { ) .run()?; } - // Compress with gzip and write to crate - let srcpath: Utf8PathBuf = Utf8Path::new("target").join(format!("{namev}.crate")); - cmd!(sh, "gzip --force --best {p}").run()?; - std::fs::rename(format!("{p}.gz"), &srcpath)?; + // Compress with zstd + let srcpath: Utf8PathBuf = format!("{p}.zstd").into(); + cmd!(sh, "zstd --rm -f {p} -o {srcpath}").run()?; Ok(Package { version: v, From 7922835025cbbdfa7cc826147134c91c75fac1ea Mon Sep 17 00:00:00 2001 From: Huijing Hei Date: Fri, 6 Mar 2026 22:15:14 +0800 Subject: [PATCH 11/12] hack: test --- tmt/tests/package/test-copy-to-boot.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/tmt/tests/package/test-copy-to-boot.sh b/tmt/tests/package/test-copy-to-boot.sh index 8cd86862..3f7f96a5 100644 --- a/tmt/tests/package/test-copy-to-boot.sh +++ b/tmt/tests/package/test-copy-to-boot.sh @@ -54,6 +54,7 @@ if [ "$TMT_REBOOT_COUNT" -eq 0 ]; then # change grub.efi and it will be synced after copy-to-boot echo test > "${grub_target_path}" bootupctl backend copy-to-boot + sync # get checksum from source /usr/lib/efi/grub2/xx/EFI/fedora/grub.efi source_checksum=$(sha256sum "${grub_source_path}" | cut -d' ' -f1) From 3aeedececa505d8d96cac4d06003f2ce06ff5284 Mon Sep 17 00:00:00 2001 From: Yasmin de Souza Date: Mon, 9 Mar 2026 17:21:50 -0300 Subject: [PATCH 12/12] fix: preserve ESP-only files --- Cargo.toml | 6 +----- src/efi.rs | 32 +++++++++++++++----------------- 2 files changed, 16 insertions(+), 22 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 3e9c7e8f..9056b860 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,11 +41,7 @@ openssl = "^0.10" os-release = "0.1.0" regex = "1.12.3" rpm-rs = { package = "rpm", version = "0.16.1", default-features = false, optional = true } -<<<<<<< HEAD -rustix = { version = "1.1.4", features = ["process", "fs"] } -======= -rustix = { version = "1.1.3", features = ["process", "fs", "mount"] } ->>>>>>> 40f9692 (efi: refactor mount state and use new mount API for ESP) +rustix = { version = "1.1.4", features = ["process", "fs", "mount"] } serde = { version = "^1.0", features = ["derive"] } serde_json = "^1.0" tempfile = "^3.26" diff --git a/src/efi.rs b/src/efi.rs index c2170987..11ce6323 100644 --- a/src/efi.rs +++ b/src/efi.rs @@ -23,8 +23,6 @@ use rustix::mount::{ fsconfig_create, fsconfig_set_path, fsmount, fsopen, move_mount, FsMountFlags, FsOpenFlags, MountAttrFlags, MoveMountFlags, UnmountFlags, }; -use rustix::{fd::AsFd, fd::BorrowedFd, fs::StatVfsMountFlags}; -use walkdir::WalkDir; use widestring::U16CString; use bootc_internal_blockdev::Device; @@ -95,19 +93,19 @@ fn mount_esp(esp_device: &Path, target: &Path) -> Result<()> { use rustix::fs::CWD; let fs_fd = fsopen("vfat", FsOpenFlags::empty()).context("fsopen vfat")?; - fsconfig_set_path(fs_fd.as_fd(), "source", esp_device, CWD) + fsconfig_set_path(rustix::fd::AsFd::as_fd(&fs_fd), "source", esp_device, CWD) .context("fsconfig_set_path source")?; - fsconfig_create(fs_fd.as_fd()).context("fsconfig_create")?; + fsconfig_create(rustix::fd::AsFd::as_fd(&fs_fd)).context("fsconfig_create")?; let mount_fd = fsmount( - fs_fd.as_fd(), + rustix::fd::AsFd::as_fd(&fs_fd), FsMountFlags::empty(), MountAttrFlags::empty(), ) .context("fsmount")?; let target_dir = std::fs::File::open(target).context("open target dir for move_mount")?; - let target_fd = unsafe { BorrowedFd::borrow_raw(target_dir.as_raw_fd()) }; + let target_fd = unsafe { rustix::fd::BorrowedFd::borrow_raw(target_dir.as_raw_fd()) }; move_mount( - mount_fd.as_fd(), + rustix::fd::AsFd::as_fd(&mount_fd), "", target_fd, ".", @@ -263,10 +261,10 @@ impl Efi { let efi = sysroot .open_dir(EFIVARFS.strip_prefix("/").unwrap()) .context("Opening efivars dir")?; - let st = rustix::fs::fstatvfs(efi.as_fd())?; + let st = rustix::fs::fstatvfs(rustix::fd::AsFd::as_fd(&efi))?; // Do nothing if efivars is readonly or empty // See https://github.com/coreos/bootupd/issues/972 - if st.f_flag.contains(StatVfsMountFlags::RDONLY) + if st.f_flag.contains(rustix::fs::StatVfsMountFlags::RDONLY) || std::fs::read_dir(EFIVARFS)?.next().is_none() { log::info!("Skipped EFI variables update: efivars not writable or empty"); @@ -318,8 +316,6 @@ impl Efi { efi_comp.name, efi_comp.version ); - // Copy contents of component's EFI dir (e.g. fedora/) into temp_efi_path so merged - // layout is EFI/fedora/..., not EFI/EFI/fedora/... let src_efi_contents = format!("{}/.", efi_comp.path); filetree::copy_dir_with_args( sysroot_dir, @@ -330,7 +326,6 @@ impl Efi { .with_context(|| format!("Copying {} to merge dir", efi_comp.path))?; } - // Ensure ESP/EFI exists (e.g. first install) esp_dir.ensure_dir_all(std::path::Path::new("EFI"), 0o755)?; let esp_efi_dir = esp_dir.sub_dir("EFI").context("Opening ESP EFI dir")?; @@ -370,7 +365,6 @@ impl Efi { fn package_mode_copy_to_boot_impl(&self, sysroot: &Path) -> Result<()> { let sysroot_path = Utf8Path::from_path(sysroot) .with_context(|| format!("Invalid UTF-8: {}", sysroot.display()))?; - let sysroot_dir = openat::Dir::open(sysroot).context("Opening sysroot for reading")?; let efi_comps = match get_efi_component_from_usr(sysroot_path, EFILIB)? { Some(comps) if !comps.is_empty() => comps, @@ -398,6 +392,7 @@ impl Efi { .with_context(|| format!("Opening ESP at {}", esp_path.display()))?; validate_esp_fstype(&esp_dir)?; + let sysroot_dir = openat::Dir::open(sysroot).context("Opening sysroot for reading")?; self.copy_efi_components_to_esp(&sysroot_dir, &esp_dir, &esp_path, &efi_comps)?; log::info!( @@ -868,7 +863,7 @@ impl Drop for Efi { } fn validate_esp_fstype(dir: &openat::Dir) -> Result<()> { - let dir = unsafe { BorrowedFd::borrow_raw(dir.as_raw_fd()) }; + let dir = unsafe { rustix::fd::BorrowedFd::borrow_raw(dir.as_raw_fd()) }; let stat = rustix::fs::fstatfs(&dir)?; if stat.f_type != libc::MSDOS_SUPER_MAGIC { bail!( @@ -958,7 +953,10 @@ pub(crate) fn create_efi_boot_entry( fn find_file_recursive>(dir: P, target_file: &str) -> Result> { let mut result = Vec::new(); - for entry in WalkDir::new(dir).into_iter().filter_map(|e| e.ok()) { + for entry in walkdir::WalkDir::new(dir) + .into_iter() + .filter_map(|e| e.ok()) + { if entry.file_type().is_file() { if let Some(file_name) = entry.file_name().to_str() { if file_name == target_file { @@ -988,7 +986,7 @@ fn get_efi_component_from_usr<'a>( let efilib_path = sysroot.join(usr_path); let skip_count = Utf8Path::new(usr_path).components().count(); - let mut components: Vec = WalkDir::new(&efilib_path) + let mut components: Vec = walkdir::WalkDir::new(&efilib_path) .min_depth(3) // //EFI: so 3 levels down .max_depth(3) .into_iter() @@ -1032,7 +1030,7 @@ fn transfer_ostree_boot_to_usr(sysroot: &Path) -> Result<()> { if !efi.exists() { return Ok(()); } - for entry in WalkDir::new(&efi) { + for entry in walkdir::WalkDir::new(&efi) { let entry = entry?; if entry.file_type().is_file() {