From af665124ae37a596821c53281d62b80f3f6efba6 Mon Sep 17 00:00:00 2001 From: hanbings Date: Fri, 24 May 2024 22:48:20 +0800 Subject: [PATCH 01/10] implement samefile feature. --- src/find/matchers/mod.rs | 14 ++++++++++++++ src/find/matchers/samefile.rs | 31 +++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+) create mode 100644 src/find/matchers/samefile.rs diff --git a/src/find/matchers/mod.rs b/src/find/matchers/mod.rs index fad80a97..f436ed1a 100644 --- a/src/find/matchers/mod.rs +++ b/src/find/matchers/mod.rs @@ -19,6 +19,7 @@ mod printf; mod prune; mod quit; mod regex; +mod samefile; mod size; mod stat; mod time; @@ -26,6 +27,7 @@ mod type_matcher; use ::regex::Regex; use chrono::{DateTime, Datelike, NaiveDateTime, Utc}; +use std::fs; use std::path::Path; use std::time::SystemTime; use std::{error::Error, str::FromStr}; @@ -47,6 +49,7 @@ use self::printf::Printf; use self::prune::PruneMatcher; use self::quit::QuitMatcher; use self::regex::RegexMatcher; +use self::samefile::SameFileMatcher; use self::size::SizeMatcher; use self::stat::{InodeMatcher, LinksMatcher}; use self::time::{ @@ -493,6 +496,17 @@ fn build_matcher_tree( i += 1; Some(LinksMatcher::new(inum)?.into_box()) } + "-samefile" => { + if i >= args.len() - 1 { + return Err(From::from(format!("missing argument to {}", args[i]))); + } + let path = fs::metadata(args[i + 1]); + i += 1; + match path { + Ok(metadata) => Some(SameFileMatcher::new(metadata).into_box()), + Err(err) => return Err(From::from(err)), + } + } "-executable" => Some(AccessMatcher::Executable.into_box()), "-perm" => { if i >= args.len() - 1 { diff --git a/src/find/matchers/samefile.rs b/src/find/matchers/samefile.rs new file mode 100644 index 00000000..bb636a57 --- /dev/null +++ b/src/find/matchers/samefile.rs @@ -0,0 +1,31 @@ +// This file is part of the uutils findutils package. +// +// For the full copyright and license information, please view the LICENSE +// file that was distributed with this source code. + +use std::{fs::Metadata, os::unix::fs::MetadataExt}; + +use super::Matcher; + +pub struct SameFileMatcher { + metadata: Metadata, +} + +impl SameFileMatcher { + pub fn new(metadata: Metadata) -> SameFileMatcher { + SameFileMatcher { metadata } + } +} + +impl Matcher for SameFileMatcher { + #[cfg(unix)] + fn matches(&self, file_info: &walkdir::DirEntry, _matcher_io: &mut super::MatcherIO) -> bool { + let meta = file_info.metadata().unwrap(); + meta.ino() == self.metadata.ino() + } + + #[cfg(not(unix))] + fn matches(&self, _file_info: &walkdir::DirEntry, _matcher_io: &mut super::MatcherIO) -> bool { + false + } +} From e270705990cf6ef60eb8834972d448e462d9ace9 Mon Sep 17 00:00:00 2001 From: hanbings Date: Fri, 24 May 2024 22:59:34 +0800 Subject: [PATCH 02/10] Fix windows module use. --- src/find/matchers/samefile.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/find/matchers/samefile.rs b/src/find/matchers/samefile.rs index bb636a57..2ee83124 100644 --- a/src/find/matchers/samefile.rs +++ b/src/find/matchers/samefile.rs @@ -3,7 +3,9 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -use std::{fs::Metadata, os::unix::fs::MetadataExt}; +use std::fs::Metadata; +#[cfg(unix)] +use std::os::unix::fs::MetadataExt; use super::Matcher; From bba21a59ac950c210dc08bc11b58bcac37f39c71 Mon Sep 17 00:00:00 2001 From: hanbings Date: Sat, 25 May 2024 00:31:56 +0800 Subject: [PATCH 03/10] Add test code. --- src/find/matchers/samefile.rs | 33 +++++++++++++++++++++++ tests/find_cmd_tests.rs | 51 +++++++++++++++++++++++++++++++++++ 2 files changed, 84 insertions(+) diff --git a/src/find/matchers/samefile.rs b/src/find/matchers/samefile.rs index 2ee83124..85f18967 100644 --- a/src/find/matchers/samefile.rs +++ b/src/find/matchers/samefile.rs @@ -28,6 +28,39 @@ impl Matcher for SameFileMatcher { #[cfg(not(unix))] fn matches(&self, _file_info: &walkdir::DirEntry, _matcher_io: &mut super::MatcherIO) -> bool { + // FIXME + // MetadataExt under Windows system does not have an interface for obtaining inode, + // so the implementation of this function needs to introduce other libraries or use unsafe code. false } } + +#[cfg(test)] +mod tests { + use std::fs; + + #[test] + #[cfg(unix)] + fn test_samefile() { + use crate::find::{ + matchers::{samefile::SameFileMatcher, tests::get_dir_entry_for, Matcher}, + tests::FakeDependencies, + }; + + // remove file if hard link file exist. + // But you can't delete a file that doesn't exist, + // so ignore the error returned here. + let _ = fs::remove_file("test_data/links/hard_link"); + fs::hard_link("test_data/links/abbbc", "test_data/links/hard_link").unwrap(); + + let file = get_dir_entry_for("test_data/links", "abbbc"); + let file_metadata = file + .metadata() + .expect("Failed to get original file metadata"); + let hard_link_file = get_dir_entry_for("test_data/links", "hard_link"); + let matcher = SameFileMatcher::new(file_metadata); + + let deps = FakeDependencies::new(); + assert!(matcher.matches(&hard_link_file, &mut deps.new_matcher_io())); + } +} diff --git a/tests/find_cmd_tests.rs b/tests/find_cmd_tests.rs index f107db3c..64f4a351 100644 --- a/tests/find_cmd_tests.rs +++ b/tests/find_cmd_tests.rs @@ -587,3 +587,54 @@ fn find_newer_xy() { } } } + +#[test] +#[serial(working_dir)] +#[cfg(target_os = "linux")] +fn find_samefile() { + use std::fs; + + // remove file if hard link file exist. + // But you can't delete a file that doesn't exist, + // so ignore the error returned here. + let _ = fs::remove_file("test_data/links/hard_link"); + fs::hard_link("test_data/links/abbbc", "test_data/links/hard_link").unwrap(); + + Command::cargo_bin("find") + .expect("found binary") + .args([ + "./test_data/links/abbbc", + "-samefile", + "./test_data/links/hard_link", + ]) + .assert() + .success() + .stdout(predicate::str::contains("./test_data/links/abbbc")) + .stderr(predicate::str::is_empty()); + + // test . path + Command::cargo_bin("find") + .expect("found binary") + .args([".", "-samefile", "."]) + .assert() + .success() + .stdout(predicate::str::contains(".")) + .stderr(predicate::str::is_empty()); + + Command::cargo_bin("find") + .expect("found binary") + .args([".", "-samefile", "./test_data/links/abbbc"]) + .assert() + .success() + .stdout(predicate::str::contains("./test_data/links/abbbc")) + .stderr(predicate::str::is_empty()); + + // test not exist file + Command::cargo_bin("find") + .expect("found binary") + .args([".", "-samefile", "./test_data/links/not-exist-file"]) + .assert() + .failure() + .stdout(predicate::str::contains("")) + .stderr(predicate::str::contains("No such file or directory")); +} From 99e27180ceb0712aa027c39c62eaa6f43dc19f8f Mon Sep 17 00:00:00 2001 From: hanbings Date: Tue, 28 May 2024 00:49:56 +0800 Subject: [PATCH 04/10] Change the operating system target of the test. --- tests/find_cmd_tests.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/find_cmd_tests.rs b/tests/find_cmd_tests.rs index 64f4a351..887c679b 100644 --- a/tests/find_cmd_tests.rs +++ b/tests/find_cmd_tests.rs @@ -590,7 +590,7 @@ fn find_newer_xy() { #[test] #[serial(working_dir)] -#[cfg(target_os = "linux")] +#[cfg(unix)] fn find_samefile() { use std::fs; From 47fce96694df01796e8619b6f0d626eaba479b1b Mon Sep 17 00:00:00 2001 From: hanbings Date: Tue, 28 May 2024 04:13:34 +0800 Subject: [PATCH 05/10] Added check for different devices. --- src/find/matchers/samefile.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/find/matchers/samefile.rs b/src/find/matchers/samefile.rs index 85f18967..490107df 100644 --- a/src/find/matchers/samefile.rs +++ b/src/find/matchers/samefile.rs @@ -23,6 +23,11 @@ impl Matcher for SameFileMatcher { #[cfg(unix)] fn matches(&self, file_info: &walkdir::DirEntry, _matcher_io: &mut super::MatcherIO) -> bool { let meta = file_info.metadata().unwrap(); + + if meta.dev() != self.metadata.dev() { + return false; + } + meta.ino() == self.metadata.ino() } From 0cb923d46859fdc73369c537d0ff52a3d2d1ce99 Mon Sep 17 00:00:00 2001 From: hanbings Date: Sat, 8 Jun 2024 02:47:58 +0800 Subject: [PATCH 06/10] Resolve merge conflicts. --- tests/find_cmd_tests.rs | 71 +++++++++++++++++++++-------------------- 1 file changed, 36 insertions(+), 35 deletions(-) diff --git a/tests/find_cmd_tests.rs b/tests/find_cmd_tests.rs index 30d8742f..c6728b79 100644 --- a/tests/find_cmd_tests.rs +++ b/tests/find_cmd_tests.rs @@ -590,7 +590,43 @@ fn find_newer_xy() { #[test] #[serial(working_dir)] +fn find_age_range() { + let args = ["-amin", "-cmin", "-mmin"]; + let times = ["-60", "-120", "-240", "+60", "+120", "+240"]; + let time_strings = [ + "\"-60\"", "\"-120\"", "\"-240\"", "\"-60\"", "\"-120\"", "\"-240\"", + ]; + + for arg in args { + for time in times { + Command::cargo_bin("find") + .expect("the time should match") + .args(["test_data/simple", arg, time]) + .assert() + .success() + .code(0); + } + } + + for arg in args { + for time_string in time_strings { + Command::cargo_bin("find") + .expect("the time should not match") + .args(["test_data/simple", arg, time_string]) + .assert() + .failure() + .code(1) + .stderr(predicate::str::contains( + "Error: Expected a decimal integer (with optional + or - prefix) argument to", + )) + .stdout(predicate::str::is_empty()); + } + } +} + +#[test] #[cfg(unix)] +#[serial(working_dir)] fn find_samefile() { use std::fs; @@ -638,38 +674,3 @@ fn find_samefile() { .stdout(predicate::str::contains("")) .stderr(predicate::str::contains("No such file or directory")); } - -#[cfg(unix)] -fn find_age_range() { - let args = ["-amin", "-cmin", "-mmin"]; - let times = ["-60", "-120", "-240", "+60", "+120", "+240"]; - let time_strings = [ - "\"-60\"", "\"-120\"", "\"-240\"", "\"-60\"", "\"-120\"", "\"-240\"", - ]; - - for arg in args { - for time in times { - Command::cargo_bin("find") - .expect("the time should match") - .args(["test_data/simple", arg, time]) - .assert() - .success() - .code(0); - } - } - - for arg in args { - for time_string in time_strings { - Command::cargo_bin("find") - .expect("the time should not match") - .args(["test_data/simple", arg, time_string]) - .assert() - .failure() - .code(1) - .stderr(predicate::str::contains( - "Error: Expected a decimal integer (with optional + or - prefix) argument to", - )) - .stdout(predicate::str::is_empty()); - } - } -} From 9976ce97fd90013fdf854ab8bc73d463150430c9 Mon Sep 17 00:00:00 2001 From: hanbings Date: Sat, 8 Jun 2024 04:30:39 +0800 Subject: [PATCH 07/10] Changed to use paths_refer_to_same_file to compare two files. --- src/find/matchers/mod.rs | 10 +++------- src/find/matchers/samefile.rs | 23 ++++++----------------- 2 files changed, 9 insertions(+), 24 deletions(-) diff --git a/src/find/matchers/mod.rs b/src/find/matchers/mod.rs index 5743be66..70adab23 100644 --- a/src/find/matchers/mod.rs +++ b/src/find/matchers/mod.rs @@ -27,8 +27,7 @@ mod type_matcher; use ::regex::Regex; use chrono::{DateTime, Datelike, NaiveDateTime, Utc}; -use std::fs; -use std::path::Path; +use std::path::{Path, PathBuf}; use std::time::SystemTime; use std::{error::Error, str::FromStr}; use walkdir::DirEntry; @@ -514,12 +513,9 @@ fn build_matcher_tree( if i >= args.len() - 1 { return Err(From::from(format!("missing argument to {}", args[i]))); } - let path = fs::metadata(args[i + 1]); + let path = args[i + 1]; i += 1; - match path { - Ok(metadata) => Some(SameFileMatcher::new(metadata).into_box()), - Err(err) => return Err(From::from(err)), - } + Some(SameFileMatcher::new(PathBuf::from(path)).into_box()) } "-executable" => Some(AccessMatcher::Executable.into_box()), "-perm" => { diff --git a/src/find/matchers/samefile.rs b/src/find/matchers/samefile.rs index 490107df..728b9656 100644 --- a/src/find/matchers/samefile.rs +++ b/src/find/matchers/samefile.rs @@ -3,32 +3,24 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -use std::fs::Metadata; -#[cfg(unix)] -use std::os::unix::fs::MetadataExt; +use std::path::PathBuf; use super::Matcher; pub struct SameFileMatcher { - metadata: Metadata, + path: PathBuf, } impl SameFileMatcher { - pub fn new(metadata: Metadata) -> SameFileMatcher { - SameFileMatcher { metadata } + pub fn new(path: PathBuf) -> SameFileMatcher { + SameFileMatcher { path } } } impl Matcher for SameFileMatcher { #[cfg(unix)] fn matches(&self, file_info: &walkdir::DirEntry, _matcher_io: &mut super::MatcherIO) -> bool { - let meta = file_info.metadata().unwrap(); - - if meta.dev() != self.metadata.dev() { - return false; - } - - meta.ino() == self.metadata.ino() + uucore::fs::paths_refer_to_same_file(file_info.path(), &self.path, true) } #[cfg(not(unix))] @@ -59,11 +51,8 @@ mod tests { fs::hard_link("test_data/links/abbbc", "test_data/links/hard_link").unwrap(); let file = get_dir_entry_for("test_data/links", "abbbc"); - let file_metadata = file - .metadata() - .expect("Failed to get original file metadata"); let hard_link_file = get_dir_entry_for("test_data/links", "hard_link"); - let matcher = SameFileMatcher::new(file_metadata); + let matcher = SameFileMatcher::new(file.into_path()); let deps = FakeDependencies::new(); assert!(matcher.matches(&hard_link_file, &mut deps.new_matcher_io())); From f612c0da06859beed4aeab498f6195eafe8de7b2 Mon Sep 17 00:00:00 2001 From: hanbings Date: Sat, 8 Jun 2024 04:50:19 +0800 Subject: [PATCH 08/10] Processing of non-existent files. --- src/find/matchers/mod.rs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/find/matchers/mod.rs b/src/find/matchers/mod.rs index 70adab23..b48f32f5 100644 --- a/src/find/matchers/mod.rs +++ b/src/find/matchers/mod.rs @@ -27,7 +27,7 @@ mod type_matcher; use ::regex::Regex; use chrono::{DateTime, Datelike, NaiveDateTime, Utc}; -use std::path::{Path, PathBuf}; +use std::path::Path; use std::time::SystemTime; use std::{error::Error, str::FromStr}; use walkdir::DirEntry; @@ -513,9 +513,16 @@ fn build_matcher_tree( if i >= args.len() - 1 { return Err(From::from(format!("missing argument to {}", args[i]))); } - let path = args[i + 1]; + let path = Path::new(args[i + 1]).to_path_buf(); + // check if path is not found + if !path.exists() { + return Err(From::from(format!( + "{}: No such file or directory", + args[i + 1] + ))); + } i += 1; - Some(SameFileMatcher::new(PathBuf::from(path)).into_box()) + Some(SameFileMatcher::new(path).into_box()) } "-executable" => Some(AccessMatcher::Executable.into_box()), "-perm" => { From 8f3014338c9dc0e166d7b4a0192b8821496beccd Mon Sep 17 00:00:00 2001 From: hanbings Date: Wed, 26 Jun 2024 20:36:09 -0400 Subject: [PATCH 09/10] Added Windows platform support. --- src/find/matchers/samefile.rs | 10 ---------- tests/find_cmd_tests.rs | 3 +-- 2 files changed, 1 insertion(+), 12 deletions(-) diff --git a/src/find/matchers/samefile.rs b/src/find/matchers/samefile.rs index 728b9656..c2744e7d 100644 --- a/src/find/matchers/samefile.rs +++ b/src/find/matchers/samefile.rs @@ -18,18 +18,9 @@ impl SameFileMatcher { } impl Matcher for SameFileMatcher { - #[cfg(unix)] fn matches(&self, file_info: &walkdir::DirEntry, _matcher_io: &mut super::MatcherIO) -> bool { uucore::fs::paths_refer_to_same_file(file_info.path(), &self.path, true) } - - #[cfg(not(unix))] - fn matches(&self, _file_info: &walkdir::DirEntry, _matcher_io: &mut super::MatcherIO) -> bool { - // FIXME - // MetadataExt under Windows system does not have an interface for obtaining inode, - // so the implementation of this function needs to introduce other libraries or use unsafe code. - false - } } #[cfg(test)] @@ -37,7 +28,6 @@ mod tests { use std::fs; #[test] - #[cfg(unix)] fn test_samefile() { use crate::find::{ matchers::{samefile::SameFileMatcher, tests::get_dir_entry_for, Matcher}, diff --git a/tests/find_cmd_tests.rs b/tests/find_cmd_tests.rs index b0799930..0c0f913b 100644 --- a/tests/find_cmd_tests.rs +++ b/tests/find_cmd_tests.rs @@ -745,7 +745,6 @@ fn find_age_range() { } #[test] -#[cfg(unix)] #[serial(working_dir)] fn find_samefile() { use std::fs; @@ -782,7 +781,7 @@ fn find_samefile() { .args([".", "-samefile", "./test_data/links/abbbc"]) .assert() .success() - .stdout(predicate::str::contains("./test_data/links/abbbc")) + .stdout(predicate::str::contains(fix_up_slashes("./test_data/links/abbbc"))) .stderr(predicate::str::is_empty()); // test not exist file From a523addbaaa22a066b856ef75ec11b43b75de013 Mon Sep 17 00:00:00 2001 From: hanbings Date: Wed, 26 Jun 2024 20:38:21 -0400 Subject: [PATCH 10/10] Run cargo fmt. --- tests/find_cmd_tests.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/find_cmd_tests.rs b/tests/find_cmd_tests.rs index 0c0f913b..95c8da75 100644 --- a/tests/find_cmd_tests.rs +++ b/tests/find_cmd_tests.rs @@ -781,7 +781,9 @@ fn find_samefile() { .args([".", "-samefile", "./test_data/links/abbbc"]) .assert() .success() - .stdout(predicate::str::contains(fix_up_slashes("./test_data/links/abbbc"))) + .stdout(predicate::str::contains(fix_up_slashes( + "./test_data/links/abbbc", + ))) .stderr(predicate::str::is_empty()); // test not exist file