Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 33 additions & 1 deletion src/dirext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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...
Expand All @@ -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<F>(mut self, cmp: F) -> Self
where
Expand Down Expand Up @@ -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;
Expand Down
53 changes: 53 additions & 0 deletions tests/it/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<()> {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fine as is however one option is for us to add an integration suite; with containers we can control the mounts isntead of just hoping for the best with what we find in /.

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<()> {
Expand Down