diff --git a/src/efi.rs b/src/efi.rs index e89fb05b..cb7a7468 100644 --- a/src/efi.rs +++ b/src/efi.rs @@ -72,6 +72,17 @@ impl Efi { Ok(esp) } + fn esp_path_tmp(&self) -> Result { + self.ensure_mounted_esp(Path::new("/")) + .map(|v| v.join(".EFI.tmp")) + } + + fn open_esp_tmp_optional(&self) -> Result> { + let sysroot = openat::Dir::open("/")?; + let esp = sysroot.sub_dir_optional(&self.esp_path_tmp()?)?; + Ok(esp) + } + pub(crate) fn ensure_mounted_esp(&self, root: &Path) -> Result { let mut mountpoint = self.mountpoint.borrow_mut(); if let Some(mountpoint) = mountpoint.as_deref() { @@ -348,12 +359,40 @@ impl Component for Efi { .context("opening update dir")?; let updatef = filetree::FileTree::new_from_dir(&updated).context("reading update dir")?; let diff = currentf.diff(&updatef)?; - self.ensure_mounted_esp(Path::new("/"))?; - let destdir = self.open_esp().context("opening EFI dir")?; - validate_esp(&destdir)?; + let mountdir = self.ensure_mounted_esp(Path::new("/"))?; + + // copy esp dir to temp to do apply diff + let esp = &self.esp_path()?; + let tmpesp = &self.esp_path_tmp()?; + copy_dir_all(esp, tmpesp).context("copying esp dir to temp dir")?; + assert!(tmpesp.exists()); + + let tmpdir = if let Some(p) = self.open_esp_tmp_optional()? { + p + } else { + bail!("Failed to open temp efi dir"); + }; + validate_esp(&tmpdir)?; log::trace!("applying diff: {}", &diff); - filetree::apply_diff(&updated, &destdir, &diff, None) - .context("applying filesystem changes")?; + filetree::apply_diff(&updated, &tmpdir, &diff, None) + .context("applying filesystem changes to temp EFI")?; + { + // do local exchange of the temp dir and esp dir + let parentdir = if let Some(p) = sysroot.sub_dir_optional(&mountdir)? { + p + } else { + bail!("Failed to get parent dir"); + }; + parentdir.local_exchange(tmpesp, esp).with_context(|| { + format!( + "local exchange for {} and {}", + tmpesp.display(), + esp.display() + ) + })?; + // finally remove the temp dir + std::fs::remove_dir_all(tmpesp)?; + } let adopted_from = None; Ok(InstalledContent { meta: updatemeta, @@ -580,6 +619,28 @@ fn find_file_recursive>(dir: P, target_file: &str) -> Result Result<()> { + // Create the destination directory if it doesn't exist + if !dst.exists() { + std::fs::create_dir_all(dst)?; + } + + // Iterate over directory entries + for entry in std::fs::read_dir(src)? { + let entry = entry?; + let path = entry.path(); + let relative_path = path.strip_prefix(src)?; + let destination = dst.join(relative_path); + + if path.is_dir() { + copy_dir_all(&path, &destination)?; + } else { + std::fs::copy(&path, &destination)?; + } + } + Ok(()) +} + #[cfg(test)] mod tests { use super::*; @@ -655,4 +716,25 @@ Boot0003* test"; ); Ok(()) } + + #[test] + fn test_copy_dir_all() -> Result<()> { + env_logger::init(); + let td = tempfile::tempdir()?; + let tdp = td.path(); + let src = tdp.join("efi"); + std::fs::create_dir_all(&src)?; + std::fs::create_dir_all(tdp.join("efi/BOOT"))?; + std::fs::create_dir_all(tdp.join("efi/fedora"))?; + std::fs::write(tdp.join("efi/BOOT").join("fbx64.efi"), "fall back data")?; + std::fs::write(tdp.join("efi/fedora").join(crate::efi::SHIM), "shim data")?; + std::fs::write(tdp.join("efi/fedora").join("grub.cfg"), "grub config data")?; + let dest = tdp.join("tmpefi"); + copy_dir_all(src.as_path(), dest.as_path())?; + assert_eq!(dest.join("BOOT/fbx64.efi").exists(), true); + assert_eq!(dest.join("fedora").join(crate::efi::SHIM).exists(), true); + assert_eq!(dest.join("fedora/grub.cfg").exists(), true); + + Ok(()) + } }