diff --git a/src/find/matchers/access.rs b/src/find/matchers/access.rs index 3d8c695f..e707c35d 100644 --- a/src/find/matchers/access.rs +++ b/src/find/matchers/access.rs @@ -5,9 +5,8 @@ // https://opensource.org/licenses/MIT. use faccess::PathExt; -use walkdir::DirEntry; -use super::{Matcher, MatcherIO}; +use super::{Matcher, MatcherIO, WalkEntry}; /// Matcher for -{read,writ,execut}able. pub enum AccessMatcher { @@ -17,7 +16,7 @@ pub enum AccessMatcher { } impl Matcher for AccessMatcher { - fn matches(&self, file_info: &DirEntry, _: &mut MatcherIO) -> bool { + fn matches(&self, file_info: &WalkEntry, _: &mut MatcherIO) -> bool { let path = file_info.path(); match self { diff --git a/src/find/matchers/delete.rs b/src/find/matchers/delete.rs index cd9ba7b5..1d1e5e94 100644 --- a/src/find/matchers/delete.rs +++ b/src/find/matchers/delete.rs @@ -7,13 +7,11 @@ * file that was distributed with this source code. */ -use std::fs::{self, FileType}; +use std::fs; use std::io::{self, stderr, Write}; use std::path::Path; -use walkdir::DirEntry; - -use super::{Matcher, MatcherIO}; +use super::{FileType, Matcher, MatcherIO, WalkEntry}; pub struct DeleteMatcher; @@ -32,7 +30,7 @@ impl DeleteMatcher { } impl Matcher for DeleteMatcher { - fn matches(&self, file_info: &DirEntry, _: &mut MatcherIO) -> bool { + fn matches(&self, file_info: &WalkEntry, _: &mut MatcherIO) -> bool { let path = file_info.path(); let path_str = path.to_string_lossy(); diff --git a/src/find/matchers/empty.rs b/src/find/matchers/empty.rs index 9b6431d5..6726c17d 100644 --- a/src/find/matchers/empty.rs +++ b/src/find/matchers/empty.rs @@ -9,7 +9,7 @@ use std::{ io::{stderr, Write}, }; -use super::Matcher; +use super::{Matcher, MatcherIO, WalkEntry}; pub struct EmptyMatcher; @@ -20,7 +20,7 @@ impl EmptyMatcher { } impl Matcher for EmptyMatcher { - fn matches(&self, file_info: &walkdir::DirEntry, _: &mut super::MatcherIO) -> bool { + fn matches(&self, file_info: &WalkEntry, _: &mut MatcherIO) -> bool { if file_info.file_type().is_file() { match file_info.metadata() { Ok(meta) => meta.len() == 0, diff --git a/src/find/matchers/entry.rs b/src/find/matchers/entry.rs new file mode 100644 index 00000000..e35fc984 --- /dev/null +++ b/src/find/matchers/entry.rs @@ -0,0 +1,341 @@ +//! Paths encountered during a walk. + +use std::cell::OnceCell; +use std::error::Error; +use std::ffi::OsStr; +use std::fmt::{self, Display, Formatter}; +use std::fs::{self, Metadata}; +use std::io::{self, ErrorKind}; +#[cfg(unix)] +use std::os::unix::fs::FileTypeExt; +use std::path::{Path, PathBuf}; + +use walkdir::DirEntry; + +/// Wrapper for a directory entry. +#[derive(Debug)] +enum Entry { + /// Wraps an explicit path and depth. + Explicit(PathBuf, usize), + /// Wraps a WalkDir entry. + WalkDir(DirEntry), +} + +/// File types. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum FileType { + Unknown, + Fifo, + CharDevice, + Directory, + BlockDevice, + Regular, + Symlink, + Socket, +} + +impl FileType { + pub fn is_dir(self) -> bool { + self == Self::Directory + } + + pub fn is_file(self) -> bool { + self == Self::Regular + } + + pub fn is_symlink(self) -> bool { + self == Self::Symlink + } +} + +impl From for FileType { + fn from(t: fs::FileType) -> FileType { + if t.is_dir() { + return FileType::Directory; + } + if t.is_file() { + return FileType::Regular; + } + if t.is_symlink() { + return FileType::Symlink; + } + + #[cfg(unix)] + { + if t.is_fifo() { + return FileType::Fifo; + } + if t.is_char_device() { + return FileType::CharDevice; + } + if t.is_block_device() { + return FileType::BlockDevice; + } + if t.is_socket() { + return FileType::Socket; + } + } + + FileType::Unknown + } +} + +/// An error encountered while walking a file system. +#[derive(Clone, Debug)] +pub struct WalkError { + /// The path that caused the error, if known. + path: Option, + /// The depth below the root path, if known. + depth: Option, + /// The io::Error::raw_os_error(), if known. + raw: Option, +} + +impl WalkError { + /// Get the path this error occurred on, if known. + pub fn path(&self) -> Option<&Path> { + self.path.as_deref() + } + + /// Get the traversal depth when this error occurred, if known. + pub fn depth(&self) -> Option { + self.depth + } + + /// Get the kind of I/O error. + pub fn kind(&self) -> ErrorKind { + io::Error::from(self).kind() + } + + /// Check for ErrorKind::{NotFound,NotADirectory}. + pub fn is_not_found(&self) -> bool { + if self.kind() == ErrorKind::NotFound { + return true; + } + + // NotADirectory is nightly-only + #[cfg(unix)] + { + if self.raw == Some(uucore::libc::ENOTDIR) { + return true; + } + } + + false + } + + /// Check for ErrorKind::FilesystemLoop. + pub fn is_loop(&self) -> bool { + #[cfg(unix)] + return self.raw == Some(uucore::libc::ELOOP); + + #[cfg(not(unix))] + return false; + } +} + +impl Display for WalkError { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> { + let ioe = io::Error::from(self); + if let Some(path) = &self.path { + write!(f, "{}: {}", path.display(), ioe) + } else { + write!(f, "{}", ioe) + } + } +} + +impl Error for WalkError {} + +impl From for WalkError { + fn from(e: io::Error) -> WalkError { + WalkError::from(&e) + } +} + +impl From<&io::Error> for WalkError { + fn from(e: &io::Error) -> WalkError { + WalkError { + path: None, + depth: None, + raw: e.raw_os_error(), + } + } +} + +impl From for WalkError { + fn from(e: walkdir::Error) -> WalkError { + WalkError::from(&e) + } +} + +impl From<&walkdir::Error> for WalkError { + fn from(e: &walkdir::Error) -> WalkError { + WalkError { + path: e.path().map(|p| p.to_owned()), + depth: Some(e.depth()), + raw: e.io_error().and_then(|e| e.raw_os_error()), + } + } +} + +impl From for io::Error { + fn from(e: WalkError) -> io::Error { + io::Error::from(&e) + } +} + +impl From<&WalkError> for io::Error { + fn from(e: &WalkError) -> io::Error { + e.raw + .map(io::Error::from_raw_os_error) + .unwrap_or_else(|| ErrorKind::Other.into()) + } +} + +/// A path encountered while walking a file system. +#[derive(Debug)] +pub struct WalkEntry { + /// The wrapped path/dirent. + inner: Entry, + /// Whether to follow symlinks. + follow: bool, + /// Cached metadata. + meta: OnceCell>, +} + +impl WalkEntry { + /// Create a new WalkEntry for a specific file. + pub fn new(path: impl Into, depth: usize, follow: bool) -> Self { + Self { + inner: Entry::Explicit(path.into(), depth), + follow, + meta: OnceCell::new(), + } + } + + /// Get the path to this entry. + pub fn path(&self) -> &Path { + match &self.inner { + Entry::Explicit(path, _) => path.as_path(), + Entry::WalkDir(ent) => ent.path(), + } + } + + /// Get the path to this entry. + pub fn into_path(self) -> PathBuf { + match self.inner { + Entry::Explicit(path, _) => path, + Entry::WalkDir(ent) => ent.into_path(), + } + } + + /// Get the name of this entry. + pub fn file_name(&self) -> &OsStr { + match &self.inner { + Entry::Explicit(path, _) => { + // Path::file_name() only works if the last component is normal + path.components() + .last() + .map(|c| c.as_os_str()) + .unwrap_or_else(|| path.as_os_str()) + } + Entry::WalkDir(ent) => ent.file_name(), + } + } + + /// Get the depth of this entry below the root. + pub fn depth(&self) -> usize { + match &self.inner { + Entry::Explicit(_, depth) => *depth, + Entry::WalkDir(ent) => ent.depth(), + } + } + + /// Get the metadata on a cache miss. + fn get_metadata(&self) -> io::Result { + if self.follow { + match self.path().metadata() { + Ok(meta) => return Ok(meta), + Err(e) if !WalkError::from(&e).is_not_found() => return Err(e), + _ => (), + } + } + + self.path().symlink_metadata() + } + + /// Get the [Metadata] for this entry, following symbolic links if appropriate. + /// Multiple calls to this function will cache and re-use the same [Metadata]. + pub fn metadata(&self) -> Result<&Metadata, WalkError> { + let result = self.meta.get_or_init(|| match &self.inner { + Entry::Explicit(_, _) => Ok(self.get_metadata()?), + Entry::WalkDir(ent) => Ok(ent.metadata()?), + }); + result.as_ref().map_err(|e| e.clone()) + } + + /// Get the file type of this entry. + pub fn file_type(&self) -> FileType { + match &self.inner { + Entry::Explicit(_, _) => self + .metadata() + .map(|m| m.file_type().into()) + .unwrap_or(FileType::Unknown), + Entry::WalkDir(ent) => ent.file_type().into(), + } + } + + /// Check whether this entry is a symbolic link, regardless of whether links + /// are being followed. + pub fn path_is_symlink(&self) -> bool { + match &self.inner { + Entry::Explicit(path, _) => { + if self.follow { + path.symlink_metadata() + .is_ok_and(|m| m.file_type().is_symlink()) + } else { + self.file_type().is_symlink() + } + } + Entry::WalkDir(ent) => ent.path_is_symlink(), + } + } +} + +impl From for WalkEntry { + fn from(entry: DirEntry) -> WalkEntry { + let follow = entry.path_is_symlink() && !entry.file_type().is_symlink(); + WalkEntry { + inner: Entry::WalkDir(entry), + follow, + meta: OnceCell::new(), + } + } +} + +impl TryFrom> for WalkEntry { + type Error = WalkError; + + fn try_from(result: walkdir::Result) -> Result { + match result { + Ok(entry) => Ok(entry.into()), + Err(e) => { + // Detect broken symlinks and replace them with explicit entries + if let (Some(path), Some(ioe)) = (e.path(), e.io_error()) { + if WalkError::from(ioe).is_not_found() { + if let Ok(meta) = path.symlink_metadata() { + return Ok(WalkEntry { + inner: Entry::Explicit(path.into(), e.depth()), + follow: false, + meta: Ok(meta).into(), + }); + } + } + } + + Err(e.into()) + } + } + } +} diff --git a/src/find/matchers/exec.rs b/src/find/matchers/exec.rs index 487cd332..23055e06 100644 --- a/src/find/matchers/exec.rs +++ b/src/find/matchers/exec.rs @@ -9,9 +9,8 @@ use std::ffi::OsString; use std::io::{stderr, Write}; use std::path::Path; use std::process::Command; -use walkdir::DirEntry; -use super::{Matcher, MatcherIO}; +use super::{Matcher, MatcherIO, WalkEntry}; enum Arg { FileArg(Vec), @@ -52,7 +51,7 @@ impl SingleExecMatcher { } impl Matcher for SingleExecMatcher { - fn matches(&self, file_info: &DirEntry, _: &mut MatcherIO) -> bool { + fn matches(&self, file_info: &WalkEntry, _: &mut MatcherIO) -> bool { let mut command = Command::new(&self.executable); let path_to_file = if self.exec_in_parent_dir { if let Some(f) = file_info.path().file_name() { diff --git a/src/find/matchers/fs.rs b/src/find/matchers/fs.rs index 40090f77..6229efef 100644 --- a/src/find/matchers/fs.rs +++ b/src/find/matchers/fs.rs @@ -10,7 +10,7 @@ use std::{ io::{stderr, Write}, }; -use super::Matcher; +use super::{Matcher, MatcherIO, WalkEntry}; /// The latest mapping from dev_id to fs_type, used for saving mount info reads pub struct Cache { @@ -88,7 +88,7 @@ impl FileSystemMatcher { } impl Matcher for FileSystemMatcher { - fn matches(&self, file_info: &walkdir::DirEntry, _: &mut super::MatcherIO) -> bool { + fn matches(&self, file_info: &WalkEntry, _: &mut MatcherIO) -> bool { #[cfg(not(unix))] { false diff --git a/src/find/matchers/group.rs b/src/find/matchers/group.rs index bceb80b9..d97525cc 100644 --- a/src/find/matchers/group.rs +++ b/src/find/matchers/group.rs @@ -3,7 +3,7 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -use super::Matcher; +use super::{Matcher, MatcherIO, WalkEntry}; #[cfg(unix)] use nix::unistd::Group; @@ -57,8 +57,8 @@ impl GroupMatcher { impl Matcher for GroupMatcher { #[cfg(unix)] - fn matches(&self, file_info: &walkdir::DirEntry, _: &mut super::MatcherIO) -> bool { - let Ok(metadata) = file_info.path().metadata() else { + fn matches(&self, file_info: &WalkEntry, _: &mut MatcherIO) -> bool { + let Ok(metadata) = file_info.metadata() else { return false; }; @@ -71,7 +71,7 @@ impl Matcher for GroupMatcher { } #[cfg(windows)] - fn matches(&self, _file_info: &walkdir::DirEntry, _: &mut super::MatcherIO) -> bool { + fn matches(&self, _file_info: &WalkEntry, _: &mut MatcherIO) -> bool { // The user group acquisition function for Windows systems is not implemented in MetadataExt, // so it is somewhat difficult to implement it. :( false @@ -82,14 +82,14 @@ pub struct NoGroupMatcher {} impl Matcher for NoGroupMatcher { #[cfg(unix)] - fn matches(&self, file_info: &walkdir::DirEntry, _: &mut super::MatcherIO) -> bool { + fn matches(&self, file_info: &WalkEntry, _: &mut MatcherIO) -> bool { use nix::unistd::Gid; if file_info.path().is_symlink() { return false; } - let Ok(metadata) = file_info.path().metadata() else { + let Ok(metadata) = file_info.metadata() else { return true; }; @@ -105,7 +105,7 @@ impl Matcher for NoGroupMatcher { } #[cfg(windows)] - fn matches(&self, _file_info: &walkdir::DirEntry, _: &mut super::MatcherIO) -> bool { + fn matches(&self, _file_info: &WalkEntry, _: &mut MatcherIO) -> bool { false } } @@ -130,7 +130,7 @@ mod tests { let foo_path = temp_dir.path().join("foo"); let _ = File::create(foo_path).expect("create temp file"); let file_info = get_dir_entry_for(&temp_dir.path().to_string_lossy(), "foo"); - let file_gid = file_info.path().metadata().unwrap().gid(); + let file_gid = file_info.metadata().unwrap().gid(); let file_group = Group::from_gid(Gid::from_raw(file_gid)) .unwrap() .unwrap() diff --git a/src/find/matchers/lname.rs b/src/find/matchers/lname.rs index 95192392..dcbcd500 100644 --- a/src/find/matchers/lname.rs +++ b/src/find/matchers/lname.rs @@ -7,12 +7,10 @@ use std::io::{stderr, Write}; use std::path::PathBuf; -use walkdir::DirEntry; - use super::glob::Pattern; -use super::{Matcher, MatcherIO}; +use super::{Matcher, MatcherIO, WalkEntry}; -fn read_link_target(file_info: &DirEntry) -> Option { +fn read_link_target(file_info: &WalkEntry) -> Option { match file_info.path().read_link() { Ok(target) => Some(target), Err(err) => { @@ -47,7 +45,7 @@ impl LinkNameMatcher { } impl Matcher for LinkNameMatcher { - fn matches(&self, file_info: &DirEntry, _: &mut MatcherIO) -> bool { + fn matches(&self, file_info: &WalkEntry, _: &mut MatcherIO) -> bool { if let Some(target) = read_link_target(file_info) { self.pattern.matches(&target.to_string_lossy()) } else { diff --git a/src/find/matchers/logical_matchers.rs b/src/find/matchers/logical_matchers.rs index 349b4876..8ec70f6f 100644 --- a/src/find/matchers/logical_matchers.rs +++ b/src/find/matchers/logical_matchers.rs @@ -11,9 +11,8 @@ //! to "-foo -o ( -bar -baz )", not "( -foo -o -bar ) -baz"). use std::error::Error; use std::path::Path; -use walkdir::DirEntry; -use super::{Matcher, MatcherIO}; +use super::{Matcher, MatcherIO, WalkEntry}; /// This matcher contains a collection of other matchers. A file only matches /// if it matches ALL the contained sub-matchers. For sub-matchers that have @@ -33,7 +32,7 @@ impl Matcher for AndMatcher { /// Returns true if all sub-matchers return true. Short-circuiting does take /// place. If the nth sub-matcher returns false, then we immediately return /// and don't make any further calls. - fn matches(&self, dir_entry: &DirEntry, matcher_io: &mut MatcherIO) -> bool { + fn matches(&self, dir_entry: &WalkEntry, matcher_io: &mut MatcherIO) -> bool { for matcher in &self.submatchers { if !matcher.matches(dir_entry, matcher_io) { return false; @@ -109,7 +108,7 @@ impl Matcher for OrMatcher { /// Returns true if any sub-matcher returns true. Short-circuiting does take /// place. If the nth sub-matcher returns true, then we immediately return /// and don't make any further calls. - fn matches(&self, dir_entry: &DirEntry, matcher_io: &mut MatcherIO) -> bool { + fn matches(&self, dir_entry: &WalkEntry, matcher_io: &mut MatcherIO) -> bool { for matcher in &self.submatchers { if matcher.matches(dir_entry, matcher_io) { return true; @@ -206,7 +205,7 @@ impl ListMatcher { impl Matcher for ListMatcher { /// Calls matches on all submatcher objects, with no short-circuiting. /// Returns the result of the call to the final submatcher - fn matches(&self, dir_entry: &DirEntry, matcher_io: &mut MatcherIO) -> bool { + fn matches(&self, dir_entry: &WalkEntry, matcher_io: &mut MatcherIO) -> bool { let mut rc = false; for matcher in &self.submatchers { rc = matcher.matches(dir_entry, matcher_io); @@ -311,7 +310,7 @@ impl ListMatcherBuilder { pub struct TrueMatcher; impl Matcher for TrueMatcher { - fn matches(&self, _dir_entry: &DirEntry, _: &mut MatcherIO) -> bool { + fn matches(&self, _dir_entry: &WalkEntry, _: &mut MatcherIO) -> bool { true } } @@ -320,7 +319,7 @@ impl Matcher for TrueMatcher { pub struct FalseMatcher; impl Matcher for FalseMatcher { - fn matches(&self, _dir_entry: &DirEntry, _: &mut MatcherIO) -> bool { + fn matches(&self, _dir_entry: &WalkEntry, _: &mut MatcherIO) -> bool { false } } @@ -339,7 +338,7 @@ impl NotMatcher { } impl Matcher for NotMatcher { - fn matches(&self, dir_entry: &DirEntry, matcher_io: &mut MatcherIO) -> bool { + fn matches(&self, dir_entry: &WalkEntry, matcher_io: &mut MatcherIO) -> bool { !self.submatcher.matches(dir_entry, matcher_io) } @@ -370,7 +369,7 @@ mod tests { pub struct HasSideEffects; impl Matcher for HasSideEffects { - fn matches(&self, _: &DirEntry, _: &mut MatcherIO) -> bool { + fn matches(&self, _: &WalkEntry, _: &mut MatcherIO) -> bool { false } @@ -383,7 +382,7 @@ mod tests { struct Counter(Rc>); impl Matcher for Counter { - fn matches(&self, _: &DirEntry, _: &mut MatcherIO) -> bool { + fn matches(&self, _: &WalkEntry, _: &mut MatcherIO) -> bool { *self.0.borrow_mut() += 1; true } diff --git a/src/find/matchers/mod.rs b/src/find/matchers/mod.rs index bcd1a3f3..ca78cecc 100644 --- a/src/find/matchers/mod.rs +++ b/src/find/matchers/mod.rs @@ -7,6 +7,7 @@ mod access; mod delete; mod empty; +mod entry; pub mod exec; pub mod fs; mod glob; @@ -36,7 +37,6 @@ use std::fs::File; use std::path::Path; use std::time::SystemTime; use std::{error::Error, str::FromStr}; -use walkdir::DirEntry; use self::access::AccessMatcher; use self::delete::DeleteMatcher; @@ -68,6 +68,8 @@ use self::user::{NoUserMatcher, UserMatcher}; use super::{Config, Dependencies}; +pub use entry::{FileType, WalkEntry, WalkError}; + /// Struct holding references to outputs and any inputs that can't be derived /// from the file/directory info. pub struct MatcherIO<'a> { @@ -134,7 +136,7 @@ pub trait Matcher: 'static { } /// Returns whether the given file matches the object's predicate. - fn matches(&self, file_info: &DirEntry, matcher_io: &mut MatcherIO) -> bool; + fn matches(&self, entry: &WalkEntry, matcher_io: &mut MatcherIO) -> bool; /// Returns whether the matcher has any side-effects (e.g. executing a /// command, deleting a file). Iff no such matcher exists in the chain, then @@ -160,8 +162,8 @@ impl Matcher for Box { self } - fn matches(&self, file_info: &DirEntry, matcher_io: &mut MatcherIO) -> bool { - (**self).matches(file_info, matcher_io) + fn matches(&self, entry: &WalkEntry, matcher_io: &mut MatcherIO) -> bool { + (**self).matches(entry, matcher_io) } fn has_side_effects(&self) -> bool { @@ -851,25 +853,23 @@ mod tests { use super::*; use crate::find::tests::fix_up_slashes; use crate::find::tests::FakeDependencies; - use walkdir::WalkDir; - /// Helper function for tests to get a `DirEntry` object. directory should + /// Helper function for tests to get a [WalkEntry] object. root should /// probably be a string starting with `test_data/` (cargo's tests run with /// a working directory set to the root findutils folder). - pub fn get_dir_entry_for(directory: &str, filename: &str) -> DirEntry { - for wrapped_dir_entry in WalkDir::new(fix_up_slashes(directory)) { - let dir_entry = wrapped_dir_entry.unwrap(); - if dir_entry - .path() - .strip_prefix(directory) - .unwrap() - .to_string_lossy() - == fix_up_slashes(filename) - { - return dir_entry; - } - } - panic!("Couldn't find {filename} in {directory}"); + pub fn get_dir_entry_for(root: &str, path: &str) -> WalkEntry { + let root = fix_up_slashes(root); + let root = Path::new(&root); + + let path = fix_up_slashes(path); + let path = if path.is_empty() { + root.to_owned() + } else { + root.join(path) + }; + + let depth = path.components().count() - root.components().count(); + WalkEntry::new(path, depth, false) } #[test] diff --git a/src/find/matchers/name.rs b/src/find/matchers/name.rs index a455937a..80b2be13 100644 --- a/src/find/matchers/name.rs +++ b/src/find/matchers/name.rs @@ -4,10 +4,8 @@ // license that can be found in the LICENSE file or at // https://opensource.org/licenses/MIT. -use walkdir::DirEntry; - use super::glob::Pattern; -use super::{Matcher, MatcherIO}; +use super::{Matcher, MatcherIO, WalkEntry}; /// This matcher makes a comparison of the name against a shell wildcard /// pattern. See `glob::Pattern` for details on the exact syntax. @@ -23,7 +21,7 @@ impl NameMatcher { } impl Matcher for NameMatcher { - fn matches(&self, file_info: &DirEntry, _: &mut MatcherIO) -> bool { + fn matches(&self, file_info: &WalkEntry, _: &mut MatcherIO) -> bool { let name = file_info.file_name().to_string_lossy(); self.pattern.matches(&name) } diff --git a/src/find/matchers/path.rs b/src/find/matchers/path.rs index df31b263..c9db3498 100644 --- a/src/find/matchers/path.rs +++ b/src/find/matchers/path.rs @@ -4,10 +4,8 @@ // license that can be found in the LICENSE file or at // https://opensource.org/licenses/MIT. -use walkdir::DirEntry; - use super::glob::Pattern; -use super::{Matcher, MatcherIO}; +use super::{Matcher, MatcherIO, WalkEntry}; /// This matcher makes a comparison of the path against a shell wildcard /// pattern. See `glob::Pattern` for details on the exact syntax. @@ -23,7 +21,7 @@ impl PathMatcher { } impl Matcher for PathMatcher { - fn matches(&self, file_info: &DirEntry, _: &mut MatcherIO) -> bool { + fn matches(&self, file_info: &WalkEntry, _: &mut MatcherIO) -> bool { let path = file_info.path().to_string_lossy(); self.pattern.matches(&path) } diff --git a/src/find/matchers/perm.rs b/src/find/matchers/perm.rs index 2ece4530..075d34b4 100644 --- a/src/find/matchers/perm.rs +++ b/src/find/matchers/perm.rs @@ -12,9 +12,8 @@ use std::error::Error; use std::io::{stderr, Write}; #[cfg(unix)] use uucore::mode::{parse_numeric, parse_symbolic}; -use walkdir::DirEntry; -use super::{Matcher, MatcherIO}; +use super::{Matcher, MatcherIO, WalkEntry}; #[derive(Clone, Copy, Debug, Eq, PartialEq)] #[cfg(unix)] @@ -101,7 +100,7 @@ impl PermMatcher { impl Matcher for PermMatcher { #[cfg(unix)] - fn matches(&self, file_info: &DirEntry, _: &mut MatcherIO) -> bool { + fn matches(&self, file_info: &WalkEntry, _: &mut MatcherIO) -> bool { use std::os::unix::fs::PermissionsExt; match file_info.metadata() { Ok(metadata) => { @@ -127,7 +126,7 @@ impl Matcher for PermMatcher { } #[cfg(not(unix))] - fn matches(&self, _dummy_file_info: &DirEntry, _: &mut MatcherIO) -> bool { + fn matches(&self, _dummy_file_info: &WalkEntry, _: &mut MatcherIO) -> bool { writeln!( &mut stderr(), "Permission matching not available on this platform!" diff --git a/src/find/matchers/printer.rs b/src/find/matchers/printer.rs index 6e01a84f..52646e4b 100644 --- a/src/find/matchers/printer.rs +++ b/src/find/matchers/printer.rs @@ -4,14 +4,10 @@ // license that can be found in the LICENSE file or at // https://opensource.org/licenses/MIT. -use std::{ - fs::File, - io::{stderr, Write}, -}; +use std::fs::File; +use std::io::{stderr, Write}; -use walkdir::DirEntry; - -use super::{Matcher, MatcherIO}; +use super::{Matcher, MatcherIO, WalkEntry}; pub enum PrintDelimiter { Newline, @@ -41,7 +37,7 @@ impl Printer { } } - fn print(&self, file_info: &DirEntry, mut out: impl Write, print_error_message: bool) { + fn print(&self, file_info: &WalkEntry, mut out: impl Write, print_error_message: bool) { match write!( out, "{}{}", @@ -67,7 +63,7 @@ impl Printer { } impl Matcher for Printer { - fn matches(&self, file_info: &DirEntry, matcher_io: &mut MatcherIO) -> bool { + fn matches(&self, file_info: &WalkEntry, matcher_io: &mut MatcherIO) -> bool { if let Some(file) = &self.output_file { self.print(file_info, file, true); } else { @@ -86,7 +82,6 @@ impl Matcher for Printer { } #[cfg(test)] - mod tests { use super::*; use crate::find::matchers::tests::get_dir_entry_for; diff --git a/src/find/matchers/printf.rs b/src/find/matchers/printf.rs index 6929ccc7..831a3c58 100644 --- a/src/find/matchers/printf.rs +++ b/src/find/matchers/printf.rs @@ -4,15 +4,18 @@ // license that can be found in the LICENSE file or at // https://opensource.org/licenses/MIT. -use std::{borrow::Cow, error::Error, fs, path::Path, time::SystemTime}; +use std::borrow::Cow; +use std::error::Error; +use std::fs; +use std::path::Path; +use std::time::SystemTime; use chrono::{format::StrftimeItems, DateTime, Local}; -use once_cell::unsync::OnceCell; -use super::{Matcher, MatcherIO}; +use super::{FileType, Matcher, MatcherIO, WalkEntry, WalkError}; #[cfg(unix)] -use std::os::unix::prelude::{FileTypeExt, MetadataExt}; +use std::os::unix::prelude::MetadataExt; const STANDARD_BLOCK_SIZE: u64 = 512; @@ -340,7 +343,7 @@ impl FormatString { } } -fn get_starting_point(file_info: &walkdir::DirEntry) -> &Path { +fn get_starting_point(file_info: &WalkEntry) -> &Path { file_info .path() .ancestors() @@ -350,47 +353,23 @@ fn get_starting_point(file_info: &walkdir::DirEntry) -> &Path { .unwrap() } -fn format_non_link_file_type(file_type: fs::FileType) -> char { - if file_type.is_file() { - 'f' - } else if file_type.is_dir() { - 'd' - } else { - #[cfg(unix)] - if file_type.is_block_device() { - 'b' - } else if file_type.is_char_device() { - 'c' - } else if file_type.is_fifo() { - 'p' - } else if file_type.is_socket() { - 's' - } else { - 'U' - } - #[cfg(not(unix))] - 'U' +fn format_non_link_file_type(file_type: FileType) -> char { + match file_type { + FileType::Regular => 'f', + FileType::Directory => 'd', + FileType::BlockDevice => 'b', + FileType::CharDevice => 'c', + FileType::Fifo => 'p', + FileType::Socket => 's', + _ => 'U', } } fn format_directive<'entry>( - file_info: &'entry walkdir::DirEntry, + file_info: &'entry WalkEntry, directive: &FormatDirective, - meta_cell: &OnceCell, ) -> Result, Box> { - let meta = || { - meta_cell.get_or_try_init(|| { - if file_info.path_is_symlink() && !file_info.file_type().is_symlink() { - // The file_info already followed the symlink, meaning that the - // metadata will be for the target file, which isn't the - // behavior we want, so manually re-compute the metadata for the - // symlink itself instead. - file_info.path().symlink_metadata() - } else { - file_info.metadata().map_err(std::convert::Into::into) - } - }) - }; + let meta = || file_info.metadata(); // NOTE ON QUOTING: // GNU find's man page claims that several directives that print names (like @@ -556,17 +535,10 @@ fn format_directive<'entry>( FormatDirective::Type { follow_links } => if file_info.path_is_symlink() { if *follow_links { - match file_info.path().metadata() { - Ok(meta) => format_non_link_file_type(meta.file_type()), - Err(e) if e.kind() == std::io::ErrorKind::NotFound => 'N', - // The ErrorKinds corresponding to ELOOP and ENOTDIR are - // nightly-only: - // https://doc.rust-lang.org/std/io/enum.ErrorKind.html#variant.FilesystemLoop - // so we need to use the raw errno values instead. - #[cfg(unix)] - Err(e) if e.raw_os_error().unwrap_or(0) == uucore::libc::ENOTDIR => 'N', - #[cfg(unix)] - Err(e) if e.raw_os_error().unwrap_or(0) == uucore::libc::ELOOP => 'L', + match file_info.path().metadata().map_err(WalkError::from) { + Ok(meta) => format_non_link_file_type(meta.file_type().into()), + Err(e) if e.is_not_found() => 'N', + Err(e) if e.is_loop() => 'L', Err(_) => '?', } } else { @@ -610,11 +582,8 @@ impl Printf { } impl Matcher for Printf { - fn matches(&self, file_info: &walkdir::DirEntry, matcher_io: &mut MatcherIO) -> bool { + fn matches(&self, file_info: &WalkEntry, matcher_io: &mut MatcherIO) -> bool { let mut out = matcher_io.deps.get_output().borrow_mut(); - // The metadata is computed lazily, so that anything being printed - // without needing metadata won't incur any performance overhead. - let meta_cell = OnceCell::new(); for component in &self.format.components { match component { @@ -624,7 +593,7 @@ impl Matcher for Printf { directive, width, justify, - } => match format_directive(file_info, directive, &meta_cell) { + } => match format_directive(file_info, directive) { Ok(content) => { if let Some(width) = width { match justify { @@ -1110,13 +1079,13 @@ mod tests { let new_file_name = "newFile"; let file = File::create(temp_dir.path().join(new_file_name)).expect("create temp file"); - let file_info = get_dir_entry_for(&temp_dir_path, new_file_name); - let deps = FakeDependencies::new(); - - let mut perms = file_info.metadata().unwrap().permissions(); + let mut perms = file.metadata().unwrap().permissions(); perms.set_mode(0o755); file.set_permissions(perms).unwrap(); + let file_info = get_dir_entry_for(&temp_dir_path, new_file_name); + let deps = FakeDependencies::new(); + let matcher = Printf::new("%m %M").unwrap(); assert!(matcher.matches(&file_info, &mut deps.new_matcher_io())); assert_eq!("755 -rwxr-xr-x", deps.get_output_as_string()); diff --git a/src/find/matchers/prune.rs b/src/find/matchers/prune.rs index 623b417d..36e2cffa 100644 --- a/src/find/matchers/prune.rs +++ b/src/find/matchers/prune.rs @@ -4,9 +4,7 @@ // license that can be found in the LICENSE file or at // https://opensource.org/licenses/MIT. -use walkdir::DirEntry; - -use super::{Matcher, MatcherIO}; +use super::{Matcher, MatcherIO, WalkEntry}; /// This matcher checks the type of the file. pub struct PruneMatcher; @@ -18,7 +16,7 @@ impl PruneMatcher { } impl Matcher for PruneMatcher { - fn matches(&self, file_info: &DirEntry, matcher_io: &mut MatcherIO) -> bool { + fn matches(&self, file_info: &WalkEntry, matcher_io: &mut MatcherIO) -> bool { if file_info.file_type().is_dir() { matcher_io.mark_current_dir_to_be_skipped(); } diff --git a/src/find/matchers/quit.rs b/src/find/matchers/quit.rs index 36e1acfa..57e541ed 100644 --- a/src/find/matchers/quit.rs +++ b/src/find/matchers/quit.rs @@ -4,15 +4,13 @@ // license that can be found in the LICENSE file or at // https://opensource.org/licenses/MIT. -use walkdir::DirEntry; - -use super::{Matcher, MatcherIO}; +use super::{Matcher, MatcherIO, WalkEntry}; /// This matcher quits the search immediately. pub struct QuitMatcher; impl Matcher for QuitMatcher { - fn matches(&self, _: &DirEntry, matcher_io: &mut MatcherIO) -> bool { + fn matches(&self, _: &WalkEntry, matcher_io: &mut MatcherIO) -> bool { matcher_io.quit(); true } diff --git a/src/find/matchers/regex.rs b/src/find/matchers/regex.rs index 99590877..7c01fc79 100644 --- a/src/find/matchers/regex.rs +++ b/src/find/matchers/regex.rs @@ -8,7 +8,7 @@ use std::{error::Error, fmt, str::FromStr}; use onig::{Regex, RegexOptions, Syntax}; -use super::Matcher; +use super::{Matcher, MatcherIO, WalkEntry}; #[derive(Debug)] pub struct ParseRegexTypeError(String); @@ -111,7 +111,7 @@ impl RegexMatcher { } impl Matcher for RegexMatcher { - fn matches(&self, file_info: &walkdir::DirEntry, _: &mut super::MatcherIO) -> bool { + fn matches(&self, file_info: &WalkEntry, _: &mut MatcherIO) -> bool { self.regex .is_match(file_info.path().to_string_lossy().as_ref()) } diff --git a/src/find/matchers/samefile.rs b/src/find/matchers/samefile.rs index 7d98d784..eb197bda 100644 --- a/src/find/matchers/samefile.rs +++ b/src/find/matchers/samefile.rs @@ -3,7 +3,7 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -use super::Matcher; +use super::{Matcher, MatcherIO, WalkEntry}; use std::error::Error; use std::path::Path; use uucore::fs::FileInformation; @@ -20,7 +20,7 @@ impl SameFileMatcher { } impl Matcher for SameFileMatcher { - fn matches(&self, file_info: &walkdir::DirEntry, _matcher_io: &mut super::MatcherIO) -> bool { + fn matches(&self, file_info: &WalkEntry, _matcher_io: &mut MatcherIO) -> bool { if let Ok(info) = FileInformation::from_path(file_info.path(), false) { info == self.info } else { diff --git a/src/find/matchers/size.rs b/src/find/matchers/size.rs index 5c6a9e1b..5d2d0615 100644 --- a/src/find/matchers/size.rs +++ b/src/find/matchers/size.rs @@ -7,9 +7,8 @@ use std::error::Error; use std::io::{stderr, Write}; use std::str::FromStr; -use walkdir::DirEntry; -use super::{ComparableValue, Matcher, MatcherIO}; +use super::{ComparableValue, Matcher, MatcherIO, WalkEntry}; #[derive(Clone, Copy, Debug)] enum Unit { @@ -83,7 +82,7 @@ impl SizeMatcher { } impl Matcher for SizeMatcher { - fn matches(&self, file_info: &DirEntry, _: &mut MatcherIO) -> bool { + fn matches(&self, file_info: &WalkEntry, _: &mut MatcherIO) -> bool { match file_info.metadata() { Ok(metadata) => self .value_to_match diff --git a/src/find/matchers/stat.rs b/src/find/matchers/stat.rs index e48738c6..a4ddea34 100644 --- a/src/find/matchers/stat.rs +++ b/src/find/matchers/stat.rs @@ -6,9 +6,7 @@ use std::os::unix::fs::MetadataExt; -use walkdir::DirEntry; - -use super::{ComparableValue, Matcher, MatcherIO}; +use super::{ComparableValue, Matcher, MatcherIO, WalkEntry}; /// Inode number matcher. pub struct InodeMatcher { @@ -22,7 +20,7 @@ impl InodeMatcher { } impl Matcher for InodeMatcher { - fn matches(&self, file_info: &DirEntry, _: &mut MatcherIO) -> bool { + fn matches(&self, file_info: &WalkEntry, _: &mut MatcherIO) -> bool { match file_info.metadata() { Ok(metadata) => self.ino.matches(metadata.ino()), Err(_) => false, @@ -42,7 +40,7 @@ impl LinksMatcher { } impl Matcher for LinksMatcher { - fn matches(&self, file_info: &DirEntry, _: &mut MatcherIO) -> bool { + fn matches(&self, file_info: &WalkEntry, _: &mut MatcherIO) -> bool { match file_info.metadata() { Ok(metadata) => self.nlink.matches(metadata.nlink()), Err(_) => false, diff --git a/src/find/matchers/time.rs b/src/find/matchers/time.rs index 7c19e820..199df0bd 100644 --- a/src/find/matchers/time.rs +++ b/src/find/matchers/time.rs @@ -8,12 +8,11 @@ use std::error::Error; use std::fs::{self, Metadata}; use std::io::{stderr, Write}; use std::time::{Duration, SystemTime, UNIX_EPOCH}; -use walkdir::DirEntry; #[cfg(unix)] use std::os::unix::fs::MetadataExt; -use super::{ComparableValue, Matcher, MatcherIO}; +use super::{ComparableValue, Matcher, MatcherIO, WalkEntry}; const SECONDS_PER_DAY: i64 = 60 * 60 * 24; @@ -44,7 +43,7 @@ impl NewerMatcher { /// Implementation of matches that returns a result, allowing use to use try! /// to deal with the errors. - fn matches_impl(&self, file_info: &DirEntry) -> Result> { + fn matches_impl(&self, file_info: &WalkEntry) -> Result> { let this_time = file_info.metadata()?.modified()?; // duration_since returns an Ok duration if this_time <= given_modification_time // and returns an Err (with a duration) otherwise. So if this_time > @@ -58,7 +57,7 @@ impl NewerMatcher { } impl Matcher for NewerMatcher { - fn matches(&self, file_info: &DirEntry, _: &mut MatcherIO) -> bool { + fn matches(&self, file_info: &WalkEntry, _: &mut MatcherIO) -> bool { match self.matches_impl(file_info) { Err(e) => { writeln!( @@ -100,7 +99,7 @@ impl NewerOptionType { } } - fn get_file_time(self, metadata: Metadata) -> std::io::Result { + fn get_file_time(self, metadata: &Metadata) -> std::io::Result { match self { NewerOptionType::Accessed => metadata.accessed(), NewerOptionType::Birthed => metadata.created(), @@ -134,7 +133,7 @@ impl NewerOptionMatcher { }) } - fn matches_impl(&self, file_info: &DirEntry) -> Result> { + fn matches_impl(&self, file_info: &WalkEntry) -> Result> { let x_option_time = self.x_option.get_file_time(file_info.metadata()?)?; let y_option_time = self.y_option.get_file_time(file_info.metadata()?)?; @@ -150,7 +149,7 @@ impl NewerOptionMatcher { } impl Matcher for NewerOptionMatcher { - fn matches(&self, file_info: &DirEntry, _: &mut MatcherIO) -> bool { + fn matches(&self, file_info: &WalkEntry, _: &mut MatcherIO) -> bool { match self.matches_impl(file_info) { Err(e) => { writeln!( @@ -184,7 +183,7 @@ impl NewerTimeMatcher { } } - fn matches_impl(&self, file_info: &DirEntry) -> Result> { + fn matches_impl(&self, file_info: &WalkEntry) -> Result> { let this_time = self.newer_time_type.get_file_time(file_info.metadata()?)?; let timestamp = this_time .duration_since(UNIX_EPOCH) @@ -201,7 +200,7 @@ impl NewerTimeMatcher { } impl Matcher for NewerTimeMatcher { - fn matches(&self, file_info: &DirEntry, _: &mut MatcherIO) -> bool { + fn matches(&self, file_info: &WalkEntry, _: &mut MatcherIO) -> bool { match self.matches_impl(file_info) { Err(e) => { writeln!( @@ -256,7 +255,7 @@ pub enum FileTimeType { } impl FileTimeType { - fn get_file_time(self, metadata: Metadata) -> std::io::Result { + fn get_file_time(self, metadata: &Metadata) -> std::io::Result { match self { FileTimeType::Accessed => metadata.accessed(), FileTimeType::Changed => metadata.changed(), @@ -274,7 +273,7 @@ pub struct FileTimeMatcher { } impl Matcher for FileTimeMatcher { - fn matches(&self, file_info: &DirEntry, matcher_io: &mut MatcherIO) -> bool { + fn matches(&self, file_info: &WalkEntry, matcher_io: &mut MatcherIO) -> bool { let start_time = get_time(matcher_io, self.today_start); match self.matches_impl(file_info, start_time) { Err(e) => { @@ -298,7 +297,7 @@ impl FileTimeMatcher { /// to deal with the errors. fn matches_impl( &self, - file_info: &DirEntry, + file_info: &WalkEntry, start_time: SystemTime, ) -> Result> { let this_time = self.file_time_type.get_file_time(file_info.metadata()?)?; @@ -347,7 +346,7 @@ pub struct FileAgeRangeMatcher { } impl Matcher for FileAgeRangeMatcher { - fn matches(&self, file_info: &DirEntry, matcher_io: &mut MatcherIO) -> bool { + fn matches(&self, file_info: &WalkEntry, matcher_io: &mut MatcherIO) -> bool { let start_time = get_time(matcher_io, self.today_start); match self.matches_impl(file_info, start_time) { Err(e) => { @@ -369,7 +368,7 @@ impl Matcher for FileAgeRangeMatcher { impl FileAgeRangeMatcher { fn matches_impl( &self, - file_info: &DirEntry, + file_info: &WalkEntry, start_time: SystemTime, ) -> Result> { let this_time = self.file_time_type.get_file_time(file_info.metadata()?)?; @@ -677,7 +676,7 @@ mod tests { /// helper function for `file_time_matcher_modified_changed_accessed` fn test_matcher_for_file_time_type( - file_info: &DirEntry, + file_info: &WalkEntry, file_time: SystemTime, file_time_type: FileTimeType, ) { @@ -733,30 +732,6 @@ mod tests { .matches(&new_file, &mut deps.new_matcher_io()), "new_file should be newer than old_dir" ); - - // After the file is deleted, DirEntry will point to an empty file location, - // thus causing the Matcher to generate an IO error after matching. - // - // Note: This test is nondeterministic on Windows, - // because fs::remove_file may not actually remove the file from - // the file system even if it returns Ok. - // Therefore, this test will only be performed on Linux/Unix. - let _ = fs::remove_file(&*new_file.path().to_string_lossy()); - - #[cfg(unix)] - { - let matcher = NewerOptionMatcher::new( - x_option.to_string(), - y_option.to_string(), - &old_file.path().to_string_lossy(), - ); - assert!( - !matcher - .unwrap() - .matches(&new_file, &mut deps.new_matcher_io()), - "The correct situation is that the file reading here cannot be successful." - ); - } } } } @@ -836,29 +811,6 @@ mod tests { inode_changed_matcher.matches(&file_info, &mut deps.new_matcher_io()), "file inode changed time should after 'std_time'" ); - - // After the file is deleted, DirEntry will point to an empty file location, - // thus causing the Matcher to generate an IO error after matching. - // - // Note: This test is nondeterministic on Windows, - // because fs::remove_file may not actually remove the file from - // the file system even if it returns Ok. - // Therefore, this test will only be performed on Linux/Unix. - let _ = fs::remove_file(&*file_info.path().to_string_lossy()); - - let matchers = [ - &created_matcher, - &accessed_matcher, - &modified_matcher, - &inode_changed_matcher, - ]; - - for matcher in &matchers { - assert!( - !matcher.matches(&file_info, &mut deps.new_matcher_io()), - "The correct situation is that the file reading here cannot be successful." - ); - } } } diff --git a/src/find/matchers/type_matcher.rs b/src/find/matchers/type_matcher.rs index b923215d..deb5b765 100644 --- a/src/find/matchers/type_matcher.rs +++ b/src/find/matchers/type_matcher.rs @@ -5,30 +5,24 @@ // https://opensource.org/licenses/MIT. use std::error::Error; -use std::fs::FileType; -use walkdir::DirEntry; -#[cfg(unix)] -use std::os::unix::fs::FileTypeExt; - -use super::{Matcher, MatcherIO}; +use super::{FileType, Matcher, MatcherIO, WalkEntry}; /// This matcher checks the type of the file. pub struct TypeMatcher { - file_type_fn: fn(&FileType) -> bool, + file_type: FileType, } impl TypeMatcher { pub fn new(type_string: &str) -> Result> { - #[cfg(unix)] - let function = match type_string { - "f" => FileType::is_file, - "d" => FileType::is_dir, - "l" => FileType::is_symlink, - "b" => FileType::is_block_device, - "c" => FileType::is_char_device, - "p" => FileType::is_fifo, // named pipe (FIFO) - "s" => FileType::is_socket, + let file_type = match type_string { + "f" => FileType::Regular, + "d" => FileType::Directory, + "l" => FileType::Symlink, + "b" => FileType::BlockDevice, + "c" => FileType::CharDevice, + "p" => FileType::Fifo, // named pipe (FIFO) + "s" => FileType::Socket, // D: door (Solaris) "D" => { return Err(From::from(format!( @@ -41,27 +35,13 @@ impl TypeMatcher { ))) } }; - #[cfg(not(unix))] - let function = match type_string { - "f" => FileType::is_file, - "d" => FileType::is_dir, - "l" => FileType::is_symlink, - _ => { - return Err(From::from(format!( - "Unrecognised type argument {}", - type_string - ))) - } - }; - Ok(Self { - file_type_fn: function, - }) + Ok(Self { file_type }) } } impl Matcher for TypeMatcher { - fn matches(&self, file_info: &DirEntry, _: &mut MatcherIO) -> bool { - (self.file_type_fn)(&file_info.file_type()) + fn matches(&self, file_info: &WalkEntry, _: &mut MatcherIO) -> bool { + file_info.file_type() == self.file_type } } diff --git a/src/find/matchers/user.rs b/src/find/matchers/user.rs index 0b881db1..f1a321c7 100644 --- a/src/find/matchers/user.rs +++ b/src/find/matchers/user.rs @@ -3,7 +3,7 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -use super::Matcher; +use super::{Matcher, MatcherIO, WalkEntry}; #[cfg(unix)] use nix::unistd::User; @@ -57,8 +57,8 @@ impl UserMatcher { impl Matcher for UserMatcher { #[cfg(unix)] - fn matches(&self, file_info: &walkdir::DirEntry, _: &mut super::MatcherIO) -> bool { - let Ok(metadata) = file_info.path().metadata() else { + fn matches(&self, file_info: &WalkEntry, _: &mut MatcherIO) -> bool { + let Ok(metadata) = file_info.metadata() else { return false; }; @@ -71,7 +71,7 @@ impl Matcher for UserMatcher { } #[cfg(windows)] - fn matches(&self, _file_info: &walkdir::DirEntry, _: &mut super::MatcherIO) -> bool { + fn matches(&self, _file_info: &WalkEntry, _: &mut MatcherIO) -> bool { false } } @@ -80,14 +80,14 @@ pub struct NoUserMatcher {} impl Matcher for NoUserMatcher { #[cfg(unix)] - fn matches(&self, file_info: &walkdir::DirEntry, _: &mut super::MatcherIO) -> bool { + fn matches(&self, file_info: &WalkEntry, _: &mut MatcherIO) -> bool { use nix::unistd::Uid; if file_info.path().is_symlink() { return false; } - let Ok(metadata) = file_info.path().metadata() else { + let Ok(metadata) = file_info.metadata() else { return true; }; @@ -103,7 +103,7 @@ impl Matcher for NoUserMatcher { } #[cfg(windows)] - fn matches(&self, _file_info: &walkdir::DirEntry, _: &mut super::MatcherIO) -> bool { + fn matches(&self, _file_info: &WalkEntry, _: &mut MatcherIO) -> bool { false } } @@ -128,7 +128,7 @@ mod tests { let foo_path = temp_dir.path().join("foo"); let _ = File::create(foo_path).expect("create temp file"); let file_info = get_dir_entry_for(&temp_dir.path().to_string_lossy(), "foo"); - let file_uid = file_info.path().metadata().unwrap().uid(); + let file_uid = file_info.metadata().unwrap().uid(); let file_user = User::from_uid(Uid::from_raw(file_uid)) .unwrap() .unwrap() diff --git a/src/find/mod.rs b/src/find/mod.rs index 7fcdbb8f..b9d44b55 100644 --- a/src/find/mod.rs +++ b/src/find/mod.rs @@ -6,6 +6,7 @@ pub mod matchers; +use matchers::WalkEntry; use std::cell::RefCell; use std::error::Error; use std::io::{stderr, stdout, Write}; @@ -160,10 +161,10 @@ fn process_dir( // WalkDirIterator::skip_current_dir for explanation. let mut it = walkdir.into_iter(); while let Some(result) = it.next() { - match result { + match WalkEntry::try_from(result) { Err(err) => { ret = 1; - writeln!(&mut stderr(), "Error: {dir}: {err}").unwrap() + writeln!(&mut stderr(), "Error: {err}").unwrap() } Ok(entry) => { let mut matcher_io = matchers::MatcherIO::new(deps); diff --git a/tests/common/test_helpers.rs b/tests/common/test_helpers.rs index d39d8609..0077c4a5 100644 --- a/tests/common/test_helpers.rs +++ b/tests/common/test_helpers.rs @@ -7,10 +7,10 @@ use std::cell::RefCell; use std::env; use std::io::{Cursor, Read, Write}; +use std::path::Path; use std::time::SystemTime; -use walkdir::{DirEntry, WalkDir}; -use findutils::find::matchers::MatcherIO; +use findutils::find::matchers::{MatcherIO, WalkEntry}; use findutils::find::Dependencies; /// A copy of `find::tests::FakeDependencies`. @@ -80,15 +80,21 @@ pub fn fix_up_slashes(path: &str) -> String { path.to_string() } -/// A copy of `find::tests::FakeDependencies`. +/// A copy of `find::matchers::tests::get_dir_entry_for`. /// TODO: find out how to share #[cfg(test)] functions/structs between unit /// and integration tests. -pub fn get_dir_entry_for(directory: &str, filename: &str) -> DirEntry { - for wrapped_dir_entry in WalkDir::new(fix_up_slashes(directory)) { - let dir_entry = wrapped_dir_entry.unwrap(); - if dir_entry.file_name().to_string_lossy() == filename { - return dir_entry; - } - } - panic!("Couldn't find {directory} in {filename}"); +pub fn get_dir_entry_for(root: &str, path: &str) -> WalkEntry { + let root = fix_up_slashes(root); + let root = Path::new(&root); + + let path = fix_up_slashes(path); + let path = if path.is_empty() { + root.to_owned() + } else { + root.join(path) + }; + + let depth = path.components().count() - root.components().count(); + + WalkEntry::new(path, depth, false) } diff --git a/tests/exec_unit_tests.rs b/tests/exec_unit_tests.rs index 409a2f0b..7f1502c4 100644 --- a/tests/exec_unit_tests.rs +++ b/tests/exec_unit_tests.rs @@ -12,7 +12,6 @@ use std::env; use std::fs::File; use std::io::Read; use tempfile::Builder; -use walkdir::WalkDir; use common::test_helpers::{ fix_up_slashes, get_dir_entry_for, path_to_testing_commandline, FakeDependencies, @@ -125,11 +124,7 @@ fn execdir_in_current_directory() { .unwrap(); let temp_dir_path = temp_dir.path().to_string_lossy(); - let current_dir_entry = WalkDir::new(".") - .into_iter() - .next() - .expect("iterator was empty") - .expect("result wasn't OK"); + let current_dir_entry = get_dir_entry_for(".", ""); let matcher = SingleExecMatcher::new( &path_to_testing_commandline(), &[temp_dir_path.as_ref(), "abc", "{}", "xyz"], @@ -166,11 +161,7 @@ fn execdir_in_root_directory() { .ancestors() .last() .expect("current directory has no root"); - let root_dir_entry = WalkDir::new(root_dir) - .into_iter() - .next() - .expect("iterator was empty") - .expect("result wasn't OK"); + let root_dir_entry = get_dir_entry_for(root_dir.to_str().unwrap(), ""); let matcher = SingleExecMatcher::new( &path_to_testing_commandline(),