Skip to content

Commit 251b08a

Browse files
filetree: Add new_from_dir_strip_prefix_for() for EFILIB layouts
The /usr/lib/efi/ layout stores component files under a two-level prefix <component>/<evr>/ (e.g. shim/15.8/EFI/fedora/shimaa64.efi). To compare this tree against the flat ESP layout, the prefix must be stripped so that keys match the destination paths (EFI/fedora/shimaa64.efi). Add a file tree constructor that walks a set of <component>/<evr>/ subdirs, strips that prefix from each path, and records the original source path. This can be used by the source_map mechanism when applying diffs, to only include the desired version when multiple versions of a component coexist, avoiding the non-deterministic version mixing that currently exists. Assisted-by: Cursor (Claude Opus 4)
1 parent b3c51b8 commit 251b08a

1 file changed

Lines changed: 70 additions & 0 deletions

File tree

src/filetree.rs

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,39 @@ impl FileTree {
215215
Ok(Self { children })
216216
}
217217

218+
/// Like [`new_from_dir`] but only walks the specified
219+
/// `<name>/<version>/` subdirectories and strips that prefix from each
220+
/// entry. Each entry in `prefixes` is a relative path like
221+
/// `"shim/15.9-1"` that identifies the component version directory to
222+
/// include.
223+
///
224+
/// "FOO/1.0/EFI/vendor/foo.efi" -> "EFI/vendor/foo.efi"
225+
/// "BAR/2.0/bar.dtb" -> "bar.dtb"
226+
#[cfg(any(
227+
target_arch = "x86_64",
228+
target_arch = "aarch64",
229+
target_arch = "riscv64"
230+
))]
231+
#[allow(dead_code)]
232+
pub(crate) fn new_from_dir_strip_prefix_for<S: AsRef<str>>(
233+
dir: &openat::Dir,
234+
prefixes: &[S],
235+
) -> Result<Self> {
236+
let mut children = BTreeMap::new();
237+
for prefix_str in prefixes {
238+
let prefix = prefix_str.as_ref();
239+
let sub = dir
240+
.sub_dir(prefix)
241+
.with_context(|| format!("opening component dir {}", prefix))?;
242+
for (k, mut v) in Self::unsorted_from_dir(&sub)?.drain() {
243+
let source = format!("{}/{}", prefix, k);
244+
v.source = Some(source);
245+
children.insert(k, v);
246+
}
247+
}
248+
Ok(Self { children })
249+
}
250+
218251
/// Determine the changes *from* self to the updated tree
219252
#[cfg(any(
220253
target_arch = "x86_64",
@@ -1016,4 +1049,41 @@ mod tests {
10161049
assert!(dst_dir.exists("top.dat")?);
10171050
Ok(())
10181051
}
1052+
1053+
#[test]
1054+
fn test_new_from_dir_strip_prefix_for() -> Result<()> {
1055+
let tmpdir = tempfile::tempdir()?;
1056+
let root = tmpdir.path().join("root");
1057+
1058+
// Two-level <name>/<version>/ layout with an EFI subtree
1059+
// and a flat (non-EFI) component.
1060+
std::fs::create_dir_all(root.join("FOO/1.0/EFI/vendor"))?;
1061+
std::fs::write(root.join("FOO/1.0/EFI/vendor/foo.efi"), "foo data")?;
1062+
std::fs::create_dir_all(root.join("BAR/2.0"))?;
1063+
std::fs::write(root.join("BAR/2.0/bar.dtb"), "bar data")?;
1064+
std::fs::write(root.join("BAR/2.0/baz.bin"), "baz data")?;
1065+
1066+
let dir = openat::Dir::open(&root)?;
1067+
1068+
// With all prefixes, every component is included.
1069+
let ft = FileTree::new_from_dir_strip_prefix_for(&dir, &["FOO/1.0", "BAR/2.0"])?;
1070+
assert!(ft.children.contains_key("EFI/vendor/foo.efi"));
1071+
assert!(ft.children.contains_key("bar.dtb"));
1072+
assert!(ft.children.contains_key("baz.bin"));
1073+
assert_eq!(
1074+
ft.children["EFI/vendor/foo.efi"].source.as_deref(),
1075+
Some("FOO/1.0/EFI/vendor/foo.efi")
1076+
);
1077+
assert_eq!(
1078+
ft.children["bar.dtb"].source.as_deref(),
1079+
Some("BAR/2.0/bar.dtb")
1080+
);
1081+
1082+
// With a subset of prefixes, only matching components are included.
1083+
let ft2 = FileTree::new_from_dir_strip_prefix_for(&dir, &["FOO/1.0"])?;
1084+
assert!(ft2.children.contains_key("EFI/vendor/foo.efi"));
1085+
assert!(!ft2.children.contains_key("bar.dtb"));
1086+
1087+
Ok(())
1088+
}
10191089
}

0 commit comments

Comments
 (0)