diff --git a/CHANGELOG.md b/CHANGELOG.md index 05b4fbc3a..15d94a54e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +# Unreleased + +## Bugfixes +- Handle invalid working directories gracefully when using `--full-path`, see #1900 (@Xavrir). + # 10.4.2 ## Bugfixes diff --git a/src/config.rs b/src/config.rs index 708a99333..8b9aba4a2 100644 --- a/src/config.rs +++ b/src/config.rs @@ -15,9 +15,9 @@ pub struct Config { /// Whether the search is case-sensitive or case-insensitive. pub case_sensitive: bool, - /// Whether to search within the full file path or just the base name (filename or directory - /// name). - pub search_full_path: bool, + /// Cached current working directory for absolute path construction. + /// Populated when `--full-path` is set; `None` means search by filename only. + pub cwd: Option, /// Whether to ignore hidden files and directories (or not). pub ignore_hidden: bool, diff --git a/src/filesystem.rs b/src/filesystem.rs index 4a04f9d52..92a58b7fe 100644 --- a/src/filesystem.rs +++ b/src/filesystem.rs @@ -20,6 +20,17 @@ pub fn path_absolute_form(path: &Path) -> io::Result { env::current_dir().map(|path_buf| path_buf.join(path)) } +/// Construct an absolute path from a potentially relative path and a +/// pre-resolved working directory. Unlike `path_absolute_form`, this +/// does not call `env::current_dir()` and cannot fail. +pub fn make_absolute(path: &Path, cwd: &Path) -> PathBuf { + if path.is_absolute() { + return path.to_path_buf(); + } + let path = path.strip_prefix(".").unwrap_or(path); + cwd.join(path) +} + pub fn absolute_path(path: &Path) -> io::Result { let path_buf = path_absolute_form(path)?; @@ -153,4 +164,40 @@ mod tests { Path::new("foo/bar/baz") ); } + + #[test] + fn make_absolute_with_relative_path() { + use super::make_absolute; + use std::path::PathBuf; + + let cwd = Path::new("/home/user"); + assert_eq!( + make_absolute(Path::new("foo/bar"), cwd), + PathBuf::from("/home/user/foo/bar") + ); + } + + #[test] + fn make_absolute_strips_dot_prefix() { + use super::make_absolute; + use std::path::PathBuf; + + let cwd = Path::new("/home/user"); + assert_eq!( + make_absolute(Path::new("./foo/bar"), cwd), + PathBuf::from("/home/user/foo/bar") + ); + } + + #[test] + fn make_absolute_with_absolute_path() { + use super::make_absolute; + use std::path::PathBuf; + + let cwd = Path::new("/home/user"); + assert_eq!( + make_absolute(Path::new("/absolute/path"), cwd), + PathBuf::from("/absolute/path") + ); + } } diff --git a/src/main.rs b/src/main.rs index 80e380fe9..edb32bb82 100644 --- a/src/main.rs +++ b/src/main.rs @@ -245,9 +245,18 @@ fn construct_config(mut opts: Opts, pattern_regexps: &[String]) -> Result = if config.search_full_path { - let path_abs_buf = filesystem::path_absolute_form(entry_path) - .expect("Retrieving absolute path succeeds"); - Cow::Owned(path_abs_buf.as_os_str().to_os_string()) - } else { - match entry_path.file_name() { - Some(filename) => Cow::Borrowed(filename), - None => unreachable!( - "Encountered file system entry without a file name. This should only \ - happen for paths like 'foo/bar/..' or '/' which are not supposed to \ - appear in a file system traversal." - ), - } - }; + let search_str = search_str_for_entry(entry_path, config.cwd.as_deref()); if !patterns .iter() @@ -676,6 +663,25 @@ impl WorkerState { } } +fn search_str_for_entry<'a>( + entry_path: &'a std::path::Path, + cwd: Option<&std::path::Path>, +) -> Cow<'a, OsStr> { + if let Some(cwd) = cwd { + let abs_path = filesystem::make_absolute(entry_path, cwd); + Cow::Owned(abs_path.into_os_string()) + } else { + match entry_path.file_name() { + Some(filename) => Cow::Borrowed(filename), + None => unreachable!( + "Encountered file system entry without a file name. This should only \ + happen for paths like 'foo/bar/..' or '/' which are not supposed to \ + appear in a file system traversal." + ), + } + } +} + /// Recursively scan the given search path for files / pathnames matching the patterns. /// /// If the `--exec` argument was supplied, this will create a thread pool for executing