diff options
Diffstat (limited to 'src/lib.rs')
-rw-r--r-- | src/lib.rs | 86 |
1 files changed, 79 insertions, 7 deletions
@@ -132,6 +132,8 @@ pub use unix::DirEntryExt; mod tests; #[cfg(unix)] mod unix; +#[cfg(windows)] +mod windows; /// Like try, but for iterators that return [`Option<Result<_, _>>`]. /// @@ -247,6 +249,7 @@ struct WalkDirOptions { FnMut(&DirEntry,&DirEntry) -> Ordering + Send + Sync + 'static >>, contents_first: bool, + same_file_system: bool, } impl fmt::Debug for WalkDirOptions { @@ -264,6 +267,7 @@ impl fmt::Debug for WalkDirOptions { .field("max_depth", &self.max_depth) .field("sorter", &sorter_str) .field("contents_first", &self.contents_first) + .field("same_file_system", &self.same_file_system) .finish() } } @@ -283,6 +287,7 @@ impl WalkDir { max_depth: ::std::usize::MAX, sorter: None, contents_first: false, + same_file_system: false, }, root: root.as_ref().to_path_buf(), } @@ -448,6 +453,19 @@ impl WalkDir { self.opts.contents_first = yes; self } + + /// Do not cross file system boundaries. + /// + /// When this option is enabled, directory traversal will not descend into + /// directories that are on a different file system from the root path. + /// + /// Currently, this option is only supported on Unix and Windows. If this + /// option is used on an unsupported platform, then directory traversal + /// will immediately return an error and will not yield any entries. + pub fn same_file_system(mut self, yes: bool) -> Self { + self.opts.same_file_system = yes; + self + } } impl IntoIterator for WalkDir { @@ -463,6 +481,7 @@ impl IntoIterator for WalkDir { oldest_opened: 0, depth: 0, deferred_dirs: vec![], + root_device: None, } } } @@ -512,6 +531,13 @@ pub struct IntoIter { /// yielded after their contents has been fully yielded. This is only /// used when `contents_first` is enabled. deferred_dirs: Vec<DirEntry>, + /// The device of the root file path when the first call to `next` was + /// made. + /// + /// If the `same_file_system` option isn't enabled, then this is always + /// `None`. Conversely, if it is enabled, this is always `Some(...)` after + /// handling the root path. + root_device: Option<u64>, } /// An ancestor is an item in the directory tree traversed by walkdir, and is @@ -650,6 +676,11 @@ impl Iterator for IntoIter { /// an error value. The error will be wrapped in an Option::Some. fn next(&mut self) -> Option<Result<DirEntry>> { if let Some(start) = self.start.take() { + if self.opts.same_file_system { + let result = device_num(&start) + .map_err(|e| Error::from_path(0, start.clone(), e)); + self.root_device = Some(itry!(result)); + } let dent = itry!(DirEntry::from_path(0, start, false)); if let Some(result) = self.handle_entry(dent) { return Some(result); @@ -668,7 +699,11 @@ impl Iterator for IntoIter { } // Unwrap is safe here because we've verified above that // `self.stack_list` is not empty - match self.stack_list.last_mut().expect("bug in walkdir").next() { + let next = self.stack_list + .last_mut() + .expect("BUG: stack should be non-empty") + .next(); + match next { None => self.pop(), Some(Err(err)) => return Some(Err(err)), Some(Ok(dent)) => { @@ -802,7 +837,13 @@ impl IntoIter { } let is_normal_dir = !dent.file_type().is_symlink() && dent.is_dir(); if is_normal_dir { - itry!(self.push(&dent)); + if self.opts.same_file_system && dent.depth > 0 { + if itry!(self.is_same_file_system(&dent)) { + itry!(self.push(&dent)); + } + } else { + itry!(self.push(&dent)); + } } if is_normal_dir && self.opts.contents_first { self.deferred_dirs.push(dent); @@ -820,7 +861,7 @@ impl IntoIter { // Unwrap is safe here because we've guaranteed that // `self.deferred_dirs.len()` can never be less than 1 let deferred: DirEntry = self.deferred_dirs.pop() - .expect("bug in walkdir"); + .expect("BUG: deferred_dirs should be non-empty"); if !self.skippable() { return Some(deferred); } @@ -874,7 +915,7 @@ impl IntoIter { } fn pop(&mut self) { - self.stack_list.pop().expect("cannot pop from empty stack"); + self.stack_list.pop().expect("BUG: cannot pop from empty stack"); if self.opts.follow_links { self.stack_path.pop().expect("BUG: list/path stacks out of sync"); } @@ -920,6 +961,14 @@ impl IntoIter { Ok(()) } + fn is_same_file_system(&mut self, dent: &DirEntry) -> Result<bool> { + let dent_device = device_num(&dent.path) + .map_err(|err| Error::from_entry(dent, err))?; + Ok(self.root_device + .map(|d| d == dent_device) + .expect("BUG: called is_same_file_system without root device")) + } + fn skippable(&self) -> bool { self.depth < self.opts.min_depth || self.depth > self.opts.max_depth } @@ -1584,10 +1633,12 @@ impl fmt::Display for Error { } impl From<Error> for io::Error { - /// Convert the [`Error`] to an [`io::Error`], preserving the original [`Error`] as the ["inner - /// error"]. Note that this also makes the display of the error include the context. + /// Convert the [`Error`] to an [`io::Error`], preserving the original + /// [`Error`] as the ["inner error"]. Note that this also makes the display + /// of the error include the context. /// - /// This is different from [`into_io_error`] which returns the original [`io::Error`]. + /// This is different from [`into_io_error`] which returns the original + /// [`io::Error`]. /// /// [`Error`]: struct.Error.html /// [`io::Error`]: https://doc.rust-lang.org/stable/std/io/struct.Error.html @@ -1605,3 +1656,24 @@ impl From<Error> for io::Error { io::Error::new(kind, walk_err) } } + +#[cfg(unix)] +fn device_num<P: AsRef<Path>>(path: P)-> std::io::Result<u64> { + use std::os::unix::fs::MetadataExt; + + path.as_ref().metadata().map(|md| md.dev()) +} + + #[cfg(windows)] +fn device_num<P: AsRef<Path>>(path: P) -> std::io::Result<u64> { + windows::windows_file_handle_info(path) + .map(|info| info.dwVolumeSerialNumber as u64) +} + +#[cfg(not(any(unix, windows)))] +fn device_num<P: AsRef<Path>>(_: P)-> std::io::Result<u64> { + Err(io::Error::new( + io::ErrorKind::Other, + "walkdir: same_file_system option not supported on this platform", + )) +} |