diff options
author | Andrew Gallant <jamslam@gmail.com> | 2018-02-02 05:08:16 +0300 |
---|---|---|
committer | Andrew Gallant <jamslam@gmail.com> | 2018-02-02 05:08:16 +0300 |
commit | 0f4441f9bc3908f8e3e58888c11504cf6b5ea0f2 (patch) | |
tree | 9777afa4ce5ca5fc2051bedcc2419ef324154f7b | |
parent | 42a5b959f1952733f45ceff6c004093a271e58ae (diff) |
windows: fix OneDrive traversals
This commit fixes a bug on Windows where walkdir refused to traverse
directories that resided on OneDrive via its "file on demand" strategy.
The specific bug is that Rust's standard library treats a reparse point
(which is what OneDrive uses) as distinct from a file or directory, which
wreaks havoc on any code that uses FileType::{is_file, is_dir}. We fix
this by checking the directory status of a file by looking only at whether
its directory bit is set.
This bug was originally reported in ripgrep:
https://github.com/BurntSushi/ripgrep/issues/705
It has also been filed upstream:
https://github.com/rust-lang/rust/issues/46484
And has a pending fix:
https://github.com/rust-lang/rust/pull/47956
-rw-r--r-- | Cargo.toml | 4 | ||||
-rw-r--r-- | src/lib.rs | 44 |
2 files changed, 41 insertions, 7 deletions
@@ -19,6 +19,10 @@ appveyor = { repository = "BurntSushi/walkdir" } [dependencies] same-file = "1" +[target.'cfg(windows)'.dependencies.winapi] +version = "0.3" +features = ["std", "winnt"] + [dev-dependencies] docopt = "0.8" quickcheck = { version = "0.6", default-features = false } @@ -111,6 +111,8 @@ extern crate quickcheck; #[cfg(test)] extern crate rand; extern crate same_file; +#[cfg(windows)] +extern crate winapi; use std::cmp::{Ordering, min}; use std::error; @@ -629,6 +631,13 @@ pub struct DirEntry { /// The underlying inode number (Unix only). #[cfg(unix)] ino: u64, + /// The underlying metadata (Windows only). + /// + /// We use this to determine whether an entry is a directory or not, which + /// works around a bug in Rust's standard library: + /// https://github.com/rust-lang/rust/issues/46484 + #[cfg(windows)] + metadata: fs::Metadata, } impl Iterator for IntoIter { @@ -791,10 +800,10 @@ impl IntoIter { if self.opts.follow_links && dent.file_type().is_symlink() { dent = itry!(self.follow(dent)); } - if dent.file_type().is_dir() { + if dent.is_dir() { itry!(self.push(&dent)); } - if dent.file_type().is_dir() && self.opts.contents_first { + if dent.is_dir() && self.opts.contents_first { self.deferred_dirs.push(dent); None } else if self.skippable() { @@ -879,7 +888,7 @@ impl IntoIter { // The only way a symlink can cause a loop is if it points // to a directory. Otherwise, it always points to a leaf // and we can omit any loop checks. - if dent.file_type().is_dir() { + if dent.is_dir() { self.check_loop(dent.path())?; } Ok(dent) @@ -1030,16 +1039,35 @@ impl DirEntry { self.depth } + /// Returns true if and only if this entry points to a directory. + /// + /// This works around a bug in Rust's standard library: + /// https://github.com/rust-lang/rust/issues/46484 + #[cfg(windows)] + fn is_dir(&self) -> bool { + use std::os::windows::fs::MetadataExt; + use winapi::um::winnt::FILE_ATTRIBUTE_DIRECTORY; + self.metadata.file_attributes() & FILE_ATTRIBUTE_DIRECTORY != 0 + } + + /// Returns true if and only if this entry points to a directory. + #[cfg(not(windows))] + fn is_dir(&self) -> bool { + self.ty.is_dir() + } + #[cfg(not(unix))] fn from_entry(depth: usize, ent: &fs::DirEntry) -> Result<DirEntry> { - let ty = ent.file_type().map_err(|err| { - Error::from_path(depth, ent.path(), err) + let path = ent.path(); + let md = fs::metadata(&path).map_err(|err| { + Error::from_path(depth, path.clone(), err) })?; Ok(DirEntry { - path: ent.path(), - ty: ty, + path: path, + ty: md.file_type(), follow_link: false, depth: depth, + metadata: md, }) } @@ -1069,6 +1097,7 @@ impl DirEntry { ty: md.file_type(), follow_link: true, depth: depth, + metadata: md, }) } @@ -1097,6 +1126,7 @@ impl Clone for DirEntry { ty: self.ty, follow_link: self.follow_link, depth: self.depth, + metadata: self.metadata.clone(), } } |