mirror of
https://github.com/uutils/findutils.git
synced 2026-06-10 15:48:30 -07:00
Compare commits
49 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e3f4224f12 | |||
| 8fc5ddb299 | |||
| b4f22f0747 | |||
| db8e265547 | |||
| f9d74ada2e | |||
| 907f3841ab | |||
| 888d3cd516 | |||
| f2e49d8d48 | |||
| d847a7bf7e | |||
| abf851dd7d | |||
| d9d2a4e865 | |||
| 9b0fea9880 | |||
| a7a73c325d | |||
| 465856ce24 | |||
| e456c23d5c | |||
| 4dc350e5fb | |||
| fcc929a978 | |||
| 423453eaf8 | |||
| 79986a26f2 | |||
| de1bc84812 | |||
| f0cd3748b8 | |||
| 780d106b34 | |||
| 4043c00bde | |||
| f0c3562aca | |||
| d7bf7da4a1 | |||
| 73dcd94827 | |||
| 60e4a8cf92 | |||
| 8b295e7d8a | |||
| 2dc916f980 | |||
| 19012452e1 | |||
| 6042d5a47f | |||
| 45e5964e6a | |||
| 9544f78977 | |||
| 5be00c85d7 | |||
| 8771861b0c | |||
| c4a56e9c3f | |||
| 3b222d6040 | |||
| 6f53585a4c | |||
| 1b9904eb5f | |||
| 4807264faa | |||
| 9f44977270 | |||
| 7f4423e703 | |||
| e9634f1ed4 | |||
| 37a543b9d6 | |||
| 9c11f11045 | |||
| a0b5713b61 | |||
| 5d4b9ca5e2 | |||
| 3bf5567397 | |||
| bbc9f06543 |
@@ -92,7 +92,7 @@ jobs:
|
||||
with:
|
||||
repository: tavianator/bfs
|
||||
path: bfs
|
||||
ref: "3.1.3"
|
||||
ref: "4.0"
|
||||
- name: Install `rust` toolchain
|
||||
run: |
|
||||
## Install `rust` toolchain
|
||||
|
||||
Generated
+85
-47
@@ -42,9 +42,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "anstyle"
|
||||
version = "1.0.0"
|
||||
version = "1.0.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41ed9a86bf92ae6580e0a31281f65a1b1d867c0cc68d5346e2ae128dddfa6a7d"
|
||||
checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1"
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-parse"
|
||||
@@ -76,13 +76,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "assert_cmd"
|
||||
version = "2.0.14"
|
||||
version = "2.0.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed72493ac66d5804837f480ab3766c72bdfab91a65e565fc54fa9e42db0073a8"
|
||||
checksum = "dc1835b7f27878de8525dc71410b5a31cdcc5f230aed5ba5df968e09c201b23d"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"bstr",
|
||||
"doc-comment",
|
||||
"libc",
|
||||
"predicates",
|
||||
"predicates-core",
|
||||
"predicates-tree",
|
||||
@@ -159,23 +160,23 @@ dependencies = [
|
||||
"js-sys",
|
||||
"num-traits",
|
||||
"wasm-bindgen",
|
||||
"windows-targets 0.52.0",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.7"
|
||||
version = "4.5.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5db83dced34638ad474f39f250d7fea9598bdd239eaced1bdf45d597da0f433f"
|
||||
checksum = "3e5a21b8495e732f1b3c364c9949b201ca7bae518c502c80256c96ad79eaf6ac"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.5.7"
|
||||
version = "4.5.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f7e204572485eb3fbf28f871612191521df159bc3e15a9f5064c66dba3a8c05f"
|
||||
checksum = "8cf2dd12af7a047ad9d6da2b6b249759a22a7abc0f474c1dae1777afa4b21a73"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
@@ -255,19 +256,19 @@ checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5"
|
||||
|
||||
[[package]]
|
||||
name = "filetime"
|
||||
version = "0.2.23"
|
||||
version = "0.2.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ee447700ac8aa0b2f2bd7bc4462ad686ba06baa6727ac149a2d6277f0d240fd"
|
||||
checksum = "35c0522e981e68cbfa8c3f978441a5f34b30b96e146b33cd3359176b50fe8586"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"redox_syscall",
|
||||
"windows-sys 0.52.0",
|
||||
"libredox",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "findutils"
|
||||
version = "0.6.0"
|
||||
version = "0.7.0"
|
||||
dependencies = [
|
||||
"assert_cmd",
|
||||
"chrono",
|
||||
@@ -407,6 +408,17 @@ version = "0.2.155"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c"
|
||||
|
||||
[[package]]
|
||||
name = "libredox"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d"
|
||||
dependencies = [
|
||||
"bitflags 2.4.1",
|
||||
"libc",
|
||||
"redox_syscall 0.5.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.4.12"
|
||||
@@ -538,7 +550,7 @@ checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"redox_syscall",
|
||||
"redox_syscall 0.4.1",
|
||||
"smallvec",
|
||||
"windows-targets 0.48.0",
|
||||
]
|
||||
@@ -563,9 +575,9 @@ checksum = "58893f751c9b0412871a09abd62ecd2a00298c6c83befa223ef98c52aef40cbe"
|
||||
|
||||
[[package]]
|
||||
name = "predicates"
|
||||
version = "3.1.0"
|
||||
version = "3.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "68b87bfd4605926cdfefc1c3b5f8fe560e3feca9d5552cf68c466d3d8236c7e8"
|
||||
checksum = "7e9086cc7640c29a356d1a29fd134380bee9d8f79a17410aa76e7ad295f42c97"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"difflib",
|
||||
@@ -629,10 +641,19 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.10.5"
|
||||
name = "redox_syscall"
|
||||
version = "0.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f"
|
||||
checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4"
|
||||
dependencies = [
|
||||
"bitflags 2.4.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.10.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
@@ -762,14 +783,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.10.1"
|
||||
version = "3.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1"
|
||||
checksum = "04cbcdd0c794ebb0d4cf35e88edd2f7d2c4c3e9a5a6dab322839b321c6a87a64"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"fastrand",
|
||||
"once_cell",
|
||||
"rustix",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -971,7 +993,16 @@ version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
|
||||
dependencies = [
|
||||
"windows-targets 0.52.0",
|
||||
"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 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -991,17 +1022,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.52.0"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd"
|
||||
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm 0.52.0",
|
||||
"windows_aarch64_msvc 0.52.0",
|
||||
"windows_i686_gnu 0.52.0",
|
||||
"windows_i686_msvc 0.52.0",
|
||||
"windows_x86_64_gnu 0.52.0",
|
||||
"windows_x86_64_gnullvm 0.52.0",
|
||||
"windows_x86_64_msvc 0.52.0",
|
||||
"windows_aarch64_gnullvm 0.52.6",
|
||||
"windows_aarch64_msvc 0.52.6",
|
||||
"windows_i686_gnu 0.52.6",
|
||||
"windows_i686_gnullvm",
|
||||
"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]]
|
||||
@@ -1012,9 +1044,9 @@ checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.52.0"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea"
|
||||
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
@@ -1024,9 +1056,9 @@ checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.52.0"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef"
|
||||
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
@@ -1036,9 +1068,15 @@ checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.52.0"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313"
|
||||
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
@@ -1048,9 +1086,9 @@ checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.52.0"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a"
|
||||
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
@@ -1060,9 +1098,9 @@ checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.52.0"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd"
|
||||
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
@@ -1072,9 +1110,9 @@ checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.52.0"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e"
|
||||
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
@@ -1084,9 +1122,9 @@ checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.52.0"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"
|
||||
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
||||
|
||||
[[package]]
|
||||
name = "yansi"
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "findutils"
|
||||
version = "0.6.0"
|
||||
version = "0.7.0"
|
||||
homepage = "https://github.com/uutils/findutils"
|
||||
repository = "https://github.com/uutils/findutils"
|
||||
edition = "2021"
|
||||
|
||||
@@ -5,9 +5,8 @@
|
||||
// https://opensource.org/licenses/MIT.
|
||||
|
||||
use faccess::PathExt;
|
||||
use walkdir::DirEntry;
|
||||
|
||||
use super::{Matcher, MatcherIO};
|
||||
use super::{Matcher, MatcherIO, WalkEntry};
|
||||
|
||||
/// Matcher for -{read,writ,execut}able.
|
||||
pub enum AccessMatcher {
|
||||
@@ -17,7 +16,7 @@ pub enum AccessMatcher {
|
||||
}
|
||||
|
||||
impl Matcher for AccessMatcher {
|
||||
fn matches(&self, file_info: &DirEntry, _: &mut MatcherIO) -> bool {
|
||||
fn matches(&self, file_info: &WalkEntry, _: &mut MatcherIO) -> bool {
|
||||
let path = file_info.path();
|
||||
|
||||
match self {
|
||||
|
||||
@@ -7,13 +7,10 @@
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
use std::fs::{self, FileType};
|
||||
use std::fs;
|
||||
use std::io::{self, stderr, Write};
|
||||
use std::path::Path;
|
||||
|
||||
use walkdir::DirEntry;
|
||||
|
||||
use super::{Matcher, MatcherIO};
|
||||
use super::{Matcher, MatcherIO, WalkEntry};
|
||||
|
||||
pub struct DeleteMatcher;
|
||||
|
||||
@@ -22,17 +19,17 @@ impl DeleteMatcher {
|
||||
DeleteMatcher
|
||||
}
|
||||
|
||||
fn delete(&self, file_path: &Path, file_type: FileType) -> io::Result<()> {
|
||||
if file_type.is_dir() {
|
||||
fs::remove_dir(file_path)
|
||||
fn delete(&self, entry: &WalkEntry) -> io::Result<()> {
|
||||
if entry.file_type().is_dir() && !entry.path_is_symlink() {
|
||||
fs::remove_dir(entry.path())
|
||||
} else {
|
||||
fs::remove_file(file_path)
|
||||
fs::remove_file(entry.path())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Matcher for DeleteMatcher {
|
||||
fn matches(&self, file_info: &DirEntry, _: &mut MatcherIO) -> bool {
|
||||
fn matches(&self, file_info: &WalkEntry, matcher_io: &mut MatcherIO) -> bool {
|
||||
let path = file_info.path();
|
||||
let path_str = path.to_string_lossy();
|
||||
|
||||
@@ -43,9 +40,10 @@ impl Matcher for DeleteMatcher {
|
||||
return true;
|
||||
}
|
||||
|
||||
match self.delete(path, file_info.file_type()) {
|
||||
match self.delete(file_info) {
|
||||
Ok(()) => true,
|
||||
Err(e) => {
|
||||
matcher_io.set_exit_code(1);
|
||||
writeln!(&mut stderr(), "Failed to delete {path_str}: {e}").unwrap();
|
||||
false
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ use std::{
|
||||
io::{stderr, Write},
|
||||
};
|
||||
|
||||
use super::Matcher;
|
||||
use super::{Matcher, MatcherIO, WalkEntry};
|
||||
|
||||
pub struct EmptyMatcher;
|
||||
|
||||
@@ -20,7 +20,7 @@ impl EmptyMatcher {
|
||||
}
|
||||
|
||||
impl Matcher for EmptyMatcher {
|
||||
fn matches(&self, file_info: &walkdir::DirEntry, _: &mut super::MatcherIO) -> bool {
|
||||
fn matches(&self, file_info: &WalkEntry, _: &mut MatcherIO) -> bool {
|
||||
if file_info.file_type().is_file() {
|
||||
match file_info.metadata() {
|
||||
Ok(meta) => meta.len() == 0,
|
||||
|
||||
@@ -0,0 +1,343 @@
|
||||
//! Paths encountered during a walk.
|
||||
|
||||
use std::cell::OnceCell;
|
||||
use std::error::Error;
|
||||
use std::ffi::OsStr;
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
use std::fs::{self, Metadata};
|
||||
use std::io::{self, ErrorKind};
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::fs::FileTypeExt;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use walkdir::DirEntry;
|
||||
|
||||
use super::Follow;
|
||||
|
||||
/// Wrapper for a directory entry.
|
||||
#[derive(Debug)]
|
||||
enum Entry {
|
||||
/// Wraps an explicit path and depth.
|
||||
Explicit(PathBuf, usize),
|
||||
/// Wraps a WalkDir entry.
|
||||
WalkDir(DirEntry),
|
||||
}
|
||||
|
||||
/// File types.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum FileType {
|
||||
Unknown,
|
||||
Fifo,
|
||||
CharDevice,
|
||||
Directory,
|
||||
BlockDevice,
|
||||
Regular,
|
||||
Symlink,
|
||||
Socket,
|
||||
}
|
||||
|
||||
impl FileType {
|
||||
pub fn is_dir(self) -> bool {
|
||||
self == Self::Directory
|
||||
}
|
||||
|
||||
pub fn is_file(self) -> bool {
|
||||
self == Self::Regular
|
||||
}
|
||||
|
||||
pub fn is_symlink(self) -> bool {
|
||||
self == Self::Symlink
|
||||
}
|
||||
}
|
||||
|
||||
impl From<fs::FileType> for FileType {
|
||||
fn from(t: fs::FileType) -> FileType {
|
||||
if t.is_dir() {
|
||||
return FileType::Directory;
|
||||
}
|
||||
if t.is_file() {
|
||||
return FileType::Regular;
|
||||
}
|
||||
if t.is_symlink() {
|
||||
return FileType::Symlink;
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
{
|
||||
if t.is_fifo() {
|
||||
return FileType::Fifo;
|
||||
}
|
||||
if t.is_char_device() {
|
||||
return FileType::CharDevice;
|
||||
}
|
||||
if t.is_block_device() {
|
||||
return FileType::BlockDevice;
|
||||
}
|
||||
if t.is_socket() {
|
||||
return FileType::Socket;
|
||||
}
|
||||
}
|
||||
|
||||
FileType::Unknown
|
||||
}
|
||||
}
|
||||
|
||||
/// An error encountered while walking a file system.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct WalkError {
|
||||
/// The path that caused the error, if known.
|
||||
path: Option<PathBuf>,
|
||||
/// The depth below the root path, if known.
|
||||
depth: Option<usize>,
|
||||
/// The io::Error::raw_os_error(), if known.
|
||||
raw: Option<i32>,
|
||||
}
|
||||
|
||||
impl WalkError {
|
||||
/// Get the path this error occurred on, if known.
|
||||
pub fn path(&self) -> Option<&Path> {
|
||||
self.path.as_deref()
|
||||
}
|
||||
|
||||
/// Get the traversal depth when this error occurred, if known.
|
||||
pub fn depth(&self) -> Option<usize> {
|
||||
self.depth
|
||||
}
|
||||
|
||||
/// Get the kind of I/O error.
|
||||
pub fn kind(&self) -> ErrorKind {
|
||||
io::Error::from(self).kind()
|
||||
}
|
||||
|
||||
/// Check for ErrorKind::{NotFound,NotADirectory}.
|
||||
pub fn is_not_found(&self) -> bool {
|
||||
if self.kind() == ErrorKind::NotFound {
|
||||
return true;
|
||||
}
|
||||
|
||||
// NotADirectory is nightly-only
|
||||
#[cfg(unix)]
|
||||
{
|
||||
if self.raw == Some(uucore::libc::ENOTDIR) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
/// Check for ErrorKind::FilesystemLoop.
|
||||
pub fn is_loop(&self) -> bool {
|
||||
#[cfg(unix)]
|
||||
return self.raw == Some(uucore::libc::ELOOP);
|
||||
|
||||
#[cfg(not(unix))]
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for WalkError {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> {
|
||||
let ioe = io::Error::from(self);
|
||||
if let Some(path) = &self.path {
|
||||
write!(f, "{}: {}", path.display(), ioe)
|
||||
} else {
|
||||
write!(f, "{}", ioe)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for WalkError {}
|
||||
|
||||
impl From<io::Error> for WalkError {
|
||||
fn from(e: io::Error) -> WalkError {
|
||||
WalkError::from(&e)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&io::Error> for WalkError {
|
||||
fn from(e: &io::Error) -> WalkError {
|
||||
WalkError {
|
||||
path: None,
|
||||
depth: None,
|
||||
raw: e.raw_os_error(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<walkdir::Error> for WalkError {
|
||||
fn from(e: walkdir::Error) -> WalkError {
|
||||
WalkError::from(&e)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&walkdir::Error> for WalkError {
|
||||
fn from(e: &walkdir::Error) -> WalkError {
|
||||
WalkError {
|
||||
path: e.path().map(|p| p.to_owned()),
|
||||
depth: Some(e.depth()),
|
||||
raw: e.io_error().and_then(|e| e.raw_os_error()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<WalkError> for io::Error {
|
||||
fn from(e: WalkError) -> io::Error {
|
||||
io::Error::from(&e)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&WalkError> for io::Error {
|
||||
fn from(e: &WalkError) -> io::Error {
|
||||
e.raw
|
||||
.map(io::Error::from_raw_os_error)
|
||||
.unwrap_or_else(|| ErrorKind::Other.into())
|
||||
}
|
||||
}
|
||||
|
||||
/// A path encountered while walking a file system.
|
||||
#[derive(Debug)]
|
||||
pub struct WalkEntry {
|
||||
/// The wrapped path/dirent.
|
||||
inner: Entry,
|
||||
/// Whether to follow symlinks.
|
||||
follow: Follow,
|
||||
/// Cached metadata.
|
||||
meta: OnceCell<Result<Metadata, WalkError>>,
|
||||
}
|
||||
|
||||
impl WalkEntry {
|
||||
/// Create a new WalkEntry for a specific file.
|
||||
pub fn new(path: impl Into<PathBuf>, depth: usize, follow: Follow) -> Self {
|
||||
Self {
|
||||
inner: Entry::Explicit(path.into(), depth),
|
||||
follow,
|
||||
meta: OnceCell::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert a [walkdir::DirEntry] to a [WalkEntry]. Errors due to broken symbolic links will be
|
||||
/// converted to valid entries, but other errors will be propagated.
|
||||
pub fn from_walkdir(
|
||||
result: walkdir::Result<DirEntry>,
|
||||
follow: Follow,
|
||||
) -> Result<WalkEntry, WalkError> {
|
||||
let result = result.map_err(WalkError::from);
|
||||
|
||||
match result {
|
||||
Ok(entry) => {
|
||||
let ret = if entry.depth() == 0 && follow != Follow::Never {
|
||||
// DirEntry::file_type() is wrong for root symlinks when follow_root_links is set
|
||||
Self::new(entry.path(), 0, follow)
|
||||
} else {
|
||||
Self {
|
||||
inner: Entry::WalkDir(entry),
|
||||
follow,
|
||||
meta: OnceCell::new(),
|
||||
}
|
||||
};
|
||||
Ok(ret)
|
||||
}
|
||||
Err(e) if e.is_not_found() => {
|
||||
// Detect broken symlinks and replace them with explicit entries
|
||||
if let (Some(path), Some(depth)) = (e.path(), e.depth()) {
|
||||
if let Ok(meta) = path.symlink_metadata() {
|
||||
return Ok(WalkEntry {
|
||||
inner: Entry::Explicit(path.into(), depth),
|
||||
follow: Follow::Never,
|
||||
meta: Ok(meta).into(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Err(e)
|
||||
}
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the path to this entry.
|
||||
pub fn path(&self) -> &Path {
|
||||
match &self.inner {
|
||||
Entry::Explicit(path, _) => path.as_path(),
|
||||
Entry::WalkDir(ent) => ent.path(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the path to this entry.
|
||||
pub fn into_path(self) -> PathBuf {
|
||||
match self.inner {
|
||||
Entry::Explicit(path, _) => path,
|
||||
Entry::WalkDir(ent) => ent.into_path(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the name of this entry.
|
||||
pub fn file_name(&self) -> &OsStr {
|
||||
match &self.inner {
|
||||
Entry::Explicit(path, _) => {
|
||||
// Path::file_name() only works if the last component is normal
|
||||
path.components()
|
||||
.last()
|
||||
.map(|c| c.as_os_str())
|
||||
.unwrap_or_else(|| path.as_os_str())
|
||||
}
|
||||
Entry::WalkDir(ent) => ent.file_name(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the depth of this entry below the root.
|
||||
pub fn depth(&self) -> usize {
|
||||
match &self.inner {
|
||||
Entry::Explicit(_, depth) => *depth,
|
||||
Entry::WalkDir(ent) => ent.depth(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get whether symbolic links are followed for this entry.
|
||||
pub fn follow(&self) -> bool {
|
||||
self.follow.follow_at_depth(self.depth())
|
||||
}
|
||||
|
||||
/// Get the metadata on a cache miss.
|
||||
fn get_metadata(&self) -> Result<Metadata, WalkError> {
|
||||
self.follow.metadata_at_depth(self.path(), self.depth())
|
||||
}
|
||||
|
||||
/// Get the [Metadata] for this entry, following symbolic links if appropriate.
|
||||
/// Multiple calls to this function will cache and re-use the same [Metadata].
|
||||
pub fn metadata(&self) -> Result<&Metadata, WalkError> {
|
||||
let result = self.meta.get_or_init(|| match &self.inner {
|
||||
Entry::Explicit(_, _) => Ok(self.get_metadata()?),
|
||||
Entry::WalkDir(ent) => Ok(ent.metadata()?),
|
||||
});
|
||||
result.as_ref().map_err(|e| e.clone())
|
||||
}
|
||||
|
||||
/// Get the file type of this entry.
|
||||
pub fn file_type(&self) -> FileType {
|
||||
match &self.inner {
|
||||
Entry::Explicit(_, _) => self
|
||||
.metadata()
|
||||
.map(|m| m.file_type().into())
|
||||
.unwrap_or(FileType::Unknown),
|
||||
Entry::WalkDir(ent) => ent.file_type().into(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Check whether this entry is a symbolic link, regardless of whether links
|
||||
/// are being followed.
|
||||
pub fn path_is_symlink(&self) -> bool {
|
||||
match &self.inner {
|
||||
Entry::Explicit(path, _) => {
|
||||
if self.follow() {
|
||||
path.symlink_metadata()
|
||||
.is_ok_and(|m| m.file_type().is_symlink())
|
||||
} else {
|
||||
self.file_type().is_symlink()
|
||||
}
|
||||
}
|
||||
Entry::WalkDir(ent) => ent.path_is_symlink(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,9 +9,8 @@ use std::ffi::OsString;
|
||||
use std::io::{stderr, Write};
|
||||
use std::path::Path;
|
||||
use std::process::Command;
|
||||
use walkdir::DirEntry;
|
||||
|
||||
use super::{Matcher, MatcherIO};
|
||||
use super::{Matcher, MatcherIO, WalkEntry};
|
||||
|
||||
enum Arg {
|
||||
FileArg(Vec<OsString>),
|
||||
@@ -52,7 +51,7 @@ impl SingleExecMatcher {
|
||||
}
|
||||
|
||||
impl Matcher for SingleExecMatcher {
|
||||
fn matches(&self, file_info: &DirEntry, _: &mut MatcherIO) -> bool {
|
||||
fn matches(&self, file_info: &WalkEntry, _: &mut MatcherIO) -> bool {
|
||||
let mut command = Command::new(&self.executable);
|
||||
let path_to_file = if self.exec_in_parent_dir {
|
||||
if let Some(f) = file_info.path().file_name() {
|
||||
@@ -67,7 +66,7 @@ impl Matcher for SingleExecMatcher {
|
||||
for arg in &self.args {
|
||||
match *arg {
|
||||
Arg::LiteralArg(ref a) => command.arg(a.as_os_str()),
|
||||
Arg::FileArg(ref parts) => command.arg(&parts.join(path_to_file.as_os_str())),
|
||||
Arg::FileArg(ref parts) => command.arg(parts.join(path_to_file.as_os_str())),
|
||||
};
|
||||
}
|
||||
if self.exec_in_parent_dir {
|
||||
|
||||
+91
-31
@@ -2,19 +2,19 @@
|
||||
//
|
||||
// For the full copyright and license information, please view the LICENSE
|
||||
// file that was distributed with this source code.
|
||||
use super::{Matcher, MatcherIO, WalkEntry};
|
||||
|
||||
use std::path::Path;
|
||||
use std::{
|
||||
error::Error,
|
||||
io::{stderr, Write},
|
||||
};
|
||||
|
||||
use super::Matcher;
|
||||
/// The latest mapping from dev_id to fs_type, used for saving mount info reads
|
||||
#[cfg(unix)]
|
||||
pub struct Cache {
|
||||
dev_id: String,
|
||||
fs_type: String,
|
||||
}
|
||||
|
||||
/// Get the filesystem type of a file.
|
||||
/// 1. get the metadata of the file
|
||||
/// 2. get the device ID of the metadata
|
||||
/// 3. search the filesystem list
|
||||
/// 3. search the cache, then the filesystem list
|
||||
///
|
||||
/// Returns an empty string when no file system list matches.
|
||||
///
|
||||
@@ -24,14 +24,33 @@ use super::Matcher;
|
||||
///
|
||||
/// This is only supported on Unix.
|
||||
#[cfg(unix)]
|
||||
pub fn get_file_system_type(path: &Path) -> Result<String, Box<dyn Error>> {
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
error::Error,
|
||||
io::{stderr, Write},
|
||||
path::Path,
|
||||
};
|
||||
#[cfg(unix)]
|
||||
pub fn get_file_system_type(
|
||||
path: &Path,
|
||||
cache: &RefCell<Option<Cache>>,
|
||||
) -> Result<String, Box<dyn Error>> {
|
||||
use std::os::unix::fs::MetadataExt;
|
||||
|
||||
let metadata = match path.metadata() {
|
||||
// use symlink_metadata (lstat under the hood) instead of metadata (stat) to make sure that it
|
||||
// does not return an error when there is a (broken) symlink; this is aligned with GNU find.
|
||||
let metadata = match path.symlink_metadata() {
|
||||
Ok(metadata) => metadata,
|
||||
Err(err) => Err(err)?,
|
||||
};
|
||||
let dev_id = metadata.dev().to_string();
|
||||
|
||||
if let Some(cache) = cache.borrow().as_ref() {
|
||||
if cache.dev_id == dev_id {
|
||||
return Ok(cache.fs_type.clone());
|
||||
}
|
||||
}
|
||||
|
||||
let fs_list = match uucore::fsext::read_fs_list() {
|
||||
Ok(fs_list) => fs_list,
|
||||
Err(err) => Err(err)?,
|
||||
@@ -41,6 +60,12 @@ pub fn get_file_system_type(path: &Path) -> Result<String, Box<dyn Error>> {
|
||||
.find(|fs| fs.dev_id == dev_id)
|
||||
.map_or_else(String::new, |fs| fs.fs_type);
|
||||
|
||||
// cache the latest query if not a match before
|
||||
cache.replace(Some(Cache {
|
||||
dev_id,
|
||||
fs_type: result.clone(),
|
||||
}));
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
@@ -49,38 +74,49 @@ pub fn get_file_system_type(path: &Path) -> Result<String, Box<dyn Error>> {
|
||||
///
|
||||
/// This is only supported on Unix.
|
||||
pub struct FileSystemMatcher {
|
||||
#[cfg(unix)]
|
||||
fs_text: String,
|
||||
#[cfg(unix)]
|
||||
cache: RefCell<Option<Cache>>,
|
||||
}
|
||||
|
||||
impl FileSystemMatcher {
|
||||
#[cfg(unix)]
|
||||
pub fn new(fs_text: String) -> Self {
|
||||
Self { fs_text }
|
||||
Self {
|
||||
fs_text,
|
||||
cache: RefCell::new(None),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(unix))]
|
||||
pub fn new(_fs_text: String) -> Self {
|
||||
Self {}
|
||||
}
|
||||
}
|
||||
|
||||
impl Matcher for FileSystemMatcher {
|
||||
fn matches(&self, file_info: &walkdir::DirEntry, _: &mut super::MatcherIO) -> bool {
|
||||
#[cfg(not(unix))]
|
||||
{
|
||||
false
|
||||
}
|
||||
#[cfg(unix)]
|
||||
{
|
||||
match get_file_system_type(file_info.path()) {
|
||||
Ok(result) => result == self.fs_text,
|
||||
Err(_) => {
|
||||
writeln!(
|
||||
&mut stderr(),
|
||||
"Error getting filesystem type for {}",
|
||||
file_info.path().to_string_lossy()
|
||||
)
|
||||
.unwrap();
|
||||
#[cfg(unix)]
|
||||
fn matches(&self, file_info: &WalkEntry, _: &mut MatcherIO) -> bool {
|
||||
match get_file_system_type(file_info.path(), &self.cache) {
|
||||
Ok(result) => result == self.fs_text,
|
||||
Err(_) => {
|
||||
writeln!(
|
||||
&mut stderr(),
|
||||
"Error getting filesystem type for {}",
|
||||
file_info.path().to_string_lossy()
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
false
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(unix))]
|
||||
fn matches(&self, _file_info: &WalkEntry, _: &mut MatcherIO) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -89,9 +125,14 @@ mod tests {
|
||||
#[cfg(unix)]
|
||||
fn test_fs_matcher() {
|
||||
use crate::find::{
|
||||
matchers::{fs::get_file_system_type, tests::get_dir_entry_for, Matcher},
|
||||
matchers::{
|
||||
fs::{get_file_system_type, Cache},
|
||||
tests::get_dir_entry_for,
|
||||
Matcher,
|
||||
},
|
||||
tests::FakeDependencies,
|
||||
};
|
||||
use std::cell::RefCell;
|
||||
use std::fs::File;
|
||||
use tempfile::Builder;
|
||||
|
||||
@@ -105,7 +146,26 @@ mod tests {
|
||||
let _ = File::create(foo_path).expect("create temp file");
|
||||
let file_info = get_dir_entry_for(&temp_dir.path().to_string_lossy(), "foo");
|
||||
|
||||
let target_fs_type = get_file_system_type(file_info.path()).unwrap();
|
||||
// create an empty cache for initial fs type lookup
|
||||
let empty_cache = RefCell::new(None);
|
||||
let target_fs_type = get_file_system_type(file_info.path(), &empty_cache).unwrap();
|
||||
|
||||
// should work with unmatched cache, and the cache should be set to the last query result
|
||||
let unmatched_cache = RefCell::new(Some(Cache {
|
||||
dev_id: "foo".to_string(),
|
||||
fs_type: "bar".to_string(),
|
||||
}));
|
||||
let target_fs_type_unmatched_cache =
|
||||
get_file_system_type(file_info.path(), &unmatched_cache).unwrap();
|
||||
assert_eq!(
|
||||
target_fs_type, target_fs_type_unmatched_cache,
|
||||
"get_file_system_type should return correct result with unmatched cache"
|
||||
);
|
||||
assert_eq!(
|
||||
unmatched_cache.borrow().as_ref().unwrap().fs_type,
|
||||
target_fs_type,
|
||||
"get_file_system_type should set the cache to the last query result"
|
||||
);
|
||||
|
||||
// should match fs type
|
||||
let matcher = super::FileSystemMatcher::new(target_fs_type.clone());
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
// For the full copyright and license information, please view the LICENSE
|
||||
// file that was distributed with this source code.
|
||||
|
||||
use super::Matcher;
|
||||
use super::{Matcher, MatcherIO, WalkEntry};
|
||||
|
||||
#[cfg(unix)]
|
||||
use nix::unistd::Group;
|
||||
@@ -57,8 +57,8 @@ impl GroupMatcher {
|
||||
|
||||
impl Matcher for GroupMatcher {
|
||||
#[cfg(unix)]
|
||||
fn matches(&self, file_info: &walkdir::DirEntry, _: &mut super::MatcherIO) -> bool {
|
||||
let Ok(metadata) = file_info.path().metadata() else {
|
||||
fn matches(&self, file_info: &WalkEntry, _: &mut MatcherIO) -> bool {
|
||||
let Ok(metadata) = file_info.metadata() else {
|
||||
return false;
|
||||
};
|
||||
|
||||
@@ -71,7 +71,7 @@ impl Matcher for GroupMatcher {
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn matches(&self, _file_info: &walkdir::DirEntry, _: &mut super::MatcherIO) -> bool {
|
||||
fn matches(&self, _file_info: &WalkEntry, _: &mut MatcherIO) -> bool {
|
||||
// The user group acquisition function for Windows systems is not implemented in MetadataExt,
|
||||
// so it is somewhat difficult to implement it. :(
|
||||
false
|
||||
@@ -82,14 +82,14 @@ pub struct NoGroupMatcher {}
|
||||
|
||||
impl Matcher for NoGroupMatcher {
|
||||
#[cfg(unix)]
|
||||
fn matches(&self, file_info: &walkdir::DirEntry, _: &mut super::MatcherIO) -> bool {
|
||||
fn matches(&self, file_info: &WalkEntry, _: &mut MatcherIO) -> bool {
|
||||
use nix::unistd::Gid;
|
||||
|
||||
if file_info.path().is_symlink() {
|
||||
return false;
|
||||
}
|
||||
|
||||
let Ok(metadata) = file_info.path().metadata() else {
|
||||
let Ok(metadata) = file_info.metadata() else {
|
||||
return true;
|
||||
};
|
||||
|
||||
@@ -105,7 +105,7 @@ impl Matcher for NoGroupMatcher {
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn matches(&self, _file_info: &walkdir::DirEntry, _: &mut super::MatcherIO) -> bool {
|
||||
fn matches(&self, _file_info: &WalkEntry, _: &mut MatcherIO) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
@@ -130,7 +130,7 @@ mod tests {
|
||||
let foo_path = temp_dir.path().join("foo");
|
||||
let _ = File::create(foo_path).expect("create temp file");
|
||||
let file_info = get_dir_entry_for(&temp_dir.path().to_string_lossy(), "foo");
|
||||
let file_gid = file_info.path().metadata().unwrap().gid();
|
||||
let file_gid = file_info.metadata().unwrap().gid();
|
||||
let file_group = Group::from_gid(Gid::from_raw(file_gid))
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
|
||||
@@ -7,12 +7,10 @@
|
||||
use std::io::{stderr, Write};
|
||||
use std::path::PathBuf;
|
||||
|
||||
use walkdir::DirEntry;
|
||||
|
||||
use super::glob::Pattern;
|
||||
use super::{Matcher, MatcherIO};
|
||||
use super::{Matcher, MatcherIO, WalkEntry};
|
||||
|
||||
fn read_link_target(file_info: &DirEntry) -> Option<PathBuf> {
|
||||
fn read_link_target(file_info: &WalkEntry) -> Option<PathBuf> {
|
||||
match file_info.path().read_link() {
|
||||
Ok(target) => Some(target),
|
||||
Err(err) => {
|
||||
@@ -47,7 +45,7 @@ impl LinkNameMatcher {
|
||||
}
|
||||
|
||||
impl Matcher for LinkNameMatcher {
|
||||
fn matches(&self, file_info: &DirEntry, _: &mut MatcherIO) -> bool {
|
||||
fn matches(&self, file_info: &WalkEntry, _: &mut MatcherIO) -> bool {
|
||||
if let Some(target) = read_link_target(file_info) {
|
||||
self.pattern.matches(&target.to_string_lossy())
|
||||
} else {
|
||||
|
||||
@@ -11,9 +11,8 @@
|
||||
//! to "-foo -o ( -bar -baz )", not "( -foo -o -bar ) -baz").
|
||||
use std::error::Error;
|
||||
use std::path::Path;
|
||||
use walkdir::DirEntry;
|
||||
|
||||
use super::{Matcher, MatcherIO};
|
||||
use super::{Matcher, MatcherIO, WalkEntry};
|
||||
|
||||
/// This matcher contains a collection of other matchers. A file only matches
|
||||
/// if it matches ALL the contained sub-matchers. For sub-matchers that have
|
||||
@@ -33,7 +32,7 @@ impl Matcher for AndMatcher {
|
||||
/// Returns true if all sub-matchers return true. Short-circuiting does take
|
||||
/// place. If the nth sub-matcher returns false, then we immediately return
|
||||
/// and don't make any further calls.
|
||||
fn matches(&self, dir_entry: &DirEntry, matcher_io: &mut MatcherIO) -> bool {
|
||||
fn matches(&self, dir_entry: &WalkEntry, matcher_io: &mut MatcherIO) -> bool {
|
||||
for matcher in &self.submatchers {
|
||||
if !matcher.matches(dir_entry, matcher_io) {
|
||||
return false;
|
||||
@@ -109,7 +108,7 @@ impl Matcher for OrMatcher {
|
||||
/// Returns true if any sub-matcher returns true. Short-circuiting does take
|
||||
/// place. If the nth sub-matcher returns true, then we immediately return
|
||||
/// and don't make any further calls.
|
||||
fn matches(&self, dir_entry: &DirEntry, matcher_io: &mut MatcherIO) -> bool {
|
||||
fn matches(&self, dir_entry: &WalkEntry, matcher_io: &mut MatcherIO) -> bool {
|
||||
for matcher in &self.submatchers {
|
||||
if matcher.matches(dir_entry, matcher_io) {
|
||||
return true;
|
||||
@@ -206,7 +205,7 @@ impl ListMatcher {
|
||||
impl Matcher for ListMatcher {
|
||||
/// Calls matches on all submatcher objects, with no short-circuiting.
|
||||
/// Returns the result of the call to the final submatcher
|
||||
fn matches(&self, dir_entry: &DirEntry, matcher_io: &mut MatcherIO) -> bool {
|
||||
fn matches(&self, dir_entry: &WalkEntry, matcher_io: &mut MatcherIO) -> bool {
|
||||
let mut rc = false;
|
||||
for matcher in &self.submatchers {
|
||||
rc = matcher.matches(dir_entry, matcher_io);
|
||||
@@ -311,7 +310,7 @@ impl ListMatcherBuilder {
|
||||
pub struct TrueMatcher;
|
||||
|
||||
impl Matcher for TrueMatcher {
|
||||
fn matches(&self, _dir_entry: &DirEntry, _: &mut MatcherIO) -> bool {
|
||||
fn matches(&self, _dir_entry: &WalkEntry, _: &mut MatcherIO) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
@@ -320,7 +319,7 @@ impl Matcher for TrueMatcher {
|
||||
pub struct FalseMatcher;
|
||||
|
||||
impl Matcher for FalseMatcher {
|
||||
fn matches(&self, _dir_entry: &DirEntry, _: &mut MatcherIO) -> bool {
|
||||
fn matches(&self, _dir_entry: &WalkEntry, _: &mut MatcherIO) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
@@ -339,7 +338,7 @@ impl NotMatcher {
|
||||
}
|
||||
|
||||
impl Matcher for NotMatcher {
|
||||
fn matches(&self, dir_entry: &DirEntry, matcher_io: &mut MatcherIO) -> bool {
|
||||
fn matches(&self, dir_entry: &WalkEntry, matcher_io: &mut MatcherIO) -> bool {
|
||||
!self.submatcher.matches(dir_entry, matcher_io)
|
||||
}
|
||||
|
||||
@@ -370,7 +369,7 @@ mod tests {
|
||||
pub struct HasSideEffects;
|
||||
|
||||
impl Matcher for HasSideEffects {
|
||||
fn matches(&self, _: &DirEntry, _: &mut MatcherIO) -> bool {
|
||||
fn matches(&self, _: &WalkEntry, _: &mut MatcherIO) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
@@ -383,7 +382,7 @@ mod tests {
|
||||
struct Counter(Rc<RefCell<u32>>);
|
||||
|
||||
impl Matcher for Counter {
|
||||
fn matches(&self, _: &DirEntry, _: &mut MatcherIO) -> bool {
|
||||
fn matches(&self, _: &WalkEntry, _: &mut MatcherIO) -> bool {
|
||||
*self.0.borrow_mut() += 1;
|
||||
true
|
||||
}
|
||||
|
||||
@@ -0,0 +1,294 @@
|
||||
// This file is part of the uutils findutils package.
|
||||
//
|
||||
// For the full copyright and license information, please view the LICENSE
|
||||
// file that was distributed with this source code.
|
||||
|
||||
use chrono::DateTime;
|
||||
use std::{
|
||||
fs::File,
|
||||
io::{stderr, Write},
|
||||
};
|
||||
|
||||
use super::{Matcher, MatcherIO, WalkEntry};
|
||||
|
||||
#[cfg(unix)]
|
||||
fn format_permissions(mode: uucore::libc::mode_t) -> String {
|
||||
let file_type = match mode & (uucore::libc::S_IFMT as uucore::libc::mode_t) {
|
||||
uucore::libc::S_IFDIR => "d",
|
||||
uucore::libc::S_IFREG => "-",
|
||||
_ => "?",
|
||||
};
|
||||
|
||||
// S_$$USR means "user permissions"
|
||||
let user_perms = format!(
|
||||
"{}{}{}",
|
||||
if mode & uucore::libc::S_IRUSR != 0 {
|
||||
"r"
|
||||
} else {
|
||||
"-"
|
||||
},
|
||||
if mode & uucore::libc::S_IWUSR != 0 {
|
||||
"w"
|
||||
} else {
|
||||
"-"
|
||||
},
|
||||
if mode & uucore::libc::S_IXUSR != 0 {
|
||||
"x"
|
||||
} else {
|
||||
"-"
|
||||
}
|
||||
);
|
||||
|
||||
// S_$$GRP means "group permissions"
|
||||
let group_perms = format!(
|
||||
"{}{}{}",
|
||||
if mode & uucore::libc::S_IRGRP != 0 {
|
||||
"r"
|
||||
} else {
|
||||
"-"
|
||||
},
|
||||
if mode & uucore::libc::S_IWGRP != 0 {
|
||||
"w"
|
||||
} else {
|
||||
"-"
|
||||
},
|
||||
if mode & uucore::libc::S_IXGRP != 0 {
|
||||
"x"
|
||||
} else {
|
||||
"-"
|
||||
}
|
||||
);
|
||||
|
||||
// S_$$OTH means "other permissions"
|
||||
let other_perms = format!(
|
||||
"{}{}{}",
|
||||
if mode & uucore::libc::S_IROTH != 0 {
|
||||
"r"
|
||||
} else {
|
||||
"-"
|
||||
},
|
||||
if mode & uucore::libc::S_IWOTH != 0 {
|
||||
"w"
|
||||
} else {
|
||||
"-"
|
||||
},
|
||||
if mode & uucore::libc::S_IXOTH != 0 {
|
||||
"x"
|
||||
} else {
|
||||
"-"
|
||||
}
|
||||
);
|
||||
|
||||
format!("{}{}{}{}", file_type, user_perms, group_perms, other_perms)
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn format_permissions(file_attributes: u32) -> String {
|
||||
let mut attributes = Vec::new();
|
||||
|
||||
// https://learn.microsoft.com/en-us/windows/win32/fileio/file-attribute-constants
|
||||
if file_attributes & 0x0001 != 0 {
|
||||
attributes.push("read-only");
|
||||
}
|
||||
if file_attributes & 0x0002 != 0 {
|
||||
attributes.push("hidden");
|
||||
}
|
||||
if file_attributes & 0x0004 != 0 {
|
||||
attributes.push("system");
|
||||
}
|
||||
if file_attributes & 0x0020 != 0 {
|
||||
attributes.push("archive");
|
||||
}
|
||||
if file_attributes & 0x0040 != 0 {
|
||||
attributes.push("compressed");
|
||||
}
|
||||
if file_attributes & 0x0080 != 0 {
|
||||
attributes.push("offline");
|
||||
}
|
||||
|
||||
attributes.join(", ")
|
||||
}
|
||||
|
||||
pub struct Ls {
|
||||
output_file: Option<File>,
|
||||
}
|
||||
|
||||
impl Ls {
|
||||
pub fn new(output_file: Option<File>) -> Self {
|
||||
Self { output_file }
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn print(&self, file_info: &WalkEntry, mut out: impl Write, print_error_message: bool) {
|
||||
use nix::unistd::{Gid, Group, Uid, User};
|
||||
use std::os::unix::fs::{MetadataExt, PermissionsExt};
|
||||
|
||||
let metadata = file_info.metadata().unwrap();
|
||||
|
||||
let inode_number = metadata.ino();
|
||||
let number_of_blocks = {
|
||||
let size = metadata.size();
|
||||
let number_of_blocks = size / 1024;
|
||||
let remainder = number_of_blocks % 4;
|
||||
|
||||
if remainder == 0 {
|
||||
if number_of_blocks == 0 {
|
||||
4
|
||||
} else {
|
||||
number_of_blocks
|
||||
}
|
||||
} else {
|
||||
number_of_blocks + (4 - (remainder))
|
||||
}
|
||||
};
|
||||
let permission =
|
||||
{ format_permissions(metadata.permissions().mode() as uucore::libc::mode_t) };
|
||||
let hard_links = metadata.nlink();
|
||||
let user = {
|
||||
let uid = metadata.uid();
|
||||
User::from_uid(Uid::from_raw(uid)).unwrap().unwrap().name
|
||||
};
|
||||
let group = {
|
||||
let gid = metadata.gid();
|
||||
Group::from_gid(Gid::from_raw(gid)).unwrap().unwrap().name
|
||||
};
|
||||
let size = metadata.size();
|
||||
let last_modified = {
|
||||
let system_time = metadata.modified().unwrap();
|
||||
let now_utc: DateTime<chrono::Utc> = system_time.into();
|
||||
now_utc.format("%b %e %H:%M")
|
||||
};
|
||||
let path = file_info.path().to_string_lossy();
|
||||
|
||||
match writeln!(
|
||||
out,
|
||||
" {:<4} {:>6} {:<10} {:>3} {:<8} {:<8} {:>8} {} {}",
|
||||
inode_number,
|
||||
number_of_blocks,
|
||||
permission,
|
||||
hard_links,
|
||||
user,
|
||||
group,
|
||||
size,
|
||||
last_modified,
|
||||
path,
|
||||
) {
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
if print_error_message {
|
||||
writeln!(
|
||||
&mut stderr(),
|
||||
"Error writing {:?} for {}",
|
||||
file_info.path().to_string_lossy(),
|
||||
e
|
||||
)
|
||||
.unwrap();
|
||||
uucore::error::set_exit_code(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn print(&self, file_info: &WalkEntry, mut out: impl Write, print_error_message: bool) {
|
||||
use std::os::windows::fs::MetadataExt;
|
||||
|
||||
let metadata = file_info.metadata().unwrap();
|
||||
|
||||
let inode_number = 0;
|
||||
let number_of_blocks = {
|
||||
let size = metadata.file_size();
|
||||
let number_of_blocks = size / 1024;
|
||||
let remainder = number_of_blocks % 4;
|
||||
|
||||
if remainder == 0 {
|
||||
if number_of_blocks == 0 {
|
||||
4
|
||||
} else {
|
||||
number_of_blocks
|
||||
}
|
||||
} else {
|
||||
number_of_blocks + (4 - (remainder))
|
||||
}
|
||||
};
|
||||
let permission = { format_permissions(metadata.file_attributes()) };
|
||||
let hard_links = 0;
|
||||
let user = 0;
|
||||
let group = 0;
|
||||
let size = metadata.file_size();
|
||||
let last_modified = {
|
||||
let system_time = metadata.modified().unwrap();
|
||||
let now_utc: DateTime<chrono::Utc> = system_time.into();
|
||||
now_utc.format("%b %e %H:%M")
|
||||
};
|
||||
let path = file_info.path().to_string_lossy();
|
||||
|
||||
match write!(
|
||||
out,
|
||||
" {:<4} {:>6} {:<10} {:>3} {:<8} {:<8} {:>8} {} {}\n",
|
||||
inode_number,
|
||||
number_of_blocks,
|
||||
permission,
|
||||
hard_links,
|
||||
user,
|
||||
group,
|
||||
size,
|
||||
last_modified,
|
||||
path,
|
||||
) {
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
if print_error_message {
|
||||
writeln!(
|
||||
&mut stderr(),
|
||||
"Error writing {:?} for {}",
|
||||
file_info.path().to_string_lossy(),
|
||||
e
|
||||
)
|
||||
.unwrap();
|
||||
uucore::error::set_exit_code(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Matcher for Ls {
|
||||
fn matches(&self, file_info: &WalkEntry, matcher_io: &mut MatcherIO) -> bool {
|
||||
if let Some(file) = &self.output_file {
|
||||
self.print(file_info, file, true);
|
||||
} else {
|
||||
self.print(
|
||||
file_info,
|
||||
&mut *matcher_io.deps.get_output().borrow_mut(),
|
||||
false,
|
||||
);
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
fn has_side_effects(&self) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
fn test_format_permissions() {
|
||||
use super::format_permissions;
|
||||
|
||||
let mode: uucore::libc::mode_t = 0o100644;
|
||||
let expected = "-rw-r--r--";
|
||||
assert_eq!(format_permissions(mode), expected);
|
||||
|
||||
let mode: uucore::libc::mode_t = 0o040755;
|
||||
let expected = "drwxr-xr-x";
|
||||
assert_eq!(format_permissions(mode), expected);
|
||||
|
||||
let mode: uucore::libc::mode_t = 0o100777;
|
||||
let expected = "-rwxrwxrwx";
|
||||
assert_eq!(format_permissions(mode), expected);
|
||||
}
|
||||
}
|
||||
+248
-42
@@ -7,12 +7,14 @@
|
||||
mod access;
|
||||
mod delete;
|
||||
mod empty;
|
||||
mod entry;
|
||||
pub mod exec;
|
||||
pub mod fs;
|
||||
mod glob;
|
||||
mod group;
|
||||
mod lname;
|
||||
mod logical_matchers;
|
||||
mod ls;
|
||||
mod name;
|
||||
mod path;
|
||||
mod perm;
|
||||
@@ -25,17 +27,18 @@ mod samefile;
|
||||
mod size;
|
||||
#[cfg(unix)]
|
||||
mod stat;
|
||||
mod time;
|
||||
pub mod time;
|
||||
mod type_matcher;
|
||||
mod user;
|
||||
|
||||
use ::regex::Regex;
|
||||
use chrono::{DateTime, Datelike, NaiveDateTime, Utc};
|
||||
use fs::FileSystemMatcher;
|
||||
use ls::Ls;
|
||||
use std::fs::{File, Metadata};
|
||||
use std::path::Path;
|
||||
use std::time::SystemTime;
|
||||
use std::{error::Error, str::FromStr};
|
||||
use walkdir::DirEntry;
|
||||
|
||||
use self::access::AccessMatcher;
|
||||
use self::delete::DeleteMatcher;
|
||||
@@ -62,25 +65,91 @@ use self::time::{
|
||||
FileAgeRangeMatcher, FileTimeMatcher, FileTimeType, NewerMatcher, NewerOptionMatcher,
|
||||
NewerOptionType, NewerTimeMatcher,
|
||||
};
|
||||
use self::type_matcher::TypeMatcher;
|
||||
use self::type_matcher::{TypeMatcher, XtypeMatcher};
|
||||
use self::user::{NoUserMatcher, UserMatcher};
|
||||
|
||||
use super::{Config, Dependencies};
|
||||
|
||||
pub use entry::{FileType, WalkEntry, WalkError};
|
||||
|
||||
/// Symlink following mode.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum Follow {
|
||||
/// Never follow symlinks (-P; default).
|
||||
Never,
|
||||
/// Follow symlinks on root paths only (-H).
|
||||
Roots,
|
||||
/// Always follow symlinks (-L).
|
||||
Always,
|
||||
}
|
||||
|
||||
impl Follow {
|
||||
/// Check whether to follow a path of the given depth.
|
||||
pub fn follow_at_depth(self, depth: usize) -> bool {
|
||||
match self {
|
||||
Follow::Never => false,
|
||||
Follow::Roots => depth == 0,
|
||||
Follow::Always => true,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get metadata for a [WalkEntry].
|
||||
pub fn metadata(self, entry: &WalkEntry) -> Result<Metadata, WalkError> {
|
||||
if self.follow_at_depth(entry.depth()) == entry.follow() {
|
||||
// Same follow flag, re-use cached metadata
|
||||
entry.metadata().cloned()
|
||||
} else if !entry.follow() && !entry.file_type().is_symlink() {
|
||||
// Not a symlink, re-use cached metadata
|
||||
entry.metadata().cloned()
|
||||
} else if entry.follow() && entry.file_type().is_symlink() {
|
||||
// Broken symlink, re-use cached metadata
|
||||
entry.metadata().cloned()
|
||||
} else {
|
||||
self.metadata_at_depth(entry.path(), entry.depth())
|
||||
}
|
||||
}
|
||||
|
||||
/// Get metadata for a path from the command line.
|
||||
pub fn root_metadata(self, path: impl AsRef<Path>) -> Result<Metadata, WalkError> {
|
||||
self.metadata_at_depth(path, 0)
|
||||
}
|
||||
|
||||
/// Get metadata for a path, following symlinks as necessary.
|
||||
pub fn metadata_at_depth(
|
||||
self,
|
||||
path: impl AsRef<Path>,
|
||||
depth: usize,
|
||||
) -> Result<Metadata, WalkError> {
|
||||
let path = path.as_ref();
|
||||
|
||||
if self.follow_at_depth(depth) {
|
||||
match path.metadata().map_err(WalkError::from) {
|
||||
Ok(meta) => return Ok(meta),
|
||||
Err(e) if !e.is_not_found() => return Err(e),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(path.symlink_metadata()?)
|
||||
}
|
||||
}
|
||||
|
||||
/// Struct holding references to outputs and any inputs that can't be derived
|
||||
/// from the file/directory info.
|
||||
pub struct MatcherIO<'a> {
|
||||
should_skip_dir: bool,
|
||||
exit_code: i32,
|
||||
quit: bool,
|
||||
deps: &'a dyn Dependencies<'a>,
|
||||
deps: &'a dyn Dependencies,
|
||||
}
|
||||
|
||||
impl<'a> MatcherIO<'a> {
|
||||
pub fn new(deps: &'a dyn Dependencies<'a>) -> MatcherIO<'a> {
|
||||
pub fn new(deps: &dyn Dependencies) -> MatcherIO<'_> {
|
||||
MatcherIO {
|
||||
deps,
|
||||
should_skip_dir: false,
|
||||
exit_code: 0,
|
||||
quit: false,
|
||||
deps,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,6 +162,15 @@ impl<'a> MatcherIO<'a> {
|
||||
self.should_skip_dir
|
||||
}
|
||||
|
||||
pub fn set_exit_code(&mut self, code: i32) {
|
||||
self.exit_code = code;
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn exit_code(&self) -> i32 {
|
||||
self.exit_code
|
||||
}
|
||||
|
||||
pub fn quit(&mut self) {
|
||||
self.quit = true;
|
||||
}
|
||||
@@ -122,7 +200,7 @@ pub trait Matcher: 'static {
|
||||
}
|
||||
|
||||
/// Returns whether the given file matches the object's predicate.
|
||||
fn matches(&self, file_info: &DirEntry, matcher_io: &mut MatcherIO) -> bool;
|
||||
fn matches(&self, entry: &WalkEntry, matcher_io: &mut MatcherIO) -> bool;
|
||||
|
||||
/// Returns whether the matcher has any side-effects (e.g. executing a
|
||||
/// command, deleting a file). Iff no such matcher exists in the chain, then
|
||||
@@ -148,8 +226,8 @@ impl Matcher for Box<dyn Matcher> {
|
||||
self
|
||||
}
|
||||
|
||||
fn matches(&self, file_info: &DirEntry, matcher_io: &mut MatcherIO) -> bool {
|
||||
(**self).matches(file_info, matcher_io)
|
||||
fn matches(&self, entry: &WalkEntry, matcher_io: &mut MatcherIO) -> bool {
|
||||
(**self).matches(entry, matcher_io)
|
||||
}
|
||||
|
||||
fn has_side_effects(&self) -> bool {
|
||||
@@ -202,7 +280,7 @@ pub fn build_top_level_matcher(
|
||||
if !top_level_matcher.has_side_effects() {
|
||||
let mut new_and_matcher = AndMatcherBuilder::new();
|
||||
new_and_matcher.new_and_condition(top_level_matcher);
|
||||
new_and_matcher.new_and_condition(Printer::new(PrintDelimiter::Newline));
|
||||
new_and_matcher.new_and_condition(Printer::new(PrintDelimiter::Newline, None));
|
||||
return Ok(new_and_matcher.build());
|
||||
}
|
||||
Ok(top_level_matcher)
|
||||
@@ -339,6 +417,13 @@ fn parse_str_to_newer_args(input: &str) -> Option<(String, String)> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a file if it doesn't exist.
|
||||
/// If it does exist, it will be overwritten.
|
||||
fn get_or_create_file(path: &str) -> Result<File, Box<dyn Error>> {
|
||||
let file = File::create(path)?;
|
||||
Ok(file)
|
||||
}
|
||||
|
||||
/// The main "translate command-line args into a matcher" function. Will call
|
||||
/// itself recursively if it encounters an opening bracket. A successful return
|
||||
/// consists of a tuple containing the new index into the args array to use (if
|
||||
@@ -347,7 +432,7 @@ fn build_matcher_tree(
|
||||
args: &[&str],
|
||||
config: &mut Config,
|
||||
arg_index: usize,
|
||||
expecting_bracket: bool,
|
||||
mut expecting_bracket: bool,
|
||||
) -> Result<(usize, Box<dyn Matcher>), Box<dyn Error>> {
|
||||
let mut top_level_matcher = ListMatcherBuilder::new();
|
||||
|
||||
@@ -361,8 +446,8 @@ fn build_matcher_tree(
|
||||
let mut invert_next_matcher = false;
|
||||
while i < args.len() {
|
||||
let possible_submatcher = match args[i] {
|
||||
"-print" => Some(Printer::new(PrintDelimiter::Newline).into_box()),
|
||||
"-print0" => Some(Printer::new(PrintDelimiter::Null).into_box()),
|
||||
"-print" => Some(Printer::new(PrintDelimiter::Newline, None).into_box()),
|
||||
"-print0" => Some(Printer::new(PrintDelimiter::Null, None).into_box()),
|
||||
"-printf" => {
|
||||
if i >= args.len() - 1 {
|
||||
return Err(From::from(format!("missing argument to {}", args[i])));
|
||||
@@ -370,6 +455,25 @@ fn build_matcher_tree(
|
||||
i += 1;
|
||||
Some(Printf::new(args[i])?.into_box())
|
||||
}
|
||||
"-fprint" => {
|
||||
if i >= args.len() - 1 {
|
||||
return Err(From::from(format!("missing argument to {}", args[i])));
|
||||
}
|
||||
i += 1;
|
||||
|
||||
let file = get_or_create_file(args[i])?;
|
||||
Some(Printer::new(PrintDelimiter::Newline, Some(file)).into_box())
|
||||
}
|
||||
"-ls" => Some(Ls::new(None).into_box()),
|
||||
"-fls" => {
|
||||
if i >= args.len() - 1 {
|
||||
return Err(From::from(format!("missing argument to {}", args[i])));
|
||||
}
|
||||
i += 1;
|
||||
|
||||
let file = get_or_create_file(args[i])?;
|
||||
Some(Ls::new(Some(file)).into_box())
|
||||
}
|
||||
"-true" => Some(TrueMatcher.into_box()),
|
||||
"-false" => Some(FalseMatcher.into_box()),
|
||||
"-lname" | "-ilname" => {
|
||||
@@ -400,7 +504,7 @@ fn build_matcher_tree(
|
||||
}
|
||||
i += 1;
|
||||
regex_type = regex::RegexType::from_str(args[i])?;
|
||||
None
|
||||
Some(TrueMatcher.into_box())
|
||||
}
|
||||
"-regex" => {
|
||||
if i >= args.len() - 1 {
|
||||
@@ -423,6 +527,13 @@ fn build_matcher_tree(
|
||||
i += 1;
|
||||
Some(TypeMatcher::new(args[i])?.into_box())
|
||||
}
|
||||
"-xtype" => {
|
||||
if i >= args.len() - 1 {
|
||||
return Err(From::from(format!("missing argument to {}", args[i])));
|
||||
}
|
||||
i += 1;
|
||||
Some(XtypeMatcher::new(args[i])?.into_box())
|
||||
}
|
||||
"-fstype" => {
|
||||
if i >= args.len() - 1 {
|
||||
return Err(From::from(format!("missing argument to {}", args[i])));
|
||||
@@ -440,7 +551,7 @@ fn build_matcher_tree(
|
||||
return Err(From::from(format!("missing argument to {}", args[i])));
|
||||
}
|
||||
i += 1;
|
||||
Some(NewerMatcher::new(args[i])?.into_box())
|
||||
Some(NewerMatcher::new(args[i], config.follow)?.into_box())
|
||||
}
|
||||
"-mtime" | "-atime" | "-ctime" => {
|
||||
if i >= args.len() - 1 {
|
||||
@@ -448,7 +559,7 @@ fn build_matcher_tree(
|
||||
}
|
||||
let file_time_type = match args[i] {
|
||||
"-atime" => FileTimeType::Accessed,
|
||||
"-ctime" => FileTimeType::Created,
|
||||
"-ctime" => FileTimeType::Changed,
|
||||
"-mtime" => FileTimeType::Modified,
|
||||
// This shouldn't be possible. We've already checked the value
|
||||
// is one of those three values.
|
||||
@@ -456,7 +567,7 @@ fn build_matcher_tree(
|
||||
};
|
||||
let days = convert_arg_to_comparable_value(args[i], args[i + 1])?;
|
||||
i += 1;
|
||||
Some(FileTimeMatcher::new(file_time_type, days).into_box())
|
||||
Some(FileTimeMatcher::new(file_time_type, days, config.today_start).into_box())
|
||||
}
|
||||
"-amin" | "-cmin" | "-mmin" => {
|
||||
if i >= args.len() - 1 {
|
||||
@@ -464,13 +575,16 @@ fn build_matcher_tree(
|
||||
}
|
||||
let file_time_type = match args[i] {
|
||||
"-amin" => FileTimeType::Accessed,
|
||||
"-cmin" => FileTimeType::Created,
|
||||
"-cmin" => FileTimeType::Changed,
|
||||
"-mmin" => FileTimeType::Modified,
|
||||
_ => unreachable!("Encountered unexpected value {}", args[i]),
|
||||
};
|
||||
let minutes = convert_arg_to_comparable_value(args[i], args[i + 1])?;
|
||||
i += 1;
|
||||
Some(FileAgeRangeMatcher::new(file_time_type, minutes).into_box())
|
||||
Some(
|
||||
FileAgeRangeMatcher::new(file_time_type, minutes, config.today_start)
|
||||
.into_box(),
|
||||
)
|
||||
}
|
||||
"-size" => {
|
||||
if i >= args.len() - 1 {
|
||||
@@ -542,7 +656,8 @@ fn build_matcher_tree(
|
||||
}
|
||||
i += 1;
|
||||
let path = args[i];
|
||||
let matcher = SameFileMatcher::new(path).map_err(|e| format!("{path}: {e}"))?;
|
||||
let matcher = SameFileMatcher::new(path, config.follow)
|
||||
.map_err(|e| format!("{path}: {e}"))?;
|
||||
Some(matcher.into_box())
|
||||
}
|
||||
"-user" => {
|
||||
@@ -692,20 +807,44 @@ fn build_matcher_tree(
|
||||
|
||||
return Ok((i, top_level_matcher.build()));
|
||||
}
|
||||
"-follow" => {
|
||||
// This option affects multiple matchers.
|
||||
// 1. It will use noleaf by default. (but -noleaf No change of behavior)
|
||||
// Unless -L or -H is specified:
|
||||
// 2. changes the behaviour of the -newer predicate.
|
||||
// 3. consideration applies to -newerXY, -anewer and -cnewer
|
||||
// 4. -type predicate will always match against the type of
|
||||
// the file that a symbolic link points to rather than the link itself.
|
||||
//
|
||||
// 5. causes the -lname and -ilname predicates always to return false.
|
||||
// (unless they happen to match broken symbolic links)
|
||||
config.follow = Follow::Always;
|
||||
config.no_leaf_dirs = true;
|
||||
Some(TrueMatcher.into_box())
|
||||
}
|
||||
"-daystart" => {
|
||||
config.today_start = true;
|
||||
Some(TrueMatcher.into_box())
|
||||
}
|
||||
"-noleaf" => {
|
||||
// No change of behavior
|
||||
config.no_leaf_dirs = true;
|
||||
Some(TrueMatcher.into_box())
|
||||
}
|
||||
"-d" | "-depth" => {
|
||||
// TODO add warning if it appears after actual testing criterion
|
||||
config.depth_first = true;
|
||||
None
|
||||
Some(TrueMatcher.into_box())
|
||||
}
|
||||
"-mount" | "-xdev" => {
|
||||
// TODO add warning if it appears after actual testing criterion
|
||||
config.same_file_system = true;
|
||||
None
|
||||
Some(TrueMatcher.into_box())
|
||||
}
|
||||
"-sorted" => {
|
||||
// TODO add warning if it appears after actual testing criterion
|
||||
config.sorted_output = true;
|
||||
None
|
||||
Some(TrueMatcher.into_box())
|
||||
}
|
||||
"-maxdepth" => {
|
||||
if i >= args.len() - 1 {
|
||||
@@ -713,7 +852,7 @@ fn build_matcher_tree(
|
||||
}
|
||||
config.max_depth = convert_arg_to_number(args[i], args[i + 1])?;
|
||||
i += 1;
|
||||
None
|
||||
Some(TrueMatcher.into_box())
|
||||
}
|
||||
"-mindepth" => {
|
||||
if i >= args.len() - 1 {
|
||||
@@ -721,7 +860,7 @@ fn build_matcher_tree(
|
||||
}
|
||||
config.min_depth = convert_arg_to_number(args[i], args[i + 1])?;
|
||||
i += 1;
|
||||
None
|
||||
Some(TrueMatcher.into_box())
|
||||
}
|
||||
"-help" | "--help" => {
|
||||
config.help_requested = true;
|
||||
@@ -767,6 +906,12 @@ fn build_matcher_tree(
|
||||
}
|
||||
}
|
||||
};
|
||||
i += 1;
|
||||
if config.help_requested || config.version_requested {
|
||||
// Ignore anything, even invalid expressions, after -help/-version
|
||||
expecting_bracket = false;
|
||||
break;
|
||||
}
|
||||
if let Some(submatcher) = possible_submatcher {
|
||||
if invert_next_matcher {
|
||||
top_level_matcher.new_and_condition(NotMatcher::new(submatcher));
|
||||
@@ -775,7 +920,6 @@ fn build_matcher_tree(
|
||||
top_level_matcher.new_and_condition(submatcher);
|
||||
}
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
if expecting_bracket {
|
||||
return Err(From::from(
|
||||
@@ -791,25 +935,28 @@ mod tests {
|
||||
use super::*;
|
||||
use crate::find::tests::fix_up_slashes;
|
||||
use crate::find::tests::FakeDependencies;
|
||||
use walkdir::WalkDir;
|
||||
|
||||
/// Helper function for tests to get a `DirEntry` object. directory should
|
||||
/// Helper function for tests to get a [WalkEntry] object. root should
|
||||
/// probably be a string starting with `test_data/` (cargo's tests run with
|
||||
/// a working directory set to the root findutils folder).
|
||||
pub fn get_dir_entry_for(directory: &str, filename: &str) -> DirEntry {
|
||||
for wrapped_dir_entry in WalkDir::new(fix_up_slashes(directory)) {
|
||||
let dir_entry = wrapped_dir_entry.unwrap();
|
||||
if dir_entry
|
||||
.path()
|
||||
.strip_prefix(directory)
|
||||
.unwrap()
|
||||
.to_string_lossy()
|
||||
== fix_up_slashes(filename)
|
||||
{
|
||||
return dir_entry;
|
||||
}
|
||||
}
|
||||
panic!("Couldn't find {filename} in {directory}");
|
||||
pub fn get_dir_entry_for(root: &str, path: &str) -> WalkEntry {
|
||||
get_dir_entry_follow(root, path, Follow::Never)
|
||||
}
|
||||
|
||||
/// Get a [WalkEntry] with an explicit [Follow] flag.
|
||||
pub fn get_dir_entry_follow(root: &str, path: &str, follow: Follow) -> WalkEntry {
|
||||
let root = fix_up_slashes(root);
|
||||
let root = Path::new(&root);
|
||||
|
||||
let path = fix_up_slashes(path);
|
||||
let path = if path.is_empty() {
|
||||
root.to_owned()
|
||||
} else {
|
||||
root.join(path)
|
||||
};
|
||||
|
||||
let depth = path.components().count() - root.components().count();
|
||||
WalkEntry::new(path, depth, follow)
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1170,6 +1317,15 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_top_level_matcher_follow_config() {
|
||||
let mut config = Config::default();
|
||||
|
||||
build_top_level_matcher(&["-follow"], &mut config).unwrap();
|
||||
|
||||
assert_eq!(config.follow, Follow::Always);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn comparable_value_matches() {
|
||||
assert!(
|
||||
@@ -1477,4 +1633,54 @@ mod tests {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_top_level_matcher_option_logical() {
|
||||
let mut config = Config::default();
|
||||
build_top_level_matcher(&["-maxdepth", "0", "-a", "-print"], &mut config)
|
||||
.expect("logical operators like -a should work with options");
|
||||
assert_eq!(config.max_depth, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_top_level_matcher_help_invalid() {
|
||||
let mut config = Config::default();
|
||||
build_top_level_matcher(&["(", "-help", "-a"], &mut config)
|
||||
.expect("-help should stop parsing");
|
||||
assert!(config.help_requested);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_top_level_matcher_version_invalid() {
|
||||
let mut config = Config::default();
|
||||
build_top_level_matcher(&["(", "-version", "-o", ")", ")"], &mut config)
|
||||
.expect("-version should stop parsing");
|
||||
assert!(config.version_requested);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_or_create_file_test() {
|
||||
use std::fs;
|
||||
|
||||
// remove file if hard link file exist.
|
||||
// But you can't delete a file that doesn't exist,
|
||||
// so ignore the error returned here.
|
||||
let _ = fs::remove_file("test_data/get_or_create_file_test");
|
||||
|
||||
// test create file
|
||||
let file = get_or_create_file("test_data/get_or_create_file_test");
|
||||
assert!(file.is_ok());
|
||||
|
||||
let file = get_or_create_file("test_data/get_or_create_file_test");
|
||||
assert!(file.is_ok());
|
||||
|
||||
// test error when file no permission
|
||||
#[cfg(unix)]
|
||||
{
|
||||
let result = get_or_create_file("/etc/shadow");
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
let _ = fs::remove_file("test_data/get_or_create_file_test");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,10 +4,8 @@
|
||||
// license that can be found in the LICENSE file or at
|
||||
// https://opensource.org/licenses/MIT.
|
||||
|
||||
use walkdir::DirEntry;
|
||||
|
||||
use super::glob::Pattern;
|
||||
use super::{Matcher, MatcherIO};
|
||||
use super::{Matcher, MatcherIO, WalkEntry};
|
||||
|
||||
/// This matcher makes a comparison of the name against a shell wildcard
|
||||
/// pattern. See `glob::Pattern` for details on the exact syntax.
|
||||
@@ -23,7 +21,7 @@ impl NameMatcher {
|
||||
}
|
||||
|
||||
impl Matcher for NameMatcher {
|
||||
fn matches(&self, file_info: &DirEntry, _: &mut MatcherIO) -> bool {
|
||||
fn matches(&self, file_info: &WalkEntry, _: &mut MatcherIO) -> bool {
|
||||
let name = file_info.file_name().to_string_lossy();
|
||||
self.pattern.matches(&name)
|
||||
}
|
||||
|
||||
@@ -4,10 +4,8 @@
|
||||
// license that can be found in the LICENSE file or at
|
||||
// https://opensource.org/licenses/MIT.
|
||||
|
||||
use walkdir::DirEntry;
|
||||
|
||||
use super::glob::Pattern;
|
||||
use super::{Matcher, MatcherIO};
|
||||
use super::{Matcher, MatcherIO, WalkEntry};
|
||||
|
||||
/// This matcher makes a comparison of the path against a shell wildcard
|
||||
/// pattern. See `glob::Pattern` for details on the exact syntax.
|
||||
@@ -23,7 +21,7 @@ impl PathMatcher {
|
||||
}
|
||||
|
||||
impl Matcher for PathMatcher {
|
||||
fn matches(&self, file_info: &DirEntry, _: &mut MatcherIO) -> bool {
|
||||
fn matches(&self, file_info: &WalkEntry, _: &mut MatcherIO) -> bool {
|
||||
let path = file_info.path().to_string_lossy();
|
||||
self.pattern.matches(&path)
|
||||
}
|
||||
|
||||
@@ -12,9 +12,8 @@ use std::error::Error;
|
||||
use std::io::{stderr, Write};
|
||||
#[cfg(unix)]
|
||||
use uucore::mode::{parse_numeric, parse_symbolic};
|
||||
use walkdir::DirEntry;
|
||||
|
||||
use super::{Matcher, MatcherIO};
|
||||
use super::{Matcher, MatcherIO, WalkEntry};
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
#[cfg(unix)]
|
||||
@@ -101,7 +100,7 @@ impl PermMatcher {
|
||||
|
||||
impl Matcher for PermMatcher {
|
||||
#[cfg(unix)]
|
||||
fn matches(&self, file_info: &DirEntry, _: &mut MatcherIO) -> bool {
|
||||
fn matches(&self, file_info: &WalkEntry, _: &mut MatcherIO) -> bool {
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
match file_info.metadata() {
|
||||
Ok(metadata) => {
|
||||
@@ -127,7 +126,7 @@ impl Matcher for PermMatcher {
|
||||
}
|
||||
|
||||
#[cfg(not(unix))]
|
||||
fn matches(&self, _dummy_file_info: &DirEntry, _: &mut MatcherIO) -> bool {
|
||||
fn matches(&self, _dummy_file_info: &WalkEntry, _: &mut MatcherIO) -> bool {
|
||||
writeln!(
|
||||
&mut stderr(),
|
||||
"Permission matching not available on this platform!"
|
||||
|
||||
@@ -4,9 +4,10 @@
|
||||
// license that can be found in the LICENSE file or at
|
||||
// https://opensource.org/licenses/MIT.
|
||||
|
||||
use walkdir::DirEntry;
|
||||
use std::fs::File;
|
||||
use std::io::{stderr, Write};
|
||||
|
||||
use super::{Matcher, MatcherIO};
|
||||
use super::{Matcher, MatcherIO, WalkEntry};
|
||||
|
||||
pub enum PrintDelimiter {
|
||||
Newline,
|
||||
@@ -25,25 +26,53 @@ impl std::fmt::Display for PrintDelimiter {
|
||||
/// This matcher just prints the name of the file to stdout.
|
||||
pub struct Printer {
|
||||
delimiter: PrintDelimiter,
|
||||
output_file: Option<File>,
|
||||
}
|
||||
|
||||
impl Printer {
|
||||
pub fn new(delimiter: PrintDelimiter) -> Self {
|
||||
Self { delimiter }
|
||||
pub fn new(delimiter: PrintDelimiter, output_file: Option<File>) -> Self {
|
||||
Self {
|
||||
delimiter,
|
||||
output_file,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Matcher for Printer {
|
||||
fn matches(&self, file_info: &DirEntry, matcher_io: &mut MatcherIO) -> bool {
|
||||
let mut out = matcher_io.deps.get_output().borrow_mut();
|
||||
write!(
|
||||
fn print(&self, file_info: &WalkEntry, mut out: impl Write, print_error_message: bool) {
|
||||
match write!(
|
||||
out,
|
||||
"{}{}",
|
||||
file_info.path().to_string_lossy(),
|
||||
self.delimiter
|
||||
)
|
||||
.unwrap();
|
||||
) {
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
if print_error_message {
|
||||
writeln!(
|
||||
&mut stderr(),
|
||||
"Error writing {:?} for {}",
|
||||
file_info.path().to_string_lossy(),
|
||||
e
|
||||
)
|
||||
.unwrap();
|
||||
uucore::error::set_exit_code(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
out.flush().unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
impl Matcher for Printer {
|
||||
fn matches(&self, file_info: &WalkEntry, matcher_io: &mut MatcherIO) -> bool {
|
||||
if let Some(file) = &self.output_file {
|
||||
self.print(file_info, file, true);
|
||||
} else {
|
||||
self.print(
|
||||
file_info,
|
||||
&mut *matcher_io.deps.get_output().borrow_mut(),
|
||||
false,
|
||||
);
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
@@ -53,7 +82,6 @@ impl Matcher for Printer {
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::find::matchers::tests::get_dir_entry_for;
|
||||
@@ -64,7 +92,7 @@ mod tests {
|
||||
fn prints_newline() {
|
||||
let abbbc = get_dir_entry_for("./test_data/simple", "abbbc");
|
||||
|
||||
let matcher = Printer::new(PrintDelimiter::Newline);
|
||||
let matcher = Printer::new(PrintDelimiter::Newline, None);
|
||||
let deps = FakeDependencies::new();
|
||||
assert!(matcher.matches(&abbbc, &mut deps.new_matcher_io()));
|
||||
assert_eq!(
|
||||
@@ -77,7 +105,7 @@ mod tests {
|
||||
fn prints_null() {
|
||||
let abbbc = get_dir_entry_for("./test_data/simple", "abbbc");
|
||||
|
||||
let matcher = Printer::new(PrintDelimiter::Null);
|
||||
let matcher = Printer::new(PrintDelimiter::Null, None);
|
||||
let deps = FakeDependencies::new();
|
||||
assert!(matcher.matches(&abbbc, &mut deps.new_matcher_io()));
|
||||
assert_eq!(
|
||||
@@ -85,4 +113,22 @@ mod tests {
|
||||
deps.get_output_as_string()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(target_os = "linux")]
|
||||
fn prints_error_message() {
|
||||
let dev_full = File::open("/dev/full").unwrap();
|
||||
let abbbc = get_dir_entry_for("./test_data/simple", "abbbc");
|
||||
|
||||
let matcher = Printer::new(PrintDelimiter::Newline, Some(dev_full));
|
||||
let deps = FakeDependencies::new();
|
||||
|
||||
assert!(matcher.matches(&abbbc, &mut deps.new_matcher_io()));
|
||||
|
||||
// Reset the exit code global variable in case we run another test after this one
|
||||
// See https://github.com/uutils/coreutils/issues/5777
|
||||
uucore::error::set_exit_code(0);
|
||||
|
||||
assert!(deps.get_output_as_string().is_empty());
|
||||
}
|
||||
}
|
||||
|
||||
+30
-61
@@ -4,15 +4,18 @@
|
||||
// license that can be found in the LICENSE file or at
|
||||
// https://opensource.org/licenses/MIT.
|
||||
|
||||
use std::{borrow::Cow, error::Error, fs, path::Path, time::SystemTime};
|
||||
use std::borrow::Cow;
|
||||
use std::error::Error;
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
use std::time::SystemTime;
|
||||
|
||||
use chrono::{format::StrftimeItems, DateTime, Local};
|
||||
use once_cell::unsync::OnceCell;
|
||||
|
||||
use super::{Matcher, MatcherIO};
|
||||
use super::{FileType, Matcher, MatcherIO, WalkEntry, WalkError};
|
||||
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::prelude::{FileTypeExt, MetadataExt};
|
||||
use std::os::unix::prelude::MetadataExt;
|
||||
|
||||
const STANDARD_BLOCK_SIZE: u64 = 512;
|
||||
|
||||
@@ -302,7 +305,7 @@ impl FormatStringParser<'_> {
|
||||
pub fn parse(&mut self) -> Result<FormatString, Box<dyn Error>> {
|
||||
let mut components = vec![];
|
||||
|
||||
while let Some(i) = self.string.find(|c| c == '%' || c == '\\') {
|
||||
while let Some(i) = self.string.find(['%', '\\']) {
|
||||
if i > 0 {
|
||||
// safe to unwrap: i is an index into the string, so it cannot
|
||||
// be any shorter.
|
||||
@@ -340,7 +343,7 @@ impl FormatString {
|
||||
}
|
||||
}
|
||||
|
||||
fn get_starting_point(file_info: &walkdir::DirEntry) -> &Path {
|
||||
fn get_starting_point(file_info: &WalkEntry) -> &Path {
|
||||
file_info
|
||||
.path()
|
||||
.ancestors()
|
||||
@@ -350,47 +353,23 @@ fn get_starting_point(file_info: &walkdir::DirEntry) -> &Path {
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn format_non_link_file_type(file_type: fs::FileType) -> char {
|
||||
if file_type.is_file() {
|
||||
'f'
|
||||
} else if file_type.is_dir() {
|
||||
'd'
|
||||
} else {
|
||||
#[cfg(unix)]
|
||||
if file_type.is_block_device() {
|
||||
'b'
|
||||
} else if file_type.is_char_device() {
|
||||
'c'
|
||||
} else if file_type.is_fifo() {
|
||||
'p'
|
||||
} else if file_type.is_socket() {
|
||||
's'
|
||||
} else {
|
||||
'U'
|
||||
}
|
||||
#[cfg(not(unix))]
|
||||
'U'
|
||||
fn format_non_link_file_type(file_type: FileType) -> char {
|
||||
match file_type {
|
||||
FileType::Regular => 'f',
|
||||
FileType::Directory => 'd',
|
||||
FileType::BlockDevice => 'b',
|
||||
FileType::CharDevice => 'c',
|
||||
FileType::Fifo => 'p',
|
||||
FileType::Socket => 's',
|
||||
_ => 'U',
|
||||
}
|
||||
}
|
||||
|
||||
fn format_directive<'entry>(
|
||||
file_info: &'entry walkdir::DirEntry,
|
||||
file_info: &'entry WalkEntry,
|
||||
directive: &FormatDirective,
|
||||
meta_cell: &OnceCell<fs::Metadata>,
|
||||
) -> Result<Cow<'entry, str>, Box<dyn Error>> {
|
||||
let meta = || {
|
||||
meta_cell.get_or_try_init(|| {
|
||||
if file_info.path_is_symlink() && !file_info.file_type().is_symlink() {
|
||||
// The file_info already followed the symlink, meaning that the
|
||||
// metadata will be for the target file, which isn't the
|
||||
// behavior we want, so manually re-compute the metadata for the
|
||||
// symlink itself instead.
|
||||
file_info.path().symlink_metadata()
|
||||
} else {
|
||||
file_info.metadata().map_err(std::convert::Into::into)
|
||||
}
|
||||
})
|
||||
};
|
||||
let meta = || file_info.metadata();
|
||||
|
||||
// NOTE ON QUOTING:
|
||||
// GNU find's man page claims that several directives that print names (like
|
||||
@@ -556,17 +535,10 @@ fn format_directive<'entry>(
|
||||
|
||||
FormatDirective::Type { follow_links } => if file_info.path_is_symlink() {
|
||||
if *follow_links {
|
||||
match file_info.path().metadata() {
|
||||
Ok(meta) => format_non_link_file_type(meta.file_type()),
|
||||
Err(e) if e.kind() == std::io::ErrorKind::NotFound => 'N',
|
||||
// The ErrorKinds corresponding to ELOOP and ENOTDIR are
|
||||
// nightly-only:
|
||||
// https://doc.rust-lang.org/std/io/enum.ErrorKind.html#variant.FilesystemLoop
|
||||
// so we need to use the raw errno values instead.
|
||||
#[cfg(unix)]
|
||||
Err(e) if e.raw_os_error().unwrap_or(0) == uucore::libc::ENOTDIR => 'N',
|
||||
#[cfg(unix)]
|
||||
Err(e) if e.raw_os_error().unwrap_or(0) == uucore::libc::ELOOP => 'L',
|
||||
match file_info.path().metadata().map_err(WalkError::from) {
|
||||
Ok(meta) => format_non_link_file_type(meta.file_type().into()),
|
||||
Err(e) if e.is_not_found() => 'N',
|
||||
Err(e) if e.is_loop() => 'L',
|
||||
Err(_) => '?',
|
||||
}
|
||||
} else {
|
||||
@@ -610,11 +582,8 @@ impl Printf {
|
||||
}
|
||||
|
||||
impl Matcher for Printf {
|
||||
fn matches(&self, file_info: &walkdir::DirEntry, matcher_io: &mut MatcherIO) -> bool {
|
||||
fn matches(&self, file_info: &WalkEntry, matcher_io: &mut MatcherIO) -> bool {
|
||||
let mut out = matcher_io.deps.get_output().borrow_mut();
|
||||
// The metadata is computed lazily, so that anything being printed
|
||||
// without needing metadata won't incur any performance overhead.
|
||||
let meta_cell = OnceCell::new();
|
||||
|
||||
for component in &self.format.components {
|
||||
match component {
|
||||
@@ -624,7 +593,7 @@ impl Matcher for Printf {
|
||||
directive,
|
||||
width,
|
||||
justify,
|
||||
} => match format_directive(file_info, directive, &meta_cell) {
|
||||
} => match format_directive(file_info, directive) {
|
||||
Ok(content) => {
|
||||
if let Some(width) = width {
|
||||
match justify {
|
||||
@@ -1110,13 +1079,13 @@ mod tests {
|
||||
let new_file_name = "newFile";
|
||||
let file = File::create(temp_dir.path().join(new_file_name)).expect("create temp file");
|
||||
|
||||
let file_info = get_dir_entry_for(&temp_dir_path, new_file_name);
|
||||
let deps = FakeDependencies::new();
|
||||
|
||||
let mut perms = file_info.metadata().unwrap().permissions();
|
||||
let mut perms = file.metadata().unwrap().permissions();
|
||||
perms.set_mode(0o755);
|
||||
file.set_permissions(perms).unwrap();
|
||||
|
||||
let file_info = get_dir_entry_for(&temp_dir_path, new_file_name);
|
||||
let deps = FakeDependencies::new();
|
||||
|
||||
let matcher = Printf::new("%m %M").unwrap();
|
||||
assert!(matcher.matches(&file_info, &mut deps.new_matcher_io()));
|
||||
assert_eq!("755 -rwxr-xr-x", deps.get_output_as_string());
|
||||
|
||||
@@ -4,9 +4,7 @@
|
||||
// license that can be found in the LICENSE file or at
|
||||
// https://opensource.org/licenses/MIT.
|
||||
|
||||
use walkdir::DirEntry;
|
||||
|
||||
use super::{Matcher, MatcherIO};
|
||||
use super::{Matcher, MatcherIO, WalkEntry};
|
||||
|
||||
/// This matcher checks the type of the file.
|
||||
pub struct PruneMatcher;
|
||||
@@ -18,7 +16,7 @@ impl PruneMatcher {
|
||||
}
|
||||
|
||||
impl Matcher for PruneMatcher {
|
||||
fn matches(&self, file_info: &DirEntry, matcher_io: &mut MatcherIO) -> bool {
|
||||
fn matches(&self, file_info: &WalkEntry, matcher_io: &mut MatcherIO) -> bool {
|
||||
if file_info.file_type().is_dir() {
|
||||
matcher_io.mark_current_dir_to_be_skipped();
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user