diff --git a/Cargo.lock b/Cargo.lock index 4cc6bb789..007926063 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1853,6 +1853,7 @@ dependencies = [ "pkgar-keys 0.1.17", "redoxer", "serde", + "tempfile", "termion", "toml 0.8.19", "walkdir", diff --git a/Cargo.toml b/Cargo.toml index 15608f407..c5aa5b09b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,3 +32,6 @@ serde = { version = "=1.0.197", features = ["derive"] } termion = "4" toml = "0.8" walkdir = "2.3.1" + +[dev-dependencies] +tempfile = "3" diff --git a/src/bin/cook.rs b/src/bin/cook.rs index dca5329f0..7624755ef 100644 --- a/src/bin/cook.rs +++ b/src/bin/cook.rs @@ -2,6 +2,7 @@ use cookbook::blake3::blake3_progress; use cookbook::package::StageToml; use cookbook::recipe::{BuildKind, CookRecipe, Recipe, SourceRecipe}; use cookbook::recipe_find::recipe_find; +use std::collections::VecDeque; use std::{ collections::BTreeSet, env, fs, @@ -505,7 +506,32 @@ fi"#, fn auto_deps(stage_dir: &Path, dep_pkgars: &BTreeSet<(String, PathBuf)>) -> BTreeSet { let mut paths = BTreeSet::new(); - for dir in &[stage_dir.join("usr/bin"), stage_dir.join("usr/lib")] { + let mut visited = BTreeSet::new(); + // Base directories may need to be updated for packages that place binaries in odd locations. + let mut walk = VecDeque::from([ + stage_dir.join("libexec"), + stage_dir.join("usr/bin"), + stage_dir.join("usr/games"), + stage_dir.join("usr/lib"), + ]); + + // Recursively (DFS) walk each directory to ensure nested libs and bins are checked. + while let Some(dir) = walk.pop_front() { + let Ok(dir) = dir.canonicalize() else { + continue; + }; + if visited.contains(&dir) { + #[cfg(debug_assertions)] + eprintln!("DEBUG: auto_deps => Skipping `{dir:?}` (already visited)"); + continue; + } + assert!( + visited.insert(dir.clone()), + "Directory `{:?}` should not be in visited\nVisited: {:#?}", + dir, + visited + ); + let Ok(read_dir) = fs::read_dir(&dir) else { continue; }; @@ -516,6 +542,8 @@ fn auto_deps(stage_dir: &Path, dep_pkgars: &BTreeSet<(String, PathBuf)>) -> BTre }; if file_type.is_file() { paths.insert(entry.path()); + } else if file_type.is_dir() { + walk.push_front(entry.path()); } } } @@ -1185,3 +1213,32 @@ fn main() { } } } + +#[cfg(test)] +mod tests { + use std::os::unix; + + use super::auto_deps; + + #[test] + fn file_system_loop_no_infinite_loop() { + // Hierarchy with an infinite loop + let temp = tempfile::tempdir().unwrap(); + let root = temp.path(); + let dir = root.join("loop"); + unix::fs::symlink(root, &dir).expect("Linking {dir:?} to {root:?}"); + + // Sanity check that we have a loop + assert_eq!( + root.canonicalize().unwrap(), + dir.canonicalize().unwrap(), + "Expected a loop where {dir:?} points to {root:?}" + ); + + let entries = auto_deps(root, &Default::default()); + assert!( + entries.is_empty(), + "auto_deps shouldn't have yielded any libraries" + ); + } +}