diff options
author | Andrew Gallant <jamslam@gmail.com> | 2018-02-01 21:11:02 -0500 |
---|---|---|
committer | Andrew Gallant <jamslam@gmail.com> | 2018-02-01 21:11:02 -0500 |
commit | e36b65a11a7fa72e4b43f89c71441884269f0522 (patch) | |
tree | a5689bc6178f6665e0c441bc7f2243f0fff49cf7 /src | |
parent | 11ad7ab204f415d6e3af35b9e8c8907092857266 (diff) |
windows: fix OneDrive traversals
This commit fixes a bug on Windows where directory traversals were
completely broken when attempting to scan OneDrive directories that use
the "file on demand" strategy.
The specific problem was that Rust's standard library treats OneDrive
directories as reparse points instead of directories, which causes
methods like `FileType::is_file` and `FileType::is_dir` to always return
false, even when retrieved via methods like `metadata` that purport to
follow symbolic links.
We fix this by peppering our code with checks on the underlying file
attributes exposed by Windows. We consider an entry a directory if and
only if the directory bit is set on the attributes. We are careful to
make sure that the code remains the same on non-Windows platforms.
Note that we also bump the dependency on `walkdir`, which contains a
similar fix for its traversals.
This bug is recorded upstream:
https://github.com/rust-lang/rust/issues/46484
Upstream also has a pending PR:
https://github.com/rust-lang/rust/pull/47956
Fixes #705
Diffstat (limited to 'src')
-rw-r--r-- | src/args.rs | 47 | ||||
-rw-r--r-- | src/main.rs | 52 |
2 files changed, 90 insertions, 9 deletions
diff --git a/src/args.rs b/src/args.rs index 030adf0f..a0c1d7db 100644 --- a/src/args.rs +++ b/src/args.rs @@ -209,7 +209,7 @@ impl Args { /// Returns true if there is exactly one file path given to search. pub fn is_one_path(&self) -> bool { self.paths.len() == 1 - && (self.paths[0] == Path::new("-") || self.paths[0].is_file()) + && (self.paths[0] == Path::new("-") || path_is_file(&self.paths[0])) } /// Create a worker whose configuration is taken from the @@ -562,7 +562,7 @@ impl<'a> ArgMatches<'a> { self.is_present("with-filename") || self.is_present("vimgrep") || paths.len() > 1 - || paths.get(0).map_or(false, |p| p.is_dir()) + || paths.get(0).map_or(false, |p| path_is_dir(p)) } } @@ -609,7 +609,7 @@ impl<'a> ArgMatches<'a> { } else { // If we're only searching a few paths and all of them are // files, then memory maps are probably faster. - paths.len() <= 10 && paths.iter().all(|p| p.is_file()) + paths.len() <= 10 && paths.iter().all(|p| path_is_file(p)) }) } @@ -1036,3 +1036,44 @@ fn stdin_is_readable() -> bool { // always return true. true } + +/// Returns true if and only if this path 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 path_is_dir(path: &Path) -> bool { + fs::metadata(path).map(|md| metadata_is_dir(&md)).unwrap_or(false) +} + +/// Returns true if and only if this entry points to a directory. +#[cfg(not(windows))] +fn path_is_dir(path: &Path) -> bool { + path.is_dir() +} + +/// Returns true if and only if this path points to a file. +/// +/// This works around a bug in Rust's standard library: +/// https://github.com/rust-lang/rust/issues/46484 +#[cfg(windows)] +fn path_is_file(path: &Path) -> bool { + !path_is_dir(path) +} + +/// Returns true if and only if this entry points to a directory. +#[cfg(not(windows))] +fn path_is_file(path: &Path) -> bool { + path.is_file() +} + +/// Returns true if and only if the given metadata 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 metadata_is_dir(md: &fs::Metadata) -> bool { + use std::os::windows::fs::MetadataExt; + use winapi::um::winnt::FILE_ATTRIBUTE_DIRECTORY; + md.file_attributes() & FILE_ATTRIBUTE_DIRECTORY != 0 +} diff --git a/src/main.rs b/src/main.rs index 7d39aa66..fea89e44 100644 --- a/src/main.rs +++ b/src/main.rs @@ -18,6 +18,8 @@ extern crate num_cpus; extern crate regex; extern crate same_file; extern crate termcolor; +#[cfg(windows)] +extern crate winapi; use std::error::Error; use std::process; @@ -267,15 +269,14 @@ fn get_or_log_dir_entry( eprintln!("{}", err); } } - let ft = match dent.file_type() { - None => return Some(dent), // entry is stdin - Some(ft) => ft, - }; + if dent.file_type().is_none() { + return Some(dent); // entry is stdin + } // A depth of 0 means the user gave the path explicitly, so we // should always try to search it. - if dent.depth() == 0 && !ft.is_dir() { + if dent.depth() == 0 && !ignore_entry_is_dir(&dent) { return Some(dent); - } else if !ft.is_file() { + } else if !ignore_entry_is_file(&dent) { return None; } // If we are redirecting stdout to a file, then don't search that @@ -288,6 +289,45 @@ fn get_or_log_dir_entry( } } +/// Returns true if and only if the given `ignore::DirEntry` 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 ignore_entry_is_dir(dent: &ignore::DirEntry) -> bool { + use std::os::windows::fs::MetadataExt; + use winapi::um::winnt::FILE_ATTRIBUTE_DIRECTORY; + + dent.metadata().map(|md| { + md.file_attributes() & FILE_ATTRIBUTE_DIRECTORY != 0 + }).unwrap_or(false) +} + +/// Returns true if and only if the given `ignore::DirEntry` points to a +/// directory. +#[cfg(not(windows))] +fn ignore_entry_is_dir(dent: &ignore::DirEntry) -> bool { + dent.file_type().map_or(false, |ft| ft.is_dir()) +} + +/// Returns true if and only if the given `ignore::DirEntry` points to a +/// file. +/// +/// This works around a bug in Rust's standard library: +/// https://github.com/rust-lang/rust/issues/46484 +#[cfg(windows)] +fn ignore_entry_is_file(dent: &ignore::DirEntry) -> bool { + !ignore_entry_is_dir(dent) +} + +/// Returns true if and only if the given `ignore::DirEntry` points to a +/// file. +#[cfg(not(windows))] +fn ignore_entry_is_file(dent: &ignore::DirEntry) -> bool { + dent.file_type().map_or(false, |ft| ft.is_file()) +} + fn is_stdout_file( dent: &ignore::DirEntry, stdout_handle: Option<&same_file::Handle>, |