Skip to content

Commit 681bcc5

Browse files
efi: Walk entire ostree-boot/efi/ tree in transfer_ostree_boot_to_usr()
On OSTree-based deployments, the /boot/efi/ files are stored under the usr/lib/ostree-boot/efi/ directory, and must transferred to usr/lib/efi/ before bootupd can manage them. The transfer currently only walks the EFI/ sub-directory, so root-level firmware files are never picked up. It also splits the RPM package name on the first hyphen to derive the component directory name, which truncates names that contain hyphens (e.g. "bcm2711-firmware" becomes "bcm2711"). Walk the entire usr/lib/ostree-boot/efi/ tree so that root-level files are discovered alongside EFI boot entries, and use the full RPM package name as the component directory name. Assisted-by: Cursor (Claude Opus 4)
1 parent 67f0ba8 commit 681bcc5

File tree

1 file changed

+119
-39
lines changed

1 file changed

+119
-39
lines changed

src/efi.rs

Lines changed: 119 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -635,11 +635,11 @@ impl Component for Efi {
635635
for p in cruft.iter() {
636636
ostreeboot.remove_all_optional(p)?;
637637
}
638-
// Transfer ostree-boot EFI files to usr/lib/efi
638+
// Transfer ostree-boot efi/ files to usr/lib/efi
639639
transfer_ostree_boot_to_usr(sysroot_path)?;
640640

641-
// Remove usr/lib/ostree-boot/efi/EFI dir (after transfer) or if it is empty
642-
ostreeboot.remove_all_optional("efi/EFI")?;
641+
// Remove the entire efi/ tree after transfer, or if it is empty
642+
ostreeboot.remove_all_optional("efi")?;
643643
}
644644

645645
if let Some(efi_components) =
@@ -961,55 +961,73 @@ fn latest_versions(components: &[EFIComponent]) -> Vec<&EFIComponent> {
961961
result
962962
}
963963

