49 Commits

Author SHA1 Message Date
Sylvestre Ledru e3f4224f12 prepare version 0.7.0 2024-09-08 10:04:46 +02:00
Sylvestre Ledru 8fc5ddb299 Merge pull request #450 from cakebaker/find_fix_clippy_warning
find: use array instead of closure
2024-09-06 08:53:55 +02:00
Daniel Hofstetter b4f22f0747 find: use array instead of closure
fix warning from manual_pattern_char_comparison lint
2024-09-06 07:44:59 +02:00
hanbings db8e265547 Clean up unused generated during Windows platform build process. (#448)
* Fix Windows' warning in build.

* Fix Windows' warning in build.

* Fix Windows' warning in build.

* Run cargo fmt.
2024-09-05 10:10:34 +02:00
hanbings f9d74ada2e Implement -ls and -fls (#435) 2024-09-05 09:06:54 +02:00
Daniel Hofstetter 907f3841ab Merge pull request #449 from uutils/dependabot/cargo/clap-4.5.17
build(deps): bump clap from 4.5.16 to 4.5.17
2024-09-05 07:11:53 +02:00
dependabot[bot] 888d3cd516 build(deps): bump clap from 4.5.16 to 4.5.17
Bumps [clap](https://github.com/clap-rs/clap) from 4.5.16 to 4.5.17.
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.16...clap_complete-v4.5.17)

---
updated-dependencies:
- dependency-name: clap
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-05 03:38:19 +00:00
Daniel Hofstetter f2e49d8d48 Merge pull request #447 from uutils/dependabot/cargo/filetime-0.2.25
build(deps): bump filetime from 0.2.24 to 0.2.25
2024-08-28 07:05:08 +02:00
dependabot[bot] d847a7bf7e build(deps): bump filetime from 0.2.24 to 0.2.25
Bumps [filetime](https://github.com/alexcrichton/filetime) from 0.2.24 to 0.2.25.
- [Commits](https://github.com/alexcrichton/filetime/compare/0.2.24...0.2.25)

---
updated-dependencies:
- dependency-name: filetime
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-28 03:28:47 +00:00
Tavian Barnes abf851dd7d ci: Update bfs to 4.0 (#446)
And fix diff-bfs.sh for the new test result format.
2024-08-17 08:13:30 +01:00
Daniel Hofstetter d9d2a4e865 Merge pull request #445 from uutils/dependabot/cargo/clap-4.5.16
build(deps): bump clap from 4.5.15 to 4.5.16
2024-08-16 09:28:56 +02:00
dependabot[bot] 9b0fea9880 build(deps): bump clap from 4.5.15 to 4.5.16
Bumps [clap](https://github.com/clap-rs/clap) from 4.5.15 to 4.5.16.
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.15...clap_complete-v4.5.16)

---
updated-dependencies:
- dependency-name: clap
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-16 03:39:20 +00:00
Tavian Barnes a7a73c325d Wrap walkdir::DirEntry in a new type (#436)
* Add -follow support.

* tests/find: Serialize find_time()

find_time() relies on the working directory, but e.g.
delete_on_dot_dir() will temporarily change directories, causing
find_time() to fail when run in parallel.

* find: Don't use uutils::error::set_exit_code()

The global exit code can polute the results of other tests.

Link: https://github.com/uutils/coreutils/issues/5777

* find: New WalkEntry wrapper

The new type wraps DirEntry when possible, but also lets us pass a valid
entry to matchers when walkdir returns a broken symlink error.  It also
implements a Metadata cache (part of #430).

* find: Implement -H, -L, -P flags

* find: Fix -follow -samefile

* find: Fix -follow -newer

* find: Implement -xtype

* find: Fix -delete error handling

---------

Co-authored-by: hanbings <hanbings@hanbings.io>
2024-08-15 04:01:19 +08:00
Daniel Hofstetter 465856ce24 Merge pull request #442 from uutils/dependabot/cargo/filetime-0.2.24
build(deps): bump filetime from 0.2.23 to 0.2.24
2024-08-12 08:16:29 +02:00
Daniel Hofstetter e456c23d5c Merge pull request #441 from uutils/dependabot/cargo/clap-4.5.15
build(deps): bump clap from 4.5.14 to 4.5.15
2024-08-12 08:14:51 +02:00
dependabot[bot] 4dc350e5fb build(deps): bump filetime from 0.2.23 to 0.2.24
Bumps [filetime](https://github.com/alexcrichton/filetime) from 0.2.23 to 0.2.24.
- [Commits](https://github.com/alexcrichton/filetime/compare/0.2.23...0.2.24)

---
updated-dependencies:
- dependency-name: filetime
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-12 03:51:06 +00:00
dependabot[bot] fcc929a978 build(deps): bump clap from 4.5.14 to 4.5.15
Bumps [clap](https://github.com/clap-rs/clap) from 4.5.14 to 4.5.15.
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.14...v4.5.15)

---
updated-dependencies:
- dependency-name: clap
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-12 03:51:01 +00:00
hanbings 423453eaf8 Implement -fprint (#421) 2024-08-11 09:38:05 +02:00
Daniel Hofstetter 79986a26f2 Merge pull request #437 from uutils/dependabot/cargo/assert_cmd-2.0.16
build(deps): bump assert_cmd from 2.0.15 to 2.0.16
2024-08-09 08:07:12 +02:00
Daniel Hofstetter de1bc84812 Merge pull request #438 from uutils/dependabot/cargo/clap-4.5.14
build(deps): bump clap from 4.5.13 to 4.5.14
2024-08-09 08:05:59 +02:00
dependabot[bot] f0cd3748b8 build(deps): bump clap from 4.5.13 to 4.5.14
Bumps [clap](https://github.com/clap-rs/clap) from 4.5.13 to 4.5.14.
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.13...v4.5.14)

---
updated-dependencies:
- dependency-name: clap
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-09 03:24:36 +00:00
dependabot[bot] 780d106b34 build(deps): bump assert_cmd from 2.0.15 to 2.0.16
Bumps [assert_cmd](https://github.com/assert-rs/assert_cmd) from 2.0.15 to 2.0.16.
- [Changelog](https://github.com/assert-rs/assert_cmd/blob/master/CHANGELOG.md)
- [Commits](https://github.com/assert-rs/assert_cmd/compare/v2.0.15...v2.0.16)

---
updated-dependencies:
- dependency-name: assert_cmd
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-09 03:24:28 +00:00
Daniel Hofstetter 4043c00bde Merge pull request #434 from uutils/dependabot/cargo/tempfile-3.12.0
build(deps): bump tempfile from 3.11.0 to 3.12.0
2024-08-07 08:01:56 +02:00
dependabot[bot] f0c3562aca build(deps): bump tempfile from 3.11.0 to 3.12.0
Bumps [tempfile](https://github.com/Stebalien/tempfile) from 3.11.0 to 3.12.0.
- [Changelog](https://github.com/Stebalien/tempfile/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Stebalien/tempfile/commits)

---
updated-dependencies:
- dependency-name: tempfile
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-07 03:44:37 +00:00
dependabot[bot] d7bf7da4a1 build(deps): bump predicates from 3.1.0 to 3.1.2 (#424)
Bumps [predicates](https://github.com/assert-rs/predicates-rs) from 3.1.0 to 3.1.2.
- [Changelog](https://github.com/assert-rs/predicates-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/assert-rs/predicates-rs/compare/v3.1.0...v3.1.2)

---
updated-dependencies:
- dependency-name: predicates
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: hanbings <hanbings@hanbings.io>
2024-08-06 00:39:50 +08:00
dependabot[bot] 73dcd94827 build(deps): bump assert_cmd from 2.0.14 to 2.0.15 (#426)
Bumps [assert_cmd](https://github.com/assert-rs/assert_cmd) from 2.0.14 to 2.0.15.
- [Changelog](https://github.com/assert-rs/assert_cmd/blob/master/CHANGELOG.md)
- [Commits](https://github.com/assert-rs/assert_cmd/compare/v2.0.14...v2.0.15)

---
updated-dependencies:
- dependency-name: assert_cmd
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: hanbings <hanbings@hanbings.io>
2024-08-06 00:29:40 +08:00
Tavian Barnes 60e4a8cf92 find: -daystart returns true (#428) 2024-08-06 00:22:42 +08:00
Daniel Hofstetter 8b295e7d8a Merge pull request #433 from uutils/dependabot/cargo/tempfile-3.11.0
build(deps): bump tempfile from 3.10.1 to 3.11.0
2024-08-05 07:59:49 +02:00
Daniel Hofstetter 2dc916f980 Merge pull request #432 from uutils/dependabot/cargo/regex-1.10.6
build(deps): bump regex from 1.10.5 to 1.10.6
2024-08-05 07:58:59 +02:00
dependabot[bot] 19012452e1 build(deps): bump tempfile from 3.10.1 to 3.11.0
Bumps [tempfile](https://github.com/Stebalien/tempfile) from 3.10.1 to 3.11.0.
- [Changelog](https://github.com/Stebalien/tempfile/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Stebalien/tempfile/compare/v3.10.1...v3.11.0)

---
updated-dependencies:
- dependency-name: tempfile
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-05 03:55:36 +00:00
dependabot[bot] 6042d5a47f build(deps): bump regex from 1.10.5 to 1.10.6
Bumps [regex](https://github.com/rust-lang/regex) from 1.10.5 to 1.10.6.
- [Release notes](https://github.com/rust-lang/regex/releases)
- [Changelog](https://github.com/rust-lang/regex/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/regex/compare/1.10.5...1.10.6)

---
updated-dependencies:
- dependency-name: regex
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-05 03:55:28 +00:00
Daniel Hofstetter 45e5964e6a Merge pull request #429 from uutils/dependabot/cargo/clap-4.5.13
build(deps): bump clap from 4.5.10 to 4.5.13
2024-08-01 09:33:03 +02:00
dependabot[bot] 9544f78977 build(deps): bump clap from 4.5.10 to 4.5.13
Bumps [clap](https://github.com/clap-rs/clap) from 4.5.10 to 4.5.13.
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.10...v4.5.13)

---
updated-dependencies:
- dependency-name: clap
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-01 03:35:21 +00:00
Sylvestre Ledru 5be00c85d7 Merge pull request #427 from tavianator/simplify-matcher-io
Remove unnecessary lifetime parameter from Dependencies
2024-07-28 09:53:25 +02:00
Tavian Barnes 8771861b0c Remove unnecessary lifetime parameter from Dependencies 2024-07-27 19:54:28 -04:00
Daniel Hofstetter c4a56e9c3f find: fix needless borrow clippy warning (#423) 2024-07-26 03:15:07 +08:00
Daniel Hofstetter 3b222d6040 Merge pull request #422 from uutils/dependabot/cargo/clap-4.5.10
build(deps): bump clap from 4.5.9 to 4.5.10
2024-07-24 07:27:06 +02:00
dependabot[bot] 6f53585a4c build(deps): bump clap from 4.5.9 to 4.5.10
Bumps [clap](https://github.com/clap-rs/clap) from 4.5.9 to 4.5.10.
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.9...v4.5.10)

---
updated-dependencies:
- dependency-name: clap
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-24 03:12:02 +00:00
hanbings 1b9904eb5f Implement -daystart (#413) 2024-07-21 16:06:15 +02:00
Tavian Barnes 4807264faa find: ctime is "changed" time, not "created" time (#416)
Fixes #358
2024-07-17 21:14:00 +08:00
Chen Chen 9f44977270 Implement simple caching for -fstype (#419)
* Implement simple caching for -fstype

* Using lstat instead of stat in -fstype
2024-07-13 01:43:56 +08:00
Sylvestre Ledru 7f4423e703 Merge pull request #417 from tavianator/options-return-true
options return true
2024-07-11 21:49:40 +02:00
Tavian Barnes e9634f1ed4 find: Stop parsing after we see -help or -version
GNU find accepts things like

    $ find \( -help -foo -not
2024-07-10 11:55:14 -04:00
Tavian Barnes 37a543b9d6 find: Options like -maxdepth return true
Previously, this would give an error:

    $ find -maxdepth 0 -a -print
    Error: invalid expression; you have used a binary operator '-a' with nothing before it.

Now, options are treated like other primary expressions:

    $ find -maxdepth 0 -a print
    .
2024-07-10 11:42:58 -04:00
hanbings 9c11f11045 Implement -noleaf (#414) 2024-07-09 23:27:18 +02:00
Daniel Hofstetter a0b5713b61 Merge pull request #415 from uutils/dependabot/cargo/clap-4.5.9
build(deps): bump clap from 4.5.8 to 4.5.9
2024-07-09 08:23:11 +02:00
dependabot[bot] 5d4b9ca5e2 build(deps): bump clap from 4.5.8 to 4.5.9
Bumps [clap](https://github.com/clap-rs/clap) from 4.5.8 to 4.5.9.
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/v4.5.8...v4.5.9)

---
updated-dependencies:
- dependency-name: clap
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-09 03:09:20 +00:00
Daniel Hofstetter 3bf5567397 Merge pull request #410 from uutils/dependabot/cargo/clap-4.5.8
build(deps): bump clap from 4.5.7 to 4.5.8
2024-07-01 07:41:29 +02:00
dependabot[bot] bbc9f06543 build(deps): bump clap from 4.5.7 to 4.5.8
Bumps [clap](https://github.com/clap-rs/clap) from 4.5.7 to 4.5.8.
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.7...v4.5.8)

---
updated-dependencies:
- dependency-name: clap
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-01 03:30:04 +00:00
34 changed files with 2043 additions and 549 deletions
+1 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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"
+2 -3
View File
@@ -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 {
+9 -11
View File
@@ -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
}
+2 -2
View File
@@ -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,
+343
View File
@@ -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(),
}
}
}
+3 -4
View File
@@ -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
View File
@@ -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());
+8 -8
View File
@@ -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()
+3 -5
View File
@@ -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 {
+9 -10
View File
@@ -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
}
+294
View File
@@ -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
View File
@@ -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");
}
}
+2 -4
View File
@@ -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)
}
+2 -4
View File
@@ -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)
}
+3 -4
View File
@@ -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!"
+60 -14
View File
@@ -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
View File
@@ -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());
+2 -4
View File
@@ -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