diff --git a/src/dirext.rs b/src/dirext.rs index 45694ca..5f3e0df 100644 --- a/src/dirext.rs +++ b/src/dirext.rs @@ -59,6 +59,9 @@ pub struct WalkConfiguration<'p> { /// Do not cross devices. noxdev: bool, + /// Skip mount points entirely (do not visit or traverse them). + skip_mountpoints: bool, + path_base: Option<&'p Path>, // It's not *that* complex of a type, come on clippy... @@ -73,18 +76,39 @@ impl std::fmt::Debug for WalkConfiguration<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("WalkConfiguration") .field("noxdev", &self.noxdev) + .field("skip_mountpoints", &self.skip_mountpoints) .field("sorter", &self.sorter.as_ref().map(|_| true)) .finish() } } impl<'p> WalkConfiguration<'p> { - /// Enable configuration to not traverse mount points + /// Enable configuration to not traverse mount points. + /// + /// Note that the mount point directory itself will still be visited + /// (passed to the callback); only traversal into it is prevented. + /// To skip mount point directories entirely, use [`skip_mountpoints`](Self::skip_mountpoints). pub fn noxdev(mut self) -> Self { self.noxdev = true; self } + /// Skip mount point directories entirely during the walk. + /// + /// When enabled, directories that are mount points will not be visited + /// (the callback will not be invoked for them) and will not be traversed. + /// + /// This is independent from [`noxdev`](Self::noxdev) -- `skip_mountpoints` + /// controls whether mount point directories are visited at all, while + /// `noxdev` controls whether the walk traverses into directories on + /// different devices. In practice you may want to use both together. + /// + /// This option currently only has an effect on Linux. + pub fn skip_mountpoints(mut self) -> Self { + self.skip_mountpoints = true; + self + } + /// Set a function for sorting directory entries. pub fn sort_by(mut self, cmp: F) -> Self where @@ -556,6 +580,14 @@ where let ty = entry.file_type()?; let is_dir = ty.is_dir(); let name = entry.file_name(); + // If skip_mountpoints is enabled and this is a directory, + // check if it's a mount point and skip it entirely. + #[cfg(any(target_os = "android", target_os = "linux"))] + if is_dir && config.skip_mountpoints { + if let Some(true) = is_mountpoint_impl_statx(d, Path::new(&name))? { + continue; + } + } // The path provided to the user includes the current filename path.push(&name); let filename = &name; diff --git a/tests/it/main.rs b/tests/it/main.rs index 69f591b..5589650 100644 --- a/tests/it/main.rs +++ b/tests/it/main.rs @@ -722,6 +722,59 @@ fn test_walk_noxdev() -> Result<()> { Ok(()) } +#[test] +#[cfg(any(target_os = "android", target_os = "linux"))] +fn test_walk_skip_mountpoints() -> Result<()> { + let rootfs = Dir::open_ambient_dir("/", cap_std::ambient_authority())?; + + // Count root entries that are mountpoints + let n_root_entries = std::fs::read_dir("/")?.count(); + let mut n_mountpoints = 0; + for entry in std::fs::read_dir("/")? { + let entry = entry?; + if entry.file_type()?.is_dir() { + if let Some(true) = rootfs.is_mountpoint(entry.file_name())? { + n_mountpoints += 1; + } + } + } + assert!( + n_mountpoints > 0, + "expected at least one mountpoint under /" + ); + + // Walk with skip_mountpoints; don't traverse into any directories. + let mut visited = 0; + rootfs + .walk( + &WalkConfiguration::default().skip_mountpoints(), + |e| -> std::io::Result<_> { + // Verify this entry is not a mountpoint + if e.file_type.is_dir() { + let is_mp = e.dir.is_mountpoint(e.filename)?; + assert_ne!( + is_mp, + Some(true), + "mountpoint {:?} should have been skipped", + e.path + ); + } + visited += 1; + // Don't traverse into subdirectories, but continue iterating siblings + if e.file_type.is_dir() { + Ok(ControlFlow::Break(())) + } else { + Ok(ControlFlow::Continue(())) + } + }, + ) + .unwrap(); + // We should see all root entries minus the mountpoints + assert_eq!(visited, n_root_entries - n_mountpoints); + + Ok(()) +} + #[test] #[cfg(any(target_os = "android", target_os = "linux"))] fn test_xattrs() -> Result<()> {