From 3ecea97e8bd6fa38e076cf8848840a6decf376ec Mon Sep 17 00:00:00 2001 From: Jeremy Smart Date: Sun, 13 Apr 2025 23:44:43 -0400 Subject: [PATCH 01/13] implement locate --- Cargo.toml | 6 +- src/lib.rs | 1 + src/locate/main.rs | 5 + src/locate/mod.rs | 433 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 444 insertions(+), 1 deletion(-) create mode 100644 src/locate/main.rs create mode 100644 src/locate/mod.rs diff --git a/Cargo.toml b/Cargo.toml index 6ed71241..362dee20 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ authors = ["uutils developers"] [dependencies] chrono = "0.4.40" -clap = "4.5" +clap = { version = "4.5", features = ["env"] } faccess = "0.2.4" walkdir = "2.5" regex = "1.11" @@ -33,6 +33,10 @@ pretty_assertions = "1.4.1" name = "find" path = "src/find/main.rs" +[[bin]] +name = "locate" +path = "src/locate/main.rs" + [[bin]] name = "xargs" path = "src/xargs/main.rs" diff --git a/src/lib.rs b/src/lib.rs index 9f959d0e..208a3855 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,4 +5,5 @@ // https://opensource.org/licenses/MIT. pub mod find; +pub mod locate; pub mod xargs; diff --git a/src/locate/main.rs b/src/locate/main.rs new file mode 100644 index 00000000..60ec7ee6 --- /dev/null +++ b/src/locate/main.rs @@ -0,0 +1,5 @@ +fn main() { + let args = std::env::args().collect::>(); + let strs: Vec<&str> = args.iter().map(std::convert::AsRef::as_ref).collect(); + std::process::exit(findutils::locate::locate_main(strs.as_slice())); +} diff --git a/src/locate/mod.rs b/src/locate/mod.rs new file mode 100644 index 00000000..7873a480 --- /dev/null +++ b/src/locate/mod.rs @@ -0,0 +1,433 @@ +use std::{ + default, env, + error::Error, + ffi::{CString, OsStr, OsString}, + fmt::Display, + fs::File, + io::{stderr, BufRead, BufReader, Read, Write}, + os::unix::ffi::OsStringExt, + path::{Path, PathBuf}, +}; + +use clap::{crate_version, value_parser, Arg, ArgAction, ArgMatches, Command}; +use regex::Regex; +use uucore::error::{UClapError, UError, UResult}; + +pub struct Config { + all: bool, + basename: bool, + mode: Mode, + db: PathBuf, + existing: ExistenceMode, + follow_symlinks: bool, + ignore_case: bool, + limit: Option, + max_age: usize, + null_bytes: bool, + print: bool, + regex: Option, +} + +#[derive(Debug, Clone, Copy, Default)] +pub enum Mode { + #[default] + Normal, + Count, + Statistics, +} + +#[derive(Debug, Clone, Copy, Default)] +pub enum ExistenceMode { + #[default] + Any, + Present, + NotPresent, +} + +#[derive(Debug, Clone, Copy)] +pub enum RegexType { + FindutilsDefault, + Emacs, + GnuAwk, + Grep, + PosixAwk, + Awk, + PosixBasic, + PosixEgrep, + Egrep, + PosixExtended, +} + +#[derive(Debug)] +pub enum LocateError { + InvalidDbType, +} + +impl Display for LocateError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + LocateError::InvalidDbType => f.write_str("Unknown database type"), + } + } +} + +impl Error for LocateError {} + +impl UError for LocateError { + fn code(&self) -> i32 { + 1 + } + + fn usage(&self) -> bool { + match self { + LocateError::InvalidDbType => false, + } + } +} + +pub struct ParsedInfo { + patterns: Vec, + config: Config, +} + +impl From for ParsedInfo { + fn from(value: ArgMatches) -> Self { + Self { + patterns: value + .get_many::("patterns") + .unwrap() + .cloned() + .collect(), + config: Config { + all: value.get_flag("all"), + basename: value.get_flag("basename"), + db: value.get_one::("database").cloned().unwrap(), + mode: value + .get_many::("mode") + .unwrap_or_default() + .last() + .map(|s| match s.as_str() { + "count" => Mode::Count, + "statistics" => Mode::Statistics, + _ => unreachable!(), + }) + .unwrap_or_default(), + existing: value + .get_many::("exist") + .unwrap_or_default() + .last() + .map(|s| match s.as_str() { + "existing" => ExistenceMode::Present, + "statistics" => ExistenceMode::NotPresent, + _ => unreachable!(), + }) + .unwrap_or_default(), + follow_symlinks: value.get_flag("follow") || !value.get_flag("nofollow"), + ignore_case: value.get_flag("ignore-case"), + limit: value.get_one::("limit").copied(), + max_age: *value.get_one::("max-database-age").unwrap(), + null_bytes: value.get_flag("null"), + print: value.get_flag("print"), + regex: value + .get_flag("regex") + .then(|| value.get_one::("regextype")) + .flatten() + .cloned(), + }, + } + } +} + +fn uu_app() -> Command { + Command::new("locate") + .version(crate_version!()) + .args_override_self(true) + .arg( + Arg::new("all") + .short('a') + .long("all") + .action(ArgAction::SetTrue), + ) + .arg( + Arg::new("basename") + .short('b') + .long("basename") + .action(ArgAction::SetTrue) + .group("name"), + ) + .arg( + Arg::new("count") + .short('c') + .long("count") + .action(ArgAction::SetTrue) + .group("mode"), + ) + .arg( + Arg::new("database") + .short('d') + .long("database") + .env("LOCATE_PATH") + .default_value("/usr/local/var/locatedb") + .value_parser(value_parser!(PathBuf)) + .action(ArgAction::Set), + ) + .arg( + Arg::new("existing") + .short('e') + .long("existing") + .action(ArgAction::SetTrue) + .group("exist"), + ) + .arg( + Arg::new("non-existing") + .short('E') + .long("non-existing") + .action(ArgAction::SetTrue) + .group("exist"), + ) + .arg( + Arg::new("follow") + .short('L') + .action(ArgAction::SetTrue) + .overrides_with("nofollow"), + ) + .arg( + Arg::new("nofollow") + .short('P') + .short_alias('H') + .action(ArgAction::SetTrue) + .overrides_with("follow"), + ) + .arg( + Arg::new("ignore-case") + .short('i') + .long("ignore-case") + .action(ArgAction::SetTrue), + ) + .arg( + Arg::new("limit") + .short('l') + .long("limit") + .value_parser(value_parser!(usize)) + .action(ArgAction::Set), + ) + .arg( + Arg::new("max-database-age") + .long("max-database-age") + .value_parser(value_parser!(usize)) + .default_value("8") + .action(ArgAction::Set), + ) + .arg( + Arg::new("mmap") + .short('m') + .long("mmap") + .action(ArgAction::SetTrue), + ) + .arg( + Arg::new("null") + .short('0') + .long("null") + .action(ArgAction::SetTrue), + ) + .arg( + Arg::new("print") + .short('p') + .long("print") + .action(ArgAction::SetTrue), + ) + .arg( + Arg::new("wholename") + .short('w') + .long("wholename") + .action(ArgAction::SetFalse) + .group("name"), + ) + .arg( + Arg::new("regex") + .short('r') + .long("regex") + .action(ArgAction::SetTrue), + ) + .arg( + Arg::new("regextype") + .long("regextype") + .value_parser([ + "findutils-default", + "emacs", + "gnu-awk", + "grep", + "posix-awk", + "awk", + "posix-basic", + "posix-egrep", + "egrep", + "posix-extended", + ]) + .action(ArgAction::Set), + ) + .arg( + Arg::new("stdio") + .short('s') + .long("stdio") + .action(ArgAction::SetTrue), + ) + .arg( + Arg::new("statistics") + .short('S') + .long("statistics") + .action(ArgAction::SetTrue) + .group("mode"), + ) + .arg( + Arg::new("patterns") + .num_args(1..) + .action(ArgAction::Append) + .value_parser(value_parser!(String)) + .required(true), + ) +} + +struct DbReader { + reader: BufReader, + prev: Option, + prefix: usize, +} + +impl Iterator for DbReader { + type Item = CString; + + fn next(&mut self) -> Option { + let mut buf = [0]; + self.reader.read_exact(&mut buf).ok()?; + let size = if buf[0] == 0x80 { + let mut buf = [0; 2]; + self.reader.read_exact(&mut buf).ok()?; + u16::from_be_bytes(buf) as isize + } else { + buf[0] as isize + }; + self.prefix = (self.prefix as isize + size) as usize; + let mut buf = Vec::new(); + self.reader.read_until(b'\0', &mut buf).ok()?; + let prefix = self + .prev + .as_ref() + .map(|s| s.to_bytes().iter().take(self.prefix).collect::>()); + if (prefix.as_ref().map(|v| v.len()).unwrap_or(0) as isize) < size { + return None; + } + let res = CString::from_vec_with_nul( + prefix + .unwrap_or_else(|| vec![]) + .into_iter() + .copied() + .chain(buf) + .collect(), + ) + .ok()?; + self.prev = Some(res.clone()); + Some(res) + } +} + +impl DbReader { + fn new(path: impl AsRef) -> UResult { + let mut s = Self { + reader: BufReader::new(File::open(path.as_ref())?), + prev: None, + prefix: 0, + }; + match s.check_db() { + true => Ok(s), + false => Err(Box::new(LocateError::InvalidDbType)), + } + } + + fn check_db(&mut self) -> bool { + let mut buf = [0]; + let Ok(_) = self.reader.read_exact(&mut buf) else { + return false; + }; + let mut buf = Vec::new(); + let Ok(_) = self.reader.read_until(b'\0', &mut buf) else { + return false; + }; + match String::from_utf8_lossy(buf.as_slice()).as_ref() { + "LOCATE02" => true, + _ => false, + } + } +} + +fn match_entry(entry: &OsStr, config: &Config, patterns: &[String]) -> bool { + let buf = PathBuf::from(entry); + let entry = if config.basename { + let Some(path) = buf.file_name() else { + return false; + }; + + path + } else { + entry + }; + match config.regex { + Some(_) => patterns + .iter() + .filter_map(|s| Regex::new(s).ok()) + .any(|r| r.is_match(entry.to_string_lossy().as_ref())), + None => { + if entry + .to_string_lossy() + .chars() + .any(|c| r"*?[]\".contains(c)) + { + // parse metacharacters + false + } else { + patterns + .iter() + .any(|s| s.contains(entry.to_string_lossy().as_ref())) + } + } + } +} + +fn do_locate(args: &[&str]) -> UResult<()> { + let matches = uu_app().try_get_matches_from(args); + match matches { + Err(e) => { + let mut app = uu_app(); + + match e.kind() { + clap::error::ErrorKind::DisplayHelp => { + app.print_help()?; + } + clap::error::ErrorKind::DisplayVersion => print!("{}", app.render_version()), + _ => return Err(Box::new(e.with_exit_code(1))), + } + } + Ok(matches) => { + let ParsedInfo { patterns, config } = ParsedInfo::from(matches); + let dbreader = DbReader::new(config.db.as_path())?; + + for s in dbreader { + if match_entry(s.as_os_str(), &config, patterns.as_slice()) { + println!("{}", s.to_string_lossy()); + } + } + } + } + + Ok(()) +} + +pub fn locate_main(args: &[&str]) -> i32 { + match do_locate(&args[1..]) { + Ok(()) => 0, + Err(e) => { + writeln!(&mut stderr(), "Error: {e}").unwrap(); + 1 + } + } +} From ae7b831a57bc1730be9b79e89cd1e5c6bb1c218e Mon Sep 17 00:00:00 2001 From: Jeremy Smart Date: Wed, 16 Apr 2025 01:26:15 -0400 Subject: [PATCH 02/13] locate fixes, implement updatedb, add tests --- Cargo.lock | 417 +++++++++++++++++++++++++++++---- Cargo.toml | 7 + src/db_tests.rs | 79 +++++++ src/find/matchers/mod.rs | 1 + src/lib.rs | 2 + src/locate/main.rs | 6 + src/locate/mod.rs | 481 ++++++++++++++++++++++++++++----------- src/updatedb/main.rs | 11 + src/updatedb/mod.rs | 335 +++++++++++++++++++++++++++ test_data_db | Bin 0 -> 184 bytes 10 files changed, 1163 insertions(+), 176 deletions(-) create mode 100644 src/db_tests.rs create mode 100644 src/updatedb/main.rs create mode 100644 src/updatedb/mod.rs create mode 100644 test_data_db diff --git a/Cargo.lock b/Cargo.lock index a091143a..de3c5833 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -62,7 +62,7 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" dependencies = [ - "windows-sys", + "windows-sys 0.59.0", ] [[package]] @@ -73,7 +73,7 @@ checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" dependencies = [ "anstyle", "once_cell", - "windows-sys", + "windows-sys 0.59.0", ] [[package]] @@ -234,6 +234,31 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[package]] +name = "ctor" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07e9666f4a9a948d4f1dff0c08a4512b0f7c86414b23960104c243c10d79f4c3" +dependencies = [ + "ctor-proc-macro", + "dtor", +] + +[[package]] +name = "ctor-proc-macro" +version = "0.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f211af61d8efdd104f96e57adf5e426ba1bc3ed7a4ead616e15e5881fd79c4d" + +[[package]] +name = "deranged" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" +dependencies = [ + "powerfmt", +] + [[package]] name = "diff" version = "0.1.13" @@ -246,18 +271,51 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" +[[package]] +name = "dns-lookup" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5766087c2235fec47fafa4cfecc81e494ee679d0fd4a59887ea0919bfb0e4fc" +dependencies = [ + "cfg-if", + "libc", + "socket2", + "windows-sys 0.48.0", +] + [[package]] name = "doc-comment" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" +[[package]] +name = "dtor" +version = "0.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "222ef136a1c687d4aa0395c175f2c4586e379924c352fd02f7870cf7de783c23" +dependencies = [ + "dtor-proc-macro", +] + +[[package]] +name = "dtor-proc-macro" +version = "0.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7454e41ff9012c00d53cf7f475c5e3afa3b91b7c90568495495e8d9bf47a1055" + [[package]] name = "dunce" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b" +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + [[package]] name = "errno" version = "0.3.10" @@ -265,7 +323,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.59.0", ] [[package]] @@ -294,7 +352,7 @@ dependencies = [ "cfg-if", "libc", "libredox", - "windows-sys", + "windows-sys 0.59.0", ] [[package]] @@ -307,14 +365,17 @@ dependencies = [ "clap", "faccess", "filetime", + "itertools", "nix 0.29.0", "onig", "predicates", "pretty_assertions", + "quick-error", "regex", "serial_test", "tempfile", "uucore", + "uutests", "walkdir", ] @@ -344,9 +405,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.21" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", "futures-sink", @@ -354,9 +415,9 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.21" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" [[package]] name = "futures-executor" @@ -371,27 +432,27 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.21" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" [[package]] name = "futures-sink" -version = "0.3.21" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" [[package]] name = "futures-task" -version = "0.3.21" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" [[package]] name = "futures-util" -version = "0.3.21" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ "futures-channel", "futures-core", @@ -413,7 +474,7 @@ dependencies = [ "cfg-if", "libc", "wasi", - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -452,6 +513,21 @@ version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + [[package]] name = "js-sys" version = "0.3.68" @@ -550,6 +626,12 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + [[package]] name = "num-traits" version = "0.2.14" @@ -559,6 +641,15 @@ dependencies = [ "autocfg", ] +[[package]] +name = "num_threads" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9" +dependencies = [ + "libc", +] + [[package]] name = "number_prefix" version = "0.4.0" @@ -622,7 +713,7 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -660,7 +751,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" dependencies = [ "phf_shared", - "rand", + "rand 0.8.5", ] [[package]] @@ -690,6 +781,21 @@ version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "58893f751c9b0412871a09abd62ecd2a00298c6c83befa223ef98c52aef40cbe" +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + [[package]] name = "predicates" version = "3.1.3" @@ -739,6 +845,12 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "quick-error" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" + [[package]] name = "quote" version = "1.0.38" @@ -754,7 +866,28 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ - "rand_core", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94" +dependencies = [ + "rand_chacha", + "rand_core 0.9.3", + "zerocopy", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.3", ] [[package]] @@ -763,6 +896,15 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +dependencies = [ + "getrandom", +] + [[package]] name = "redox_syscall" version = "0.5.3" @@ -801,6 +943,15 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +[[package]] +name = "rlimit" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7043b63bd0cd1aaa628e476b80e6d4023a3b50eb32789f2728908107bd0c793a" +dependencies = [ + "libc", +] + [[package]] name = "rustix" version = "0.38.44" @@ -811,7 +962,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.4.14", - "windows-sys", + "windows-sys 0.59.0", ] [[package]] @@ -824,7 +975,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.9.2", - "windows-sys", + "windows-sys 0.59.0", ] [[package]] @@ -859,9 +1010,23 @@ checksum = "b84345e4c9bd703274a082fb80caaa99b7612be48dfaa1dd9266577ec412309d" [[package]] name = "serde" -version = "1.0.147" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d193d69bae983fc11a79df82342761dfbf28a99fc8d203dca4c3c1b590948965" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] [[package]] name = "serial_test" @@ -912,6 +1077,16 @@ version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" +[[package]] +name = "socket2" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + [[package]] name = "strsim" version = "0.11.0" @@ -920,9 +1095,9 @@ checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01" [[package]] name = "syn" -version = "2.0.18" +version = "2.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32d41677bcbe24c20c52e7c70b0d8db04134c5d1066bf98662e2871ad200ea3e" +checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" dependencies = [ "proc-macro2", "quote", @@ -939,7 +1114,7 @@ dependencies = [ "getrandom", "once_cell", "rustix 1.0.0", - "windows-sys", + "windows-sys 0.59.0", ] [[package]] @@ -949,7 +1124,40 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4f599bd7ca042cfdf8f4512b277c02ba102247820f9d9d4a9f521f496751a6ef" dependencies = [ "rustix 0.38.44", - "windows-sys", + "windows-sys 0.59.0", +] + +[[package]] +name = "time" +version = "0.3.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" +dependencies = [ + "deranged", + "itoa", + "libc", + "num-conv", + "num_threads", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" + +[[package]] +name = "time-macros" +version = "0.2.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49" +dependencies = [ + "num-conv", + "time-core", ] [[package]] @@ -960,9 +1168,9 @@ checksum = "a7f741b240f1a48843f9b8e0444fb55fb2a4ff67293b50a9179dfd5ea67f8d41" [[package]] name = "unicode-ident" -version = "1.0.9" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" [[package]] name = "unicode-width" @@ -985,6 +1193,7 @@ dependencies = [ "chrono", "chrono-tz", "clap", + "dns-lookup", "dunce", "glob", "iana-time-zone", @@ -992,10 +1201,11 @@ dependencies = [ "nix 0.29.0", "number_prefix", "os_display", + "time", "uucore_procs", "wild", "winapi-util", - "windows-sys", + "windows-sys 0.59.0", ] [[package]] @@ -1015,6 +1225,26 @@ version = "0.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bb6d972f580f8223cb7052d8580aea2b7061e368cf476de32ea9457b19459ed" +[[package]] +name = "uutests" +version = "0.0.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f33bc1f552cd82939d3e07867b118ed7ef7bc0fef04b330e1ac69f98593cb22" +dependencies = [ + "ctor", + "glob", + "libc", + "nix 0.29.0", + "pretty_assertions", + "rand 0.9.0", + "regex", + "rlimit", + "tempfile", + "time", + "uucore", + "xattr", +] + [[package]] name = "wait-timeout" version = "0.2.0" @@ -1128,7 +1358,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys", + "windows-sys 0.59.0", ] [[package]] @@ -1143,7 +1373,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -1152,13 +1382,46 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + [[package]] name = "windows-sys" version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", ] [[package]] @@ -1167,28 +1430,46 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", ] +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -1201,24 +1482,48 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + [[package]] name = "windows_x86_64_msvc" version = "0.52.6" @@ -1234,8 +1539,38 @@ dependencies = [ "bitflags 2.4.1", ] +[[package]] +name = "xattr" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d65cbf2f12c15564212d48f4e3dfb87923d25d611f2aed18f4cb23f0413d89e" +dependencies = [ + "libc", + "rustix 1.0.0", +] + [[package]] name = "yansi" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" + +[[package]] +name = "zerocopy" +version = "0.8.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2586fea28e186957ef732a5f8b3be2da217d65c5969d4b1e17f973ebbe876879" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a996a8f63c5c4448cd959ac1bab0aaa3306ccfd060472f85943ee0750f0169be" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/Cargo.toml b/Cargo.toml index 362dee20..52272925 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,9 @@ onig = { version = "6.4", default-features = false } uucore = { version = "0.0.30", features = ["entries", "fs", "fsext", "mode"] } nix = { version = "0.29", features = ["fs", "user"] } argmax = "0.3.1" +itertools = "0.14.0" +quick-error = "2.0.1" +uutests = "0.0.30" [dev-dependencies] assert_cmd = "2" @@ -37,6 +40,10 @@ path = "src/find/main.rs" name = "locate" path = "src/locate/main.rs" +[[bin]] +name = "updatedb" +path = "src/updatedb/main.rs" + [[bin]] name = "xargs" path = "src/xargs/main.rs" diff --git a/src/db_tests.rs b/src/db_tests.rs new file mode 100644 index 00000000..4dc32a4f --- /dev/null +++ b/src/db_tests.rs @@ -0,0 +1,79 @@ +// Copyright 2017 Google Inc. +// +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +#[cfg(test)] +mod tests { + use std::process::Command; + + use assert_cmd::{assert::OutputAssertExt, cargo::CommandCargoExt}; + + #[test] + fn test_locate_no_matches() { + Command::cargo_bin("locate") + .expect("couldn't find locate binary") + .args(["usr", "--database=test_data_db"]) + .assert() + .failure(); + } + + #[test] + fn test_locate_match() { + Command::cargo_bin("locate") + .expect("couldn't find locate binary") + .args(["test_data", "--database=test_data_db"]) + .assert() + .success(); + } + + #[test] + fn test_locate_no_matches_basename() { + Command::cargo_bin("locate") + .expect("couldn't find locate binary") + .args([ + "test_data1234567890", + "--basename", + "--database=test_data_db", + ]) + .assert() + .failure(); + } + + #[test] + fn test_locate_match_basename() { + Command::cargo_bin("locate") + .expect("couldn't find locate binary") + .args(["abbbc", "--basename", "--database=test_data_db"]) + .assert() + .success(); + } + + #[test] + fn test_locate_existing() { + Command::cargo_bin("locate") + .expect("couldn't find locate binary") + .args(["abbbc", "--existing", "--database=test_data_db"]) + .assert() + .success(); + } + + #[test] + fn test_locate_non_existing() { + Command::cargo_bin("locate") + .expect("couldn't find locate binary") + .args(["abbbc", "--non-existing", "--database=test_data_db"]) + .assert() + .failure(); + } + + #[test] + fn test_updatedb() { + Command::cargo_bin("updatedb") + .expect("couldn't find updatedb binary") + .args(["--localpaths=./test_data", "--output=/dev/null"]) + .assert() + .success(); + } +} diff --git a/src/find/matchers/mod.rs b/src/find/matchers/mod.rs index 05ad37ae..86464196 100644 --- a/src/find/matchers/mod.rs +++ b/src/find/matchers/mod.rs @@ -74,6 +74,7 @@ use std::{ use super::{Config, Dependencies}; pub use entry::{FileType, WalkEntry, WalkError}; +pub use regex::RegexType; /// Symlink following mode. #[derive(Clone, Copy, Debug, Eq, PartialEq)] diff --git a/src/lib.rs b/src/lib.rs index 208a3855..846731c9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,6 +4,8 @@ // license that can be found in the LICENSE file or at // https://opensource.org/licenses/MIT. +mod db_tests; pub mod find; pub mod locate; +pub mod updatedb; pub mod xargs; diff --git a/src/locate/main.rs b/src/locate/main.rs index 60ec7ee6..e4454ecb 100644 --- a/src/locate/main.rs +++ b/src/locate/main.rs @@ -1,3 +1,9 @@ +// Copyright 2017 Google Inc. +// +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + fn main() { let args = std::env::args().collect::>(); let strs: Vec<&str> = args.iter().map(std::convert::AsRef::as_ref).collect(); diff --git a/src/locate/mod.rs b/src/locate/mod.rs index 7873a480..d5ab191a 100644 --- a/src/locate/mod.rs +++ b/src/locate/mod.rs @@ -1,23 +1,34 @@ +// Copyright 2017 Google Inc. +// +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + use std::{ - default, env, - error::Error, - ffi::{CString, OsStr, OsString}, - fmt::Display, - fs::File, - io::{stderr, BufRead, BufReader, Read, Write}, - os::unix::ffi::OsStringExt, + borrow::Cow, + env, + ffi::{CStr, CString, OsStr}, + fs::{self, File}, + io::{self, stderr, BufRead, BufReader, Read, Write}, + os::unix::{ffi::OsStrExt, fs::MetadataExt}, path::{Path, PathBuf}, + str::FromStr, }; -use clap::{crate_version, value_parser, Arg, ArgAction, ArgMatches, Command}; -use regex::Regex; -use uucore::error::{UClapError, UError, UResult}; +use chrono::{DateTime, Local, TimeDelta}; +use clap::{self, crate_version, value_parser, Arg, ArgAction, ArgMatches, Command, Id}; +use onig::{Regex, RegexOptions, Syntax}; +use quick_error::quick_error; +use uucore::error::{ClapErrorWrapper, UClapError, UError, UResult}; + +use crate::{find::matchers::RegexType, updatedb::DbFormat}; +#[derive(Debug)] pub struct Config { all: bool, basename: bool, mode: Mode, - db: PathBuf, + db: Vec, existing: ExistenceMode, follow_symlinks: bool, ignore_case: bool, @@ -25,10 +36,9 @@ pub struct Config { max_age: usize, null_bytes: bool, print: bool, - regex: Option, } -#[derive(Debug, Clone, Copy, Default)] +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] pub enum Mode { #[default] Normal, @@ -44,97 +54,205 @@ pub enum ExistenceMode { NotPresent, } -#[derive(Debug, Clone, Copy)] -pub enum RegexType { - FindutilsDefault, - Emacs, - GnuAwk, - Grep, - PosixAwk, - Awk, - PosixBasic, - PosixEgrep, - Egrep, - PosixExtended, +quick_error! { + #[derive(Debug)] + pub enum Error { + NoMatches {} + InvalidDbType { display("Unknown database type") } + IoErr(err: io::Error) { from() source(err) display("{err}") } + ClapErr(err: ClapErrorWrapper) { from() source(err) display("{err}") } + /// General copy error + Error(err: String) { + display("{err}") + from(err: String) -> (err) + from(err: &'static str) -> (err.to_string()) + } + + } } -#[derive(Debug)] -pub enum LocateError { - InvalidDbType, +type LocateResult = Result; + +impl UError for Error { + fn code(&self) -> i32 { + 1 + } } -impl Display for LocateError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - LocateError::InvalidDbType => f.write_str("Unknown database type"), +pub struct Statistics { + matches: usize, + total_length: usize, + whitespace: usize, + newlines: usize, + high_bit: usize, +} + +impl Statistics { + fn new() -> Self { + Self { + matches: 0, + total_length: 0, + whitespace: 0, + newlines: 0, + high_bit: 0, + } + } + + fn add_match(&mut self, mat: &CStr) { + let s = mat.to_string_lossy(); + self.matches += 1; + self.total_length += s.len(); + if s.chars().any(char::is_whitespace) { + self.whitespace += 1; + } + if s.chars().any(|c| c == '\n') { + self.newlines += 1; + } + if !s.is_ascii() { + self.high_bit += 1; + } + } + + fn print_header(&self, dbreader: &DbReader) { + println!( + "Database {} is in the {} format.", + dbreader.path.to_string_lossy(), + dbreader.format, + ); + } + + fn print(&self, dbreader: &DbReader) { + if let Ok(metadata) = fs::metadata(&dbreader.path) { + if let Ok(time) = metadata.modified() { + let time: DateTime = time.into(); + println!("Database was last modified at {}", time); + } + println!("Locate database size: {} bytes", metadata.size()); } + println!("Matching Filenames: {}", self.matches); + println!( + "File names have a cumulative length of {} bytes", + self.total_length + ); + println!("Of those file names,\n"); + println!(" {} contain whitespace,", self.whitespace); + println!(" {} contain newline characters,", self.newlines); + println!( + " and {} contain characters with the high bit set.", + self.high_bit + ); + println!(); } } -impl Error for LocateError {} +enum Patterns { + String(Vec), + Regex(Vec), +} -impl UError for LocateError { - fn code(&self) -> i32 { - 1 +impl Patterns { + fn any_match(&self, entry: &str) -> bool { + match self { + Self::String(v) => v.iter().any(|s| entry.contains(s)), + Self::Regex(v) => v.iter().any(|r| r.is_match(entry)), + } } - fn usage(&self) -> bool { + fn all_match(&self, entry: &str) -> bool { match self { - LocateError::InvalidDbType => false, + Self::String(v) => v.iter().all(|s| entry.contains(s)), + Self::Regex(v) => v.iter().all(|r| r.is_match(entry)), } } } pub struct ParsedInfo { - patterns: Vec, + patterns: Patterns, config: Config, } +fn make_regex(ty: RegexType, config: &Config, pattern: &str) -> Option { + let syntax = match ty { + RegexType::Emacs => Syntax::emacs(), + RegexType::Grep => Syntax::grep(), + RegexType::PosixBasic => Syntax::posix_basic(), + RegexType::PosixExtended => Syntax::posix_extended(), + }; + + Regex::with_options( + pattern, + if config.ignore_case { + RegexOptions::REGEX_OPTION_IGNORECASE + } else { + RegexOptions::REGEX_OPTION_NONE + }, + syntax, + ) + .ok() +} + impl From for ParsedInfo { fn from(value: ArgMatches) -> Self { - Self { - patterns: value - .get_many::("patterns") + let config = Config { + all: value.get_flag("all"), + basename: value.get_flag("basename"), + db: value + .get_one::("database") .unwrap() - .cloned() + .split(':') + .map(PathBuf::from) .collect(), - config: Config { - all: value.get_flag("all"), - basename: value.get_flag("basename"), - db: value.get_one::("database").cloned().unwrap(), - mode: value - .get_many::("mode") - .unwrap_or_default() - .last() - .map(|s| match s.as_str() { - "count" => Mode::Count, - "statistics" => Mode::Statistics, - _ => unreachable!(), - }) - .unwrap_or_default(), - existing: value - .get_many::("exist") - .unwrap_or_default() - .last() - .map(|s| match s.as_str() { - "existing" => ExistenceMode::Present, - "statistics" => ExistenceMode::NotPresent, - _ => unreachable!(), - }) - .unwrap_or_default(), - follow_symlinks: value.get_flag("follow") || !value.get_flag("nofollow"), - ignore_case: value.get_flag("ignore-case"), - limit: value.get_one::("limit").copied(), - max_age: *value.get_one::("max-database-age").unwrap(), - null_bytes: value.get_flag("null"), - print: value.get_flag("print"), - regex: value - .get_flag("regex") - .then(|| value.get_one::("regextype")) - .flatten() - .cloned(), - }, - } + mode: value + .get_many::("mode") + .unwrap_or_default() + .next_back() + .map(|s| match s.as_str() { + "count" => Mode::Count, + "statistics" => Mode::Statistics, + _ => unreachable!(), + }) + .unwrap_or_default(), + existing: value + .get_many::("exist") + .unwrap_or_default() + .next_back() + .map(|s| match s.as_str() { + "existing" => ExistenceMode::Present, + "statistics" => ExistenceMode::NotPresent, + _ => unreachable!(), + }) + .unwrap_or_default(), + follow_symlinks: value.get_flag("follow") || !value.get_flag("nofollow"), + ignore_case: value.get_flag("ignore-case"), + limit: value.get_one::("limit").copied(), + max_age: *value.get_one::("max-database-age").unwrap(), + null_bytes: value.get_flag("null"), + print: value.get_flag("print"), + }; + let patterns: Vec = value + .get_many::("patterns") + .unwrap() + .cloned() + .collect(); + let patterns = if let Some(ty) = value + .get_flag("regex") + .then(|| { + value + .get_one::("regextype") + .and_then(|s| RegexType::from_str(s.as_str()).ok()) + }) + .flatten() + { + Patterns::Regex( + patterns + .into_iter() + .filter_map(|s| make_regex(ty, &config, &s)) + .collect(), + ) + } else { + Patterns::String(patterns) + }; + Self { patterns, config } } } @@ -168,7 +286,6 @@ fn uu_app() -> Command { .long("database") .env("LOCATE_PATH") .default_value("/usr/local/var/locatedb") - .value_parser(value_parser!(PathBuf)) .action(ArgAction::Set), ) .arg( @@ -291,35 +408,43 @@ fn uu_app() -> Command { struct DbReader { reader: BufReader, prev: Option, - prefix: usize, + prefix: isize, + format: DbFormat, + path: PathBuf, } impl Iterator for DbReader { type Item = CString; fn next(&mut self) -> Option { + // 1 byte for the prefix delta let mut buf = [0]; self.reader.read_exact(&mut buf).ok()?; + // 0x80 - the prefix delta takes the next two bytes let size = if buf[0] == 0x80 { let mut buf = [0; 2]; self.reader.read_exact(&mut buf).ok()?; - u16::from_be_bytes(buf) as isize + i16::from_be_bytes(buf) as isize } else { - buf[0] as isize + // u8 as isize directly doesn't sign-extend + buf[0] as i8 as isize }; - self.prefix = (self.prefix as isize + size) as usize; + self.prefix += size; + // read the actual path fragment let mut buf = Vec::new(); self.reader.read_until(b'\0', &mut buf).ok()?; - let prefix = self - .prev - .as_ref() - .map(|s| s.to_bytes().iter().take(self.prefix).collect::>()); + let prefix = self.prev.as_ref().map(|s| { + s.to_bytes() + .iter() + .take(self.prefix as usize) + .collect::>() + }); if (prefix.as_ref().map(|v| v.len()).unwrap_or(0) as isize) < size { return None; } let res = CString::from_vec_with_nul( prefix - .unwrap_or_else(|| vec![]) + .unwrap_or_default() .into_iter() .copied() .chain(buf) @@ -333,67 +458,88 @@ impl Iterator for DbReader { impl DbReader { fn new(path: impl AsRef) -> UResult { - let mut s = Self { - reader: BufReader::new(File::open(path.as_ref())?), + let mut reader = BufReader::new(File::open(path.as_ref())?); + let format = Self::check_db(&mut reader).ok_or(Error::InvalidDbType)?; + Ok(Self { + reader, prev: None, prefix: 0, - }; - match s.check_db() { - true => Ok(s), - false => Err(Box::new(LocateError::InvalidDbType)), - } + format, + path: path.as_ref().to_path_buf(), + }) } - fn check_db(&mut self) -> bool { + fn check_db(reader: &mut BufReader) -> Option { let mut buf = [0]; - let Ok(_) = self.reader.read_exact(&mut buf) else { - return false; + let Ok(_) = reader.read_exact(&mut buf) else { + return None; }; let mut buf = Vec::new(); - let Ok(_) = self.reader.read_until(b'\0', &mut buf) else { - return false; + let Ok(_) = reader.read_until(b'\0', &mut buf) else { + return None; }; - match String::from_utf8_lossy(buf.as_slice()).as_ref() { - "LOCATE02" => true, - _ => false, + + // drop nul byte when matching + match String::from_utf8_lossy(&buf[..buf.len() - 1]).as_ref() { + "LOCATE02" => Some(DbFormat::Locate02), + _ => None, } } } -fn match_entry(entry: &OsStr, config: &Config, patterns: &[String]) -> bool { - let buf = PathBuf::from(entry); - let entry = if config.basename { +fn match_entry(entry: &CStr, config: &Config, patterns: &Patterns) -> bool { + let buf = Path::new(OsStr::from_bytes(entry.to_bytes())); + let name = if config.basename { let Some(path) = buf.file_name() else { return false; }; - path + let c = CString::from_vec_with_nul( + path.as_encoded_bytes() + .iter() + .copied() + .chain([b'\0']) + .collect(), + ) + .unwrap(); + + Cow::Owned(c) } else { - entry + Cow::Borrowed(entry) }; - match config.regex { - Some(_) => patterns - .iter() - .filter_map(|s| Regex::new(s).ok()) - .any(|r| r.is_match(entry.to_string_lossy().as_ref())), - None => { - if entry - .to_string_lossy() - .chars() - .any(|c| r"*?[]\".contains(c)) - { - // parse metacharacters + let entry = name.to_string_lossy(); + + (match config.all { + false => { + if entry.chars().any(|c| r"*?[]\".contains(c)) { + // TODO: parse metacharacters false } else { - patterns - .iter() - .any(|s| s.contains(entry.to_string_lossy().as_ref())) + patterns.any_match(entry.as_ref()) } } - } + true => { + if entry.chars().any(|c| r"*?[]\".contains(c)) { + // TODO: parse metacharacters + false + } else { + patterns.all_match(entry.as_ref()) + } + } + }) && ((match config.existing { + ExistenceMode::Any => true, + ExistenceMode::Present => PathBuf::from(entry.to_string()).exists(), + ExistenceMode::NotPresent => !PathBuf::from(entry.to_string()).exists(), + }) || { + if config.follow_symlinks { + fs::symlink_metadata(PathBuf::from(entry.to_string())).is_ok() + } else { + false + } + }) } -fn do_locate(args: &[&str]) -> UResult<()> { +fn do_locate(args: &[&str]) -> LocateResult<()> { let matches = uu_app().try_get_matches_from(args); match matches { Err(e) => { @@ -404,17 +550,79 @@ fn do_locate(args: &[&str]) -> UResult<()> { app.print_help()?; } clap::error::ErrorKind::DisplayVersion => print!("{}", app.render_version()), - _ => return Err(Box::new(e.with_exit_code(1))), + _ => return Err(e.with_exit_code(1).into()), } } Ok(matches) => { let ParsedInfo { patterns, config } = ParsedInfo::from(matches); - let dbreader = DbReader::new(config.db.as_path())?; + let mut stats = Statistics::new(); - for s in dbreader { - if match_entry(s.as_os_str(), &config, patterns.as_slice()) { - println!("{}", s.to_string_lossy()); - } + // iterate over each given database + let count = config + .db + .iter() + .filter_map(|p| DbReader::new(p.as_path()).ok()) + .map(|mut dbreader| { + // if we can get the mtime of the file, check it against the current time + if let Ok(metadata) = fs::metadata(&dbreader.path) { + if let Ok(time) = metadata.modified() { + let modified: DateTime = time.into(); + let now = Local::now(); + let delta = now - modified; + if delta + > TimeDelta::days(config.max_age as i64) + { + eprintln!( + "{}: warning: database ‘{}’ is more than {} days old (actual age is {:.1} days)", + args[0], + dbreader.path.to_string_lossy(), + config.max_age, + delta.num_seconds() as f64 / (60 * 60 * 24) as f64 + ); + } + } + } + + // the first line of the statistics description is printed before matches + // (given --print) + if config.mode == Mode::Statistics { + stats.print_header(&dbreader); + } + + // find matches + let count = dbreader + .by_ref() + .filter(|s| match_entry(s.as_c_str(), &config, &patterns)) + .take(config.limit.unwrap_or(usize::MAX)) + .inspect(|s| { + if config.mode == Mode::Normal || config.print { + if config.null_bytes { + print!("{}\0", s.to_string_lossy()); + } else { + println!("{}", s.to_string_lossy());} + } + if config.mode == Mode::Statistics { + stats.add_match(s); + } + }) + .count(); + + // print the rest of the statistics description + if config.mode == Mode::Statistics { + stats.print(&dbreader); + } + + count + }) + .sum::(); + + if config.mode == Mode::Count { + println!("{count}"); + } + + // zero matches isn't an error if --statistics is passed + if count == 0 && config.mode != Mode::Statistics { + return Err(Error::NoMatches); } } } @@ -423,10 +631,13 @@ fn do_locate(args: &[&str]) -> UResult<()> { } pub fn locate_main(args: &[&str]) -> i32 { - match do_locate(&args[1..]) { + match do_locate(args) { Ok(()) => 0, Err(e) => { - writeln!(&mut stderr(), "Error: {e}").unwrap(); + match e { + Error::NoMatches => {} + _ => writeln!(&mut stderr(), "Error: {e}").unwrap(), + } 1 } } diff --git a/src/updatedb/main.rs b/src/updatedb/main.rs new file mode 100644 index 00000000..00188962 --- /dev/null +++ b/src/updatedb/main.rs @@ -0,0 +1,11 @@ +// Copyright 2017 Google Inc. +// +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +fn main() { + let args = std::env::args().collect::>(); + let strs: Vec<&str> = args.iter().map(std::convert::AsRef::as_ref).collect(); + std::process::exit(findutils::updatedb::updatedb_main(strs.as_slice())); +} diff --git a/src/updatedb/mod.rs b/src/updatedb/mod.rs new file mode 100644 index 00000000..daf711c6 --- /dev/null +++ b/src/updatedb/mod.rs @@ -0,0 +1,335 @@ +// Copyright 2017 Google Inc. +// +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +use std::{ + cell::RefCell, + fmt::Display, + fs::OpenOptions, + io::{stderr, BufRead, BufReader, BufWriter, Write}, + path::PathBuf, + rc::Rc, + str::FromStr, + time::SystemTime, +}; + +use clap::{crate_version, value_parser, Arg, ArgAction, ArgMatches, Command}; +use itertools::Itertools; +use uucore::error::UResult; + +use crate::find::{find_main, Dependencies}; + +// local_user and net_user are currently ignored +#[allow(dead_code)] +pub struct Config { + find_options: String, + local_paths: Vec, + net_paths: Vec, + prune_paths: Vec, + prune_fs: Vec, + output: PathBuf, + local_user: Option, + net_user: String, + db_format: DbFormat, +} + +impl From for Config { + fn from(value: ArgMatches) -> Self { + Self { + find_options: value + .get_one::("findoptions") + .cloned() + .unwrap_or_else(String::new), + local_paths: value + .get_one::("localpaths") + .map(|s| { + s.split_whitespace() + .filter_map(|s| PathBuf::from_str(s).ok()) + .collect() + }) + .unwrap_or_else(|| vec![PathBuf::from("/")]), + net_paths: value + .get_one::("netpaths") + .map(|s| s.split_whitespace().map(|s| s.to_owned()).collect()) + .unwrap_or_default(), + prune_paths: value + .get_one::("prunepaths") + .map(|s| s.split_whitespace().map(PathBuf::from).collect()) + .unwrap_or_else(|| { + ["/tmp", "/usr/tmp", "/var/tmp", "/afs"] + .into_iter() + .map(PathBuf::from) + .collect() + }), + prune_fs: value + .get_one::("prunefs") + .map(|s| s.split_whitespace().map(|s| s.to_owned()).collect()) + .unwrap_or_else(|| { + ["nfs", "NFS", "proc"] + .into_iter() + .map(str::to_string) + .collect() + }), + db_format: value + .get_one::("dbformat") + .copied() + .unwrap_or_default(), + output: value + .get_one::("output") + .cloned() + // FIXME: the default should be platform-dependent + .unwrap_or(PathBuf::from_str("/usr/local/var/locatedb").unwrap()), + local_user: value.get_one::("localuser").cloned(), + net_user: value + .get_one::("netuser") + .cloned() + .unwrap_or(String::from("daemon")), + } + } +} + +#[derive(Debug, Clone, Copy, Default)] +pub enum DbFormat { + #[default] + Locate02, +} + +// used for locate's --statistics +impl Display for DbFormat { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Locate02 => f.write_str("GNU LOCATE02"), + } + } +} + +fn uu_app() -> Command { + Command::new("updatedb") + .version(crate_version!()) + .arg( + Arg::new("findoptions") + .long("findoptions") + .require_equals(true) + .env("FINDOPTIONS") + .action(ArgAction::Append), + ) + .arg( + Arg::new("localpaths") + .long("localpaths") + .require_equals(true) + .action(ArgAction::Set), + ) + .arg( + Arg::new("netpaths") + .long("netpaths") + .require_equals(true) + .env("NETPATHS") + .action(ArgAction::Set), + ) + .arg( + Arg::new("prunepaths") + .long("prunepaths") + .require_equals(true) + .env("PRUNEPATHS") + .action(ArgAction::Set), + ) + .arg( + Arg::new("prunefs") + .long("prunefs") + .require_equals(true) + .env("PRUNEFS") + .action(ArgAction::Set), + ) + .arg( + Arg::new("output") + .long("output") + .require_equals(true) + .value_parser(value_parser!(PathBuf)) + .action(ArgAction::Set), + ) + .arg( + Arg::new("localuser") + .long("localuser") + .require_equals(true) + .env("LOCALUSER") + .action(ArgAction::Set), + ) + .arg( + Arg::new("netuser") + .long("netuser") + .require_equals(true) + .env("NETUSER") + .action(ArgAction::Set), + ) + .arg( + Arg::new("dbformat") + .long("dbformat") + .require_equals(true) + .value_parser(["LOCATE02"]) + .action(ArgAction::Set), + ) +} + +// The LOCATE02 format elides bytes from the path until the first byte that differs from the +// previous entry. It keeps a running total of the prefix length, and uses 1 or 3 bytes to write +// the difference from the previous prefix length. Paths are provided in sorted order by find. +struct Frcoder<'a> { + reader: BufReader<&'a [u8]>, + prev: Option>, + prefix: usize, + ty: DbFormat, +} + +impl<'a> Frcoder<'a> { + fn new(v: &'a [u8], ty: DbFormat) -> Self { + Self { + reader: BufReader::new(v), + prev: None, + prefix: 0, + ty, + } + } + + fn generate_header(&self) -> Vec { + match self.ty { + DbFormat::Locate02 => "\0LOCATE02\0".as_bytes().to_vec(), + } + } +} + +impl Iterator for Frcoder<'_> { + type Item = Vec; + + fn next(&mut self) -> Option { + let mut path = Vec::new(); + // find prints nul bytes after each path + if self.reader.read_until(b'\0', &mut path).ok()? == 0 { + return None; + } + + let prefix = path + .iter() + .zip(self.prev.as_deref().unwrap_or_default()) + .take_while(|(a, b)| a == b) + .count(); + + let diff = prefix as i32 - self.prefix as i32; + + // if the prefix delta exceeds 0x7f, we use 0x80 to signal that the next two bytes comprise + // the delta + let mut out = Vec::new(); + if diff > 0x7f { + out.push(0x80); + out.extend((diff as i16).to_be_bytes()); + } else { + out.push(diff as u8); + } + + out.extend(path.iter().skip(prefix)); + + self.prefix = prefix; + self.prev = Some(path); + + Some(out) + } +} + +// capture find's stdout +struct CapturedDependencies { + output: Rc>, + now: SystemTime, +} + +impl CapturedDependencies { + fn new(output: Rc>) -> Self { + Self { + output, + now: SystemTime::now(), + } + } +} + +impl Dependencies for CapturedDependencies { + fn get_output(&self) -> &RefCell { + self.output.as_ref() + } + + fn now(&self) -> SystemTime { + self.now + } +} + +fn do_updatedb(args: &[&str]) -> UResult<()> { + let matches = uu_app().try_get_matches_from(args)?; + let config = Config::from(matches); + + // TODO: handle localuser and netuser + // this will likely involve splitting the find logic into two calls + + let mut find_args = vec!["find"]; + find_args.extend(config.local_paths.iter().filter_map(|p| p.to_str())); + find_args.extend(config.net_paths.iter().map(|s| s.as_str())); + find_args.extend(config.find_options.split_whitespace()); + // offload most of the logic to find + let excludes = format!( + "( {} {} ) -prune {} {} {}", + if config.prune_fs.is_empty() { + "" + } else { + "-fstype" + }, + config.prune_fs.iter().join(" -or -fstype "), + if config.prune_paths.is_empty() { + "" + } else { + "-or -regex" + }, + config + .prune_paths + .iter() + .filter_map(|p| p.to_str()) + .join(" -prune -or -regex "), + if config.prune_paths.is_empty() { + "" + } else { + "-prune" + }, + ); + find_args.extend(excludes.split_whitespace()); + find_args.extend(["-or", "-print0", "-sorted"]); + + let output = Rc::new(RefCell::new(Vec::new())); + let deps = CapturedDependencies::new(output.clone()); + find_main(find_args.as_slice(), &deps); + + let mut writer = BufWriter::new( + OpenOptions::new() + .write(true) + .truncate(true) + .create(true) + .open(config.output)?, + ); + + let output = output.borrow(); + let frcoder = Frcoder::new(output.as_slice(), config.db_format); + writer.write_all(&frcoder.generate_header())?; + for v in frcoder { + writer.write_all(v.as_slice())?; + } + + writer.flush()?; + + Ok(()) +} + +pub fn updatedb_main(args: &[&str]) -> i32 { + match do_updatedb(args) { + Ok(()) => 0, + Err(e) => { + writeln!(&mut stderr(), "Error: {e}").unwrap(); + 1 + } + } +} diff --git a/test_data_db b/test_data_db new file mode 100644 index 0000000000000000000000000000000000000000..1a899541a299a0ced4a5780a6d441028fd185df0 GIT binary patch literal 184 zcmX|*!3x4K5JX3z1wTg8ruYF(@g{ijUK*2VFik=e5BlADQBV^R507P-+10*rGW7 Date: Wed, 16 Apr 2025 07:08:56 -0400 Subject: [PATCH 03/13] move test file, unix-gate locate --- src/lib.rs | 2 +- {src => tests}/db_tests.rs | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename {src => tests}/db_tests.rs (100%) diff --git a/src/lib.rs b/src/lib.rs index 846731c9..44e37ac5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,8 +4,8 @@ // license that can be found in the LICENSE file or at // https://opensource.org/licenses/MIT. -mod db_tests; pub mod find; +#[cfg(unix)] pub mod locate; pub mod updatedb; pub mod xargs; diff --git a/src/db_tests.rs b/tests/db_tests.rs similarity index 100% rename from src/db_tests.rs rename to tests/db_tests.rs From 26982d50c9957e789db819e756b9aa408c3212a2 Mon Sep 17 00:00:00 2001 From: Jeremy Smart Date: Wed, 16 Apr 2025 07:49:27 -0400 Subject: [PATCH 04/13] improve coverage --- src/locate/main.rs | 1 + src/locate/mod.rs | 22 ++++++++++------------ tests/db_tests.rs | 36 ++++++++++++++++++++++++++++++++++++ 3 files changed, 47 insertions(+), 12 deletions(-) diff --git a/src/locate/main.rs b/src/locate/main.rs index e4454ecb..80bdcd0d 100644 --- a/src/locate/main.rs +++ b/src/locate/main.rs @@ -7,5 +7,6 @@ fn main() { let args = std::env::args().collect::>(); let strs: Vec<&str> = args.iter().map(std::convert::AsRef::as_ref).collect(); + #[cfg(unix)] std::process::exit(findutils::locate::locate_main(strs.as_slice())); } diff --git a/src/locate/mod.rs b/src/locate/mod.rs index d5ab191a..76976adb 100644 --- a/src/locate/mod.rs +++ b/src/locate/mod.rs @@ -145,6 +145,7 @@ impl Statistics { } } +#[derive(Debug)] enum Patterns { String(Vec), Regex(Vec), @@ -154,14 +155,14 @@ impl Patterns { fn any_match(&self, entry: &str) -> bool { match self { Self::String(v) => v.iter().any(|s| entry.contains(s)), - Self::Regex(v) => v.iter().any(|r| r.is_match(entry)), + Self::Regex(v) => v.iter().any(|r| r.find(entry).is_some()), } } fn all_match(&self, entry: &str) -> bool { match self { Self::String(v) => v.iter().all(|s| entry.contains(s)), - Self::Regex(v) => v.iter().all(|r| r.is_match(entry)), + Self::Regex(v) => v.iter().all(|r| r.find(entry).is_some()), } } } @@ -234,15 +235,12 @@ impl From for ParsedInfo { .unwrap() .cloned() .collect(); - let patterns = if let Some(ty) = value - .get_flag("regex") - .then(|| { - value - .get_one::("regextype") - .and_then(|s| RegexType::from_str(s.as_str()).ok()) - }) - .flatten() - { + let patterns = if let Some(ty) = value.get_flag("regex").then(|| { + value + .get_one::("regextype") + .and_then(|s| RegexType::from_str(s.as_str()).ok()) + .unwrap_or(RegexType::Emacs) + }) { Patterns::Regex( patterns .into_iter() @@ -638,7 +636,7 @@ pub fn locate_main(args: &[&str]) -> i32 { Error::NoMatches => {} _ => writeln!(&mut stderr(), "Error: {e}").unwrap(), } - 1 + e.code() } } } diff --git a/tests/db_tests.rs b/tests/db_tests.rs index 4dc32a4f..e2b7f512 100644 --- a/tests/db_tests.rs +++ b/tests/db_tests.rs @@ -68,6 +68,42 @@ mod tests { .failure(); } + #[test] + fn test_locate_statistics() { + Command::cargo_bin("locate") + .expect("couldn't find locate binary") + .args(["abbbc", "--statistics", "--database=test_data_db"]) + .assert() + .success(); + } + + #[test] + fn test_locate_regex() { + Command::cargo_bin("locate") + .expect("couldn't find locate binary") + .args(["abbbc", "--regex", "--database=test_data_db"]) + .assert() + .success(); + } + + #[test] + fn test_locate_all() { + Command::cargo_bin("locate") + .expect("couldn't find locate binary") + .args(["abb", "bbc", "--regex", "--database=test_data_db"]) + .assert() + .success(); + } + + #[test] + fn test_locate_all_regex() { + Command::cargo_bin("locate") + .expect("couldn't find locate binary") + .args(["abb", "b*c", "--regex", "--database=test_data_db"]) + .assert() + .success(); + } + #[test] fn test_updatedb() { Command::cargo_bin("updatedb") From ff350c7154575bb554f73bb25834f361f9ef794b Mon Sep 17 00:00:00 2001 From: Jeremy Smart Date: Wed, 7 May 2025 03:08:41 -0400 Subject: [PATCH 05/13] handle windows better --- src/locate/main.rs | 7 +- src/updatedb/main.rs | 6 + test_data_db => test_data_db_unix | Bin 184 -> 185 bytes tests/db_tests.rs | 230 ++++++++++++++++-------------- 4 files changed, 134 insertions(+), 109 deletions(-) rename test_data_db => test_data_db_unix (69%) diff --git a/src/locate/main.rs b/src/locate/main.rs index 80bdcd0d..71797cd8 100644 --- a/src/locate/main.rs +++ b/src/locate/main.rs @@ -4,9 +4,14 @@ // license that can be found in the LICENSE file or at // https://opensource.org/licenses/MIT. +#[cfg(not(windows))] fn main() { let args = std::env::args().collect::>(); let strs: Vec<&str> = args.iter().map(std::convert::AsRef::as_ref).collect(); - #[cfg(unix)] std::process::exit(findutils::locate::locate_main(strs.as_slice())); } + +#[cfg(windows)] +fn main() { + println!("locate is unsupported on Windows"); +} diff --git a/src/updatedb/main.rs b/src/updatedb/main.rs index 00188962..9d372399 100644 --- a/src/updatedb/main.rs +++ b/src/updatedb/main.rs @@ -4,8 +4,14 @@ // license that can be found in the LICENSE file or at // https://opensource.org/licenses/MIT. +#[cfg(not(windows))] fn main() { let args = std::env::args().collect::>(); let strs: Vec<&str> = args.iter().map(std::convert::AsRef::as_ref).collect(); std::process::exit(findutils::updatedb::updatedb_main(strs.as_slice())); } + +#[cfg(windows)] +fn main() { + println!("updatedb is unsupported on Windows"); +} diff --git a/test_data_db b/test_data_db_unix similarity index 69% rename from test_data_db rename to test_data_db_unix index 1a899541a299a0ced4a5780a6d441028fd185df0..a4d0f07989d4572ada6f8a55b05564cd479d7a8b 100644 GIT binary patch delta 8 PcmdnNxRY_h4n{5j4xR$X delta 6 NcmdnVxPx)R4gd)p0=xhK diff --git a/tests/db_tests.rs b/tests/db_tests.rs index e2b7f512..d90d0e05 100644 --- a/tests/db_tests.rs +++ b/tests/db_tests.rs @@ -4,112 +4,126 @@ // license that can be found in the LICENSE file or at // https://opensource.org/licenses/MIT. -#[cfg(test)] -mod tests { - use std::process::Command; - - use assert_cmd::{assert::OutputAssertExt, cargo::CommandCargoExt}; - - #[test] - fn test_locate_no_matches() { - Command::cargo_bin("locate") - .expect("couldn't find locate binary") - .args(["usr", "--database=test_data_db"]) - .assert() - .failure(); - } - - #[test] - fn test_locate_match() { - Command::cargo_bin("locate") - .expect("couldn't find locate binary") - .args(["test_data", "--database=test_data_db"]) - .assert() - .success(); - } - - #[test] - fn test_locate_no_matches_basename() { - Command::cargo_bin("locate") - .expect("couldn't find locate binary") - .args([ - "test_data1234567890", - "--basename", - "--database=test_data_db", - ]) - .assert() - .failure(); - } - - #[test] - fn test_locate_match_basename() { - Command::cargo_bin("locate") - .expect("couldn't find locate binary") - .args(["abbbc", "--basename", "--database=test_data_db"]) - .assert() - .success(); - } - - #[test] - fn test_locate_existing() { - Command::cargo_bin("locate") - .expect("couldn't find locate binary") - .args(["abbbc", "--existing", "--database=test_data_db"]) - .assert() - .success(); - } - - #[test] - fn test_locate_non_existing() { - Command::cargo_bin("locate") - .expect("couldn't find locate binary") - .args(["abbbc", "--non-existing", "--database=test_data_db"]) - .assert() - .failure(); - } - - #[test] - fn test_locate_statistics() { - Command::cargo_bin("locate") - .expect("couldn't find locate binary") - .args(["abbbc", "--statistics", "--database=test_data_db"]) - .assert() - .success(); - } - - #[test] - fn test_locate_regex() { - Command::cargo_bin("locate") - .expect("couldn't find locate binary") - .args(["abbbc", "--regex", "--database=test_data_db"]) - .assert() - .success(); - } - - #[test] - fn test_locate_all() { - Command::cargo_bin("locate") - .expect("couldn't find locate binary") - .args(["abb", "bbc", "--regex", "--database=test_data_db"]) - .assert() - .success(); - } - - #[test] - fn test_locate_all_regex() { - Command::cargo_bin("locate") - .expect("couldn't find locate binary") - .args(["abb", "b*c", "--regex", "--database=test_data_db"]) - .assert() - .success(); - } - - #[test] - fn test_updatedb() { - Command::cargo_bin("updatedb") - .expect("couldn't find updatedb binary") - .args(["--localpaths=./test_data", "--output=/dev/null"]) - .assert() - .success(); - } +mod common; + +use std::process::Command; + +use assert_cmd::{assert::OutputAssertExt, cargo::CommandCargoExt}; + +use crate::common::test_helpers::fix_up_slashes; + +#[cfg(not(windows))] +const DB_FLAG: &str = "--database=test_data_db"; + +#[test] +#[cfg(not(windows))] +fn test_locate_no_matches() { + Command::cargo_bin("locate") + .expect("couldn't find locate binary") + .args(["usr", DB_FLAG]) + .assert() + .failure(); +} + +#[test] +#[cfg(not(windows))] +fn test_locate_match() { + Command::cargo_bin("locate") + .expect("couldn't find locate binary") + .args(["test_data", DB_FLAG]) + .assert() + .success(); +} + +#[test] +#[cfg(not(windows))] +fn test_locate_no_matches_basename() { + Command::cargo_bin("locate") + .expect("couldn't find locate binary") + .args(["test_data1234567890", "--basename", DB_FLAG]) + .assert() + .failure(); +} + +#[test] +#[cfg(not(windows))] +fn test_locate_match_basename() { + Command::cargo_bin("locate") + .expect("couldn't find locate binary") + .args(["abbbc", "--basename", DB_FLAG]) + .assert() + .success(); +} + +#[test] +#[cfg(not(windows))] +fn test_locate_existing() { + Command::cargo_bin("locate") + .expect("couldn't find locate binary") + .args(["abbbc", "--existing", "--database={DB}"]) + .assert() + .success(); +} + +#[test] +#[cfg(not(windows))] +fn test_locate_non_existing() { + Command::cargo_bin("locate") + .expect("couldn't find locate binary") + .args(["abbbc", "--non-existing", DB_FLAG]) + .assert() + .failure(); +} + +#[test] +#[cfg(not(windows))] +fn test_locate_statistics() { + Command::cargo_bin("locate") + .expect("couldn't find locate binary") + .args(["abbbc", "--statistics", DB_FLAG]) + .assert() + .success(); +} + +#[test] +#[cfg(not(windows))] +fn test_locate_regex() { + Command::cargo_bin("locate") + .expect("couldn't find locate binary") + .args(["abbbc", "--regex", DB_FLAG]) + .assert() + .success(); +} + +#[test] +#[cfg(not(windows))] +fn test_locate_all() { + Command::cargo_bin("locate") + .expect("couldn't find locate binary") + .args(["abb", "bbc", "--regex", DB_FLAG]) + .assert() + .success(); +} + +#[test] +#[cfg(not(windows))] +fn test_locate_all_regex() { + Command::cargo_bin("locate") + .expect("couldn't find locate binary") + .args(["abb", "b*c", "--regex", DB_FLAG]) + .assert() + .success(); +} + +#[test] +#[cfg(not(windows))] +fn test_updatedb() { + Command::cargo_bin("updatedb") + .expect("couldn't find updatedb binary") + .args([ + fix_up_slashes("--localpaths=./test_data"), + fix_up_slashes("--output=./test_data_db"), + ]) + .assert() + .success(); } From 3f65e23f2f4cc6e1526d8039c3a51ac89d1d679e Mon Sep 17 00:00:00 2001 From: Jeremy Smart Date: Thu, 8 May 2025 01:13:00 -0400 Subject: [PATCH 06/13] rename db --- test_data_db_unix => test_data_db | Bin 1 file changed, 0 insertions(+), 0 deletions(-) rename test_data_db_unix => test_data_db (100%) diff --git a/test_data_db_unix b/test_data_db similarity index 100% rename from test_data_db_unix rename to test_data_db From ea1a7c3b9beba7a5e7082f0db584bbe4fb04076e Mon Sep 17 00:00:00 2001 From: Jeremy Smart Date: Sat, 10 May 2025 02:21:22 -0400 Subject: [PATCH 07/13] fix tests --- tests/db_tests.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/db_tests.rs b/tests/db_tests.rs index d90d0e05..a42d6d01 100644 --- a/tests/db_tests.rs +++ b/tests/db_tests.rs @@ -60,7 +60,7 @@ fn test_locate_match_basename() { fn test_locate_existing() { Command::cargo_bin("locate") .expect("couldn't find locate binary") - .args(["abbbc", "--existing", "--database={DB}"]) + .args(["abbbc", "--existing", DB_FLAG]) .assert() .success(); } @@ -122,7 +122,7 @@ fn test_updatedb() { .expect("couldn't find updatedb binary") .args([ fix_up_slashes("--localpaths=./test_data"), - fix_up_slashes("--output=./test_data_db"), + fix_up_slashes("--output=/dev/null"), ]) .assert() .success(); From b450825bee6cc7e0aa9c1d7b3bdebafaf1c6d268 Mon Sep 17 00:00:00 2001 From: Jeremy Smart Date: Sat, 10 May 2025 02:50:28 -0400 Subject: [PATCH 08/13] improve coverage --- Cargo.lock | 148 +++++++++++++++++- Cargo.toml | 1 + "test_data/db/abc\ndef" | 0 test_data/db/abc def | 0 .../f0 | 0 .../f1 | 0 .../db/\342\234\250sparkles\342\234\250" | 0 test_data_db | Bin 185 -> 227 bytes tests/db_tests.rs | 20 ++- 9 files changed, 159 insertions(+), 10 deletions(-) create mode 100644 "test_data/db/abc\ndef" create mode 100644 test_data/db/abc def create mode 100644 test_data/db/depth/1234567890123456789012345678901234567890/1234567890123456789012345678901234567890/1234567890123456789012345678901234567890/1234567890123456789012345678901234567890/f0 create mode 100644 test_data/db/depth/1234567890123456789012345678901234567890/1234567890123456789012345678901234567890/1234567890123456789012345678901234567890/1234567890123456789012345678901234567890/f1 create mode 100644 "test_data/db/\342\234\250sparkles\342\234\250" diff --git a/Cargo.lock b/Cargo.lock index 72b36d0d..3804c801 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -316,6 +316,12 @@ version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + [[package]] name = "errno" version = "0.3.10" @@ -372,6 +378,7 @@ dependencies = [ "pretty_assertions", "quick-error", "regex", + "rstest", "serial_test", "tempfile", "uucore", @@ -436,6 +443,17 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + [[package]] name = "futures-sink" version = "0.3.31" @@ -448,6 +466,12 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" +[[package]] +name = "futures-timer" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" + [[package]] name = "futures-util" version = "0.3.31" @@ -457,6 +481,7 @@ dependencies = [ "futures-channel", "futures-core", "futures-io", + "futures-macro", "futures-sink", "futures-task", "memchr", @@ -483,6 +508,12 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" +[[package]] +name = "hashbrown" +version = "0.15.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3" + [[package]] name = "iana-time-zone" version = "0.1.62" @@ -507,6 +538,16 @@ dependencies = [ "cc", ] +[[package]] +name = "indexmap" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" +dependencies = [ + "equivalent", + "hashbrown", +] + [[package]] name = "is_terminal_polyfill" version = "1.70.1" @@ -848,6 +889,15 @@ dependencies = [ "yansi", ] +[[package]] +name = "proc-macro-crate" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35" +dependencies = [ + "toml_edit", +] + [[package]] name = "proc-macro2" version = "1.0.93" @@ -955,6 +1005,12 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +[[package]] +name = "relative-path" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2" + [[package]] name = "rlimit" version = "0.10.2" @@ -964,6 +1020,45 @@ dependencies = [ "libc", ] +[[package]] +name = "rstest" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fc39292f8613e913f7df8fa892b8944ceb47c247b78e1b1ae2f09e019be789d" +dependencies = [ + "futures-timer", + "futures-util", + "rstest_macros", + "rustc_version", +] + +[[package]] +name = "rstest_macros" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f168d99749d307be9de54d23fd226628d99768225ef08f6ffb52e0182a27746" +dependencies = [ + "cfg-if", + "glob", + "proc-macro-crate", + "proc-macro2", + "quote", + "regex", + "relative-path", + "rustc_version", + "syn 2.0.100", + "unicode-ident", +] + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + [[package]] name = "rustix" version = "0.38.44" @@ -1020,6 +1115,12 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b84345e4c9bd703274a082fb80caaa99b7612be48dfaa1dd9266577ec412309d" +[[package]] +name = "semver" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" + [[package]] name = "serde" version = "1.0.219" @@ -1037,7 +1138,7 @@ checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.100", ] [[package]] @@ -1062,7 +1163,7 @@ checksum = "5d69265a08751de7844521fd15003ae0a888e035773ba05695c5c759a6f89eef" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.100", ] [[package]] @@ -1105,6 +1206,17 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01" +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "syn" version = "2.0.100" @@ -1172,6 +1284,23 @@ dependencies = [ "time-core", ] +[[package]] +name = "toml_datetime" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3da5db5a963e24bc68be8b17b6fa82814bb22ee8660f192bb182771d498f09a3" + +[[package]] +name = "toml_edit" +version = "0.22.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "310068873db2c5b3e7659d2cc35d21855dbafa50d1ce336397c666e3cb08137e" +dependencies = [ + "indexmap", + "toml_datetime", + "winnow", +] + [[package]] name = "treeline" version = "0.1.0" @@ -1306,7 +1435,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn", + "syn 2.0.100", "wasm-bindgen-shared", ] @@ -1328,7 +1457,7 @@ checksum = "642f325be6301eb8107a83d12a8ac6c1e1c54345a7ef1a9261962dfefda09e66" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.100", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -1542,6 +1671,15 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "winnow" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06928c8748d81b05c9be96aad92e1b6ff01833332f281e8cfca3be4b35fc9ec" +dependencies = [ + "memchr", +] + [[package]] name = "wit-bindgen-rt" version = "0.33.0" @@ -1584,5 +1722,5 @@ checksum = "a996a8f63c5c4448cd959ac1bab0aaa3306ccfd060472f85943ee0750f0169be" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.100", ] diff --git a/Cargo.toml b/Cargo.toml index 25e0487c..b8280c19 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,7 @@ argmax = "0.3.1" itertools = "0.14.0" quick-error = "2.0.1" uutests = "0.0.30" +rstest = "0.25.0" [dev-dependencies] assert_cmd = "2" diff --git "a/test_data/db/abc\ndef" "b/test_data/db/abc\ndef" new file mode 100644 index 00000000..e69de29b diff --git a/test_data/db/abc def b/test_data/db/abc def new file mode 100644 index 00000000..e69de29b diff --git a/test_data/db/depth/1234567890123456789012345678901234567890/1234567890123456789012345678901234567890/1234567890123456789012345678901234567890/1234567890123456789012345678901234567890/f0 b/test_data/db/depth/1234567890123456789012345678901234567890/1234567890123456789012345678901234567890/1234567890123456789012345678901234567890/1234567890123456789012345678901234567890/f0 new file mode 100644 index 00000000..e69de29b diff --git a/test_data/db/depth/1234567890123456789012345678901234567890/1234567890123456789012345678901234567890/1234567890123456789012345678901234567890/1234567890123456789012345678901234567890/f1 b/test_data/db/depth/1234567890123456789012345678901234567890/1234567890123456789012345678901234567890/1234567890123456789012345678901234567890/1234567890123456789012345678901234567890/f1 new file mode 100644 index 00000000..e69de29b diff --git "a/test_data/db/\342\234\250sparkles\342\234\250" "b/test_data/db/\342\234\250sparkles\342\234\250" new file mode 100644 index 00000000..e69de29b diff --git a/test_data_db b/test_data_db index a4d0f07989d4572ada6f8a55b05564cd479d7a8b..9dbcfe89215a2dcf78ecafdcde26b24357c1da93 100644 GIT binary patch delta 81 zcmdnV_?Xd?!N=d(F~rrth=D;*za+J|Bt9jvB$0tzKP4%?xFoTpn1NG2F)5iVB{hwK hMFB+qeKco9aY15Hc1~(Bkjd~PwV)(}fn}oHEC3Wm95es` delta 40 vcmaFNxRX(i!N=d(F~rrth=HLbwYVfcC9x!tfm1&vwV)(}fo-DaEJiK>?5PW4 diff --git a/tests/db_tests.rs b/tests/db_tests.rs index a42d6d01..e682074b 100644 --- a/tests/db_tests.rs +++ b/tests/db_tests.rs @@ -9,6 +9,7 @@ mod common; use std::process::Command; use assert_cmd::{assert::OutputAssertExt, cargo::CommandCargoExt}; +use rstest::rstest; use crate::common::test_helpers::fix_up_slashes; @@ -85,12 +86,21 @@ fn test_locate_statistics() { .success(); } -#[test] +#[rstest] +#[case("emacs")] +#[case("grep")] +#[case("posix-basic")] +#[case("posix-extended")] #[cfg(not(windows))] -fn test_locate_regex() { +fn test_locate_regex(#[case] input: &str) { Command::cargo_bin("locate") .expect("couldn't find locate binary") - .args(["abbbc", "--regex", DB_FLAG]) + .args([ + "abbbc", + "--regex", + format!("--regextype={input}").as_str(), + DB_FLAG, + ]) .assert() .success(); } @@ -100,7 +110,7 @@ fn test_locate_regex() { fn test_locate_all() { Command::cargo_bin("locate") .expect("couldn't find locate binary") - .args(["abb", "bbc", "--regex", DB_FLAG]) + .args(["abb", "bbc", "--all", DB_FLAG]) .assert() .success(); } @@ -110,7 +120,7 @@ fn test_locate_all() { fn test_locate_all_regex() { Command::cargo_bin("locate") .expect("couldn't find locate binary") - .args(["abb", "b*c", "--regex", DB_FLAG]) + .args(["abb", "b*c", "--all", "--regex", DB_FLAG]) .assert() .success(); } From 4be51f9cf77a06d0fc94323987a9fbd589a8c010 Mon Sep 17 00:00:00 2001 From: Jeremy Smart Date: Sat, 10 May 2025 16:50:20 -0400 Subject: [PATCH 09/13] remove files that broke windows ci, updatedb: fix large negative deltas --- src/updatedb/mod.rs | 2 +- "test_data/db/abc\ndef" | 0 test_data/db/abc def | 0 .../db/\342\234\250sparkles\342\234\250" | 0 test_data_db | Bin 227 -> 410 bytes tests/db_tests.rs | 39 ++++++++++-------- 6 files changed, 23 insertions(+), 18 deletions(-) delete mode 100644 "test_data/db/abc\ndef" delete mode 100644 test_data/db/abc def delete mode 100644 "test_data/db/\342\234\250sparkles\342\234\250" diff --git a/src/updatedb/mod.rs b/src/updatedb/mod.rs index daf711c6..b18b32b4 100644 --- a/src/updatedb/mod.rs +++ b/src/updatedb/mod.rs @@ -220,7 +220,7 @@ impl Iterator for Frcoder<'_> { // if the prefix delta exceeds 0x7f, we use 0x80 to signal that the next two bytes comprise // the delta let mut out = Vec::new(); - if diff > 0x7f { + if diff.abs() > 0x7f { out.push(0x80); out.extend((diff as i16).to_be_bytes()); } else { diff --git "a/test_data/db/abc\ndef" "b/test_data/db/abc\ndef" deleted file mode 100644 index e69de29b..00000000 diff --git a/test_data/db/abc def b/test_data/db/abc def deleted file mode 100644 index e69de29b..00000000 diff --git "a/test_data/db/\342\234\250sparkles\342\234\250" "b/test_data/db/\342\234\250sparkles\342\234\250" deleted file mode 100644 index e69de29b..00000000 diff --git a/test_data_db b/test_data_db index 9dbcfe89215a2dcf78ecafdcde26b24357c1da93..3fafa8ee8e9a0ed22979e559653006720fb41845 100644 GIT binary patch literal 410 zcmc&xyG{c!5S)aBBK$LM(W56|uD#VrKk>iR;feXULx+j%Jr(efEseF7wmr$0E#*^u6 zo}FeGoL{Olv?<>951;QJ!SiZUs*osljh7CgXcLx@C+Hk|&9KunA;V8;>?S0?TnMp7 zt1gEUT@94Zd0=ZoF!m9!E=p73H$00vUpAM-mvt+zsz&835X{z>Ds>O@j_O5G6ia-+ Ns io::Result<()> { + File::create("test_data/db/abc def")?; + File::create("test_data/db/abc\ndef")?; + File::create("test_data/db/✨sparkles✨")?; + Ok(()) +} #[cfg(not(windows))] const DB_FLAG: &str = "--database=test_data_db"; @@ -76,14 +82,16 @@ fn test_locate_non_existing() { .failure(); } -#[test] +#[rstest] #[cfg(not(windows))] -fn test_locate_statistics() { - Command::cargo_bin("locate") - .expect("couldn't find locate binary") - .args(["abbbc", "--statistics", DB_FLAG]) - .assert() - .success(); +fn test_locate_statistics(add_special_files: io::Result<()>) { + if add_special_files.is_ok() { + Command::cargo_bin("locate") + .expect("couldn't find locate binary") + .args(["abbbc", "--statistics", DB_FLAG]) + .assert() + .success(); + } } #[rstest] @@ -125,15 +133,12 @@ fn test_locate_all_regex() { .success(); } -#[test] +#[rstest] #[cfg(not(windows))] -fn test_updatedb() { +fn test_updatedb(_add_special_files: io::Result<()>) { Command::cargo_bin("updatedb") .expect("couldn't find updatedb binary") - .args([ - fix_up_slashes("--localpaths=./test_data"), - fix_up_slashes("--output=/dev/null"), - ]) + .args(["--localpaths=./test_data", "--output=/dev/null"]) .assert() .success(); } From e886128d2e558f05c46933622a37e5a9be90039a Mon Sep 17 00:00:00 2001 From: Jeremy Smart Date: Sat, 10 May 2025 20:51:29 -0400 Subject: [PATCH 10/13] improve coverage, handle invalid databases --- invalid_db | Bin 0 -> 410 bytes src/locate/mod.rs | 46 +++++++++++++++++++++++++++------------------- tests/db_tests.rs | 12 ++++++++++++ 3 files changed, 39 insertions(+), 19 deletions(-) create mode 100644 invalid_db diff --git a/invalid_db b/invalid_db new file mode 100644 index 0000000000000000000000000000000000000000..f62ba7ec98fb8cf845d36a646d3a52905610ced1 GIT binary patch literal 410 zcmc&xyG{c!5FGNL$XC!(fX_aXK$(luA*F#jvSXWAx!Bg(6j1RY`2fC)Zy-$~F7cfZ zWm<~aoz;$3JG*$CPbW{ai~+YKst^~FM~<3EfiB@^daBTnMqm zMN{5L^fgdA=Yfq0!PpnTx+qPBLwFTUzHBdvZ|fFbRjtZdAefCWRqFoE`%_PfqL^a$ NuF?)e%EViG1WvyrZNLBk literal 0 HcmV?d00001 diff --git a/src/locate/mod.rs b/src/locate/mod.rs index 76976adb..404a125b 100644 --- a/src/locate/mod.rs +++ b/src/locate/mod.rs @@ -17,6 +17,7 @@ use std::{ use chrono::{DateTime, Local, TimeDelta}; use clap::{self, crate_version, value_parser, Arg, ArgAction, ArgMatches, Command, Id}; +use itertools::Itertools; use onig::{Regex, RegexOptions, Syntax}; use quick_error::quick_error; use uucore::error::{ClapErrorWrapper, UClapError, UError, UResult}; @@ -59,6 +60,7 @@ quick_error! { pub enum Error { NoMatches {} InvalidDbType { display("Unknown database type") } + InvalidDb(path: String) { display("locate database {path} is corrupt or invalid") } IoErr(err: io::Error) { from() source(err) display("{err}") } ClapErr(err: ClapErrorWrapper) { from() source(err) display("{err}") } /// General copy error @@ -412,7 +414,7 @@ struct DbReader { } impl Iterator for DbReader { - type Item = CString; + type Item = LocateResult; fn next(&mut self) -> Option { // 1 byte for the prefix delta @@ -438,7 +440,9 @@ impl Iterator for DbReader { .collect::>() }); if (prefix.as_ref().map(|v| v.len()).unwrap_or(0) as isize) < size { - return None; + return Some(Err(Error::InvalidDb( + self.path.to_string_lossy().to_string(), + ))); } let res = CString::from_vec_with_nul( prefix @@ -450,7 +454,7 @@ impl Iterator for DbReader { ) .ok()?; self.prev = Some(res.clone()); - Some(res) + Some(Ok(res)) } } @@ -590,29 +594,33 @@ fn do_locate(args: &[&str]) -> LocateResult<()> { // find matches let count = dbreader .by_ref() - .filter(|s| match_entry(s.as_c_str(), &config, &patterns)) - .take(config.limit.unwrap_or(usize::MAX)) - .inspect(|s| { - if config.mode == Mode::Normal || config.print { - if config.null_bytes { - print!("{}\0", s.to_string_lossy()); - } else { - println!("{}", s.to_string_lossy());} - } - if config.mode == Mode::Statistics { - stats.add_match(s); - } - }) - .count(); + .process_results(|iter| + iter + .filter(|s| match_entry(s.as_c_str(), &config, &patterns)) + .take(config.limit.unwrap_or(usize::MAX)) + .inspect(|s| { + if config.mode == Mode::Normal || config.print { + if config.null_bytes { + print!("{}\0", s.to_string_lossy()); + } else { + println!("{}", s.to_string_lossy()); + } + } + if config.mode == Mode::Statistics { + stats.add_match(s); + } + }) + .count() + ); // print the rest of the statistics description - if config.mode == Mode::Statistics { + if config.mode == Mode::Statistics && count.is_ok() { stats.print(&dbreader); } count }) - .sum::(); + .try_fold(0, |acc, e| e.map(|e| acc + e))?; if config.mode == Mode::Count { println!("{count}"); diff --git a/tests/db_tests.rs b/tests/db_tests.rs index 9e7f1248..3220dda1 100644 --- a/tests/db_tests.rs +++ b/tests/db_tests.rs @@ -21,6 +21,8 @@ fn add_special_files() -> io::Result<()> { #[cfg(not(windows))] const DB_FLAG: &str = "--database=test_data_db"; +#[cfg(not(windows))] +const INVALID_DB_FLAG: &str = "--database=invalid_db"; #[test] #[cfg(not(windows))] @@ -133,6 +135,16 @@ fn test_locate_all_regex() { .success(); } +#[test] +#[cfg(not(windows))] +fn test_locate_invalid_db() { + Command::cargo_bin("locate") + .expect("couldn't find locate binary") + .args(["test_data", INVALID_DB_FLAG]) + .assert() + .failure(); +} + #[rstest] #[cfg(not(windows))] fn test_updatedb(_add_special_files: io::Result<()>) { From 1f8d660cc1f1f1f46e0353f4db2f86a9848a2d0b Mon Sep 17 00:00:00 2001 From: Jeremy Smart Date: Sat, 10 May 2025 22:08:41 -0400 Subject: [PATCH 11/13] add tests --- old_db | Bin 0 -> 410 bytes tests/db_tests.rs | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+) create mode 100644 old_db diff --git a/old_db b/old_db new file mode 100644 index 0000000000000000000000000000000000000000..3fafa8ee8e9a0ed22979e559653006720fb41845 GIT binary patch literal 410 zcmc&xyG{c!5S)aBBK$LM(W56|uD#VrKk>iR;feXULx+j%Jr(efEseF7wmr$0E#*^u6 zo}FeGoL{Olv?<>951;QJ!SiZUs*osljh7CgXcLx@C+Hk|&9KunA;V8;>?S0?TnMp7 zt1gEUT@94Zd0=ZoF!m9!E=p73H$00vUpAM-mvt+zsz&835X{z>Ds>O@j_O5G6ia-+ Ns io::Result<()> { const DB_FLAG: &str = "--database=test_data_db"; #[cfg(not(windows))] const INVALID_DB_FLAG: &str = "--database=invalid_db"; +#[cfg(not(windows))] +const OLD_DB_FLAG: &str = "--database=old_db"; #[test] #[cfg(not(windows))] @@ -145,6 +147,36 @@ fn test_locate_invalid_db() { .failure(); } +#[test] +#[cfg(not(windows))] +fn test_locate_outdated_db() { + Command::cargo_bin("locate") + .expect("couldn't find locate binary") + .args(["test_data", OLD_DB_FLAG]) + .assert() + .success(); +} + +#[test] +#[cfg(not(windows))] +fn test_locate_print_help() { + Command::cargo_bin("locate") + .expect("couldn't find locate binary") + .arg("--help") + .assert() + .success(); +} + +#[test] +#[cfg(not(windows))] +fn test_locate_invalid_flag() { + Command::cargo_bin("locate") + .expect("couldn't find locate binary") + .arg("--unknown") + .assert() + .failure(); +} + #[rstest] #[cfg(not(windows))] fn test_updatedb(_add_special_files: io::Result<()>) { From df5d6655a7bd15a7ec711259e0cdb8e982aad7b1 Mon Sep 17 00:00:00 2001 From: Jeremy Smart Date: Sat, 10 May 2025 22:24:54 -0400 Subject: [PATCH 12/13] add test --- tests/db_tests.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/db_tests.rs b/tests/db_tests.rs index 18f0303c..4350be9b 100644 --- a/tests/db_tests.rs +++ b/tests/db_tests.rs @@ -186,3 +186,13 @@ fn test_updatedb(_add_special_files: io::Result<()>) { .assert() .success(); } + +#[test] +#[cfg(not(windows))] +fn test_updatedb_invalid_flag() { + Command::cargo_bin("updatedb") + .expect("couldn't find updatedb binary") + .args(["--unknown"]) + .assert() + .failure(); +} From 298b78712848b848714e1066c4f6b770f292c610 Mon Sep 17 00:00:00 2001 From: Jeremy Smart Date: Sat, 10 May 2025 22:28:51 -0400 Subject: [PATCH 13/13] fix test for coverage --- tests/db_tests.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/db_tests.rs b/tests/db_tests.rs index 4350be9b..e9604a7b 100644 --- a/tests/db_tests.rs +++ b/tests/db_tests.rs @@ -92,7 +92,7 @@ fn test_locate_statistics(add_special_files: io::Result<()>) { if add_special_files.is_ok() { Command::cargo_bin("locate") .expect("couldn't find locate binary") - .args(["abbbc", "--statistics", DB_FLAG]) + .args(["", "--statistics", DB_FLAG]) .assert() .success(); }