Skip to content

Commit b3432cd

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 7129123 commit b3432cd

1 file changed

Lines changed: 119 additions & 39 deletions

File tree

src/efi.rs

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

638-
// Remove usr/lib/ostree-boot/efi/EFI dir (after transfer) or if it is empty
639-
ostreeboot.remove_all_optional("efi/EFI")?;
638+
// Remove the entire efi/ tree after transfer, or if it is empty
639+
ostreeboot.remove_all_optional("efi")?;
640640
}
641641

642642
if let Some(efi_components) =
@@ -958,55 +958,73 @@ fn latest_versions(components: &[EFIComponent]) -> Vec<&EFIComponent> {
958958
result
959959
}
960960

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

966-
let efi = ostreeboot_efi_path.join("EFI");
967-
if !efi.exists() {
988+
if !ostreeboot_efi_path.exists() {
968989
return Ok(());
969990
}
970-
for entry in WalkDir::new(&efi) {
971-
let entry = entry?;
972991

973-
if entry.file_type().is_file() {
974-
let entry_path = entry.path();
992+
let sysroot_dir = openat::Dir::open(sysroot)?;
993+
// Source dir is usr/lib/ostree-boot/efi
994+
let src = sysroot_dir
995+
.sub_dir(&ostreeboot_efi)
996+
.context("Opening ostree-boot/efi dir")?;
975997

976-
// get path EFI/{BOOT,<vendor>}/<file>
977-
let filepath = entry_path.strip_prefix(&ostreeboot_efi_path)?;
978-
// get path /boot/efi/EFI/{BOOT,<vendor>}/<file>
979-
let boot_filepath = Path::new("/boot/efi").join(filepath);
998+
for entry in WalkDir::new(&ostreeboot_efi_path) {
999+
let entry = entry?;
1000+
if !entry.file_type().is_file() {
1001+
continue;
1002+
}
9801003

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

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

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

998-
// Source dir is usr/lib/ostree-boot/efi
999-
let src = sysroot_dir
1000-
.sub_dir(&ostreeboot_efi)
1001-
.context("Opening ostree-boot dir")?;
1002-
// Dest dir is usr/lib/efi/<component>/<evr>
1003-
let dest = sysroot_dir
1004-
.sub_dir(&efilib_path)
1005-
.context("Opening usr/lib/efi dir")?;
1006-
// Copy file from ostree-boot to usr/lib/efi
1007-
src.copy_file_at(filepath, &dest, filepath)
1008-
.context("Copying file to usr/lib/efi")?;
1016+
// Ensure dest parent directory exists
1017+
if let Some(parent) = efilib_path.join(filepath).parent() {
1018+
sysroot_dir.ensure_dir_all(parent, 0o755)?;
10091019
}
1020+
1021+
// Dest dir is usr/lib/efi/<component>/<evr>
1022+
let dest = sysroot_dir
1023+
.sub_dir(&efilib_path)
1024+
.context("Opening usr/lib/efi dir")?;
1025+
// Copy file from ostree-boot to usr/lib/efi
1026+
src.copy_file_at(filepath, &dest, filepath)
1027+
.context("Copying file to usr/lib/efi")?;
10101028
}
10111029
Ok(())
10121030
}
@@ -1316,4 +1334,66 @@ Boot0003* test";
13161334

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

0 commit comments

Comments
 (0)