964-
/// Copy files from usr/lib/ostree-boot/efi/EFI to /usr/lib/efi/<component>/<evr>/
964+
/// Copy files from usr/lib/ostree-boot/efi/ to usr/lib/efi/<component>/<evr>/
965+
///
966+
/// Walks the entire `efi/` directory (both `EFI/` subdirectories and
967+
/// root-level firmware files) and uses `rpm -qf` to determine which
968+
/// package owns each file so it can be placed in the right component
969+
/// directory under `usr/lib/efi/`.
965970
fn transfer_ostree_boot_to_usr(sysroot: &Path) -> Result<()> {
971+
transfer_ostree_boot_to_usr_impl(sysroot, |sysroot_path, filepath| {
972+
let boot_filepath = Path::new("/boot/efi").join(filepath);
973+
crate::packagesystem::query_file(
974+
sysroot_path.to_str().unwrap(),
975+
boot_filepath.to_str().unwrap(),
976+
)
977+
})
978+
}
979+
980+
/// Inner implementation that accepts a package-resolver callback so it
981+
/// can be unit-tested without a real RPM database.
982+
///
983+
/// `resolve_pkg(sysroot, filepath)` must return `"<name> <evr>"`.
984+
fn transfer_ostree_boot_to_usr_impl<F>(sysroot: &Path, resolve_pkg: F) -> Result<()>
985+
where
986+
F: Fn(&Path, &Path) -> Result<String>,
987+
{
966988
let ostreeboot_efi = Path::new(ostreeutil::BOOT_PREFIX).join("efi");
967989
let ostreeboot_efi_path = sysroot.join(&ostreeboot_efi);
968990

969-
let efi = ostreeboot_efi_path.join("EFI");
970-
if !efi.exists() {
991+
if !ostreeboot_efi_path.exists() {
971992
return Ok(());
972993
}
973-
for entry in WalkDir::new(&efi) {
974-
let entry = entry?;
975994

976-
if entry.file_type().is_file() {
977-
let entry_path = entry.path();
995+
let sysroot_dir = openat::Dir::open(sysroot)?;
996+
// Source dir is usr/lib/ostree-boot/efi
997+
let src = sysroot_dir
998+
.sub_dir(&ostreeboot_efi)
999+
.context("Opening ostree-boot/efi dir")?;
9781000

979-
// get path EFI/{BOOT,<vendor>}/<file>
980-
let filepath = entry_path.strip_prefix(&ostreeboot_efi_path)?;
981-
// get path /boot/efi/EFI/{BOOT,<vendor>}/<file>
982-
let boot_filepath = Path::new("/boot/efi").join(filepath);
1001+
for entry in WalkDir::new(&ostreeboot_efi_path) {
1002+
let entry = entry?;
1003+
if !entry.file_type().is_file() {
1004+
continue;
1005+
}
9831006

984-
// Run `rpm -qf <filepath>`
985-
let pkg = crate::packagesystem::query_file(
986-
sysroot.to_str().unwrap(),
987-
boot_filepath.to_str().unwrap(),
988-
)?;
1007+
// get path relative to the efi/ root (e.g. EFI/BOOT/shim.efi or start4.elf)
1008+
let filepath = entry.path().strip_prefix(&ostreeboot_efi_path)?;
9891009

990-
let (name, evr) = pkg.split_once(' ').unwrap();
991-
let component = name.split('-').next().unwrap_or("");
992-
// get path usr/lib/efi/<component>/<evr>
993-
let efilib_path = Path::new(EFILIB).join(component).join(evr);
1010+
// Run `rpm -qf /boot/efi/<filepath>` to find the owning package
1011+
let pkg = resolve_pkg(sysroot, filepath)?;
9941012

995-
let sysroot_dir = openat::Dir::open(sysroot)?;
996-
// Ensure dest parent directory exists
997-
if let Some(parent) = efilib_path.join(filepath).parent() {
998-
sysroot_dir.ensure_dir_all(parent, 0o755)?;
999-
}
1013+
let (name, evr) = pkg
1014+
.split_once(' ')
1015+
.with_context(|| format!("parsing rpm output: {}", pkg))?;
1016+
// get path usr/lib/efi/<component>/<evr>
1017+
let efilib_path = Path::new(EFILIB).join(name).join(evr);
10001018

1001-
// Source dir is usr/lib/ostree-boot/efi
1002-
let src = sysroot_dir
1003-
.sub_dir(&ostreeboot_efi)
1004-
.context("Opening ostree-boot dir")?;
1005-
// Dest dir is usr/lib/efi/<component>/<evr>
1006-
let dest = sysroot_dir
1007-
.sub_dir(&efilib_path)
1008-
.context("Opening usr/lib/efi dir")?;
1009-
// Copy file from ostree-boot to usr/lib/efi
1010-
src.copy_file_at(filepath, &dest, filepath)
1011-
.context("Copying file to usr/lib/efi")?;
1019+
// Ensure dest parent directory exists
1020+
if let Some(parent) = efilib_path.join(filepath).parent() {
1021+
sysroot_dir.ensure_dir_all(parent, 0o755)?;
10121022
}
1023+
1024+
// Dest dir is usr/lib/efi/<component>/<evr>
1025+
let dest = sysroot_dir
1026+
.sub_dir(&efilib_path)
1027+
.context("Opening usr/lib/efi dir")?;
1028+
// Copy file from ostree-boot to usr/lib/efi
1029+
src.copy_file_at(filepath, &dest, filepath)
1030+
.context("Copying file to usr/lib/efi")?;
10131031
}
10141032
Ok(())
10151033
}
@@ -1319,4 +1337,66 @@ Boot0003* test";
13191337

13201338
Ok(())
13211339
}
1340+
1341+
#[test]
1342+
fn test_transfer_ostree_boot_to_usr() -> Result<()> {
1343+
let tmpdir = tempfile::tempdir()?;
1344+
let sysroot = tmpdir.path();
1345+
1346+
// Simulate usr/lib/ostree-boot/efi/ with both EFI/ and root-level files
1347+
let efi_dir = sysroot.join("usr/lib/ostree-boot/efi");
1348+
std::fs::create_dir_all(efi_dir.join("EFI/vendor"))?;
1349+
std::fs::write(efi_dir.join("EFI/vendor/foo.efi"), "foo data")?;
1350+
std::fs::create_dir_all(efi_dir.join("EFI/BOOT"))?;
1351+
std::fs::write(efi_dir.join("EFI/BOOT/BOOTAA64.EFI"), "boot data")?;
1352+
// Root-level files
1353+
std::fs::write(efi_dir.join("bar.dtb"), "bar data")?;
1354+
std::fs::write(efi_dir.join("baz.bin"), "baz data")?;
1355+
std::fs::create_dir_all(efi_dir.join("sub"))?;
1356+
std::fs::write(efi_dir.join("sub/quux.dat"), "quux data")?;
1357+
1358+
// Ensure the destination base directory exists
1359+
std::fs::create_dir_all(sysroot.join(EFILIB))?;
1360+
1361+
// Fake resolver: EFI files belong to "FOO 1.0", root-level
1362+
// files to "BAR 2.0"
1363+
let resolve = |_sysroot: &Path, filepath: &Path| -> Result<String> {
1364+
let s = filepath.to_str().unwrap();
1365+
if s.starts_with("EFI") {
1366+
Ok("FOO 1.0".to_string())
1367+
} else {
1368+
Ok("BAR 2.0".to_string())
1369+
}
1370+
};
1371+
1372+
transfer_ostree_boot_to_usr_impl(sysroot, resolve)?;
1373+
1374+
// EFI files should be under EFILIB/FOO/1.0/EFI/...
1375+
let foo_base = sysroot.join("usr/lib/efi/FOO/1.0");
1376+
assert_eq!(
1377+
std::fs::read_to_string(foo_base.join("EFI/vendor/foo.efi"))?,
1378+
"foo data"
1379+
);
1380+
assert_eq!(
1381+
std::fs::read_to_string(foo_base.join("EFI/BOOT/BOOTAA64.EFI"))?,
1382+
"boot data"
1383+
);
1384+
1385+
// Root-level files should be under EFILIB/BAR/2.0/
1386+
let bar_base = sysroot.join("usr/lib/efi/BAR/2.0");
1387+
assert_eq!(
1388+
std::fs::read_to_string(bar_base.join("bar.dtb"))?,
1389+
"bar data"
1390+
);
1391+
assert_eq!(
1392+
std::fs::read_to_string(bar_base.join("baz.bin"))?,
1393+
"baz data"
1394+
);
1395+
assert_eq!(
1396+
std::fs::read_to_string(bar_base.join("sub/quux.dat"))?,
1397+
"quux data"
1398+
);
1399+
1400+
Ok(())
1401+
}
13221402
}

0 commit comments

Comments
 (0)