diff --git a/.github/workflows/CICD.yml b/.github/workflows/CICD.yml index c112b29cb..338787bd6 100644 --- a/.github/workflows/CICD.yml +++ b/.github/workflows/CICD.yml @@ -701,7 +701,11 @@ jobs: outputs TARGET_ARCH TARGET_OS # package name PKG_suffix=".tar.gz" ; case '${{ matrix.job.target }}' in *-pc-windows-*) PKG_suffix=".zip" ;; esac; - PKG_BASENAME=${PROJECT_NAME}-${{ matrix.job.target }} + # Some 3rd party utils need version at file names + # But we remove it from tag/latest-commit + test ${REF_TAG} \ + && PKG_BASENAME=${PROJECT_NAME}-${REF_TAG}-${{ matrix.job.target }} \ + || PKG_BASENAME=${PROJECT_NAME}-${{ matrix.job.target }} PKG_NAME=${PKG_BASENAME}${PKG_suffix} outputs PKG_suffix PKG_BASENAME PKG_NAME # deployable tag? (ie, leading "vM" or "M"; M == version number) @@ -906,6 +910,8 @@ jobs: if: github.event_name == 'push' && github.ref == 'refs/heads/main' && matrix.job.skip-publish != true with: tag_name: latest-commit + body: | + commit: ${{ github.sha }} draft: false prerelease: true files: | diff --git a/.github/workflows/GnuComment.yml b/.github/workflows/GnuComment.yml index 509a9b866..ab218274d 100644 --- a/.github/workflows/GnuComment.yml +++ b/.github/workflows/GnuComment.yml @@ -59,4 +59,4 @@ jobs: issue_number: issue_number, body: 'GNU testsuite comparison:\n```\n' + content + '```' }); - } + } diff --git a/.github/workflows/freebsd.yml b/.github/workflows/freebsd.yml index 4b6dcf043..f963cf3ef 100644 --- a/.github/workflows/freebsd.yml +++ b/.github/workflows/freebsd.yml @@ -35,7 +35,7 @@ jobs: with: persist-credentials: false - name: Prepare, build and test - uses: vmactions/freebsd-vm@v1.2.9 + uses: vmactions/freebsd-vm@v1.3.9 with: usesh: true sync: rsync @@ -127,7 +127,7 @@ jobs: - name: Avoid no space left on device (Ubuntu runner) run: sudo rm -rf /usr/share/dotnet /usr/local/lib/android & - name: Prepare, build and test - uses: vmactions/freebsd-vm@v1.2.9 + uses: vmactions/freebsd-vm@v1.3.9 with: usesh: true sync: rsync diff --git a/.github/workflows/fuzzing.yml b/.github/workflows/fuzzing.yml index 4b5ac5e35..416e6c5c7 100644 --- a/.github/workflows/fuzzing.yml +++ b/.github/workflows/fuzzing.yml @@ -1,6 +1,7 @@ name: Fuzzing -# spell-checker:ignore fuzzer dtolnay Swatinem +# spell-checker:ignore (people) dtolnay Swatinem taiki-e +# spell-checker:ignore (misc) fuzzer on: pull_request: @@ -19,16 +20,13 @@ concurrency: jobs: uufuzz-examples: name: Build and test uufuzz examples + env: + CARGO_INCREMENTAL: 0 runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 with: persist-credentials: false - - uses: dtolnay/rust-toolchain@stable - - uses: Swatinem/rust-cache@v2 - with: - shared-key: "uufuzz-cache-key" - cache-directories: "fuzz/target" - name: Build uufuzz library run: | cd fuzz/uufuzz @@ -59,15 +57,16 @@ jobs: with: persist-credentials: false - name: Install `cargo-fuzz` - run: | - echo "RUSTC_BOOTSTRAP=1" >> "${GITHUB_ENV}" # Use -Z - cargo install cargo-fuzz --locked - - uses: Swatinem/rust-cache@v2 + uses: taiki-e/install-action@v2 with: - shared-key: "cargo-fuzz-cache-key" - cache-directories: "fuzz/target" + tool: cargo-fuzz + - name: Emulate a nightly toolchain + run: | + echo "RUSTC_BOOTSTRAP=1" >> "${GITHUB_ENV}" - name: Run `cargo-fuzz build` - run: cargo fuzz build + # Force the correct target + # https://github.com/rust-fuzz/cargo-fuzz/issues/398 + run: cargo fuzz build --target $(rustc --print host-tuple) fuzz-run: needs: fuzz-build @@ -76,6 +75,7 @@ jobs: timeout-minutes: 5 env: RUN_FOR: 60 + CARGO_INCREMENTAL: 0 strategy: matrix: test-target: @@ -104,19 +104,12 @@ jobs: with: persist-credentials: false - name: Install `cargo-fuzz` + uses: taiki-e/install-action@v2 + with: + tool: cargo-fuzz + - name: Emulate a nightly toolchain run: | - echo "RUSTC_BOOTSTRAP=1" >> "${GITHUB_ENV}" # Use nightly - cargo install cargo-fuzz --locked - - uses: Swatinem/rust-cache@v2 - with: - shared-key: "cargo-fuzz-cache-key" - cache-directories: "fuzz/target" - - name: Restore Cached Corpus - uses: actions/cache/restore@v5 - with: - key: corpus-cache-${{ matrix.test-target.name }} - path: | - fuzz/corpus/${{ matrix.test-target.name }} + echo "RUSTC_BOOTSTRAP=1" >> "${GITHUB_ENV}" - name: Run ${{ matrix.test-target.name }} for XX seconds id: run_fuzzer shell: bash @@ -124,7 +117,9 @@ jobs: run: | mkdir -p fuzz/stats STATS_FILE="fuzz/stats/${{ matrix.test-target.name }}.txt" - cargo fuzz run ${{ matrix.test-target.name }} -- -max_total_time=${{ env.RUN_FOR }} -timeout=${{ env.RUN_FOR }} -detect_leaks=0 -print_final_stats=1 2>&1 | tee "$STATS_FILE" + # Force the correct target + # https://github.com/rust-fuzz/cargo-fuzz/issues/398 + cargo fuzz run --target $(rustc --print host-tuple) ${{ matrix.test-target.name }} -- -max_total_time=${{ env.RUN_FOR }} -timeout=${{ env.RUN_FOR }} -detect_leaks=0 -print_final_stats=1 2>&1 | tee "$STATS_FILE" # Extract key stats from the output if grep -q "stat::number_of_executed_units" "$STATS_FILE"; then @@ -193,12 +188,6 @@ jobs: fi echo "" >> $GITHUB_STEP_SUMMARY - - name: Save Corpus Cache - uses: actions/cache/save@v5 - with: - key: corpus-cache-${{ matrix.test-target.name }} - path: | - fuzz/corpus/${{ matrix.test-target.name }} - name: Upload Stats uses: actions/upload-artifact@v6 with: diff --git a/.gitignore b/.gitignore index 7528e5f53..81b11d66c 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,5 @@ lib*.a ### direnv ### /.direnv/ + +*.code-workspace diff --git a/.markdownlint.yaml b/.markdownlint.yaml index 11b733e0b..57cf850a4 100644 --- a/.markdownlint.yaml +++ b/.markdownlint.yaml @@ -3,4 +3,3 @@ MD013: false # Disable 'Fenced code blocks should have a language specified' # Doesn't provide much in src/ to enforce it MD040: false - diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 91847f96b..845b03f1a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -7,7 +7,7 @@ repos: - id: check-added-large-files - id: check-executables-have-shebangs - id: check-json - exclude: '.vscode/cSpell\.json' # cSpell.json uses comments + exclude: '\.vscode/(cSpell|extensions)\.json' # cSpell.json and extensions.json use comments - id: check-shebang-scripts-are-executable exclude: '.+\.rs' # would be triggered by #![some_attribute] - id: check-symlinks diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 7ee2695db..291cbaf58 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,8 +1,8 @@ -// spell-checker:ignore (misc) matklad foxundermoon +// spell-checker:ignore (misc) foxundermoon // see for the documentation about the extensions.json format // * // "foxundermoon.shell-format" ~ shell script formatting ; note: ENABLE "Use EditorConfig" -// "matklad.rust-analyzer" ~ `rust` language support +// "rust-lang.rust-analyzer" ~ `rust` language support // "streetsidesoftware.code-spell-checker" ~ `cspell` spell-checker support { "recommendations": [ diff --git a/Cargo.lock b/Cargo.lock index 78e401b03..d32e474c6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -337,18 +337,18 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.56" +version = "4.5.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75ca66430e33a14957acc24c5077b503e7d374151b2b4b3a10c83b4ceb4be0e" +checksum = "6899ea499e3fb9305a65d5ebf6e3d2248c5fab291f300ad0a704fbe142eae31a" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.5.56" +version = "4.5.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793207c7fa6300a0608d1080b858e5fdbe713cdc1c8db9fb17777d8a13e63df0" +checksum = "7b12c8b680195a62a8364d16b8447b01b6c2c8f9aaf68bee653be34d4245e238" dependencies = [ "anstream", "anstyle", @@ -1008,7 +1008,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -1721,7 +1721,7 @@ dependencies = [ "portable-atomic", "portable-atomic-util", "serde_core", - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -2015,7 +2015,7 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -2493,9 +2493,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.12.2" +version = "1.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" dependencies = [ "aho-corasick", "memchr", @@ -2611,7 +2611,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -2921,7 +2921,7 @@ dependencies = [ "getrandom 0.3.4", "once_cell", "rustix", - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -3201,9 +3201,7 @@ name = "uu_b2sum" version = "0.6.0" dependencies = [ "clap", - "codspeed-divan-compat", "fluent", - "tempfile", "uu_checksum_common", "uucore", ] @@ -3281,9 +3279,7 @@ name = "uu_checksum_common" version = "0.6.0" dependencies = [ "clap", - "codspeed-divan-compat", "fluent", - "tempfile", "uucore", ] @@ -3468,6 +3464,7 @@ dependencies = [ "codspeed-divan-compat", "fluent", "glob", + "rustc-hash", "tempfile", "thiserror 2.0.18", "uucore", @@ -3692,10 +3689,10 @@ dependencies = [ "clap", "codspeed-divan-compat", "fluent", - "fnv", "glob", "hostname", "lscolors", + "rustc-hash", "selinux", "tempfile", "terminal_size", @@ -3709,9 +3706,7 @@ name = "uu_md5sum" version = "0.6.0" dependencies = [ "clap", - "codspeed-divan-compat", "fluent", - "tempfile", "uu_checksum_common", "uucore", ] @@ -4004,9 +3999,7 @@ name = "uu_sha1sum" version = "0.6.0" dependencies = [ "clap", - "codspeed-divan-compat", "fluent", - "tempfile", "uu_checksum_common", "uucore", ] @@ -4016,9 +4009,7 @@ name = "uu_sha224sum" version = "0.6.0" dependencies = [ "clap", - "codspeed-divan-compat", "fluent", - "tempfile", "uu_checksum_common", "uucore", ] @@ -4028,9 +4019,7 @@ name = "uu_sha256sum" version = "0.6.0" dependencies = [ "clap", - "codspeed-divan-compat", "fluent", - "tempfile", "uu_checksum_common", "uucore", ] @@ -4040,9 +4029,7 @@ name = "uu_sha384sum" version = "0.6.0" dependencies = [ "clap", - "codspeed-divan-compat", "fluent", - "tempfile", "uu_checksum_common", "uucore", ] @@ -4052,9 +4039,7 @@ name = "uu_sha512sum" version = "0.6.0" dependencies = [ "clap", - "codspeed-divan-compat", "fluent", - "tempfile", "uu_checksum_common", "uucore", ] @@ -4080,7 +4065,6 @@ dependencies = [ "itoa", "rand 0.9.2", "rand_chacha 0.9.0", - "rand_core 0.9.5", "rustc-hash", "sha3", "uucore", @@ -4306,6 +4290,7 @@ dependencies = [ "codspeed-divan-compat", "fluent", "nix", + "rustc-hash", "string-interner", "thiserror 2.0.18", "uucore", @@ -4661,7 +4646,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -5091,9 +5076,9 @@ dependencies = [ [[package]] name = "zip" -version = "7.2.0" +version = "7.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c42e33efc22a0650c311c2ef19115ce232583abbe80850bc8b66509ebef02de0" +checksum = "268bf6f9ceb991e07155234071501490bb41fd1e39c6a588106dad10ae2a5804" dependencies = [ "crc32fast", "flate2", diff --git a/Cargo.toml b/Cargo.toml index 607b1b198..5b2b1c83c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -366,11 +366,11 @@ platform-info = "2.0.3" procfs = "0.18" rand = { version = "0.9.0", features = ["small_rng"] } rand_chacha = { version = "0.9.0" } -rand_core = "0.9.0" rayon = "1.10" regex = "1.10.4" rlimit = "0.11.0" rstest = "0.26.0" +rustc-hash = "2.1.1" rust-ini = "0.21.0" same-file = "1.0.6" self_cell = "1.0.4" @@ -640,7 +640,7 @@ unexpected_cfgs = { level = "warn", check-cfg = [ 'cfg(fuzzing)', 'cfg(target_os, values("cygwin"))', ] } -#unused_qualifications = "warn" // TODO: fix warnings in uucore, then re-enable this lint +unused_qualifications = "warn" [workspace.lints.clippy] # The counts were generated with this command: @@ -664,18 +664,14 @@ match_same_arms = "allow" # 204 redundant_closure_for_method_calls = "allow" # 125 cast_possible_truncation = "allow" # 122 too_many_lines = "allow" # 101 -trivially_copy_pass_by_ref = "allow" # 84 -single_match_else = "allow" # 82 cast_possible_wrap = "allow" # 78 cast_sign_loss = "allow" # 70 struct_excessive_bools = "allow" # 68 cast_precision_loss = "allow" # 52 cast_lossless = "allow" # 35 -unnecessary_wraps = "allow" # 33 ignored_unit_patterns = "allow" # 21 similar_names = "allow" # 20 large_stack_arrays = "allow" # 20 -wildcard_imports = "allow" # 18 needless_pass_by_value = "allow" # 16 float_cmp = "allow" # 12 items_after_statements = "allow" # 11 @@ -688,7 +684,6 @@ should_panic_without_expect = "allow" # 2 doc_markdown = "allow" unused_self = "allow" enum_glob_use = "allow" -unreadable_literal = "allow" unnested_or_patterns = "allow" implicit_hasher = "allow" struct_field_names = "allow" @@ -699,4 +694,4 @@ from_iter_instead_of_collect = "allow" large_types_passed_by_value = "allow" [workspace.metadata.cargo-shear] -ignored = ["fluent", "libstdbuf"] +ignored = ["clap", "fluent", "libstdbuf"] diff --git a/README.md b/README.md index 450dae317..f0411f5d2 100644 --- a/README.md +++ b/README.md @@ -30,8 +30,8 @@ options might be missing or different behavior might be experienced.
We provide prebuilt binaries, manpages, and shell completions from main branch at https://github.com/uutils/coreutils/releases/tag/latest-commit . -The latest stable tag https://github.com/uutils/coreutils/releases/latest exists only for reproducible products and packagers. -You should use binary from latest commit generally. +The latest stable tag https://github.com/uutils/coreutils/releases/latest also exists for reproducible products and packagers. +Bug reporters should use binary from latest commit.
@@ -119,7 +119,7 @@ cargo build --release --features windows cargo build --release --features unix ``` -To build SELinux-specific features, including `chcon` and `runcon`, ensure that `libselinux` +To build SELinux-specific features, including `chcon` and `runcon`, ensure that `libselinux` and `libclang` are installed on your system. Then, run the following command: ``` cargo build --release --features unix,feat_selinux diff --git a/README.package.md b/README.package.md index ebf7724f6..c69c1f67e 100644 --- a/README.package.md +++ b/README.package.md @@ -28,4 +28,3 @@ uutils coreutils is a cross-platform reimplementation of the GNU coreutils in [Rust](http://www.rust-lang.org). This package does not have its specific `README.md`. - diff --git a/build.rs b/build.rs index 4b77018ab..3c4da1edb 100644 --- a/build.rs +++ b/build.rs @@ -66,7 +66,7 @@ pub fn main() { \n\ #[allow(clippy::too_many_lines)] #[allow(clippy::unreadable_literal)] - fn util_map() -> UtilityMap {\n" + fn util_map() -> UtilityMap {\n" .as_bytes(), ) .unwrap(); diff --git a/docs/.gitignore b/docs/.gitignore index be017dfbe..0ba78e8a9 100644 --- a/docs/.gitignore +++ b/docs/.gitignore @@ -2,4 +2,4 @@ book src/utils src/SUMMARY.md src/platform_table.md -tldr.zip \ No newline at end of file +tldr.zip diff --git a/docs/compiles_table.py b/docs/compiles_table.py old mode 100644 new mode 100755 diff --git a/docs/src/release-notes/0.5.0.md b/docs/src/release-notes/0.5.0.md new file mode 100644 index 000000000..3f1c40c65 --- /dev/null +++ b/docs/src/release-notes/0.5.0.md @@ -0,0 +1,313 @@ +### 📦 **Rust Coreutils 0.5.0 Release:** + +We are excited to announce the release of **Rust Coreutils 0.5.0** — a significant milestone featuring **comprehensive platform improvements**, and **robust testing infrastructure** with continued progress toward full GNU compatibility! + +--- + +### Highlights: + +- **Improved GNU Compatibility** + - **566 passing tests** (+22 from 0.4.0), achieving **87.75%** compatibility + - Reduced failures from 56 to 55 (-1) and skipped tests from 33 to 23 (-10) + - Updated GNU reference from 9.8 to 9.9, adding 11 new tests + - Major improvements to `fold`, `cksum`, `install`, and `numfmt` + +- **Unicode & Text Processing Enhancements** + - `fold`: Added combining character support for proper Unicode text wrapping + - `ptx`: Implemented GNU mode with dumb terminal format + - Enhanced text processing across multiple utilities + +- **Security & Performance Improvements** + - `cksum`: Merged with hashsum for unified checksum functionality + - `install`: Enhanced mode parsing with comma-separated support and umask handling + - `seq`: Improved large integer handling with dedicated benchmarks + - Various memory and performance optimizations across utilities + +- **Platform Support Expansion** + - Added OpenBSD to CI pipeline with comprehensive testing + - Re-enabled Redox OS support in CI + - Enhanced Cygwin support in uucore + - Improved build processes across platforms + +- **Developer Experience Improvements** + - New TTY helper for enhanced testing capabilities + - Comprehensive benchmarking additions for multiple utilities + - Reduced dependency bloat through feature splitting + - Enhanced hardware detection module + +- **Contributions**: This release was made possible by **6 new contributors** joining our community + +--- + +### GNU Test Suite Compatibility: + +| Result | 0.4.0 | 0.5.0 | Change 0.4.0 to 0.5.0 | % Total 0.4.0 | % Total 0.5.0 | % Change 0.4.0 to 0.5.0 | +|---------------|-------|-------|------------------------|---------------|---------------|--------------------------| +| Pass | 544 | 566 | +22 | 85.80% | 87.75% | +1.95% | +| Skip | 33 | 23 | -10 | 5.21% | 3.57% | -1.64% | +| Fail | 56 | 55 | -1 | 8.83% | 8.53% | -0.30% | +| Error | 1 | 1 | 0 | 0.16% | 0.16% | 0% | +| Total | 634 | 645 | +11 (new tests) | | | | + +--- + +![GNU testsuite evolution](https://github.com/uutils/coreutils-tracking/blob/main/gnu-results.svg?raw=true) + +--- + +### Call to Action: + +🌍 **Help us translate** - Contribute translations at [Weblate](https://hosted.weblate.org/projects/rust-coreutils/) +🚀 **Sponsor us on GitHub** to accelerate development: [github.com/sponsors/uutils](https://github.com/sponsors/uutils) +🔗 Download the latest release: [https://uutils.github.io](https://uutils.github.io) + +## What's Changed + +## basenc +* basenc: Fix basenc.pl GNU-compat tests pass by @karanabe in https://github.com/uutils/coreutils/pull/9203 +* fix(Basenc):fix GNU coreutils test bounded-memory.sh by @mattsu2020 in https://github.com/uutils/coreutils/pull/9536 +* base32, base64, baseenc:Simplifying the base encoding uu_app and adding basic buffer tests by @ChrisDryden in https://github.com/uutils/coreutils/pull/9409 + +## chmod +* chmod:fix safe traversal/access by @mattsu2020 in https://github.com/uutils/coreutils/pull/9554 + +## cksum +* cksum/hashsum: merge digest computation & various improvements by @RenjiSann in https://github.com/uutils/coreutils/pull/9135 +* cksum: Fix GNU test cksum-base64-untagged by @RenjiSann in https://github.com/uutils/coreutils/pull/9344 +* Add --debug flag to cksum by @naoNao89 in https://github.com/uutils/coreutils/pull/9088 +* cksum: Fix GNU test `cksum-c.sh` after bump to 9.9 by @RenjiSann in https://github.com/uutils/coreutils/pull/9511 +* cksum: small improvements, l10n by @RenjiSann in https://github.com/uutils/coreutils/pull/9532 +* Fix hardware capabilities detection; cksum --debug by @RenjiSann in https://github.com/uutils/coreutils/pull/9603 + +## cp +* cp: allow directory merging when destination was just created by @vikram-kangotra in https://github.com/uutils/coreutils/pull/9325 +* cp: Adding test to cover no dereference when copying symlinks by @ChrisDryden in https://github.com/uutils/coreutils/pull/9623 +* cp: Enabling cp force flag to run on windows by @ChrisDryden in https://github.com/uutils/coreutils/pull/9624 +* Add comprehensive readonly file regression tests for cp by @naoNao89 in https://github.com/uutils/coreutils/pull/9045 + +## du +* du: Alias -A --apparent-size by @oech3 in https://github.com/uutils/coreutils/pull/9555 +* du: handle `--files0-from=-` with piped in `-` by @cakebaker in https://github.com/uutils/coreutils/pull/8985 + +## env +* Adding integration tests for the braced variable parsing in env by @ChrisDryden in https://github.com/uutils/coreutils/pull/9459 +* env: remove outdated comment by @cakebaker in https://github.com/uutils/coreutils/pull/9496 +* env: use Command::exec() instead of libc::execvp() by @Ecordonnier in https://github.com/uutils/coreutils/pull/9614 + +## fold +* fold:Improve fold bench data generation by @mattsu2020 in https://github.com/uutils/coreutils/pull/9210 +* fix(fold): GNU fold-characters.sh test by @mattsu2020 in https://github.com/uutils/coreutils/pull/9126 +* Fold: Adding combining character support by @ChrisDryden in https://github.com/uutils/coreutils/pull/9328 + +## hashsum +* hashsum: Fix length processing to fix last GNU test by @RenjiSann in https://github.com/uutils/coreutils/pull/9569 + +## install +* install: ignore umask by @akretz in https://github.com/uutils/coreutils/pull/9254 +* Enhance mode parsing to support comma-separated mode strings in install command by @Vesal-J in https://github.com/uutils/coreutils/pull/9298 +* install: do not call chown when called as root by @Ecordonnier in https://github.com/uutils/coreutils/pull/9477 + +## ln +* ln: add error handling for hard link creation on directories by @FidelSch in https://github.com/uutils/coreutils/pull/9342 + +## ls +* ls: prevent ReadDir from closing before entries are processed by @vikram-kangotra in https://github.com/uutils/coreutils/pull/9410 + +## mkfifo +* tests/mkfifo: added a test to check mkfifo permission denied error for code coverage by @asder8215 in https://github.com/uutils/coreutils/pull/9586 + +## nl +* fix(nl): allow repeated flags to match GNU nl behavior (fixes #9132) by @naoNao89 in https://github.com/uutils/coreutils/pull/9140 + +## nohup +* nohup: use Command::exec() instead of libc::execvp() by @Ecordonnier in https://github.com/uutils/coreutils/pull/9613 + +## numfmt +* numfmt: support quetta/ronna suffixes and fix error messages by @naoNao89 in https://github.com/uutils/coreutils/pull/9280 + +## od +* od: fix GNU od.pl by @mattsu2020 in https://github.com/uutils/coreutils/pull/9334 +* fix(od):fix GNU coreutils test od float.sh by @mattsu2020 in https://github.com/uutils/coreutils/pull/9534 + +## pr +* pr: fix header formatting for custom date formats starting with '+' by @sylvestre in https://github.com/uutils/coreutils/pull/9252 + +## ptx +* ptx: implement GNU mode with dumb terminal format by @sylvestre in https://github.com/uutils/coreutils/pull/9573 + +## readlink +* readlink: test calling without args by @cakebaker in https://github.com/uutils/coreutils/pull/9230 +* readlink:Correction to Symbolic Link Handling by @mattsu2020 in https://github.com/uutils/coreutils/pull/9633 + +## seq +* fix(seq): handle BrokenPipe like GNU by @mattsu2020 in https://github.com/uutils/coreutils/pull/9471 +* seq:fix test_broken_pipe_still_exits_success by @mattsu2020 in https://github.com/uutils/coreutils/pull/9520 +* seq: adding large integers benchmarks by @ChrisDryden in https://github.com/uutils/coreutils/pull/9561 + +## shuf +* shuf: add benchmarks by @sylvestre in https://github.com/uutils/coreutils/pull/9320 + +## sort +* sort: make compression program failures non-fatal, warn and fallback to plain files by @sylvestre in https://github.com/uutils/coreutils/pull/9266 + +## stdbuf +* Remove unsafe unwrap() calls in stdbuf error handling by @naoNao89 in https://github.com/uutils/coreutils/pull/9429 + +## stty +* Adding TTY helper for unix to be able to create tests for stty and more by @ChrisDryden in https://github.com/uutils/coreutils/pull/9348 +* Using the pty helper function for the more bin testing by @ChrisDryden in https://github.com/uutils/coreutils/pull/9441 +* stty: baud parsing integration tests and validation by @ChrisDryden in https://github.com/uutils/coreutils/pull/9454 +* stty: Implemented saved state parser for stty by @ChrisDryden in https://github.com/uutils/coreutils/pull/9480 +* stty: Changing shell command to add recognizing a TTY for stty tests by @ChrisDryden in https://github.com/uutils/coreutils/pull/9336 + +## tail +* tail: batch inotify events to prevent redundant headers after SIGSTOP/SIGCONT by @sylvestre in https://github.com/uutils/coreutils/pull/9574 +* tail: fix intermittent overlay-headers test by batching inotify events by @sylvestre in https://github.com/uutils/coreutils/pull/9598 + +## tee +* tee: fix poll timeout causing intermittent hangs with -p flag by @sylvestre in https://github.com/uutils/coreutils/pull/9585 + +## timeout +* Reducing sleep times and timeout times in test_timeout by @ChrisDryden in https://github.com/uutils/coreutils/pull/9448 +* timeout: cleanup return values by @Ecordonnier in https://github.com/uutils/coreutils/pull/9576 +* timeout: remove FIXME in test by @Ecordonnier in https://github.com/uutils/coreutils/pull/9580 + +## dd +* dd: Handle slow transfer rates in progress display by @martinkunkel2 in https://github.com/uutils/coreutils/pull/9529 + +## uucore +* uucore: embed system locale on cargo install by @WaterWhisperer in https://github.com/uutils/coreutils/pull/8604 +* feat(uucore): add shared hardware detection module by @naoNao89 in https://github.com/uutils/coreutils/pull/9279 +* uucore: support cygwin by @ognevny in https://github.com/uutils/coreutils/pull/9535 +* uucore: mode parsing: support comma-separated modes by @martinkunkel2 in https://github.com/uutils/coreutils/pull/9578 + +## uudoc +* uudoc: fix manpage for individual utilities has wrong name (nit) by @shayelkin in https://github.com/uutils/coreutils/pull/9152 + +## CI & Build +* CICD.yml: split PROFILE= from CARGOFLAGS by @oech3 in https://github.com/uutils/coreutils/pull/9219 +* GNUmakefile: use PROFILE_CMD at make test by @oech3 in https://github.com/uutils/coreutils/pull/9214 +* GNUmakefile: generalize logic for SELINUX_PROGS for many platforms by @oech3 in https://github.com/uutils/coreutils/pull/9221 +* build-gnu.sh: Let SELinux optional to use it locally without libselinux by @oech3 in https://github.com/uutils/coreutils/pull/9220 +* freebsd.yml: remove not working PROFILE= by @oech3 in https://github.com/uutils/coreutils/pull/9225 +* GNUmakefile: Remove check for LIBSELINUX_ENABLED by @oech3 in https://github.com/uutils/coreutils/pull/9228 +* Update redoxer and reenable Redox OS in CI by @jackpot51 in https://github.com/uutils/coreutils/pull/9233 +* GNUmakefile: drop not used use_default:=1 by @oech3 in https://github.com/uutils/coreutils/pull/9231 +* ci: Mark runcon-no-reorder as SELinux required by @oech3 in https://github.com/uutils/coreutils/pull/9234 +* github action: add openbsd in the ci by @sylvestre in https://github.com/uutils/coreutils/pull/9196 +* openbsd.yml: Remove not working PROFILE= by @oech3 in https://github.com/uutils/coreutils/pull/9238 +* OpenBSD CI: increase max open files for test job by @lcheylus in https://github.com/uutils/coreutils/pull/9242 +* build-gnu.sh: Remove || true by @oech3 in https://github.com/uutils/coreutils/pull/9256 +* Enable test test_hostname_ip on OpenBSD by @lcheylus in https://github.com/uutils/coreutils/pull/9257 +* build-gnu.sh: Use system tools by @oech3 in https://github.com/uutils/coreutils/pull/9251 +* ci: remove commented out line from `freebsd.yml` by @cakebaker in https://github.com/uutils/coreutils/pull/9239 +* GnuTests.yml: reduce deps by @oech3 in https://github.com/uutils/coreutils/pull/9259 +* Update CICD.yml: Stop releasing duplicated binary by @oech3 in https://github.com/uutils/coreutils/pull/9269 +* Avoid mixing wget and curl by @oech3 in https://github.com/uutils/coreutils/pull/9258 +* CICD.yml: Remove if for .exe by @oech3 in https://github.com/uutils/coreutils/pull/9271 +* GNUmakefile: Use any profile from make install by @oech3 in https://github.com/uutils/coreutils/pull/8730 +* build-gnu.sh: Freeze SELinux build mode by @oech3 in https://github.com/uutils/coreutils/pull/9270 +* CICD.yml: Avoid no space left by @oech3 in https://github.com/uutils/coreutils/pull/9277 +* GNUmakefile: Add missing PROFILE_CMD by @oech3 in https://github.com/uutils/coreutils/pull/9293 +* Cargo.toml: move panic=abort to release profile for binary size by @oech3 in https://github.com/uutils/coreutils/pull/9240 +* Fix build failure without libselinux by @oech3 in https://github.com/uutils/coreutils/pull/9290 +* Revert a patch for runcon-no-reorder (superseded) by @oech3 in https://github.com/uutils/coreutils/pull/9291 +* build-gnu.sh: fix the error on line 110 by @sylvestre in https://github.com/uutils/coreutils/pull/9297 +* build-gnu.sh: adjust the PATH for each run by @sylvestre in https://github.com/uutils/coreutils/pull/9319 +* build-gnu.sh: Use any profile & cleanup vars by @oech3 in https://github.com/uutils/coreutils/pull/9321 +* GnuTests.yml: Check that build-gnu.sh works without libselinux by @oech3 in https://github.com/uutils/coreutils/pull/9299 +* android.yml: Reduce RAM (#9278) by @oech3 in https://github.com/uutils/coreutils/pull/9436 +* l10n.yml:Don't apt-get build-essential by @oech3 in https://github.com/uutils/coreutils/pull/9472 +* l10n.yml: Use PROFILE=release-small for faster CI by @oech3 in https://github.com/uutils/coreutils/pull/9473 +* l10n.yml: Do not brew make (support Xcode make) by @oech3 in https://github.com/uutils/coreutils/pull/9474 +* Update Dockerfile: Don't apt-get jq (preinstalled) by @oech3 in https://github.com/uutils/coreutils/pull/9481 +* build-gnu.sh: Remove 2 not working sed hacks for tr by @oech3 in https://github.com/uutils/coreutils/pull/9476 +* build-gnu.sh: Reduce time to build GNU coreutils by @oech3 in https://github.com/uutils/coreutils/pull/9475 +* CICD.yml: Stop publishing conflicting artifacts by @oech3 in https://github.com/uutils/coreutils/pull/9491 +* CICD.yml: Removed unused code for i586 by @oech3 in https://github.com/uutils/coreutils/pull/9497 +* build-gnu.sh: Remove hfs dep from hardlink-case.sh by @oech3 in https://github.com/uutils/coreutils/pull/9482 +* CICD.yml: Dedup a mkdir by @oech3 in https://github.com/uutils/coreutils/pull/9504 +* CICD.yml: Drop a workaround for old package by @oech3 in https://github.com/uutils/coreutils/pull/9505 +* Remove wget dep by @oech3 in https://github.com/uutils/coreutils/pull/9522 +* Remove Makefile.toml by @oech3 in https://github.com/uutils/coreutils/pull/9568 +* build-gnu.sh: Remove 2 non-GNU binary by @oech3 in https://github.com/uutils/coreutils/pull/9583 +* validation.rs: Remove non GNU hashsum aliases by @oech3 in https://github.com/uutils/coreutils/pull/9589 +* build-gnu.sh: Enable misc/coreutils.sh by @oech3 in https://github.com/uutils/coreutils/pull/9572 +* GHA-delete-GNU-workflow-logs.sh: Add fallback to jaq by @oech3 in https://github.com/uutils/coreutils/pull/9581 +* benchmarks.yml: Stop unnecessary apt-get by @oech3 in https://github.com/uutils/coreutils/pull/9608 +* Do not apt-get preinstalled tools to avoid delaying CI by @oech3 in https://github.com/uutils/coreutils/pull/9466 +* build-gnu.sh: use GNU sed much more for macOS by @oech3 in https://github.com/uutils/coreutils/pull/9467 +* ci: add locales for GNU tests by @cakebaker in https://github.com/uutils/coreutils/pull/9052 + +## Documentation +* README.md: profiles for binary size by @oech3 in https://github.com/uutils/coreutils/pull/9268 +* installation.md: Fix wrong reference for AUR by @oech3 in https://github.com/uutils/coreutils/pull/9447 +* README.md: note that separator is needed for PROG_PREFIX by @oech3 in https://github.com/uutils/coreutils/pull/9444 +* installation.md: Ref MSYS2 package by @oech3 in https://github.com/uutils/coreutils/pull/9456 +* installation.md: Add MSYS2 Cygwin package by @oech3 in https://github.com/uutils/coreutils/pull/9566 +* why-skip.md: Remove an OOD doc by @oech3 in https://github.com/uutils/coreutils/pull/9506 +* why-skip.md: Remove a passing test by @oech3 in https://github.com/uutils/coreutils/pull/9507 +* why-skip.md: Remove 4 sparse-* by @oech3 in https://github.com/uutils/coreutils/pull/9508 +* why-skip.md: Remove 3 tests by @oech3 in https://github.com/uutils/coreutils/pull/9509 +* why-error.md: Cleanup by @oech3 in https://github.com/uutils/coreutils/pull/9510 +* why-error.md: Cleanup and documenting by @oech3 in https://github.com/uutils/coreutils/pull/9512 +* why-skip.md: Remove 1 passing root test by @oech3 in https://github.com/uutils/coreutils/pull/9528 +* why-skip.md: Let spell-checker:ignore a comment by @oech3 in https://github.com/uutils/coreutils/pull/9540 +* why-{skip,error}.md: Cleanup by @oech3 in https://github.com/uutils/coreutils/pull/9602 +* why-{skip,error}.md: Remove stty tests and shared strings by @oech3 in https://github.com/uutils/coreutils/pull/9626 +* lib.rs: Remove non GNU hashsum aliases by @oech3 in https://github.com/uutils/coreutils/pull/9642 +* util.rs: Update obsolete comments by @oech3 in https://github.com/uutils/coreutils/pull/9643 + +## Code Quality & Cleanup +* test: existing file is newer than non-existing file by @cakebaker in https://github.com/uutils/coreutils/pull/9245 +* move the factor divan bench into the actual directory by @sylvestre in https://github.com/uutils/coreutils/pull/9296 +* Remove high variance benchmark functions by @sylvestre in https://github.com/uutils/coreutils/pull/9311 +* Bump `markdownlint_cli2_action` & fix warnings by @cakebaker in https://github.com/uutils/coreutils/pull/9309 +* replace number_prefix by unit-prefix by @sylvestre in https://github.com/uutils/coreutils/pull/9322 +* coreutils: Print utility not found to stderr by @oech3 in https://github.com/uutils/coreutils/pull/9588 +* use github URLs for fetching tldr.zip by @dgilman in https://github.com/uutils/coreutils/pull/9584 +* unit test coverage: fix missing coverage by @martinkunkel2 in https://github.com/uutils/coreutils/pull/9606 +* Removing the per process file flag to reduce the llvm filemerge time by @ChrisDryden in https://github.com/uutils/coreutils/pull/9449 +* Making wild a windows only dependency and gating unit-prefix by @ChrisDryden in https://github.com/uutils/coreutils/pull/9548 +* Splitting parser feature into multiple subfeatures to reduce dependency bloat by @ChrisDryden in https://github.com/uutils/coreutils/pull/9546 + +## Performance & Benchmarking +* benches: Migrate factor benchmarks from Criterion to Divan by @naoNao89 in https://github.com/uutils/coreutils/pull/9247 +* Add functionality to show when tests were previously skipped and now failing accurately by @ChrisDryden in https://github.com/uutils/coreutils/pull/9521 + +## Version Management +* update gnu ref to 9.9 + improve the script by @sylvestre in https://github.com/uutils/coreutils/pull/9216 +* prepare version 0.5.0 by @sylvestre in https://github.com/uutils/coreutils/pull/9596 + +## Dependency Updates +* chore(deps): update rust crate crc-fast to v1.7.1 by @renovate[bot] in https://github.com/uutils/coreutils/pull/9224 +* chore(deps): update rust crate indicatif to v0.18.3 by @renovate[bot] in https://github.com/uutils/coreutils/pull/9226 +* chore(deps): update rust crate crc-fast to v1.8.0 by @renovate[bot] in https://github.com/uutils/coreutils/pull/9282 +* Update vmactions/freebsd-vm action to v1.2.7 by @renovate[bot] in https://github.com/uutils/coreutils/pull/9304 +* chore(deps): update rust crate clap to v4.5.52 by @renovate[bot] in https://github.com/uutils/coreutils/pull/9316 +* chore(deps): update rust crate clap_complete to v4.5.61 by @renovate[bot] in https://github.com/uutils/coreutils/pull/9343 +* chore(deps): update rust crate clap to v4.5.53 by @renovate[bot] in https://github.com/uutils/coreutils/pull/9340 +* GNUmakefile: Use libstdbuf.* instead of libstdbuf* by @oech3 in https://github.com/uutils/coreutils/pull/9345 +* chore(deps): update actions/checkout action to v6 by @renovate[bot] in https://github.com/uutils/coreutils/pull/9416 +* chore(deps): update rust crate parse_datetime to v0.13.3 by @renovate[bot] in https://github.com/uutils/coreutils/pull/9434 +* chore(deps): update rust crate hostname to v0.4.2 by @renovate[bot] in https://github.com/uutils/coreutils/pull/9515 +* Bump `iana-time-zone` and `windows-core` by @cakebaker in https://github.com/uutils/coreutils/pull/9519 +* chore(deps): update vmactions/freebsd-vm action to v1.2.8 by @renovate[bot] in https://github.com/uutils/coreutils/pull/9524 +* chore(deps): update rust crate ctor to v0.6.2 by @renovate[bot] in https://github.com/uutils/coreutils/pull/9544 +* chore(deps): update rust crate ctor to v0.6.3 by @renovate[bot] in https://github.com/uutils/coreutils/pull/9562 +* chore(deps): update vmactions/freebsd-vm action to v1.2.9 by @renovate[bot] in https://github.com/uutils/coreutils/pull/9593 +* chore(deps): update davidanson/markdownlint-cli2-action action to v22 by @renovate[bot] in https://github.com/uutils/coreutils/pull/9610 +* chore(deps): update actions/cache action to v5 by @renovate[bot] in https://github.com/uutils/coreutils/pull/9639 +* chore(deps): update rust crate crc-fast to v1.8.1 by @renovate[bot] in https://github.com/uutils/coreutils/pull/9647 +* chore(deps): update github artifact actions (major) by @renovate[bot] in https://github.com/uutils/coreutils/pull/9645 +* Bump `icu` crates from `2.0.0` to `2.1.1` by @cakebaker in https://github.com/uutils/coreutils/pull/9073 + +## New Contributors +* @WaterWhisperer made their first contribution in https://github.com/uutils/coreutils/pull/8604 +* @ChrisDryden made their first contribution in https://github.com/uutils/coreutils/pull/9328 +* @FidelSch made their first contribution in https://github.com/uutils/coreutils/pull/9342 +* @ognevny made their first contribution in https://github.com/uutils/coreutils/pull/9535 +* @shayelkin made their first contribution in https://github.com/uutils/coreutils/pull/9152 +* @dgilman made their first contribution in https://github.com/uutils/coreutils/pull/9584 + +**Full Changelog**: https://github.com/uutils/coreutils/compare/0.4.0...0.5.0 diff --git a/docs/src/release-notes/0.6.0.md b/docs/src/release-notes/0.6.0.md new file mode 100644 index 000000000..b2064eb28 --- /dev/null +++ b/docs/src/release-notes/0.6.0.md @@ -0,0 +1,759 @@ +### **Rust Coreutils 0.6.0 Release:** + +We are happy to announce the release of **Rust Coreutils 0.6.0** — a major milestone achieving **96% GNU compatibility** with significant safety improvements, enhanced locale support, and extensive bug fixes! + +--- + +### Highlights: + +- **Major GNU Compatibility Leap** + - **622 passing tests** (+56 from 0.5.0), achieving **96.28%** compatibility + - Reduced failures from 55 to just 16 (-39) and skipped tests from 23 to 7 (-16) + - Extensive test suite improvements across `sort`, `ls`, `date`, `cksum`, `tail`, and many more + +- **Safety & Code Quality** + - Removed unsafe code from `date`, `sort`, `locale.rs`, and other utilities + - Replaced raw `libc` calls with safe `nix` crate equivalents throughout + - Enhanced error handling to avoid panics on `/dev/full` writes across many utilities + +- **Locale & Internationalization** + - Added ICU support for locale-aware day/month names in `date` + - Implemented locale-aware hour formatting and calendar support + - Added locale-aware numeric sorting with thousand separator support in `sort` + - Locale-aware collation support in `join` + +- **Performance Improvements** + - `base32`/`base64`/`basenc`: Optimized with reduced memset operations + - `shuf`: Optimized numeric output and added `--random-seed` option + - `date`: Wrapped stdout in BufWriter for batch processing + - `uniq`: Optimized memory usage for ignore-case comparison + - `tsort`: Avoid reading entire input into memory, intern strings + - `df`: Performance improvements with better filesystem handling + +- **Platform Support Expansion** + - Expanded safe directory traversal to all Unix platforms + - Enhanced Cygwin support across multiple utilities + - Added SMACK security module support for `ls`, `id`, `mkdir`, `mkfifo`, `mknod` + - Added RISC-V 64-bit musl target to CI + +- **Notable Utility Improvements** + - `cksum`/`hashsum`: Merged common logic, removed deprecated `hashsum` binary + - `tail`: Added `--debug` flag, fixed `-F` symlink tracking, fixed `--pid` with FIFO + - `timeout`: Added comprehensive signal handling and `--verbose` improvements + - `sort`: Legacy `+POS/-POS` handling, locale-aware collation, debug key annotations + - `pr`: Multiple fixes for headers, form feeds, and pagination options + - `chmod`: Fixed recursive handling and `--preserve-root` option + - `rm`: Fixed error reporting and symlink handling + +- **Contributions**: This release was made possible by **41 new contributors** joining our community + +--- + +### GNU Test Suite Compatibility: + +| Result | 0.5.0 | 0.6.0 | Change 0.5.0 to 0.6.0 | % Total 0.5.0 | % Total 0.6.0 | % Change 0.5.0 to 0.6.0 | +|---------------|-------|-------|------------------------|---------------|---------------|--------------------------| +| Pass | 566 | 622 | +56 | 87.75% | 96.28% | +8.53% | +| Skip | 23 | 7 | -16 | 3.57% | 1.08% | -2.49% | +| Fail | 55 | 16 | -39 | 8.53% | 2.48% | -6.05% | +| Error | 1 | 1 | 0 | 0.16% | 0.15% | ~0% | +| Total | 645 | 646 | +1 (new test) | | | | + +--- + +![GNU testsuite evolution](https://github.com/uutils/coreutils-tracking/blob/main/gnu-results.svg?raw=true) + +--- + +### Call to Action: + +**Help us translate** - Contribute translations at [Weblate](https://hosted.weblate.org/projects/rust-coreutils/) +**Sponsor us on GitHub** to accelerate development: [github.com/sponsors/uutils](https://github.com/sponsors/uutils) + +## What's Changed + +## arch +* Avoid >/dev/full panic by @oech3 in https://github.com/uutils/coreutils/pull/10516 + +## base64 +* base(nc|32|64): Optimize performances reduction memset by @mattsu2020 in https://github.com/uutils/coreutils/pull/9632 +* Improve read error message by @dezgeg in https://github.com/uutils/coreutils/pull/10512 + +## basename +* basename . >/dev/full panics by @oech3 in https://github.com/uutils/coreutils/pull/10553 + +## cat +* do not connect to unix domain socket and instead return an error by @asder8215 in https://github.com/uutils/coreutils/pull/9755 +* fix write error handling to propagate errors instead of panicking by @rynewang in https://github.com/uutils/coreutils/pull/10038 +* tests(cat,stdbuf): Add broken-pipe robustness tests (#4627) by @naoNao89 in https://github.com/uutils/coreutils/pull/8798 + +## chgrp +* correct exit code by @cerdelen in https://github.com/uutils/coreutils/pull/10035 + +## chmod +* fix error handling if multiple files are handled by @cerdelen in https://github.com/uutils/coreutils/pull/9793 +* recursive hyper nested dirs by @cerdelen in https://github.com/uutils/coreutils/pull/9990 +* preserve root by @cerdelen in https://github.com/uutils/coreutils/pull/10033 + +## chroot +* use execvp directly instead of process::Command by @mattsu2020 in https://github.com/uutils/coreutils/pull/9013 + +## cksum +* Checksum ignore binary by @RenjiSann in https://github.com/uutils/coreutils/pull/9695 +* Improve check-only flags handling by @RenjiSann in https://github.com/uutils/coreutils/pull/9770 +* Move --ckeck's deps to clap by @oech3 in https://github.com/uutils/coreutils/pull/9996 +* Move handle_tag_text_binary_flags to clap by @oech3 in https://github.com/uutils/coreutils/pull/9999 +* Simple default tag variable by @oech3 in https://github.com/uutils/coreutils/pull/10057 +* cksum,hashsum: Drop a message replaced by clap by @oech3 in https://github.com/uutils/coreutils/pull/10055 +* cksum/hashsum: Merge common logic under a common crate by @RenjiSann in https://github.com/uutils/coreutils/pull/10142 +* Revert recent checksum CLI changes by @RenjiSann in https://github.com/uutils/coreutils/pull/10141 +* Don't panic when checked file returned EIO by @oech3 in https://github.com/uutils/coreutils/pull/10534 +* --debug 2>/dev/full does not abort by @oech3 in https://github.com/uutils/coreutils/pull/10589 +* -c /dev/null 2>/dev/full should not abort by @oech3 in https://github.com/uutils/coreutils/pull/10643 + +## comm +* fix comparison when reading from pipes by @martinkunkel2 in https://github.com/uutils/coreutils/pull/9545 +* Fixes #10192 - fix(comm): improve stdout handling and add test for lossy UTF-8 output by @Paol0B in https://github.com/uutils/coreutils/pull/10206 +* Properly handle I/O errors when reading input by @dezgeg in https://github.com/uutils/coreutils/pull/10567 + +## cp +* fix preserve-gid when canonicalize fails due to inaccessible parent dirs by @ChrisDryden in https://github.com/uutils/coreutils/pull/9803 +* reduce memory usage for cp -al by skipping unnecessary tracking by @ChrisDryden in https://github.com/uutils/coreutils/pull/9805 +* set status code when encountering circular symbolic links by @Thanhphan1147 in https://github.com/uutils/coreutils/pull/9757 +* symlink flags fixing conflicting flag logic to use last flag by @Dylans123 in https://github.com/uutils/coreutils/pull/9960 +* Added test for permissions copying to an existing file by @max-amb in https://github.com/uutils/coreutils/pull/10049 +* use FileInformation without dereference for symlink destination check to match GNU behaviour for test/nfs-removal-race by @ChrisDryden in https://github.com/uutils/coreutils/pull/10086 +* Avoid other error at cp stream /dev/full by @oech3 in https://github.com/uutils/coreutils/pull/10139 +* Fixed posixly correct dangling symlink test by @max-amb in https://github.com/uutils/coreutils/pull/10247 +* cp/mv: suppress xattr ENOTSUP errors for optional preservation by @ChrisDryden in https://github.com/uutils/coreutils/pull/10083 + +## csplit +* detect and report write errors by @ChrisDryden in https://github.com/uutils/coreutils/pull/9855 + +## date +* remove unsafe by @xtqqczze in https://github.com/uutils/coreutils/pull/9688 +* fix inconsistent input parsing between -s and -d flags by @AnarchistHoneybun in https://github.com/uutils/coreutils/pull/9690 +* Implement locale-aware hour formatting for date command by @naoNao89 in https://github.com/uutils/coreutils/pull/9654 +* improve fuzzer and handle edge cases by @ChrisDryden in https://github.com/uutils/coreutils/pull/9661 +* fix hardcoded formats with nl_langinfo(D_T_FMT) by @naoNao89 in https://github.com/uutils/coreutils/pull/9756 +* Fix military date parsing not adjusting date by @cerdelen in https://github.com/uutils/coreutils/pull/9785 +* add benchmark by @CrazyRoka in https://github.com/uutils/coreutils/pull/9911 +* benchmark: keep only one value by @sylvestre in https://github.com/uutils/coreutils/pull/9989 +* avoid double parsing when resolving timezone abbreviations by @CrazyRoka in https://github.com/uutils/coreutils/pull/9997 +* perf(date): wrap stdout in BufWriter to improve batch processing by @CrazyRoka in https://github.com/uutils/coreutils/pull/9994 +* use PosixCustom formatting for GNU-compatible output by @ChrisDryden in https://github.com/uutils/coreutils/pull/10245 +* test: %x format specifier respects locale settings by @sylvestre in https://github.com/uutils/coreutils/pull/10285 +* handle parentheses as comments like GNU date by @sylvestre in https://github.com/uutils/coreutils/pull/10133 +* add ICU support for day/months names by @sylvestre in https://github.com/uutils/coreutils/pull/10457 +* add locale-aware calendar support for era years by @sylvestre in https://github.com/uutils/coreutils/pull/10473 +* use jiff-icu for locale calendar conversions by @ChrisDryden in https://github.com/uutils/coreutils/pull/10499 +* fuzz_date: skip combined short options like -Rf- that read from stdin by @ChrisDryden in https://github.com/uutils/coreutils/pull/10547 +* fix %% not being preserved in locale format strings by @ChrisDryden in https://github.com/uutils/coreutils/pull/10577 + +## dd +* should terminate with error if skip argument is too large by @ic3man5 in https://github.com/uutils/coreutils/pull/7275 +* use actual filename in nocache error messages by @ChrisDryden in https://github.com/uutils/coreutils/pull/9820 +* fix nocache flag handling at EOF by @ChrisDryden in https://github.com/uutils/coreutils/pull/9818 +* use ibs/obs-sized buffer for skip/seek on non-seekable files by @ChrisDryden in https://github.com/uutils/coreutils/pull/9806 +* use seek for stdin skip when possible by @ChrisDryden in https://github.com/uutils/coreutils/pull/9821 +* get rid of line buffered stdout by @svlv in https://github.com/uutils/coreutils/pull/10235 +* feat(dd): add first benchmark suite for performance validation by @naoNao89 in https://github.com/uutils/coreutils/pull/9136 + +## df +* add binfmt_misc to is_dummy_filesystem by @van-sprundel in https://github.com/uutils/coreutils/pull/9975 +* add benchmarks by @sylvestre in https://github.com/uutils/coreutils/pull/10123 +* improve perfs by @sylvestre in https://github.com/uutils/coreutils/pull/10122 +* add rootfs to is_dummy_filesystem by @ChrisDryden in https://github.com/uutils/coreutils/pull/10159 +* Sum scaled values to compute the totals by @3v1n0 in https://github.com/uutils/coreutils/pull/10438 + +## dir +* add "about" and "usage" instead of being "ls" by @w007878 in https://github.com/uutils/coreutils/pull/10369 + +## dirname +* refactor(dirname): implement pure string manipulation per POSIX by @naoNao89 in https://github.com/uutils/coreutils/pull/8936 +* use Cow::Borrowed to avoid unnecessary heap allocations by @sylvestre in https://github.com/uutils/coreutils/pull/10294 +* add fuzzer to test GNU compatibility by @sylvestre in https://github.com/uutils/coreutils/pull/10289 + +## du +* fix -l/--count-links option not counting hardlinks separately by @WaterWhisperer in https://github.com/uutils/coreutils/pull/9884 +* count links based on inode by @svlv in https://github.com/uutils/coreutils/pull/10313 + +## echo +* Reduce memory allocation by @AnuthaDev in https://github.com/uutils/coreutils/pull/10090 + +## env +* preserve non-UTF-8 environment variables and remove unwrap by @ChrisDryden in https://github.com/uutils/coreutils/pull/9726 +* Improve GNU coreutils Compatibility & Fix env-signal-handler.sh Test by @mattsu2020 in https://github.com/uutils/coreutils/pull/9465 +* env/printenv: dedup the code by @sylvestre in https://github.com/uutils/coreutils/pull/9841 +* dedup some code by @sylvestre in https://github.com/uutils/coreutils/pull/9874 +* --debug 2>/dev/full does not abort by @oech3 in https://github.com/uutils/coreutils/pull/10594 + +## expand +* address a cognitive_complexity warnings by @sylvestre in https://github.com/uutils/coreutils/pull/9930 +* expand, unexpand: Properly handle I/O errors by @dezgeg in https://github.com/uutils/coreutils/pull/10510 + +## expr +* fix regex matching on inputs containing newlines by @ChrisDryden in https://github.com/uutils/coreutils/pull/10543 + +## fmt +* handle invalid UTF-8 input by replacing malformed sequences by @mattsu2020 in https://github.com/uutils/coreutils/pull/9329 + +## fold +* fix gnu test fold-zero-width.sh by @mattsu2020 in https://github.com/uutils/coreutils/pull/9274 +* Add support for -c short option and fix character mode tab handling by @mattsu2020 in https://github.com/uutils/coreutils/pull/10530 + +## groups +* groups > /dev/full panics by @oech3 in https://github.com/uutils/coreutils/pull/10523 + +## hashsum +* Drop --no-names by @oech3 in https://github.com/uutils/coreutils/pull/9762 +* Drop non-GNU `--bits` flag by @RenjiSann in https://github.com/uutils/coreutils/pull/9773 +* Drop locales for --bits by @oech3 in https://github.com/uutils/coreutils/pull/9811 +* Drop benches as covered by cksum benches by @oech3 in https://github.com/uutils/coreutils/pull/9842 +* Move --ckeck's deps to clap by @oech3 in https://github.com/uutils/coreutils/pull/9998 +* hashsum, cksum: Move default stdin to clap by @oech3 in https://github.com/uutils/coreutils/pull/10043 +* hashsum, cksum: Move --check confliction to clap by @oech3 in https://github.com/uutils/coreutils/pull/10041 +* Remove hashsum by @RenjiSann in https://github.com/uutils/coreutils/pull/10587 +* Additional hashsum cleanup by @oech3 in https://github.com/uutils/coreutils/pull/10622 + +## head +* Consolidate legacy argument parsing for head/tail by @sylvestre in https://github.com/uutils/coreutils/pull/9727 +* refactor(head): replace unsafe raw fd usage with safe AsFd API by @mattsu2020 in https://github.com/uutils/coreutils/pull/10161 + +## hostid +* Avoid hostid > /dev/full panic by @oech3 in https://github.com/uutils/coreutils/pull/10172 + +## id +* -p crashes with panic when the real GID doesn't exist in /etc/group by @sylvestre in https://github.com/uutils/coreutils/pull/9670 +* Fix incorrect human-readable output by @frendsick in https://github.com/uutils/coreutils/pull/7814 + +## install +* add FreeBSD's -U (unprivileged) option by @alexandrefresnais in https://github.com/uutils/coreutils/pull/9526 +* prevent TOCTOU race attack by @Dolphindalt in https://github.com/uutils/coreutils/pull/10067 +* install -dv a >/dev/full panics by @oech3 in https://github.com/uutils/coreutils/pull/10541 + +## join +* add benchmarks by @WaterWhisperer in https://github.com/uutils/coreutils/pull/10005 +* add benchmark with the French locale by @WaterWhisperer in https://github.com/uutils/coreutils/pull/10025 +* consider locale collation in field comparison by @WaterWhisperer in https://github.com/uutils/coreutils/pull/9982 +* Benchmark join with actual Unicode data requiring locale collation by @sylvestre in https://github.com/uutils/coreutils/pull/10391 + +## kill +* kill -1 should trigger an error by @sylvestre in https://github.com/uutils/coreutils/pull/9700 + +## ln +* ln -svf /dev/null /tmp/a > /dev/full panics by @oech3 in https://github.com/uutils/coreutils/pull/10539 + +## logname +* logname > /dev/full panics by @oech3 in https://github.com/uutils/coreutils/pull/10522 + +## ls +* Make ls pass the GNU ls-misc.pl suite by @karanabe in https://github.com/uutils/coreutils/pull/9262 +* Add SMACK support by @ChrisDryden in https://github.com/uutils/coreutils/pull/9868 +* some improvements after #9431 by @sylvestre in https://github.com/uutils/coreutils/pull/10149 +* fixes for capability coloring by @bkueng in https://github.com/uutils/coreutils/pull/8615 +* Proper alignment for capabilities and ACLs notifications by @kimono-koans in https://github.com/uutils/coreutils/pull/8793 +* fix symlink chain target coloring by @ChrisDryden in https://github.com/uutils/coreutils/pull/10274 + +## mkdir +* create directories atomically with correct permissions by @rynewang in https://github.com/uutils/coreutils/pull/10036 +* Add SMACK support to id, mkdir, mkfifo, mknod utilities by @ChrisDryden in https://github.com/uutils/coreutils/pull/9910 +* chore: add test for setgid bit inheritance in mkdir -p by @0xferrous in https://github.com/uutils/coreutils/pull/10412 +* mkdir -pv a >/dev/full panics by @oech3 in https://github.com/uutils/coreutils/pull/10540 + +## mkfifo +* replace unsafe libc::mkfifo with nix::unistd::mkfifo by @sylvestre in https://github.com/uutils/coreutils/pull/10112 +* do not change permission when failed to create by @w007878 in https://github.com/uutils/coreutils/pull/10376 +* error when non-file permission mode set by @w007878 in https://github.com/uutils/coreutils/pull/10372 + +## mknod +* Avoid major/minor No overflow by @oech3 in https://github.com/uutils/coreutils/pull/10196 +* fix SELinux cleanup when context setting fails by @ChiamakaUI in https://github.com/uutils/coreutils/pull/10582 + +## mktemp +* Fix template validation to require trailing consecutive Xs by @Xylphy in https://github.com/uutils/coreutils/pull/10224 +* treat empty TMPDIR as unset and fallback to /tmp by @reubenwong97 in https://github.com/uutils/coreutils/pull/10566 + +## more +* file status made more idiomatic. by @devnexen in https://github.com/uutils/coreutils/pull/10151 +* test(more): Fix test_from_line_option race condition by increasing PTY delay by @naoNao89 in https://github.com/uutils/coreutils/pull/9629 + +## mv +* Adding fixes for i-3 GNU tests related to tty output when file is not writeable by @ChrisDryden in https://github.com/uutils/coreutils/pull/9599 +* support moving folder containing symlinks to different filesystem by @yuankunzhang in https://github.com/uutils/coreutils/pull/8605 +* test_mv.rs: Remove ignore from test_mv_broken_symlink_to_another_fs by @oech3 in https://github.com/uutils/coreutils/pull/9978 +* test-mv: Use temporary directory in /dev/shm by @3v1n0 in https://github.com/uutils/coreutils/pull/10439 + +## nice +* use Command::exec() instead of libc::execvp() by @Ecordonnier in https://github.com/uutils/coreutils/pull/9612 +* simplify the code by @sylvestre in https://github.com/uutils/coreutils/pull/9931 +* nice -n huge_num true by @oech3 in https://github.com/uutils/coreutils/pull/10213 +* nice > /dev/full panics by @oech3 in https://github.com/uutils/coreutils/pull/10524 + +## nl +* preserve raw bytes in output instead of using from_utf8_lossy by @ChrisDryden in https://github.com/uutils/coreutils/pull/9673 + +## nohup +* use POSIXLY_CORRECT to determine failure exit code by @ChrisDryden in https://github.com/uutils/coreutils/pull/9685 + +## nproc +* Avoid nproc > /dev/full panic by @oech3 in https://github.com/uutils/coreutils/pull/10157 + +## numfmt +* add --debug flag to print warnings about invalid input (#10110) +* add --unit-separator option for output formatting by @ChrisDryden in https://github.com/uutils/coreutils/pull/10108 +* support non-UTF8 delimiters for locales like GB18030 by @ChrisDryden in https://github.com/uutils/coreutils/pull/10079 +* align error messages for suffixes by @sbentmar in https://github.com/uutils/coreutils/pull/9887 + +## pr +* Enable pr-tests.pl with suppressed diff output by @ChrisDryden in https://github.com/uutils/coreutils/pull/9829 +* add -b flag for backwards compatibility by @ChrisDryden in https://github.com/uutils/coreutils/pull/9993 +* allow character, block, and fifo devices as input by @SaathwikDasari in https://github.com/uutils/coreutils/pull/9946 +* use 72 char line width for all page headers by @jfinkels in https://github.com/uutils/coreutils/pull/10059 +* add default values for -s and -S separator options by @ChrisDryden in https://github.com/uutils/coreutils/pull/10073 +* add -T/--omit-pagination option by @ChrisDryden in https://github.com/uutils/coreutils/pull/10107 +* uniformly scan for form feed and newline chars by @jfinkels in https://github.com/uutils/coreutils/pull/10303 +* refactor common code for reading from file by @jfinkels in https://github.com/uutils/coreutils/pull/10334 +* ignore empty line after form feed char by @jfinkels in https://github.com/uutils/coreutils/pull/10331 +* ignore empty line after newline char by @jfinkels in https://github.com/uutils/coreutils/pull/10332 +* remove unused lines_printed variable by @jfinkels in https://github.com/uutils/coreutils/pull/10474 + +## printenv +* Fix printenv non-UTF8 by @RenjiSann in https://github.com/uutils/coreutils/pull/9728 + +## printf +* Format String Parsing Overflow Causes Panic by @sylvestre in https://github.com/uutils/coreutils/pull/9693 +* printf 1 > /dev/full is not silent by @oech3 in https://github.com/uutils/coreutils/pull/10171 + +## ptx +* fix incorrect column width calculation and padding logic by @CrazyRoka in https://github.com/uutils/coreutils/pull/9681 +* implement -S/--sentence-regexp by @CrazyRoka in https://github.com/uutils/coreutils/pull/9682 +* fix panic when reference length exceeds line width by @CrazyRoka in https://github.com/uutils/coreutils/pull/9816 +* handle invalid regex arguments gracefully instead of panicking by @CrazyRoka in https://github.com/uutils/coreutils/pull/9825 +* handle duplicate input files by @CrazyRoka in https://github.com/uutils/coreutils/pull/9823 + +## readlink +* readlink /etc/mtab > /dev/full panics by @oech3 in https://github.com/uutils/coreutils/pull/10525 + +## rm +* fix safe traversal/access by @mattsu2020 in https://github.com/uutils/coreutils/pull/9577 +* fix error reporting for -r on Linux fixing #9011 by @ChrisDryden in https://github.com/uutils/coreutils/pull/10111 +* don't treat symlinks as write-protected by @GomesGoncalo in https://github.com/uutils/coreutils/pull/10232 +* no abbreviation no preserve root by @cerdelen in https://github.com/uutils/coreutils/pull/10205 +* fix for -rf ./ and variants silently delete current directory by @jacek-kurlit in https://github.com/uutils/coreutils/pull/9924 + +## rmdir +* Remove all trailing slashes when checking for symlinks by @cerdelen in https://github.com/uutils/coreutils/pull/9983 + +## runcon +* use `Command::exec()` instead of `libc::execvp()` by @Ecordonnier in https://github.com/uutils/coreutils/pull/9611 +* add missing utilities to enable tests/runcon/runcon-compute and removing PATH from runcon -c by @ChrisDryden in https://github.com/uutils/coreutils/pull/10088 + +## seq +* add SIGPIPE handling for GNU compatibility by @ChrisDryden in https://github.com/uutils/coreutils/pull/9657 +* Change fuzz_seq_parse_number to should_pass: false by @sylvestre in https://github.com/uutils/coreutils/pull/10335 + +## shred +* ensure deterministic pass sequence compatibility with reference implementation by @sylvestre in https://github.com/uutils/coreutils/pull/9317 +* fix(shred): stop immediately on write errors by @naoNao89 in https://github.com/uutils/coreutils/pull/9649 + +## shuf +* Add `--random-seed`, make `--random-source` GNU-compatible, report write failures, optimize by @blyxxyz in https://github.com/uutils/coreutils/pull/7585 +* optimize numeric output by avoiding write!() by @CrazyRoka in https://github.com/uutils/coreutils/pull/10048 +* Tune performance for -i 1-1000000 by @oech3 in https://github.com/uutils/coreutils/pull/10478 + +## sort +* Add legacy +POS/-POS handling in sort to pass GNU sort-field-limit test by @karanabe in https://github.com/uutils/coreutils/pull/9501 +* remove unsafe by @xtqqczze in https://github.com/uutils/coreutils/pull/9694 +* fix(sort): GNU sort-continue.sh test by @mattsu2020 in https://github.com/uutils/coreutils/pull/9107 +* Align sort debug key annotations with GNU coreutils by @mattsu2020 in https://github.com/uutils/coreutils/pull/9468 +* fixing locale benchmarks since locale is cached in OnceCell by @ChrisDryden in https://github.com/uutils/coreutils/pull/9914 +* bench(sort): add general numeric benchmark by @mattsu2020 in https://github.com/uutils/coreutils/pull/10101 +* feat(sort): add warning messages for obsolescent keys and options by @mattsu2020 in https://github.com/uutils/coreutils/pull/9900 +* refine error handling and localize field parsing errors by @mattsu2020 in https://github.com/uutils/coreutils/pull/10068 +* followup of #10068 by @sylvestre in https://github.com/uutils/coreutils/pull/10226 +* gnu coreutils compatibility (sort float.sh) by @mattsu2020 in https://github.com/uutils/coreutils/pull/9839 +* fix(sort): Enable locale-aware collation for UTF-8 locales by @quantum-encoding in https://github.com/uutils/coreutils/pull/9176 +* gnu coreutils compatibility (sort.pl) by @mattsu2020 in https://github.com/uutils/coreutils/pull/9862 +* gnu core utils test (sort-merge-fdlimit.sh) by @mattsu2020 in https://github.com/uutils/coreutils/pull/9849 +* feat(sort): add locale-aware numeric sorting support(sort-h-thousands-sep.sh) by @mattsu2020 in https://github.com/uutils/coreutils/pull/9848 +* FIX 10314 sort: locale-based collation is not supported by @Paol0B in https://github.com/uutils/coreutils/pull/10481 +* fix: numeric sort (-n) does not recognize thousand separators by @Kaua-Klassmann in https://github.com/uutils/coreutils/pull/10339 + +## split +* Added error when attempting to create file that already exists as directory by @max-amb in https://github.com/uutils/coreutils/pull/9945 + +## stat +* fix(stat): Collection of fixes for stat(1) by @Alonely0 in https://github.com/uutils/coreutils/pull/9078 + +## stdbuf +* use exec instead of forking by @Ecordonnier in https://github.com/uutils/coreutils/pull/9495 + +## stty +* implemented hex and octal parsing for rows and columns by @ChrisDryden in https://github.com/uutils/coreutils/pull/9516 +* columns env support and integration testing by @ChrisDryden in https://github.com/uutils/coreutils/pull/9490 +* Added unit tests for stty.rs to improve test coverage by @naoNao89 in https://github.com/uutils/coreutils/pull/9094 +* use stdin for TTY operations instead of /dev/tty by @ChrisDryden in https://github.com/uutils/coreutils/pull/9881 +* Implemented input and output baud rate setting for stty by @ChrisDryden in https://github.com/uutils/coreutils/pull/9517 +* bump libc & tmp stop musl-i686 by @oech3 in https://github.com/uutils/coreutils/pull/10072 +* Add bad-speed.sh test script to fetch-gnu.sh by @ChrisDryden in https://github.com/uutils/coreutils/pull/10077 +* Add cfg_aliases by @oech3 in https://github.com/uutils/coreutils/pull/10443 +* Don't panic when GNU provided stty 51 us by @oech3 in https://github.com/uutils/coreutils/pull/10526 + +## sync +* Reset O_NONBLOCK flag after file validation to match GNU behavior by @naoNao89 in https://github.com/uutils/coreutils/pull/9437 + +## tac +* fix error message by @cerdelen in https://github.com/uutils/coreutils/pull/9942 +* use temp file for stdin to respect TMPDIR and handle disk-full errors by @ChrisDryden in https://github.com/uutils/coreutils/pull/10094 +* add regex flavor translation for compatibility and new test case by @FidelSch in https://github.com/uutils/coreutils/pull/10416 +* tac, tail, dd: detect closed stdin before Rust sanitizes it to /dev/null by @ChrisDryden in https://github.com/uutils/coreutils/pull/9664 + +## tail +* fix: patch inotify-dir-recreate test for notify crate's threaded inotify by @ChrisDryden in https://github.com/uutils/coreutils/pull/9666 +* Removing flaky inotify-dir patch by @ChrisDryden in https://github.com/uutils/coreutils/pull/9800 +* fix --pid with FIFO by using non-blocking open by @ChrisDryden in https://github.com/uutils/coreutils/pull/9663 +* fix big number handling and error message quoting by @ChrisDryden in https://github.com/uutils/coreutils/pull/10155 +* add --debug flag to show follow implementation mode by @ChrisDryden in https://github.com/uutils/coreutils/pull/10105 +* fix pipe-f test by detecting broken stdout pipe by @ChrisDryden in https://github.com/uutils/coreutils/pull/10156 +* Fix tail -c panics when requested bytes exceed file size by @dhr412 in https://github.com/uutils/coreutils/pull/10268 +* tests: Add 'tests/tail/follow-name' to ignore list by @ChrisDryden in https://github.com/uutils/coreutils/pull/10297 +* fix behaviour of `tail -n0` in follow mode by @AnandajithS in https://github.com/uutils/coreutils/pull/9114 +* Fixed passing -0 to tail. Closes #10191 by @trypsynth in https://github.com/uutils/coreutils/pull/10209 +* fix -F to properly track symlinks with changing targets by @ChrisDryden in https://github.com/uutils/coreutils/pull/10158 + +## tee +* allow multiple -a flags by @sylvestre in https://github.com/uutils/coreutils/pull/10293 +* tee miss/ing 2>/dev/full should not abort by @oech3 in https://github.com/uutils/coreutils/pull/10637 + +## test +* fixing unary operators that are getting parsed as argument instead of string literal by @georgepaulsen in https://github.com/uutils/coreutils/pull/9951 +* Trim whitespace in integer comparisons by @dezgeg in https://github.com/uutils/coreutils/pull/10489 + +## timeout +* cleanup return values by @Ecordonnier in https://github.com/uutils/coreutils/pull/9576 +* use nix kill/setpgid to reduce unsafe by @mattsu2020 in https://github.com/uutils/coreutils/pull/10120 +* backport timeout tests from master by @ChrisDryden in https://github.com/uutils/coreutils/pull/10195 +* display signal 0 as '0' instead of 'EXIT' in verbose mode by @ChrisDryden in https://github.com/uutils/coreutils/pull/10194 +* Improve error handling with explicit expect messages by @naoNao89 in https://github.com/uutils/coreutils/pull/9435 +* add all signal handlers, pass on signals to child, add ignored signal handling by @ChrisDryden in https://github.com/uutils/coreutils/pull/10254 + +## touch +* fix: touch -r: dangling symlink reference is accepted Fixes #9703 by @dshemetov in https://github.com/uutils/coreutils/pull/9732 +* Use libc::UTIME_NOW in touch when updating time to now by @iburaky2 in https://github.com/uutils/coreutils/pull/9870 +* fix: use jiff time for touch tests by @aaron-ang in https://github.com/uutils/coreutils/pull/10093 +* extent a test for many device files by @oech3 in https://github.com/uutils/coreutils/pull/10199 + +## truncate +* eliminate duplicate stat() syscall by @Jean-Christian-Cirstea in https://github.com/uutils/coreutils/pull/9527 + +## tsort +* gnu misc tsort.pl by @mattsu2020 in https://github.com/uutils/coreutils/pull/9289 +* perf (tsort) : avoid reading the whole input into memory and intern strings by @anastygnome in https://github.com/uutils/coreutils/pull/9872 +* Disable tsort_input_parsing_heavy for being too intermittent by @sylvestre in https://github.com/uutils/coreutils/pull/10109 + +## uname +* remove unwrap() for > /dev/full by @oech3 in https://github.com/uutils/coreutils/pull/10517 + +## unexpand +* use byte count for multibyte characters for column width when using -a flag by @JaneIllario in https://github.com/uutils/coreutils/pull/9949 +* add support for extended tab stop syntax (+N and /N) by @sylvestre in https://github.com/uutils/coreutils/pull/9265 +* fix +0 and /0 handling, add integration tests by @ChrisDryden in https://github.com/uutils/coreutils/pull/10406 + +## uniq +* optimize memory usage for ignore-case comparison by @CrazyRoka in https://github.com/uutils/coreutils/pull/10050 +* rename `keys_differ` to `keys_are_equal` by @cakebaker in https://github.com/uutils/coreutils/pull/10070 + +## uptime +* Fix uptime on macOS using sysctl kern.boottime fallback by @naoNao89 in https://github.com/uutils/coreutils/pull/8908 +* refactor(uptime): use FluentArgs for loadavg formatting in get_format by @mattsu2020 in https://github.com/uutils/coreutils/pull/10102 +* Add -p, --pretty argument by @Ivan-Shaml in https://github.com/uutils/coreutils/pull/10143 + +## users +* Avoid > /dev/full panic by @oech3 in https://github.com/uutils/coreutils/pull/10165 + +## wc +* GNU wc-cpu.sh by @mattsu2020 in https://github.com/uutils/coreutils/pull/9144 +* Ensure the output order of stdout and stderror remains unchanged. by @mattsu2020 in https://github.com/uutils/coreutils/pull/9905 +* respect POSIXLY_CORRECT for word counting by @dhr412 in https://github.com/uutils/coreutils/pull/10344 +* fix word undercount with invalid byte sequences by @dhr412 in https://github.com/uutils/coreutils/pull/10348 +* --debug 2>/dev/full does not fail by @oech3 in https://github.com/uutils/coreutils/pull/10590 + +## yes +* Silently handle broken pipe on windows by @ChrisDryden in https://github.com/uutils/coreutils/pull/10429 + +## uucore +* use --suffix to enable backup mode by @jacek-kurlit in https://github.com/uutils/coreutils/pull/9741 +* parser: add binary support to determine_number_system and parse_size by @csko in https://github.com/uutils/coreutils/pull/9659 +* locale.rs: move more code outside of the unsafe block by @sylvestre in https://github.com/uutils/coreutils/pull/9720 +* fix clippy::pedantic warnings in build.rs and generated locale code by @skjha98 in https://github.com/uutils/coreutils/pull/9837 +* Remove lossy OS string conversions by @blyxxyz in https://github.com/uutils/coreutils/pull/9337 +* document that libselinux caches is_selinux_enabled by @ChrisDryden in https://github.com/uutils/coreutils/pull/9873 +* switch to `BigDecimal::powi` implementation by @xtqqczze in https://github.com/uutils/coreutils/pull/9957 +* uucore/uptime: remove unreachable code on Windows by @cakebaker in https://github.com/uutils/coreutils/pull/9985 +* fsext.rs: Replace getmntinfo with libc by @oech3 in https://github.com/uutils/coreutils/pull/10075 +* refactor: use `jiff` for safer time features by @aaron-ang in https://github.com/uutils/coreutils/pull/10118 +* use the enable_pipe_errors instead of libc::signal by @sylvestre in https://github.com/uutils/coreutils/pull/10113 +* feat: Expand safe directory traversal to all Unix platforms and fix related type conversions. by @abendrothj in https://github.com/uutils/coreutils/pull/9792 +* simplify cfg checks in signals.rs by @sylvestre in https://github.com/uutils/coreutils/pull/10296 +* centralize SIGPIPE handling in main macro by @ChrisDryden in https://github.com/uutils/coreutils/pull/10354 +* uucore(fs): expand path normalization by @FidelSch in https://github.com/uutils/coreutils/pull/10532 + +## uudoc +* style(uudoc): update header formatting for options and examples by @hwhsu1231 in https://github.com/uutils/coreutils/pull/10004 +* print tldr.zip warning only once per process by @rynewang in https://github.com/uutils/coreutils/pull/10039 + +## coreutils +* Remove limitation for prefix by @oech3 in https://github.com/uutils/coreutils/pull/10261 +* Drop find_prefixed_util by @oech3 in https://github.com/uutils/coreutils/pull/10306 +* uu: fix typo by @antonkesy in https://github.com/uutils/coreutils/pull/10170 +* fix: properly handle write errors for --version and --help output by @naoNao89 in https://github.com/uutils/coreutils/pull/10223 + +## Platform Support +* Add more Cygwin support by @500-internal-server-error in https://github.com/uutils/coreutils/pull/9686 +* Bump mio for cygwin by @oech3 in https://github.com/uutils/coreutils/pull/9809 +* clippy: allow "cygwin" as value for "target_os" by @cakebaker in https://github.com/uutils/coreutils/pull/10054 +* fix: allow selinux on android by @xtqqczze in https://github.com/uutils/coreutils/pull/10419 +* runcon, chcon: Don't fetch crates at unsupported target by @oech3 in https://github.com/uutils/coreutils/pull/10360 + +## CI & Build +* GnuTests: Reduce gnproc deps on BSD by @oech3 in https://github.com/uutils/coreutils/pull/9644 +* GnuTests: Split online process to a script by @oech3 in https://github.com/uutils/coreutils/pull/9652 +* GnuTests: Caches for faster configure and skipping make by @oech3 in https://github.com/uutils/coreutils/pull/9627 +* run-gnu-test.sh: Fix nproc broken by cache by @oech3 in https://github.com/uutils/coreutils/pull/9735 +* build-gnu.sh: Move {ch,run}con tests to Fedora VM and avoid wrong result by @oech3 in https://github.com/uutils/coreutils/pull/9607 +* GnuTests.yml: Fix caches by @oech3 in https://github.com/uutils/coreutils/pull/9739 +* build-gnu.sh: Don't hack test suite to force-enable tests by @oech3 in https://github.com/uutils/coreutils/pull/9744 +* GnuTests.yml: Discard caches at each build-gnu.sh update by @oech3 in https://github.com/uutils/coreutils/pull/9753 +* build-gnu.sh: Enable test/df/no-mtab-status.sh by @oech3 in https://github.com/uutils/coreutils/pull/9759 +* GNUmakefile: Prepend PROG_PREFIX to LIBSTDBUF_DIR too by @oech3 in https://github.com/uutils/coreutils/pull/9068 +* build-gnu.sh: correct path suggestion for fetch-gnu.sh by @nutthawit in https://github.com/uutils/coreutils/pull/9788 +* is_a_tty.sh: Reduce lines by @oech3 in https://github.com/uutils/coreutils/pull/9814 +* ci: add Non UTF8 locale for GNU tests by @ChrisDryden in https://github.com/uutils/coreutils/pull/9807 +* Avoid wrong PASS of cp-mv-enotsup-xattr runcon-compute and enable 1 test by @oech3 in https://github.com/uutils/coreutils/pull/9743 +* CI: Add SMACK test runner for GNU tests by @ChrisDryden in https://github.com/uutils/coreutils/pull/9888 +* openbsd ci: try to remove more temporary files by @sylvestre in https://github.com/uutils/coreutils/pull/9904 +* build-gnu.sh: Use MULTICALL=y and skip not used utils for faster build by @oech3 in https://github.com/uutils/coreutils/pull/9567 +* CICD.yml: Avoid no space left much more by @oech3 in https://github.com/uutils/coreutils/pull/9907 +* GnuTests.yml: Stop manpage generation to reduce size of log by @oech3 in https://github.com/uutils/coreutils/pull/9943 +* CICD.yml: Avoid no space left again by @oech3 in https://github.com/uutils/coreutils/pull/9939 +* GnuTests: Drop texinfo dep by @oech3 in https://github.com/uutils/coreutils/pull/9944 +* GnuTests.yml: install Rust without rustfmt by @cakebaker in https://github.com/uutils/coreutils/pull/9947 +* openbsd.yml: Replace cargo cache related disk space hack by @oech3 in https://github.com/uutils/coreutils/pull/9917 +* GnuTests.yml: Drop autopoint by @oech3 in https://github.com/uutils/coreutils/pull/9965 +* GnuTests.yml: Drop git from VM by @oech3 in https://github.com/uutils/coreutils/pull/9969 +* build-gnu.sh: Skip make at SELinux tests by @oech3 in https://github.com/uutils/coreutils/pull/9970 +* build-gnu.sh: Drop a variable & cleanup by @oech3 in https://github.com/uutils/coreutils/pull/9953 +* GnuTests.yml: use minimal Rust profile in VM by @cakebaker in https://github.com/uutils/coreutils/pull/9977 +* benchmarks: use `simulation` mode by @cakebaker in https://github.com/uutils/coreutils/pull/9986 +* CI: Default build artifact for riscv64+musl on CICD.yml by @ffgan in https://github.com/uutils/coreutils/pull/10029 +* ci: set `-no-metrics` for Android emulator by @cakebaker in https://github.com/uutils/coreutils/pull/10051 +* build-gnu.sh: Let md5sum.pl clap compatible by @oech3 in https://github.com/uutils/coreutils/pull/10065 +* FixPR.yml: Use cargo fetch --target $(rustc --print host-tuple) by @oech3 in https://github.com/uutils/coreutils/pull/10074 +* ci: add Codecov Test Analytics integration by @ChrisDryden in https://github.com/uutils/coreutils/pull/10091 +* Add some intermittent tests to the list by @sylvestre in https://github.com/uutils/coreutils/pull/10104 +* ci: re-enable i686-musl platform by @cakebaker in https://github.com/uutils/coreutils/pull/10127 +* ci: add df/skip-rootfs test to SMACK/ROOTFS CI by @ChrisDryden in https://github.com/uutils/coreutils/pull/10150 +* cp: Adding dd to SELinux CI to enable cp-a-selinux by @ChrisDryden in https://github.com/uutils/coreutils/pull/10148 +* run-gnu-tests-smack-ci.sh: Use release-small profile for faster build by @oech3 in https://github.com/uutils/coreutils/pull/10144 +* run-gnu-tests-smack-ci.sh: Use multi-call binary for faster build by @oech3 in https://github.com/uutils/coreutils/pull/10162 +* build-gnu.sh: Build seq as multicall-binary by @oech3 in https://github.com/uutils/coreutils/pull/10152 +* GNUmakefile: Support CARGO_BUILD_TARGET by @oech3 in https://github.com/uutils/coreutils/pull/9223 +* make build-gnu.sh reentrant by @sylvestre in https://github.com/uutils/coreutils/pull/10180 +* CICD.yml: Upload binaries from latest commit too by @oech3 in https://github.com/uutils/coreutils/pull/10212 +* freebsd.yml: Avoid no space left on device by @oech3 in https://github.com/uutils/coreutils/pull/10203 +* CICD.yml: Upload uudoc at least for Linux-x64-glibc by @oech3 in https://github.com/uutils/coreutils/pull/10231 +* benchmarks: Add memory benchmarks by @sylvestre in https://github.com/uutils/coreutils/pull/10229 +* CICD.yml: Drop checks for hashsum by @oech3 in https://github.com/uutils/coreutils/pull/10259 +* try to decrease the variance in the memory usage for benchmark by @sylvestre in https://github.com/uutils/coreutils/pull/10273 +* CICD.yml: Upload manpages and completions by @oech3 in https://github.com/uutils/coreutils/pull/10257 +* CICD.yml: upload binaries without version string by @oech3 in https://github.com/uutils/coreutils/pull/10217 +* CI: Purge disk space at background to avoid delaying by @oech3 in https://github.com/uutils/coreutils/pull/10276 +* GnuTests.yml: Dedup setenforce 1 by @oech3 in https://github.com/uutils/coreutils/pull/10266 +* build-gnu.sh: Replace ${SED} with gsed wrapper by @oech3 in https://github.com/uutils/coreutils/pull/10201 +* bench: try to remove more variances by @sylvestre in https://github.com/uutils/coreutils/pull/10277 +* bench: reduce memory variance in cp and numfmt benchmarks by @sylvestre in https://github.com/uutils/coreutils/pull/10283 +* ci: ensure test failures are caught in coverage script and fix them by @sylvestre in https://github.com/uutils/coreutils/pull/10286 +* bench: fix variance in remaining numfmt benchmarks by @sylvestre in https://github.com/uutils/coreutils/pull/10292 +* CICD.yml: Remove zsh completion for [ by @oech3 in https://github.com/uutils/coreutils/pull/10278 +* CICD.yml: Add man and completion for coreutils(1) to docs.tar.zst by @oech3 in https://github.com/uutils/coreutils/pull/10299 +* prepare release 0.6.0 by @sylvestre in https://github.com/uutils/coreutils/pull/10291 +* CIDD.yml: cargo fetch platform spec crates only by @oech3 in https://github.com/uutils/coreutils/pull/10304 +* build-gnu.sh: Enable help-version-getopt.sh (PASS) by @oech3 in https://github.com/uutils/coreutils/pull/10305 +* CICD.yml: Stop manually stripping by @oech3 in https://github.com/uutils/coreutils/pull/10311 +* GnuTests: Replace incompat tests & drop obsolete hack by @oech3 in https://github.com/uutils/coreutils/pull/9976 +* Backport tests/env/env.sh for better multicall support by @oech3 in https://github.com/uutils/coreutils/pull/10337 +* CICD.yml: Drop unused apt-get by @oech3 in https://github.com/uutils/coreutils/pull/10343 +* CICD.yml: Upload individual bins to release by @oech3 in https://github.com/uutils/coreutils/pull/10338 +* CICD.yml: Drop a toolchain outside of VM by @oech3 in https://github.com/uutils/coreutils/pull/10381 +* ci: move `-no-metrics` to `COMMON_EMULATOR_OPTIONS` by @cakebaker in https://github.com/uutils/coreutils/pull/10385 +* CICD.yml: Filter-out non-SELinux progs on SELinux test by @oech3 in https://github.com/uutils/coreutils/pull/10382 +* l10n.yml: Use git clone --depth=1 by @oech3 in https://github.com/uutils/coreutils/pull/10367 +* Don't build coreutils without MULTICALL=y by @oech3 in https://github.com/uutils/coreutils/pull/10359 +* gnu: patch inotify-race tests to use Rust gdb breakpoints by @ChrisDryden in https://github.com/uutils/coreutils/pull/9908 +* fetch-gnu: add tests/tail/inotify-dir-recreate.sh by @ChrisDryden in https://github.com/uutils/coreutils/pull/10392 +* ci: generate the french locale for benchmark by @sylvestre in https://github.com/uutils/coreutils/pull/10398 +* ci: disable memory profiling in benchmarks due to variance by @sylvestre in https://github.com/uutils/coreutils/pull/10399 +* check-safe-traversal.sh: Support any profile by @oech3 in https://github.com/uutils/coreutils/pull/10401 +* android.yml: Drop x86 by @oech3 in https://github.com/uutils/coreutils/pull/10353 +* CICD.yml: Merge 2 tests by @oech3 in https://github.com/uutils/coreutils/pull/10424 +* openbsd.yml: Try to reduce time by @oech3 in https://github.com/uutils/coreutils/pull/10423 +* freebsd.yml: Drop useless cache actions by @oech3 in https://github.com/uutils/coreutils/pull/10362 +* Reduce duplicated management of supported utils by @oech3 in https://github.com/uutils/coreutils/pull/10409 +* fix(ci): remove outdated `analysis` mode by @not-matthias in https://github.com/uutils/coreutils/pull/10414 +* Zsh suspended by @sylvestre in https://github.com/uutils/coreutils/pull/10431 +* fix(CI): clippy unnecessary unwrap by @aaron-ang in https://github.com/uutils/coreutils/pull/10435 +* ci/android: use `--locked` when installing `nextest` by @cakebaker in https://github.com/uutils/coreutils/pull/10467 +* CI: Disable incremental build for faster CI by @oech3 in https://github.com/uutils/coreutils/pull/10462 +* CICD.yml: Merge 2 "separately" by @oech3 in https://github.com/uutils/coreutils/pull/10425 +* fuzzing.yml: Reproducible toolchain setup by @oech3 in https://github.com/uutils/coreutils/pull/10487 +* ci: show diff for toml_format by @xtqqczze in https://github.com/uutils/coreutils/pull/10501 +* CICD: Remove unused rustfmt by @oech3 in https://github.com/uutils/coreutils/pull/10509 +* build-gnu.sh: remove workaround for timeout/yes by @Ecordonnier in https://github.com/uutils/coreutils/pull/10505 +* GnuTests: Drop cache action outside of VM, use preinstalled rust by @oech3 in https://github.com/uutils/coreutils/pull/10504 +* GNUmakefile: Complete TEST_PROGS by @oech3 in https://github.com/uutils/coreutils/pull/10495 +* CICD.yml: (partial) reproducible toolchain setup by @oech3 in https://github.com/uutils/coreutils/pull/10500 +* ci: use toolchain override shorthand by @xtqqczze in https://github.com/uutils/coreutils/pull/10600 +* GnuTests: Save setup time 9 min+ by @oech3 in https://github.com/uutils/coreutils/pull/10557 +* GNUmakefile: Drop HASHSUM by @oech3 in https://github.com/uutils/coreutils/pull/10634 +* Enable cat, sort, readlink and tr tests in busybox test suite by @ChrisDryden in https://github.com/uutils/coreutils/pull/9850 + +## Code Quality & Cleanup +* clippy: fix map_unwrap_or lint by @xtqqczze in https://github.com/uutils/coreutils/pull/9678 +* clippy: fix ptr lints by @xtqqczze in https://github.com/uutils/coreutils/pull/9687 +* clippy: enable `needless_raw_string_hashes` lint by @cakebaker in https://github.com/uutils/coreutils/pull/9840 +* clippy: fix uninlined_format_args lint by @xtqqczze in https://github.com/uutils/coreutils/pull/9962 +* Enable clippy::assigning_clones on OpenBSD by @xtqqczze in https://github.com/uutils/coreutils/pull/9956 +* Bump `signal-hook` & add it to skip list by @cakebaker in https://github.com/uutils/coreutils/pull/9979 +* benchmark: some minor improvements by @sylvestre in https://github.com/uutils/coreutils/pull/9928 +* mac: change the delay value to avoid intermittent by @sylvestre in https://github.com/uutils/coreutils/pull/9895 +* fix test_backup_mode_suffix_without_backup_option test on mac by @sylvestre in https://github.com/uutils/coreutils/pull/9891 +* fix: reduce factor parallel test runtime by @aaron-ang in https://github.com/uutils/coreutils/pull/10136 +* deny.toml: add `constant_time_eq` to skip list by @cakebaker in https://github.com/uutils/coreutils/pull/10137 +* deny.toml: remove `constant_time_eq` from skip list by @cakebaker in https://github.com/uutils/coreutils/pull/10176 +* fix: handle /dev/full write errors gracefully by @naoNao89 in https://github.com/uutils/coreutils/pull/10062 +* benchmarks: reduce allocations in run_util_function by @xtqqczze in https://github.com/uutils/coreutils/pull/10378 +* uutests: replace unsafe `libc::stat` with `nix::stat` by @xtqqczze in https://github.com/uutils/coreutils/pull/10364 +* deny.toml: remove `wasi` from skip list by @cakebaker in https://github.com/uutils/coreutils/pull/10355 +* fuzz: fix clippy lints by @xtqqczze in https://github.com/uutils/coreutils/pull/10466 +* clippy: fix used_underscore_binding lint by @xtqqczze in https://github.com/uutils/coreutils/pull/10058 +* clippy: fix needless_continue lint by @xtqqczze in https://github.com/uutils/coreutils/pull/10340 +* clippy: remove assigning_clones lint suppression by @xtqqczze in https://github.com/uutils/coreutils/pull/10377 +* clippy: fix map_clone lint by @xtqqczze in https://github.com/uutils/coreutils/pull/10619 +* deny.toml: remove unmatched items from skip list by @xtqqczze in https://github.com/uutils/coreutils/pull/10605 +* refactor: rename print functions to write for consistency by @xtqqczze in https://github.com/uutils/coreutils/pull/10536 +* editorconfig: specify toml settings by @xtqqczze in https://github.com/uutils/coreutils/pull/10601 + +## Documentation +* README.md: Guide people to release page or main by @oech3 in https://github.com/uutils/coreutils/pull/9709 +* DEVELOPMENT.md: Remove a wrong desc by @oech3 in https://github.com/uutils/coreutils/pull/9717 +* why-error.md: Cleanup by @oech3 in https://github.com/uutils/coreutils/pull/9738 +* why-error.md: Remove 2 tests by @oech3 in https://github.com/uutils/coreutils/pull/9799 +* why-error.md: Remove 1 test by @oech3 in https://github.com/uutils/coreutils/pull/9826 +* why-*.md: Drop as issue is enough by @oech3 in https://github.com/uutils/coreutils/pull/9835 +* CONTRIBUTING.md: update crate structure by @cakebaker in https://github.com/uutils/coreutils/pull/9861 +* CONTRIBUTING.md: Bug report with LANG=C by @oech3 in https://github.com/uutils/coreutils/pull/9890 +* README.md: Avoid losting unix specific progs by @oech3 in https://github.com/uutils/coreutils/pull/9867 +* DEVELOPMENT.md: mention nightly requirement for code coverage by @nutthawit in https://github.com/uutils/coreutils/pull/9919 +* contrib: add info about expectations and ping by @sylvestre in https://github.com/uutils/coreutils/pull/10275 +* {README,CONTRIBUTIONS}.md: Add link to binaries from latest commit by @oech3 in https://github.com/uutils/coreutils/pull/10280 +* README.package.md: Fix MSRV by @oech3 in https://github.com/uutils/coreutils/pull/10485 + +## Dependency Updates +* chore(deps): update rust crate console to v0.16.2 by @renovate[bot] in https://github.com/uutils/coreutils/pull/9675 +* chore(deps): update rust crate clap_complete to v4.5.62 by @renovate[bot] in https://github.com/uutils/coreutils/pull/9698 +* chore(deps): update rust crate zip to v7 by @renovate[bot] in https://github.com/uutils/coreutils/pull/9731 +* chore(deps): update rust crate crc-fast to v1.8.2 by @renovate[bot] in https://github.com/uutils/coreutils/pull/9730 +* chore(deps): update rust crate linux-raw-sys to v0.12.1 by @renovate[bot] in https://github.com/uutils/coreutils/pull/9787 +* chore(deps): update rust crate divan to v4.2.0 by @renovate[bot] in https://github.com/uutils/coreutils/pull/9786 +* chore(deps): update rust crate crc-fast to v1.9.0 by @renovate[bot] in https://github.com/uutils/coreutils/pull/9791 +* chore(deps): update rust crate jiff to v0.2.17 by @renovate[bot] in https://github.com/uutils/coreutils/pull/9830 +* chore(deps): update dawidd6/action-download-artifact action to v12 by @renovate[bot] in https://github.com/uutils/coreutils/pull/9836 +* chore(deps): update rust crate proc-macro2 to v1.0.104 by @renovate[bot] in https://github.com/uutils/coreutils/pull/9878 +* chore(deps): update rust crate bigdecimal to v0.4.10 by @renovate[bot] in https://github.com/uutils/coreutils/pull/9880 +* chore(deps): update actions/upload-artifact action to v6 by @renovate[bot] in https://github.com/uutils/coreutils/pull/9896 +* chore(deps): update rust crate self_cell to v1.2.2 by @renovate[bot] in https://github.com/uutils/coreutils/pull/9927 +* chore(deps): update rust crate clap_complete to v4.5.64 by @renovate[bot] in https://github.com/uutils/coreutils/pull/9909 +* chore(deps): update rust crate clap to v4.5.54 by @renovate[bot] in https://github.com/uutils/coreutils/pull/10000 +* Bump libc to 0.2.178 with fix for FreeBSD by @oech3 in https://github.com/uutils/coreutils/pull/10046 +* chore(deps): update rust crate jiff to v0.2.18 by @renovate[bot] in https://github.com/uutils/coreutils/pull/10084 +* chore(deps): update rust crate proc-macro2 to v1.0.105 by @renovate[bot] in https://github.com/uutils/coreutils/pull/10085 +* chore(deps): update rust crate quote to v1.0.43 by @renovate[bot] in https://github.com/uutils/coreutils/pull/10087 +* chore(deps): update rust crate libc to v0.2.179 by @renovate[bot] in https://github.com/uutils/coreutils/pull/8717 +* chore(deps): update rust crate divan to v4.2.1 by @renovate[bot] in https://github.com/uutils/coreutils/pull/10103 +* chore(deps): update rust crate clap_complete to v4.5.65 by @renovate[bot] in https://github.com/uutils/coreutils/pull/10106 +* chore(deps): update rust crate libc to v0.2.180 by @renovate[bot] in https://github.com/uutils/coreutils/pull/10126 +* replace bincode by wincode by @sylvestre in https://github.com/uutils/coreutils/pull/10134 +* chore(deps): update rust crate blake3 to v1.8.3 by @renovate[bot] in https://github.com/uutils/coreutils/pull/10132 +* chore(deps): update rust crate data-encoding-macro to v0.1.19 by @renovate[bot] in https://github.com/uutils/coreutils/pull/10163 +* chore(deps): update rust crate blake2b_simd to v1.0.4 by @renovate[bot] in https://github.com/uutils/coreutils/pull/10168 +* chore(deps): update rust crate rand_core to v0.9.4 by @renovate[bot] in https://github.com/uutils/coreutils/pull/10202 +* chore(deps): update rust crate time to v0.3.45 by @renovate[bot] in https://github.com/uutils/coreutils/pull/10216 +* chore(deps): update rust crate rand_core to v0.9.5 by @renovate[bot] in https://github.com/uutils/coreutils/pull/10215 +* chore(deps): update rust crate zip to v7.1.0 by @renovate[bot] in https://github.com/uutils/coreutils/pull/10237 +* chore(deps): update rust crate filetime to v0.2.27 by @renovate[bot] in https://github.com/uutils/coreutils/pull/10336 +* chore(deps): update rust crate thiserror to v2.0.18 by @renovate[bot] in https://github.com/uutils/coreutils/pull/10329 +* deps: remove unused rust crate linux-raw-sys by @xtqqczze in https://github.com/uutils/coreutils/pull/10374 +* chore(deps): update rust crate zip to v7.2.0 by @renovate[bot] in https://github.com/uutils/coreutils/pull/10387 +* chore(deps): update rust crate divan to v4.3.0 by @renovate[bot] in https://github.com/uutils/coreutils/pull/10413 +* deps: remove unused rust deps by @xtqqczze in https://github.com/uutils/coreutils/pull/10404 +* chore(deps): update rust crate proc-macro2 to v1.0.106 by @renovate[bot] in https://github.com/uutils/coreutils/pull/10420 +* fix(deps): remove duplicate itertools package by @xtqqczze in https://github.com/uutils/coreutils/pull/10440 +* fix(deps): refactor nix package configuration by @xtqqczze in https://github.com/uutils/coreutils/pull/10441 +* chore(deps): update rust crate quote to v1.0.44 by @renovate[bot] in https://github.com/uutils/coreutils/pull/10446 +* fix(deps): refactor xattr package configuration by @xtqqczze in https://github.com/uutils/coreutils/pull/10454 +* fix(deps): refactor rlimit package configuration by @xtqqczze in https://github.com/uutils/coreutils/pull/10464 +* chore(deps): update rust crate signal-hook to v0.4.2 by @renovate[bot] in https://github.com/uutils/coreutils/pull/10468 +* chore(deps): update rust crate signal-hook to v0.4.3 by @renovate[bot] in https://github.com/uutils/coreutils/pull/10472 +* deps: refactor crossterm package configuration by @xtqqczze in https://github.com/uutils/coreutils/pull/10484 +* chore(deps): update rust crate clap to v4.5.55 by @renovate[bot] in https://github.com/uutils/coreutils/pull/10521 +* chore(deps): update rust crate clap to v4.5.55 by @renovate[bot] in https://github.com/uutils/coreutils/pull/10544 +* chore(deps): update rust crate clap to v4.5.56 by @renovate[bot] in https://github.com/uutils/coreutils/pull/10568 +* chore(deps): update dawidd6/action-download-artifact action to v13 by @renovate[bot] in https://github.com/uutils/coreutils/pull/10586 +* chore(deps): update dawidd6/action-download-artifact action to v14 by @renovate[bot] in https://github.com/uutils/coreutils/pull/10641 +* chore(deps): update rust crate rlimit to 0.11.0 by @renovate[bot] in https://github.com/uutils/coreutils/pull/10645 + +## New Contributors +* @xtqqczze made their first contribution in https://github.com/uutils/coreutils/pull/9678 +* @Jean-Christian-Cirstea made their first contribution in https://github.com/uutils/coreutils/pull/9527 +* @jacek-kurlit made their first contribution in https://github.com/uutils/coreutils/pull/9741 +* @csko made their first contribution in https://github.com/uutils/coreutils/pull/9659 +* @nutthawit made their first contribution in https://github.com/uutils/coreutils/pull/9788 +* @dshemetov made their first contribution in https://github.com/uutils/coreutils/pull/9732 +* @500-internal-server-error made their first contribution in https://github.com/uutils/coreutils/pull/9686 +* @CrazyRoka made their first contribution in https://github.com/uutils/coreutils/pull/9681 +* @skjha98 made their first contribution in https://github.com/uutils/coreutils/pull/9837 +* @alexandrefresnais made their first contribution in https://github.com/uutils/coreutils/pull/9526 +* @iburaky2 made their first contribution in https://github.com/uutils/coreutils/pull/9870 +* @georgepaulsen made their first contribution in https://github.com/uutils/coreutils/pull/9951 +* @SaathwikDasari made their first contribution in https://github.com/uutils/coreutils/pull/9946 +* @JaneIllario made their first contribution in https://github.com/uutils/coreutils/pull/9949 +* @Dylans123 made their first contribution in https://github.com/uutils/coreutils/pull/9960 +* @max-amb made their first contribution in https://github.com/uutils/coreutils/pull/9945 +* @van-sprundel made their first contribution in https://github.com/uutils/coreutils/pull/9975 +* @ffgan made their first contribution in https://github.com/uutils/coreutils/pull/10029 +* @hwhsu1231 made their first contribution in https://github.com/uutils/coreutils/pull/10004 +* @rynewang made their first contribution in https://github.com/uutils/coreutils/pull/10038 +* @Thanhphan1147 made their first contribution in https://github.com/uutils/coreutils/pull/9757 +* @AnuthaDev made their first contribution in https://github.com/uutils/coreutils/pull/10090 +* @abendrothj made their first contribution in https://github.com/uutils/coreutils/pull/9792 +* @antonkesy made their first contribution in https://github.com/uutils/coreutils/pull/10170 +* @Ivan-Shaml made their first contribution in https://github.com/uutils/coreutils/pull/10143 +* @bkueng made their first contribution in https://github.com/uutils/coreutils/pull/8615 +* @Xylphy made their first contribution in https://github.com/uutils/coreutils/pull/10224 +* @dhr412 made their first contribution in https://github.com/uutils/coreutils/pull/10268 +* @Dolphindalt made their first contribution in https://github.com/uutils/coreutils/pull/10067 +* @quantum-encoding made their first contribution in https://github.com/uutils/coreutils/pull/9176 +* @GomesGoncalo made their first contribution in https://github.com/uutils/coreutils/pull/10232 +* @AnandajithS made their first contribution in https://github.com/uutils/coreutils/pull/9114 +* @trypsynth made their first contribution in https://github.com/uutils/coreutils/pull/10209 +* @w007878 made their first contribution in https://github.com/uutils/coreutils/pull/10369 +* @Paol0B made their first contribution in https://github.com/uutils/coreutils/pull/10206 +* @Kaua-Klassmann made their first contribution in https://github.com/uutils/coreutils/pull/10339 +* @not-matthias made their first contribution in https://github.com/uutils/coreutils/pull/10414 +* @0xferrous made their first contribution in https://github.com/uutils/coreutils/pull/10412 +* @3v1n0 made their first contribution in https://github.com/uutils/coreutils/pull/10439 +* @reubenwong97 made their first contribution in https://github.com/uutils/coreutils/pull/10566 +* @ChiamakaUI made their first contribution in https://github.com/uutils/coreutils/pull/10582 + +**Full Changelog**: https://github.com/uutils/coreutils/compare/0.5.0...0.6.0 diff --git a/docs/src/test_coverage.css b/docs/src/test_coverage.css index 37a658695..3b746dcde 100644 --- a/docs/src/test_coverage.css +++ b/docs/src/test_coverage.css @@ -43,4 +43,4 @@ } .counts { margin-right: 10px; -} \ No newline at end of file +} diff --git a/src/bin/coreutils.rs b/src/bin/coreutils.rs index 8cd1f73cf..caa23091a 100644 --- a/src/bin/coreutils.rs +++ b/src/bin/coreutils.rs @@ -10,6 +10,7 @@ use std::cmp; use std::ffi::OsString; use std::io::{self, Write}; use std::process; +use uucore::Args; const VERSION: &str = env!("CARGO_PKG_VERSION"); @@ -58,9 +59,12 @@ fn main() { let util_name = if let Some(&util) = matched_util { Some(OsString::from(util)) - } else { + } else if binary_as_util.ends_with("utils") || binary_as_util.ends_with("box") { + // todo: Remove support of "*box" from binary uucore::set_utility_is_second_arg(); args.next() + } else { + validation::not_found(&OsString::from(binary_as_util)); }; // 0th argument equals util name? @@ -71,6 +75,11 @@ fn main() { match util { "--list" => { + // If --help is also present, show usage instead of list + if args.any(|arg| arg == "--help" || arg == "-h") { + usage(&utils, binary_as_util); + process::exit(0); + } let mut utils: Vec<_> = utils.keys().collect(); utils.sort(); for util in utils { @@ -119,6 +128,9 @@ fn main() { } usage(&utils, binary_as_util); process::exit(0); + } else if util.starts_with('-') { + // Argument looks like an option but wasn't recognized + validation::unrecognized_option(binary_as_util, &util_os); } else { validation::not_found(&util_os); } diff --git a/src/bin/uudoc.rs b/src/bin/uudoc.rs index fe536b8e0..76f04774e 100644 --- a/src/bin/uudoc.rs +++ b/src/bin/uudoc.rs @@ -45,8 +45,8 @@ fn usage(utils: &UtilityMap) { } /// Generates the coreutils app for the utility map -fn gen_coreutils_app(util_map: &UtilityMap) -> clap::Command { - let mut command = clap::Command::new("coreutils"); +fn gen_coreutils_app(util_map: &UtilityMap) -> Command { + let mut command = Command::new("coreutils"); for (name, (_, sub_app)) in util_map { // Recreate a small subcommand with only the relevant info // (name & short description) @@ -54,7 +54,7 @@ fn gen_coreutils_app(util_map: &UtilityMap) -> clap::Command { .get_about() .expect("Could not get the 'about'") .to_string(); - let sub_app = clap::Command::new(name).about(about); + let sub_app = Command::new(name).about(about); command = command.subcommand(sub_app); } command @@ -84,6 +84,8 @@ fn gen_manpage( } else { validation::setup_localization_or_exit(utility); let mut cmd = util_map.get(utility).unwrap().1(); + cmd.set_bin_name(utility.clone()); + let mut cmd = cmd.display_name(utility); if let Some(zip) = tldr { if let Ok(examples) = write_zip_examples(zip, utility, false) { cmd = cmd.after_help(examples); @@ -172,7 +174,7 @@ fn main() -> io::Result<()> { } let utils = util_map::>>(); match std::fs::create_dir("docs/src/utils/") { - Err(e) if e.kind() == std::io::ErrorKind::AlreadyExists => Ok(()), + Err(e) if e.kind() == io::ErrorKind::AlreadyExists => Ok(()), x => x, }?; @@ -202,7 +204,7 @@ fn main() -> io::Result<()> { let mut map = HashMap::new(); for platform in ["unix", "macos", "windows", "unix_android"] { let platform_utils: Vec = String::from_utf8( - std::process::Command::new("./util/show-utils.sh") + process::Command::new("./util/show-utils.sh") .arg(format!("--features=feat_os_{platform}")) .output()? .stdout, @@ -217,7 +219,7 @@ fn main() -> io::Result<()> { // Linux is a special case because it can support selinux let platform_utils: Vec = String::from_utf8( - std::process::Command::new("./util/show-utils.sh") + process::Command::new("./util/show-utils.sh") .arg("--features=feat_os_unix feat_selinux") .output()? .stdout, @@ -274,12 +276,15 @@ fn main() -> io::Result<()> { println!("Writing to utils"); for (&name, (_, command)) in utils { - if name == "[" { - continue; - } - let p = format!("docs/src/utils/{name}.md"); + let (utils_name, usage_name, command) = match name { + "[" => { + continue; + } + n => (n, n, command), + }; + let p = format!("docs/src/utils/{usage_name}.md"); - let fluent = File::open(format!("src/uu/{name}/locales/en-US.ftl")) + let fluent = File::open(format!("src/uu/{utils_name}/locales/en-US.ftl")) .and_then(|mut f: File| { let mut s = String::new(); f.read_to_string(&mut s)?; @@ -291,21 +296,43 @@ fn main() -> io::Result<()> { MDWriter { w: Box::new(f), command: command(), - name, + name: usage_name, tldr_zip: &mut tldr_zip, utils_per_platform: &utils_per_platform, fluent, + fluent_key: utils_name.to_string(), } .markdown()?; println!("Wrote to '{p}'"); } else { println!("Error writing to {p}"); } - writeln!(summary, "* [{name}](utils/{name}.md)")?; + writeln!(summary, "* [{usage_name}](utils/{usage_name}.md)")?; } Ok(()) } +fn fix_usage(name: &str, usage: String) -> String { + match name { + "test" => { + // replace to [ but not the first two line + usage + .lines() + .enumerate() + .map(|(i, l)| { + if i > 1 { + l.replace("test", "[") + } else { + l.to_string() + } + }) + .collect::>() + .join("\n") + } + _ => usage, + } +} + struct MDWriter<'a, 'b> { w: Box, command: Command, @@ -313,6 +340,7 @@ struct MDWriter<'a, 'b> { tldr_zip: &'b mut Option>, utils_per_platform: &'b HashMap<&'b str, Vec>, fluent: Option, + fluent_key: String, } impl MDWriter<'_, '_> { @@ -343,9 +371,20 @@ impl MDWriter<'_, '_> { if id.name == key { // Simple text extraction - just concatenate text elements let mut result = String::new(); + use fluent_syntax::ast::{ + Expression, InlineExpression, + PatternElement::{Placeable, TextElement}, + }; for element in elements { - if let fluent_syntax::ast::PatternElement::TextElement { value } = element { - result.push_str(&value); + if let TextElement { ref value } = element { + result.push_str(value); + } + if let Placeable { + expression: + Expression::Inline(InlineExpression::StringLiteral { ref value }), + } = element + { + result.push_str(value); } } return Some(result); @@ -403,7 +442,8 @@ impl MDWriter<'_, '_> { /// # Errors /// Returns an error if the writer fails. fn usage(&mut self) -> io::Result<()> { - if let Some(usage) = self.extract_fluent_value(&format!("{}-usage", self.name)) { + if let Some(usage) = self.extract_fluent_value(&format!("{}-usage", self.fluent_key)) { + let usage = fix_usage(self.name, usage); writeln!(self.w, "\n```")?; writeln!(self.w, "{usage}")?; writeln!(self.w, "```") @@ -415,7 +455,7 @@ impl MDWriter<'_, '_> { /// # Errors /// Returns an error if the writer fails. fn about(&mut self) -> io::Result<()> { - if let Some(about) = self.extract_fluent_value(&format!("{}-about", self.name)) { + if let Some(about) = self.extract_fluent_value(&format!("{}-about", self.fluent_key)) { writeln!(self.w, "{about}") } else { Ok(()) @@ -425,7 +465,9 @@ impl MDWriter<'_, '_> { /// # Errors /// Returns an error if the writer fails. fn after_help(&mut self) -> io::Result<()> { - if let Some(after_help) = self.extract_fluent_value(&format!("{}-after-help", self.name)) { + if let Some(after_help) = + self.extract_fluent_value(&format!("{}-after-help", self.fluent_key)) + { writeln!(self.w, "\n\n{after_help}") } else { Ok(()) @@ -498,7 +540,7 @@ impl MDWriter<'_, '_> { writeln!(self.w, "")?; let help_text = arg.get_help().unwrap_or_default().to_string(); // Try to resolve Fluent key if it looks like one, otherwise use as-is - let resolved_help = if help_text.starts_with(&format!("{}-help-", self.name)) { + let resolved_help = if help_text.starts_with(&format!("{}-help-", self.fluent_key)) { self.extract_fluent_value(&help_text).unwrap_or(help_text) } else { help_text @@ -547,7 +589,7 @@ fn write_zip_examples( }; match format_examples(content, output_markdown) { - Err(e) => Err(std::io::Error::other(format!( + Err(e) => Err(io::Error::other(format!( "Failed to format the tldr examples of {name}: {e}" ))), Ok(s) => Ok(s), diff --git a/src/common/validation.rs b/src/common/validation.rs index d723ca926..41f686e8d 100644 --- a/src/common/validation.rs +++ b/src/common/validation.rs @@ -29,6 +29,16 @@ pub fn not_found(util: &OsStr) -> ! { process::exit(1); } +/// Prints an "unrecognized option" error and exits +pub fn unrecognized_option(binary_name: &str, option: &OsStr) -> ! { + eprintln!( + "{}: unrecognized option '{}'", + binary_name, + option.to_string_lossy() + ); + process::exit(1); +} + /// Sets up localization for a utility with proper error handling pub fn setup_localization_or_exit(util_name: &str) { let util_name = get_canonical_util_name(util_name); @@ -49,8 +59,8 @@ fn get_canonical_util_name(util_name: &str) -> &str { match util_name { // uu_test aliases - '[' is an alias for test "[" => "test", - - "dir" => "ls", // dir is an alias for ls + "dir" => "ls", // dir is an alias for ls + "vdir" => "ls", // vdir is an alias for ls // Default case - return the util name as is _ => util_name, diff --git a/src/uu/arch/src/arch.rs b/src/uu/arch/src/arch.rs index a01b60874..1b3e1f469 100644 --- a/src/uu/arch/src/arch.rs +++ b/src/uu/arch/src/arch.rs @@ -4,7 +4,7 @@ // file that was distributed with this source code. use clap::Command; -use platform_info::*; +use platform_info::{PlatformInfo, PlatformInfoAPI, UNameAPI}; use std::io::{Write, stdout}; use uucore::error::{UResult, USimpleError}; use uucore::translate; diff --git a/src/uu/b2sum/Cargo.toml b/src/uu/b2sum/Cargo.toml index 61b0b702a..81f6f51fc 100644 --- a/src/uu/b2sum/Cargo.toml +++ b/src/uu/b2sum/Cargo.toml @@ -29,8 +29,6 @@ uucore = { workspace = true, features = [ fluent = { workspace = true } [dev-dependencies] -divan = { workspace = true } -tempfile = { workspace = true } uucore = { workspace = true, features = ["benchmark"] } [[bin]] diff --git a/src/uu/base32/src/base_common.rs b/src/uu/base32/src/base_common.rs index b7fef0ac2..7bd67276b 100644 --- a/src/uu/base32/src/base_common.rs +++ b/src/uu/base32/src/base_common.rs @@ -925,7 +925,7 @@ fn format_read_error(error: &io::Error) -> String { /// Determines if the input buffer contains any padding ('=') ignoring trailing whitespace. #[cfg(test)] -fn read_and_has_padding(input: &mut R) -> UResult<(bool, Vec)> { +fn read_and_has_padding(input: &mut R) -> UResult<(bool, Vec)> { let mut buf = Vec::new(); input .read_to_end(&mut buf) diff --git a/src/uu/cat/src/cat.rs b/src/uu/cat/src/cat.rs index eeec89025..babb0ce92 100644 --- a/src/uu/cat/src/cat.rs +++ b/src/uu/cat/src/cat.rs @@ -639,40 +639,34 @@ fn write_end( fn write_to_end(in_buf: &[u8], writer: &mut W) -> io::Result { // using memchr2 significantly improves performances - match memchr2(b'\n', b'\r', in_buf) { - Some(p) => { - writer.write_all(&in_buf[..p])?; - Ok(p) - } - None => { - writer.write_all(in_buf)?; - Ok(in_buf.len()) - } + if let Some(p) = memchr2(b'\n', b'\r', in_buf) { + writer.write_all(&in_buf[..p])?; + Ok(p) + } else { + writer.write_all(in_buf)?; + Ok(in_buf.len()) } } fn write_tab_to_end(mut in_buf: &[u8], writer: &mut W) -> io::Result { let mut count = 0; loop { - match in_buf + if let Some(p) = in_buf .iter() .position(|c| *c == b'\n' || *c == b'\t' || *c == b'\r') { - Some(p) => { - writer.write_all(&in_buf[..p])?; - if in_buf[p] == b'\t' { - writer.write_all(b"^I")?; - in_buf = &in_buf[p + 1..]; - count += p + 1; - } else { - // b'\n' or b'\r' - return Ok(count + p); - } - } - None => { - writer.write_all(in_buf)?; - return Ok(in_buf.len() + count); + writer.write_all(&in_buf[..p])?; + if in_buf[p] == b'\t' { + writer.write_all(b"^I")?; + in_buf = &in_buf[p + 1..]; + count += p + 1; + } else { + // b'\n' or b'\r' + return Ok(count + p); } + } else { + writer.write_all(in_buf)?; + return Ok(in_buf.len() + count); } } } diff --git a/src/uu/chcon/src/chcon.rs b/src/uu/chcon/src/chcon.rs index cd3985826..685ad0dcf 100644 --- a/src/uu/chcon/src/chcon.rs +++ b/src/uu/chcon/src/chcon.rs @@ -25,7 +25,7 @@ use std::{fs, io}; mod errors; mod fts; -use errors::*; +use errors::{Error, Result, report_full_error}; pub mod options { pub static HELP: &str = "help"; diff --git a/src/uu/checksum_common/Cargo.toml b/src/uu/checksum_common/Cargo.toml index 079a46b46..b8f99e821 100644 --- a/src/uu/checksum_common/Cargo.toml +++ b/src/uu/checksum_common/Cargo.toml @@ -27,8 +27,6 @@ uucore = { workspace = true, features = [ fluent = { workspace = true } [dev-dependencies] -divan = { workspace = true } -tempfile = { workspace = true } # [[bench]] # name = "b2sum_bench" diff --git a/src/uu/chmod/src/chmod.rs b/src/uu/chmod/src/chmod.rs index 6ff03e503..78dccffdc 100644 --- a/src/uu/chmod/src/chmod.rs +++ b/src/uu/chmod/src/chmod.rs @@ -272,42 +272,41 @@ impl Chmoder { /// Calculate the new mode based on the current mode and the chmod specification. /// Returns (`new_mode`, `naively_expected_new_mode`) for symbolic modes, or (`new_mode`, `new_mode`) for numeric/reference modes. fn calculate_new_mode(&self, current_mode: u32, is_dir: bool) -> UResult<(u32, u32)> { - match self.fmode { - Some(mode) => Ok((mode, mode)), - None => { - let cmode_unwrapped = self.cmode.clone().unwrap(); - let mut new_mode = current_mode; - let mut naively_expected_new_mode = current_mode; + if let Some(mode) = self.fmode { + Ok((mode, mode)) + } else { + let cmode_unwrapped = self.cmode.clone().unwrap(); + let mut new_mode = current_mode; + let mut naively_expected_new_mode = current_mode; - for mode in cmode_unwrapped.split(',') { - let result = if mode.chars().any(|c| c.is_ascii_digit()) { - mode::parse_numeric(new_mode, mode, is_dir).map(|v| (v, v)) - } else { - mode::parse_symbolic(new_mode, mode, mode::get_umask(), is_dir).map(|m| { - // calculate the new mode as if umask was 0 - let naive_mode = - mode::parse_symbolic(naively_expected_new_mode, mode, 0, is_dir) - .unwrap(); // we know that mode must be valid, so this cannot fail - (m, naive_mode) - }) - }; + for mode in cmode_unwrapped.split(',') { + let result = if mode.chars().any(|c| c.is_ascii_digit()) { + mode::parse_numeric(new_mode, mode, is_dir).map(|v| (v, v)) + } else { + mode::parse_symbolic(new_mode, mode, mode::get_umask(), is_dir).map(|m| { + // calculate the new mode as if umask was 0 + let naive_mode = + mode::parse_symbolic(naively_expected_new_mode, mode, 0, is_dir) + .unwrap(); // we know that mode must be valid, so this cannot fail + (m, naive_mode) + }) + }; - match result { - Ok((mode, naive_mode)) => { - new_mode = mode; - naively_expected_new_mode = naive_mode; - } - Err(f) => { - return if self.quiet { - Err(ExitCode::new(1)) - } else { - Err(USimpleError::new(1, f)) - }; - } + match result { + Ok((mode, naive_mode)) => { + new_mode = mode; + naively_expected_new_mode = naive_mode; + } + Err(f) => { + return if self.quiet { + Err(ExitCode::new(1)) + } else { + Err(USimpleError::new(1, f)) + }; } } - Ok((new_mode, naively_expected_new_mode)) } + Ok((new_mode, naively_expected_new_mode)) } } @@ -655,32 +654,31 @@ impl Chmoder { self.calculate_new_mode(fperm, file.is_dir())?; // Determine how to apply the permissions - match self.fmode { - Some(mode) => self.change_file(fperm, mode, file)?, - None => { - // Special handling for symlinks when not dereferencing - if file.is_symlink() && !dereference { - // TODO: On most Unix systems, symlink permissions are ignored by the kernel, - // so changing them has no effect. We skip this operation for compatibility. - // Note that "chmod without dereferencing" effectively does nothing on symlinks. - if self.verbose { - println!( - "neither symbolic link {} nor referent has been changed", - file.quote() - ); - } - } else { - self.change_file(fperm, new_mode, file)?; - } - // if a permission would have been removed if umask was 0, but it wasn't because umask was not 0, print an error and fail - if (new_mode & !naively_expected_new_mode) != 0 { - return Err(ChmodError::NewPermissions( - file.into(), - display_permissions_unix(new_mode as mode_t, false), - display_permissions_unix(naively_expected_new_mode as mode_t, false), - ) - .into()); + if let Some(mode) = self.fmode { + self.change_file(fperm, mode, file)?; + } else { + // Special handling for symlinks when not dereferencing + if file.is_symlink() && !dereference { + // TODO: On most Unix systems, symlink permissions are ignored by the kernel, + // so changing them has no effect. We skip this operation for compatibility. + // Note that "chmod without dereferencing" effectively does nothing on symlinks. + if self.verbose { + println!( + "neither symbolic link {} nor referent has been changed", + file.quote() + ); } + } else { + self.change_file(fperm, new_mode, file)?; + } + // if a permission would have been removed if umask was 0, but it wasn't because umask was not 0, print an error and fail + if (new_mode & !naively_expected_new_mode) != 0 { + return Err(ChmodError::NewPermissions( + file.into(), + display_permissions_unix(new_mode as mode_t, false), + display_permissions_unix(naively_expected_new_mode as mode_t, false), + ) + .into()); } } diff --git a/src/uu/comm/src/comm.rs b/src/uu/comm/src/comm.rs index be77debc0..c72bbcc8a 100644 --- a/src/uu/comm/src/comm.rs +++ b/src/uu/comm/src/comm.rs @@ -40,7 +40,7 @@ enum FileNumber { } impl FileNumber { - fn as_str(&self) -> &'static str { + fn as_str(self) -> &'static str { match self { Self::One => "1", Self::Two => "2", diff --git a/src/uu/cp/src/copydir.rs b/src/uu/cp/src/copydir.rs index db2f4ff19..42e30b452 100644 --- a/src/uu/cp/src/copydir.rs +++ b/src/uu/cp/src/copydir.rs @@ -135,7 +135,7 @@ impl<'a> Context<'a> { /// /// For convenience while traversing a directory, the [`Entry::new`] /// function allows creating an entry from a [`Context`] and a -/// [`walkdir::DirEntry`]. +/// [`DirEntry`]. /// /// # Examples /// @@ -549,7 +549,7 @@ pub(crate) fn copy_directory( /// Decide whether the second path is a prefix of the first. /// /// This function canonicalizes the paths via -/// [`uucore::fs::canonicalize`] before comparing. +/// [`fs::canonicalize`] before comparing. /// /// # Errors /// diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index 9335d6ecd..04a3c1c01 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -828,12 +828,12 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let (sources, target) = parse_path_args(paths, &options)?; if let Err(error) = copy(&sources, &target, &options) { - match error { + if let CpError::NotAllFilesCopied = error { // Error::NotAllFilesCopied is non-fatal, but the error // code should still be EXIT_ERR as does GNU cp - CpError::NotAllFilesCopied => {} + } else { // Else we caught a fatal bubbled-up error, log it to stderr - _ => show_error!("{error}"), + show_error!("{error}"); } set_exit_code(EXIT_ERR); } @@ -1360,7 +1360,7 @@ fn show_error_if_needed(error: &CpError) { /// Behavior is determined by the `options` parameter, see [`Options`] for details. pub fn copy(sources: &[PathBuf], target: &Path, options: &Options) -> CopyResult<()> { let target_type = TargetType::determine(sources, target); - verify_target_type(target, &target_type)?; + verify_target_type(target, target_type)?; let mut non_fatal_errors = false; let mut seen_sources = HashSet::with_capacity(sources.len()); @@ -1614,8 +1614,8 @@ fn file_mode_for_interactive_overwrite( } impl OverwriteMode { - fn verify(&self, path: &Path, debug: bool) -> CopyResult<()> { - match *self { + fn verify(self, path: &Path, debug: bool) -> CopyResult<()> { + match self { Self::NoClobber => { if debug { println!("{}", translate!("cp-debug-skipped", "path" => path.quote())); @@ -1652,12 +1652,12 @@ impl OverwriteMode { /// Note: ENOTSUP/EOPNOTSUPP errors are silently ignored when not required, as per GNU cp /// documentation: "Try to preserve SELinux security context and extended attributes (xattr), /// but ignore any failure to do that and print no corresponding diagnostic." -fn handle_preserve CopyResult<()>>(p: &Preserve, f: F) -> CopyResult<()> { +fn handle_preserve CopyResult<()>>(p: Preserve, f: F) -> CopyResult<()> { match p { Preserve::No { .. } => {} Preserve::Yes { required } => { let result = f(); - if *required { + if required { result?; } else if let Err(ref error) = result { // Suppress ENOTSUP errors when preservation is optional. @@ -1724,7 +1724,7 @@ pub(crate) fn copy_attributes( // Ownership must be changed first to avoid interfering with mode change. #[cfg(unix)] - handle_preserve(&attributes.ownership, || -> CopyResult<()> { + handle_preserve(attributes.ownership, || -> CopyResult<()> { use std::os::unix::prelude::MetadataExt; use uucore::perms::Verbosity; use uucore::perms::VerbosityLevel; @@ -1759,7 +1759,7 @@ pub(crate) fn copy_attributes( Ok(()) })?; - handle_preserve(&attributes.mode, || -> CopyResult<()> { + handle_preserve(attributes.mode, || -> CopyResult<()> { // The `chmod()` system call that underlies the // `fs::set_permissions()` call is unable to change the // permissions of a symbolic link. In that case, we just @@ -1778,7 +1778,7 @@ pub(crate) fn copy_attributes( Ok(()) })?; - handle_preserve(&attributes.timestamps, || -> CopyResult<()> { + handle_preserve(attributes.timestamps, || -> CopyResult<()> { let atime = FileTime::from_last_access_time(&source_metadata); let mtime = FileTime::from_last_modification_time(&source_metadata); if dest.is_symlink() { @@ -1791,7 +1791,7 @@ pub(crate) fn copy_attributes( })?; #[cfg(all(feature = "selinux", any(target_os = "linux", target_os = "android")))] - handle_preserve(&attributes.context, || -> CopyResult<()> { + handle_preserve(attributes.context, || -> CopyResult<()> { // Get the source context and apply it to the destination if let Ok(context) = selinux::SecurityContext::of_path(source, false, false) { if let Some(context) = context { @@ -1809,7 +1809,7 @@ pub(crate) fn copy_attributes( Ok(()) })?; - handle_preserve(&attributes.xattr, || -> CopyResult<()> { + handle_preserve(attributes.xattr, || -> CopyResult<()> { #[cfg(all(unix, not(target_os = "android")))] { copy_extended_attrs(source, dest)?; @@ -2328,9 +2328,9 @@ fn calculate_dest_permissions( source_metadata: &Metadata, options: &Options, context: &str, -) -> CopyResult { +) -> Permissions { if let Some(metadata) = dest_metadata { - Ok(metadata.permissions()) + metadata.permissions() } else { #[cfg(unix)] { @@ -2341,12 +2341,11 @@ fn calculate_dest_permissions( use uucore::mode::get_umask; let mode = mode & !get_umask(); permissions.set_mode(mode); - Ok(permissions) + permissions } #[cfg(not(unix))] { - let permissions = source_metadata.permissions(); - Ok(permissions) + source_metadata.permissions() } } } @@ -2524,7 +2523,7 @@ fn copy_file( &source_metadata, options, context, - )?; + ); #[cfg(unix)] let source_is_fifo = source_metadata.file_type().is_fifo(); @@ -2767,11 +2766,11 @@ fn copy_link( } /// Generate an error message if `target` is not the correct `target_type` -pub fn verify_target_type(target: &Path, target_type: &TargetType) -> CopyResult<()> { +pub fn verify_target_type(target: &Path, target_type: TargetType) -> CopyResult<()> { match (target_type, target.is_dir()) { - (&TargetType::Directory, false) => Err(translate!("cp-error-target-not-directory", "target" => target.quote()) + (TargetType::Directory, false) => Err(translate!("cp-error-target-not-directory", "target" => target.quote()) .into()), - (&TargetType::File, true) => Err(translate!("cp-error-cannot-overwrite-directory-with-non-directory", "dir" => target.quote()) + (TargetType::File, true) => Err(translate!("cp-error-cannot-overwrite-directory-with-non-directory", "dir" => target.quote()) .into()), _ => Ok(()), } diff --git a/src/uu/cp/src/platform/macos.rs b/src/uu/cp/src/platform/macos.rs index efc00de62..a545c552b 100644 --- a/src/uu/cp/src/platform/macos.rs +++ b/src/uu/cp/src/platform/macos.rs @@ -85,37 +85,32 @@ pub(crate) fn copy_on_write( if raw_pfn.is_null() || error != 0 { // clonefile(2) is either not supported or it errored out (possibly because the FS does not // support COW). - match reflink_mode { - ReflinkMode::Always => { - return Err(translate!("cp-error-failed-to-clone", "source" => source.quote(), "dest" => dest.quote(), "error" => error) + if reflink_mode == ReflinkMode::Always { + return Err(translate!("cp-error-failed-to-clone", "source" => source.quote(), "dest" => dest.quote(), "error" => error) .into()); - } - _ => { - copy_debug.reflink = OffloadReflinkDebug::Yes; - if source_is_stream { - let mut src_file = File::open(source)?; - let mode = 0o622 & !get_umask(); - let mut dst_file = OpenOptions::new() - .create(true) - .write(true) - .mode(mode) - .open(dest)?; + } + copy_debug.reflink = OffloadReflinkDebug::Yes; + if source_is_stream { + let mut src_file = File::open(source)?; + let mode = 0o622 & !get_umask(); + let mut dst_file = OpenOptions::new() + .create(true) + .write(true) + .mode(mode) + .open(dest)?; - let dest_is_stream = is_stream(&dst_file.metadata()?); - if !dest_is_stream { - // `copy_stream` doesn't clear the dest file, if dest is not a stream, we should clear it manually. - dst_file.set_len(0)?; - } - - buf_copy::copy_stream(&mut src_file, &mut dst_file) - .map_err(|_| std::io::Error::from(std::io::ErrorKind::Other)) - .map_err(|e| CpError::IoErrContext(e, context.to_owned()))? - } else { - fs::copy(source, dest) - .map_err(|e| CpError::IoErrContext(e, context.to_owned()))? - } + let dest_is_stream = is_stream(&dst_file.metadata()?); + if !dest_is_stream { + // `copy_stream` doesn't clear the dest file, if dest is not a stream, we should clear it manually. + dst_file.set_len(0)?; } - }; + + buf_copy::copy_stream(&mut src_file, &mut dst_file) + .map_err(|_| std::io::Error::from(std::io::ErrorKind::Other)) + .map_err(|e| CpError::IoErrContext(e, context.to_owned()))?; + } else { + fs::copy(source, dest).map_err(|e| CpError::IoErrContext(e, context.to_owned()))?; + } } Ok(copy_debug) diff --git a/src/uu/csplit/src/csplit.rs b/src/uu/csplit/src/csplit.rs index 1c2978cea..67a5d3cc1 100644 --- a/src/uu/csplit/src/csplit.rs +++ b/src/uu/csplit/src/csplit.rs @@ -454,16 +454,11 @@ impl SplitWriter<'_> { // write the extra lines required by the offset while offset > 0 { - match input_iter.next() { - Some((_, line)) => { - self.writeln(&line?)?; - } - None => { - self.finish_split()?; - return Err(CsplitError::LineOutOfRange( - pattern_as_str.to_string(), - )); - } + if let Some((_, line)) = input_iter.next() { + self.writeln(&line?)?; + } else { + self.finish_split()?; + return Err(CsplitError::LineOutOfRange(pattern_as_str.to_string())); } offset -= 1; } diff --git a/src/uu/csplit/src/patterns.rs b/src/uu/csplit/src/patterns.rs index 83d868cb3..6710484e9 100644 --- a/src/uu/csplit/src/patterns.rs +++ b/src/uu/csplit/src/patterns.rs @@ -71,21 +71,18 @@ impl Iterator for ExecutePatternIter { type Item = (Option, usize); fn next(&mut self) -> Option<(Option, usize)> { - match self.max { + if let Some(m) = self.max { // iterate until m is reached - Some(m) => { - if self.cur == m { - None - } else { - self.cur += 1; - Some((self.max, self.cur)) - } - } - // no limit, just increment a counter - None => { + if self.cur == m { + None + } else { self.cur += 1; - Some((None, self.cur)) + Some((self.max, self.cur)) } + } else { + // no limit, just increment a counter + self.cur += 1; + Some((None, self.cur)) } } } diff --git a/src/uu/cut/src/cut.rs b/src/uu/cut/src/cut.rs index cc48edfab..dd0ea9d8a 100644 --- a/src/uu/cut/src/cut.rs +++ b/src/uu/cut/src/cut.rs @@ -153,27 +153,24 @@ fn cut_fields_explicit_out_delim( print_delim = true; } - match delim_search.next() { + if let Some((first, last)) = delim_search.next() { // print the current field up to the next field delim - Some((first, last)) => { - let segment = &line[low_idx..first]; + let segment = &line[low_idx..first]; - out.write_all(segment)?; + out.write_all(segment)?; - low_idx = last; - fields_pos = high + 1; - } - None => { - // this is the last field in the line, so print the rest - let segment = &line[low_idx..]; + low_idx = last; + fields_pos = high + 1; + } else { + // this is the last field in the line, so print the rest + let segment = &line[low_idx..]; - out.write_all(segment)?; + out.write_all(segment)?; - if line[line.len() - 1] == newline_char { - return Ok(true); - } - break; + if line[line.len() - 1] == newline_char { + return Ok(true); } + break; } } } @@ -227,26 +224,23 @@ fn cut_fields_implicit_out_delim( } } - match delim_search.nth(high - low) { - Some((first, _)) => { - let segment = &line[low_idx..first]; + if let Some((first, _)) = delim_search.nth(high - low) { + let segment = &line[low_idx..first]; - out.write_all(segment)?; + out.write_all(segment)?; - print_delim = true; - low_idx = first; - fields_pos = high + 1; - } - None => { - let segment = &line[low_idx..line.len()]; + print_delim = true; + low_idx = first; + fields_pos = high + 1; + } else { + let segment = &line[low_idx..line.len()]; - out.write_all(segment)?; + out.write_all(segment)?; - if line[line.len() - 1] == newline_char { - return Ok(true); - } - break; + if line[line.len() - 1] == newline_char { + return Ok(true); } + break; } } out.write_all(&[newline_char])?; diff --git a/src/uu/dd/src/datastructures.rs b/src/uu/dd/src/datastructures.rs index c0da8c67c..340a2e57e 100644 --- a/src/uu/dd/src/datastructures.rs +++ b/src/uu/dd/src/datastructures.rs @@ -4,7 +4,7 @@ // file that was distributed with this source code. // spell-checker:ignore ctable, outfile, iseek, oseek -use crate::conversion_tables::*; +use crate::conversion_tables::ConversionTable; type Cbs = usize; diff --git a/src/uu/dd/src/dd.rs b/src/uu/dd/src/dd.rs index 6801f84bb..48f7ef1b9 100644 --- a/src/uu/dd/src/dd.rs +++ b/src/uu/dd/src/dd.rs @@ -15,9 +15,9 @@ mod progress; use crate::bufferedoutput::BufferedOutput; use blocks::conv_block_unblock_helper; -use datastructures::*; +use datastructures::{ConversionMode, IConvFlags, IFlags, OConvFlags, OFlags, options}; #[cfg(any(target_os = "linux", target_os = "android"))] -use nix::fcntl::FcntlArg::F_SETFL; +use nix::fcntl::FcntlArg; #[cfg(any(target_os = "linux", target_os = "android"))] use nix::fcntl::OFlag; use parseargs::Parser; @@ -236,8 +236,8 @@ impl Source { /// Create a source from stdin using its raw file descriptor. /// /// This returns an instance of the `Source::StdinFile` variant, - /// using the raw file descriptor of [`std::io::Stdin`] to create - /// the [`std::fs::File`] parameter. You can use this instead of + /// using the raw file descriptor of [`io::Stdin`] to create + /// the [`File`] parameter. You can use this instead of /// `Source::Stdin` to allow reading from stdin without consuming /// the entire contents of stdin when this process terminates. #[cfg(unix)] @@ -897,7 +897,7 @@ impl<'a> Output<'a> { if let Some(libc_flags) = make_linux_oflags(&settings.oflags) { nix::fcntl::fcntl( fx.as_raw().as_fd(), - F_SETFL(OFlag::from_bits_retain(libc_flags)), + FcntlArg::F_SETFL(OFlag::from_bits_retain(libc_flags)), )?; } @@ -1092,7 +1092,7 @@ impl BlockWriter<'_> { /// depending on the command line arguments, this function /// informs the OS to flush/discard the caches for input and/or output file. -fn flush_caches_full_length(i: &Input, o: &Output) -> io::Result<()> { +fn flush_caches_full_length(i: &Input, o: &Output) { // Using len=0 in posix_fadvise means "to end of file" if i.settings.iflags.nocache { i.discard_cache(0, 0); @@ -1100,8 +1100,6 @@ fn flush_caches_full_length(i: &Input, o: &Output) -> io::Result<()> { if i.settings.oflags.nocache { o.discard_cache(0, 0); } - - Ok(()) } /// Copy the given input data to this output, consuming both. @@ -1163,7 +1161,7 @@ fn dd_copy(mut i: Input, o: Output) -> io::Result<()> { // requests that we inform the system that we no longer // need the contents of the input file in a system cache. // - flush_caches_full_length(&i, &o)?; + flush_caches_full_length(&i, &o); return finalize( BlockWriter::Unbuffered(o), rstat, diff --git a/src/uu/dd/src/numbers.rs b/src/uu/dd/src/numbers.rs index 206cd7887..f71805201 100644 --- a/src/uu/dd/src/numbers.rs +++ b/src/uu/dd/src/numbers.rs @@ -45,7 +45,7 @@ pub(crate) enum SuffixType { } impl SuffixType { - fn base_and_suffix(&self, n: u128) -> (u128, &'static str) { + fn base_and_suffix(self, n: u128) -> (u128, &'static str) { let (bases, suffixes) = match self { Self::Iec => (IEC_BASES, IEC_SUFFIXES), Self::Si => (SI_BASES, SI_SUFFIXES), diff --git a/src/uu/dd/src/parseargs.rs b/src/uu/dd/src/parseargs.rs index 2e8c104ff..3a37d97ff 100644 --- a/src/uu/dd/src/parseargs.rs +++ b/src/uu/dd/src/parseargs.rs @@ -541,7 +541,9 @@ fn get_ctable( conversion: Option, case: Option, ) -> Option<&'static ConversionTable> { + #[allow(clippy::wildcard_imports)] use crate::conversion_tables::*; + Some(match (conversion, case) { (None, None) => return None, (Some(conv), None) => match conv { diff --git a/src/uu/dd/src/progress.rs b/src/uu/dd/src/progress.rs index 416c95f27..c7aef3387 100644 --- a/src/uu/dd/src/progress.rs +++ b/src/uu/dd/src/progress.rs @@ -460,7 +460,7 @@ impl SignalHandler { pub(crate) fn install_signal_handler( f: Box, ) -> Result { - use signal_hook::consts::signal::*; + use signal_hook::consts::signal::SIGUSR1; use signal_hook::iterator::Signals; let mut signals = Signals::new([SIGUSR1])?; diff --git a/src/uu/df/src/blocks.rs b/src/uu/df/src/blocks.rs index ded8b5912..328090ffd 100644 --- a/src/uu/df/src/blocks.rs +++ b/src/uu/df/src/blocks.rs @@ -51,7 +51,7 @@ pub(crate) enum SuffixType { impl SuffixType { /// The first ten powers of 1024 and 1000, respectively. - fn bases(&self) -> [u128; 10] { + fn bases(self) -> [u128; 10] { match self { Self::Iec | Self::HumanReadable(HumanReadable::Binary) => IEC_BASES, Self::Si | Self::HumanReadable(HumanReadable::Decimal) => SI_BASES, @@ -59,7 +59,7 @@ impl SuffixType { } /// Suffixes for the first nine multi-byte unit suffixes. - fn suffixes(&self) -> [&'static str; 9] { + fn suffixes(self) -> [&'static str; 9] { match self { // we use "kB" instead of "KB", same as GNU df Self::Si => ["B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"], diff --git a/src/uu/df/src/columns.rs b/src/uu/df/src/columns.rs index 0d2d121a3..0fcd20865 100644 --- a/src/uu/df/src/columns.rs +++ b/src/uu/df/src/columns.rs @@ -191,16 +191,16 @@ impl Column { } /// Return the alignment of the specified column. - pub(crate) fn alignment(column: &Self) -> Alignment { - match column { + pub(crate) fn alignment(self) -> Alignment { + match self { Self::Source | Self::Target | Self::File | Self::Fstype => Alignment::Left, _ => Alignment::Right, } } /// Return the minimum width of the specified column. - pub(crate) fn min_width(column: &Self) -> usize { - match column { + pub(crate) fn min_width(self) -> usize { + match self { // 14 = length of "Filesystem" plus 4 spaces Self::Source => 14, Self::Used => 5, diff --git a/src/uu/df/src/df.rs b/src/uu/df/src/df.rs index ff72dce34..e0133805f 100644 --- a/src/uu/df/src/df.rs +++ b/src/uu/df/src/df.rs @@ -16,7 +16,7 @@ use uucore::error::{UError, UResult, USimpleError, get_exit_code}; use uucore::fsext::{MountInfo, read_fs_list}; use uucore::parser::parse_size::ParseSizeError; use uucore::translate; -use uucore::{format_usage, show}; +use uucore::{format_usage, show, show_warning}; use clap::{Arg, ArgAction, ArgMatches, Command, parser::ValueSource}; @@ -111,6 +111,13 @@ impl Default for Options { } } +impl Options { + /// Whether -a, -l, -t, or -x options require the mount table. + fn requires_mount_table(&self) -> bool { + self.show_all_fs || self.show_local_fs || self.include.is_some() || self.exclude.is_some() + } +} + #[derive(Debug, Error)] enum OptionsError { // TODO This needs to vary based on whether `--block-size` @@ -358,14 +365,38 @@ where P: AsRef, { // The list of all mounted filesystems. - let mounts: Vec = read_fs_list()?; + let mounts_result = read_fs_list(); + + #[allow(unused_variables)] + let (mounts, use_fallback) = match mounts_result { + Ok(m) => (m, false), + Err(e) => { + if opt.requires_mount_table() { + return Err(e); + } + show_warning!( + "{}", + translate!("df-error-cannot-read-table-of-mounted-filesystems") + ); + (vec![], true) + } + }; let mut result = vec![]; // Convert each path into a `Filesystem`, which contains // both the mount information and usage information. for path in paths { - match Filesystem::from_path(&mounts, path) { + #[cfg(unix)] + let fs_result = if use_fallback { + Filesystem::from_path_direct(path) + } else { + Filesystem::from_path(&mounts, path) + }; + #[cfg(not(unix))] + let fs_result = Filesystem::from_path(&mounts, path); + + match fs_result { Ok(fs) => { if is_included(&fs.mount_info, opt) { result.push(fs); diff --git a/src/uu/df/src/filesystem.rs b/src/uu/df/src/filesystem.rs index bfd982646..858e86917 100644 --- a/src/uu/df/src/filesystem.rs +++ b/src/uu/df/src/filesystem.rs @@ -8,10 +8,17 @@ //! filesystem mounted at a particular directory. It also includes //! information on amount of space available and amount of space used. // spell-checker:ignore canonicalized +#[cfg(unix)] +use std::io; +#[cfg(unix)] +use std::path::PathBuf; use std::{ffi::OsString, path::Path}; #[cfg(unix)] -use uucore::fsext::statfs; +use std::os::unix::fs::MetadataExt; + +#[cfg(unix)] +use uucore::fsext::{FsMeta, pretty_fstype, statfs}; use uucore::fsext::{FsUsage, MountInfo}; /// Summary representation of a filesystem. @@ -61,6 +68,31 @@ fn is_over_mounted(mounts: &[MountInfo], mount: &MountInfo) -> bool { } } +/// Find mount point by walking up the directory tree until device ID changes. +#[cfg(unix)] +pub(crate) fn find_mount_point>(path: P) -> io::Result { + let mut current = path.as_ref().canonicalize()?; + let current_dev = current.metadata()?.dev(); + + loop { + let parent = match current.parent() { + Some(p) if !p.as_os_str().is_empty() => p, + _ => return Ok(current), + }; + + let parent_dev = parent.metadata()?.dev(); + if parent_dev != current_dev { + return Ok(current); + } + + if parent == current { + return Ok(current); + } + + current = parent.to_path_buf(); + } +} + /// Find the mount info that best matches a given filesystem path. /// /// This function returns the element of `mounts` on which `path` is @@ -195,6 +227,43 @@ impl Filesystem { #[cfg(not(windows))] return result.and_then(|mount_info| Self::from_mount(mounts, mount_info, Some(file))); } + + /// Fallback using statfs when mount table is unavailable. + #[cfg(unix)] + pub(crate) fn from_path_direct

(path: P) -> Result + where + P: AsRef, + { + let file = path.as_ref().as_os_str().to_owned(); + + let canonical_path = path + .as_ref() + .canonicalize() + .map_err(|_| FsError::InvalidPath)?; + + let stat_result = statfs(canonical_path.as_os_str()).map_err(|_| FsError::MountMissing)?; + let mount_dir = find_mount_point(&canonical_path).map_err(|_| FsError::MountMissing)?; + let fs_type = pretty_fstype(stat_result.fs_type()).into_owned(); + + let mount_info = MountInfo { + dev_id: String::new(), + dev_name: "-".to_string(), + fs_type, + mount_dir: mount_dir.into_os_string(), + mount_option: String::new(), + mount_root: OsString::new(), + remote: false, + dummy: false, + }; + + let usage = FsUsage::new(stat_result); + + Ok(Self { + file: Some(file), + mount_info, + usage, + }) + } } #[cfg(test)] diff --git a/src/uu/df/src/table.rs b/src/uu/df/src/table.rs index f4d83c3aa..d4cf1ddfd 100644 --- a/src/uu/df/src/table.rs +++ b/src/uu/df/src/table.rs @@ -474,7 +474,7 @@ impl Table { .columns .iter() .enumerate() - .map(|(i, col)| Column::min_width(col).max(headers[i].len())) + .map(|(i, col)| col.min_width().max(headers[i].len())) .collect(); let mut rows = vec![headers.iter().map(Cell::from_string).collect()]; @@ -527,7 +527,7 @@ impl Table { let mut alignments = Vec::new(); for column in columns { - alignments.push(Column::alignment(column)); + alignments.push(column.alignment()); } alignments diff --git a/src/uu/dircolors/src/dircolors.rs b/src/uu/dircolors/src/dircolors.rs index 32e2d34a7..72e18d55d 100644 --- a/src/uu/dircolors/src/dircolors.rs +++ b/src/uu/dircolors/src/dircolors.rs @@ -92,30 +92,27 @@ pub fn generate_type_output(fmt: &OutputFmt) -> String { } fn generate_ls_colors(fmt: &OutputFmt, sep: &str) -> String { - match fmt { - OutputFmt::Display => { - let mut display_parts = vec![]; - let type_output = generate_type_output(fmt); - display_parts.push(type_output); - for &(extension, code) in FILE_COLORS { - let prefix = if extension.starts_with('*') { "" } else { "*" }; - let formatted_extension = format!("\x1b[{code}m{prefix}{extension}\t{code}\x1b[0m"); - display_parts.push(formatted_extension); - } - display_parts.join("\n") + if let OutputFmt::Display = fmt { + let mut display_parts = vec![]; + let type_output = generate_type_output(fmt); + display_parts.push(type_output); + for &(extension, code) in FILE_COLORS { + let prefix = if extension.starts_with('*') { "" } else { "*" }; + let formatted_extension = format!("\x1b[{code}m{prefix}{extension}\t{code}\x1b[0m"); + display_parts.push(formatted_extension); } - _ => { - // existing logic for other formats - let mut parts = vec![]; - for &(extension, code) in FILE_COLORS { - let prefix = if extension.starts_with('*') { "" } else { "*" }; - let formatted_extension = format!("{prefix}{extension}"); - parts.push(format!("{formatted_extension}={code}")); - } - let (prefix, suffix) = get_colors_format_strings(fmt); - let ls_colors = parts.join(sep); - format!("{prefix}{}:{ls_colors}:{suffix}", generate_type_output(fmt),) + display_parts.join("\n") + } else { + // existing logic for other formats + let mut parts = vec![]; + for &(extension, code) in FILE_COLORS { + let prefix = if extension.starts_with('*') { "" } else { "*" }; + let formatted_extension = format!("{prefix}{extension}"); + parts.push(format!("{formatted_extension}={code}")); } + let (prefix, suffix) = get_colors_format_strings(fmt); + let ls_colors = parts.join(sep); + format!("{prefix}{}:{ls_colors}:{suffix}", generate_type_output(fmt),) } } diff --git a/src/uu/dirname/locales/fr-FR.ftl b/src/uu/dirname/locales/fr-FR.ftl index 0cc0c1b4f..b11ab567e 100644 --- a/src/uu/dirname/locales/fr-FR.ftl +++ b/src/uu/dirname/locales/fr-FR.ftl @@ -4,4 +4,3 @@ dirname-after-help = Afficher chaque NOM avec son dernier composant non-slash et supprimés ; si NOM ne contient pas de '/', afficher '.' (signifiant le répertoire courant). dirname-missing-operand = opérande manquant dirname-zero-help = séparer la sortie avec NUL plutôt qu'avec un saut de ligne - diff --git a/src/uu/du/Cargo.toml b/src/uu/du/Cargo.toml index 192ec9dea..99bb5a5de 100644 --- a/src/uu/du/Cargo.toml +++ b/src/uu/du/Cargo.toml @@ -28,6 +28,7 @@ uucore = { workspace = true, features = [ "parser-glob", "time", ] } +rustc-hash = { workspace = true } thiserror = { workspace = true } fluent = { workspace = true } diff --git a/src/uu/du/src/du.rs b/src/uu/du/src/du.rs index d8bfd4446..a1b3596cc 100644 --- a/src/uu/du/src/du.rs +++ b/src/uu/du/src/du.rs @@ -7,7 +7,7 @@ use clap::{Arg, ArgAction, ArgMatches, Command, builder::PossibleValue}; use glob::Pattern; -use std::collections::HashSet; +use rustc_hash::FxHashSet as HashSet; use std::env; use std::ffi::{OsStr, OsString}; use std::fs::{self, DirEntry, File, Metadata}; @@ -233,6 +233,10 @@ fn get_blocks(path: &Path, _metadata: &Metadata) -> u64 { } #[cfg(not(windows))] +#[expect( + clippy::unnecessary_wraps, + reason = "fn sig must match on all platforms" +)] fn get_file_info(_path: &Path, metadata: &Metadata) -> Option { Some(FileInfo { file_id: metadata.ino() as u128, @@ -578,7 +582,7 @@ fn du_regular( ancestors: Option<&mut HashSet>, symlink_depth: Option, ) -> Result>>> { - let mut default_ancestors = HashSet::new(); + let mut default_ancestors = HashSet::default(); let ancestors = ancestors.unwrap_or(&mut default_ancestors); let symlink_depth = symlink_depth.unwrap_or(0); // Maximum symlink depth to prevent infinite loops @@ -959,6 +963,40 @@ fn read_files_from(file_name: &OsStr) -> Result, std::io::Error> { Ok(paths) } +fn get_block_size_arg_index_if_present(matches: &ArgMatches, flag: &str) -> Option { + if matches.get_flag(flag) { + // Indices of returns index even if flag is not present, thats why we need to if guard it + matches + .indices_of(flag) + .and_then(|mut indices| indices.next_back()) + } else { + None + } +} + +fn handle_block_size_arg_override(matches: &ArgMatches) -> Option { + let candidates = [ + ( + SizeFormat::BlockSize(1), + get_block_size_arg_index_if_present(matches, options::BYTES), + ), + ( + SizeFormat::BlockSize(1024), + get_block_size_arg_index_if_present(matches, options::BLOCK_SIZE_1K), + ), + ( + SizeFormat::BlockSize(1024 * 1024), + get_block_size_arg_index_if_present(matches, options::BLOCK_SIZE_1M), + ), + ]; + + candidates + .into_iter() + .filter(|(_, idx)| idx.is_some()) + .max_by_key(|&(_, idx)| idx.unwrap_or(0)) + .map(|(size_format, _)| size_format) +} + #[uucore::main] #[allow(clippy::cognitive_complexity)] pub fn uumain(args: impl uucore::Args) -> UResult<()> { @@ -995,7 +1033,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { files.collect() } else { // Deduplicate while preserving order - let mut seen = HashSet::new(); + let mut seen = HashSet::default(); files .filter(|path| seen.insert(path.clone())) .collect::>() @@ -1014,12 +1052,8 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { SizeFormat::HumanBinary } else if matches.get_flag(options::SI) { SizeFormat::HumanDecimal - } else if matches.get_flag(options::BYTES) { - SizeFormat::BlockSize(1) - } else if matches.get_flag(options::BLOCK_SIZE_1K) { - SizeFormat::BlockSize(1024) - } else if matches.get_flag(options::BLOCK_SIZE_1M) { - SizeFormat::BlockSize(1024 * 1024) + } else if let Some(size_format) = handle_block_size_arg_override(&matches) { + size_format } else { let block_size_str = matches.get_one::(options::BLOCK_SIZE); let block_size = read_block_size(block_size_str.map(AsRef::as_ref))?; @@ -1088,7 +1122,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let printing_thread = thread::spawn(move || stat_printer.print_stats(&rx)); // Check existence of path provided in argument - let mut seen_inodes: HashSet = HashSet::new(); + let mut seen_inodes: HashSet = HashSet::default(); 'loop_file: for path in files { // Skip if we don't want to ignore anything diff --git a/src/uu/env/src/env.rs b/src/uu/env/src/env.rs index e8d01fe2a..5e56fbd04 100644 --- a/src/uu/env/src/env.rs +++ b/src/uu/env/src/env.rs @@ -118,7 +118,7 @@ struct Options<'a> { fn parse_name_value_opt<'a>(opts: &mut Options<'a>, opt: &'a OsStr) -> UResult { // is it a NAME=VALUE like opt ? let wrap = NativeStr::<'a>::new(opt); - let split_o = wrap.split_once(&'='); + let split_o = wrap.split_once('='); if let Some((name, value)) = split_o { // yes, so push name, value pair opts.sets.push((name, value)); @@ -930,8 +930,8 @@ fn apply_unset_env_vars(opts: &Options<'_>) -> Result<(), Box> { for name in &opts.unsets { let native_name = NativeStr::new(name); if name.is_empty() - || native_name.contains(&'\0').unwrap() - || native_name.contains(&'=').unwrap() + || native_name.contains('\0').unwrap() + || native_name.contains('=').unwrap() { return Err(USimpleError::new( 125, @@ -1027,11 +1027,9 @@ where // Set environment variable to communicate to Rust child processes // that SIGPIPE should be default (not ignored) - if matches!(action_kind, SignalActionKind::Default) - && sig_value == nix::libc::SIGPIPE as usize - { + if matches!(action_kind, SignalActionKind::Default) && sig_value == libc::SIGPIPE as usize { unsafe { - std::env::set_var("RUST_SIGPIPE", "default"); + env::set_var("RUST_SIGPIPE", "default"); } } diff --git a/src/uu/env/src/native_int_str.rs b/src/uu/env/src/native_int_str.rs index 856948fc1..3e26dad07 100644 --- a/src/uu/env/src/native_int_str.rs +++ b/src/uu/env/src/native_int_str.rs @@ -116,7 +116,7 @@ impl<'a> Convert<&'a Vec<&'a str>, Vec>> for NCvt { impl<'a> Convert, Vec>> for NCvt { fn convert(f: Vec<&'a str>) -> Vec> { - f.iter().map(|x| Self::convert(*x)).collect() + f.into_iter().map(Self::convert).collect() } } @@ -173,7 +173,7 @@ pub fn from_native_int_representation_owned(input: NativeIntString) -> OsString } } -pub fn get_single_native_int_value(c: &char) -> Option { +pub fn get_single_native_int_value(c: char) -> Option { #[cfg(target_os = "windows")] { let mut buf = [0u16, 0]; @@ -229,7 +229,7 @@ impl<'a> NativeStr<'a> { self.native } - pub fn contains(&self, x: &char) -> Option { + pub fn contains(&self, x: char) -> Option { let n_c = get_single_native_int_value(x)?; Some(self.native.contains(&n_c)) } @@ -239,7 +239,7 @@ impl<'a> NativeStr<'a> { result.unwrap() } - pub fn split_once(&self, pred: &char) -> Option<(Cow<'a, OsStr>, Cow<'a, OsStr>)> { + pub fn split_once(&self, pred: char) -> Option<(Cow<'a, OsStr>, Cow<'a, OsStr>)> { let n_c = get_single_native_int_value(pred)?; let p = self.native.iter().position(|&x| x == n_c)?; let before = self.slice(0, p); diff --git a/src/uu/env/src/string_parser.rs b/src/uu/env/src/string_parser.rs index 155eb4781..a0d0ed7bc 100644 --- a/src/uu/env/src/string_parser.rs +++ b/src/uu/env/src/string_parser.rs @@ -153,7 +153,7 @@ impl<'a> StringParser<'a> { } pub fn skip_until_char_or_end(&mut self, c: char) { - let native_rep = get_single_native_int_value(&c).unwrap(); + let native_rep = get_single_native_int_value(c).unwrap(); let pos = self.remaining.iter().position(|x| *x == native_rep); if let Some(pos) = pos { diff --git a/src/uu/expand/src/expand.rs b/src/uu/expand/src/expand.rs index 5acd4fac2..685cb44ac 100644 --- a/src/uu/expand/src/expand.rs +++ b/src/uu/expand/src/expand.rs @@ -8,7 +8,7 @@ use clap::{Arg, ArgAction, ArgMatches, Command}; use std::ffi::OsString; use std::fs::File; -use std::io::{BufRead, BufReader, BufWriter, Read, Write, stdin, stdout}; +use std::io::{BufReader, BufWriter, Read, Write, stdin, stdout}; use std::num::IntErrorKind; use std::path::Path; use std::str::from_utf8; @@ -320,9 +320,10 @@ fn open(path: &OsString) -> UResult>> { fn next_tabstop(tabstops: &[usize], col: usize, remaining_mode: &RemainingMode) -> usize { let num_tabstops = tabstops.len(); match remaining_mode { - RemainingMode::Plus => match tabstops[0..num_tabstops - 1].iter().find(|&&t| t > col) { - Some(t) => t - col, - None => { + RemainingMode::Plus => { + if let Some(t) = tabstops[0..num_tabstops - 1].iter().find(|&&t| t > col) { + t - col + } else { let step_size = tabstops[num_tabstops - 1]; let last_fixed_tabstop = tabstops[num_tabstops - 2]; let characters_since_last_tabstop = col - last_fixed_tabstop; @@ -330,11 +331,14 @@ fn next_tabstop(tabstops: &[usize], col: usize, remaining_mode: &RemainingMode) let steps_required = 1 + characters_since_last_tabstop / step_size; steps_required * step_size - characters_since_last_tabstop } - }, - RemainingMode::Slash => match tabstops[0..num_tabstops - 1].iter().find(|&&t| t > col) { - Some(t) => t - col, - None => tabstops[num_tabstops - 1] - col % tabstops[num_tabstops - 1], - }, + } + RemainingMode::Slash => { + if let Some(t) = tabstops[0..num_tabstops - 1].iter().find(|&&t| t > col) { + t - col + } else { + tabstops[num_tabstops - 1] - col % tabstops[num_tabstops - 1] + } + } RemainingMode::None => { if num_tabstops == 1 { tabstops[0] - col % tabstops[0] @@ -411,11 +415,12 @@ fn write_tab_spaces( } } -fn expand_line( - buf: &mut Vec, +fn expand_buf( + buf: &[u8], output: &mut BufWriter, tabstops: &[usize], options: &Options, + col: &mut usize, ) -> std::io::Result<()> { use self::CharType::{Backspace, Other, Tab}; @@ -423,11 +428,12 @@ fn expand_line( // we can write the buffer directly without character-by-character processing if !buf.contains(&b'\t') && !buf.contains(&b'\x08') && (options.utf8 || !buf.contains(&b'\r')) { output.write_all(buf)?; - buf.truncate(0); + if let Some(n) = buf.iter().rposition(|&b| b == b'\n') { + *col = buf.len() - n - 1; + } return Ok(()); } - let mut col = 0; let mut byte = 0; let mut init = true; @@ -438,8 +444,8 @@ fn expand_line( match ctype { Tab => { // figure out how many spaces to the next tabstop - let nts = next_tabstop(tabstops, col, &options.remaining_mode); - col += nts; + let nts = next_tabstop(tabstops, *col, &options.remaining_mode); + *col += nts; // now dump out either spaces if we're expanding, or a literal tab if we're not if init || !options.iflag { @@ -449,25 +455,30 @@ fn expand_line( } } Backspace => { - col = col.saturating_sub(1); + *col = col.saturating_sub(1); // if we're writing anything other than a space, then we're // done with the line's leading spaces - if buf[byte] != 0x20 { + if buf[byte] != b' ' { init = false; } output.write_all(&buf[byte..byte + nbytes])?; } Other => { - col += cwidth; + *col += cwidth; // if we're writing anything other than a space, then we're // done with the line's leading spaces - if buf[byte] != 0x20 { + if buf[byte] != b' ' { init = false; } + if buf[byte] == b'\n' { + *col = 0; + init = true; + } + output.write_all(&buf[byte..byte + nbytes])?; } } @@ -475,8 +486,6 @@ fn expand_line( byte += nbytes; // advance the pointer } - buf.truncate(0); // clear the buffer - Ok(()) } @@ -485,14 +494,15 @@ fn expand_file( output: &mut BufWriter, options: &Options, ) -> UResult<()> { - let mut buf = Vec::new(); + let mut buf = [0u8; 4096]; let mut input = open(file)?; let ts = options.tabstops.as_ref(); + let mut col = 0; loop { - match input.read_until(b'\n', &mut buf) { + match input.read(&mut buf) { Ok(0) => break, - Ok(_) => { - expand_line(&mut buf, output, ts, options) + Ok(n) => { + expand_buf(&buf[..n], output, ts, options, &mut col) .map_err_context(|| translate!("expand-error-failed-to-write-output"))?; } Err(e) => return Err(e.map_err_context(|| file.maybe_quote().to_string())), diff --git a/src/uu/expr/src/syntax_tree.rs b/src/uu/expr/src/syntax_tree.rs index 3cfd2c83f..a5c453c17 100644 --- a/src/uu/expr/src/syntax_tree.rs +++ b/src/uu/expr/src/syntax_tree.rs @@ -56,11 +56,7 @@ pub enum StringOp { } impl BinOp { - fn eval( - &self, - left: ExprResult, - right: ExprResult, - ) -> ExprResult { + fn eval(self, left: ExprResult, right: ExprResult) -> ExprResult { match self { Self::Relation(op) => op.eval(left, right), Self::Numeric(op) => op.eval(left, right), @@ -70,7 +66,7 @@ impl BinOp { } impl RelationOp { - fn eval(&self, a: ExprResult, b: ExprResult) -> ExprResult { + fn eval(self, a: ExprResult, b: ExprResult) -> ExprResult { // Make sure that the given comparison validates the relational operator. let check_cmp = |cmp| { use RelationOp::{Eq, Geq, Gt, Leq, Lt, Neq}; @@ -98,11 +94,7 @@ impl RelationOp { } impl NumericOp { - fn eval( - &self, - left: ExprResult, - right: ExprResult, - ) -> ExprResult { + fn eval(self, left: ExprResult, right: ExprResult) -> ExprResult { let a = left?.eval_as_bigint()?; let b = right?.eval_as_bigint()?; Ok(NumOrStr::Num(match self { @@ -124,11 +116,7 @@ impl NumericOp { } impl StringOp { - fn eval( - &self, - left: ExprResult, - right: ExprResult, - ) -> ExprResult { + fn eval(self, left: ExprResult, right: ExprResult) -> ExprResult { match self { Self::Or => { let left = left?; @@ -379,7 +367,7 @@ fn build_regex(pattern_bytes: Vec) -> ExprResult<(Regex, String)> { } /// Find matches in the input using the compiled regex -fn find_match(regex: Regex, re_string: String, left_bytes: Vec) -> ExprResult { +fn find_match(regex: Regex, re_string: String, left_bytes: Vec) -> String { use onig::EncodedBytes; use uucore::i18n::{UEncoding, get_locale_encoding}; @@ -387,7 +375,7 @@ fn find_match(regex: Regex, re_string: String, left_bytes: Vec) -> ExprResul // Match against the input using the appropriate encoding let mut region = onig::Region::new(); - let result = match encoding { + match encoding { UEncoding::Utf8 => { // In UTF-8 locale, check if input is valid UTF-8 if let Ok(left_str) = std::str::from_utf8(&left_bytes) { @@ -495,7 +483,7 @@ fn find_match(regex: Regex, re_string: String, left_bytes: Vec) -> ExprResul if let Some((start, end)) = region.pos(1) { let capture_bytes = &left_bytes[start..end]; // Return raw bytes as String for consistency with other cases - return Ok(String::from_utf8_lossy(capture_bytes).into_owned()); + return String::from_utf8_lossy(capture_bytes).into_owned(); } String::new() } else { @@ -512,9 +500,7 @@ fn find_match(regex: Regex, re_string: String, left_bytes: Vec) -> ExprResul } } } - }; - - Ok(result) + } } /// Evaluate a match expression with locale-aware regex matching @@ -545,8 +531,7 @@ fn evaluate_match_expression(left_bytes: Vec, right_bytes: Vec) -> ExprR } } - let result = find_match(regex, re_string, left_bytes)?; - Ok(result.into()) + Ok(find_match(regex, re_string, left_bytes).into()) } /// Precedence for infix binary operators diff --git a/src/uu/false/src/false.rs b/src/uu/false/src/false.rs index 108349031..d4e6b6738 100644 --- a/src/uu/false/src/false.rs +++ b/src/uu/false/src/false.rs @@ -9,9 +9,9 @@ use uucore::error::{UResult, set_exit_code}; use uucore::translate; #[uucore::main] +// TODO: modify proc macro to allow no-result uumain +#[expect(clippy::unnecessary_wraps, reason = "proc macro requires UResult")] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let mut command = uu_app(); - // Mirror GNU options, always return `1`. In particular even the 'successful' cases of no-op, // and the interrupted display of help and version should return `1`. Also, we return Ok in all // paths to avoid the allocation of an error object, an operation that could, in theory, fail @@ -19,26 +19,24 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { set_exit_code(1); let args: Vec = args.collect(); - if args.len() > 2 { + if args.len() != 2 { return Ok(()); } - if let Err(e) = command.try_get_matches_from_mut(args) { - // For the false command, we don't want to show any error messages for UnknownArgument - // since false should produce no output and just exit with code 1 - let error = match e.kind() { - clap::error::ErrorKind::DisplayHelp => command.print_help(), - clap::error::ErrorKind::DisplayVersion => { - write!(std::io::stdout(), "{}", command.render_version()) - } - _ => Ok(()), - }; + // args[0] is the name of the binary. + let error = if args[1] == "--help" { + uu_app().print_help() + } else if args[1] == "--version" { + write!(std::io::stdout(), "{}", uu_app().render_version()) + } else { + Ok(()) + }; + if let Err(print_fail) = error { // Try to display this error. - if let Err(print_fail) = error { - // Completely ignore any error here, no more failover and we will fail in any case. - let _ = writeln!(std::io::stderr(), "{}: {print_fail}", uucore::util_name()); - } + let _ = writeln!(std::io::stderr(), "{}: {print_fail}", uucore::util_name()); + // Completely ignore any error here, no more failover and we will fail in any case. + set_exit_code(1); } Ok(()) diff --git a/src/uu/fold/src/fold.rs b/src/uu/fold/src/fold.rs index 71d4756a7..17b58e18b 100644 --- a/src/uu/fold/src/fold.rs +++ b/src/uu/fold/src/fold.rs @@ -260,57 +260,33 @@ fn next_tab_stop(col_count: usize) -> usize { } fn compute_col_count(buffer: &[u8], mode: WidthMode) -> usize { - match mode { - WidthMode::Characters => { - if let Ok(s) = std::str::from_utf8(buffer) { - let mut width = 0; - for ch in s.chars() { - match ch { - '\r' => width = 0, - '\t' => width = next_tab_stop(width), - '\x08' => width = width.saturating_sub(1), - _ => width += 1, + if let Ok(s) = std::str::from_utf8(buffer) { + let mut width = 0; + for ch in s.chars() { + match ch { + '\r' => width = 0, + '\t' => width = next_tab_stop(width), + '\x08' => width = width.saturating_sub(1), + _ => { + width += match mode { + WidthMode::Characters => 1, + WidthMode::Columns => UnicodeWidthChar::width(ch).unwrap_or(0), } } - width - } else { - let mut width = 0; - for &byte in buffer { - match byte { - CR => width = 0, - TAB => width = next_tab_stop(width), - 0x08 => width = width.saturating_sub(1), - _ => width += 1, - } - } - width } } - WidthMode::Columns => { - if let Ok(s) = std::str::from_utf8(buffer) { - let mut width = 0; - for ch in s.chars() { - match ch { - '\r' => width = 0, - '\t' => width = next_tab_stop(width), - '\x08' => width = width.saturating_sub(1), - _ => width += UnicodeWidthChar::width(ch).unwrap_or(0), - } - } - width - } else { - let mut width = 0; - for &byte in buffer { - match byte { - CR => width = 0, - TAB => width = next_tab_stop(width), - 0x08 => width = width.saturating_sub(1), - _ => width += 1, - } - } - width + width + } else { + let mut width = 0; + for &byte in buffer { + match byte { + CR => width = 0, + TAB => width = next_tab_stop(width), + 0x08 => width = width.saturating_sub(1), + _ => width += 1, } } + width } } @@ -514,7 +490,7 @@ fn process_utf8_chars(line: &str, ctx: &mut FoldContext<'_, W>) -> URe // not coalesce zero-width scalars there. if ctx.mode == WidthMode::Columns { while let Some(&(_, next_ch)) = iter.peek() { - if unicode_width::UnicodeWidthChar::width(next_ch).unwrap_or(1) == 0 { + if UnicodeWidthChar::width(next_ch).unwrap_or(1) == 0 { iter.next(); } else { break; diff --git a/src/uu/groups/src/groups.rs b/src/uu/groups/src/groups.rs index d6ecc9ec4..aff29ca1e 100644 --- a/src/uu/groups/src/groups.rs +++ b/src/uu/groups/src/groups.rs @@ -35,14 +35,13 @@ enum GroupsError { impl UError for GroupsError {} -fn infallible_gid2grp(gid: &u32) -> String { - match gid2grp(*gid) { - Ok(grp) => grp, - Err(_) => { - // The `show!()` macro sets the global exit code for the program. - show!(GroupsError::GroupNotFound(*gid)); - gid.to_string() - } +fn infallible_gid2grp(gid: u32) -> String { + if let Ok(grp) = gid2grp(gid) { + grp + } else { + // The `show!()` macro sets the global exit code for the program. + show!(GroupsError::GroupNotFound(gid)); + gid.to_string() } } @@ -59,7 +58,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let Ok(gids) = get_groups_gnu(None) else { return Err(GroupsError::GetGroupsFailed.into()); }; - let groups: Vec = gids.iter().map(infallible_gid2grp).collect(); + let groups: Vec = gids.into_iter().map(infallible_gid2grp).collect(); writeln!(stdout(), "{}", groups.join(" "))?; return Ok(()); } @@ -67,7 +66,8 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { for user in users { match Passwd::locate(user.as_str()) { Ok(p) => { - let groups: Vec = p.belongs_to().iter().map(infallible_gid2grp).collect(); + let groups: Vec = + p.belongs_to().into_iter().map(infallible_gid2grp).collect(); writeln!(stdout(), "{user} : {}", groups.join(" "))?; } Err(_) => { diff --git a/src/uu/head/src/parse.rs b/src/uu/head/src/parse.rs index 54025a89d..daf87c54d 100644 --- a/src/uu/head/src/parse.rs +++ b/src/uu/head/src/parse.rs @@ -34,7 +34,11 @@ pub fn parse_obsolete(src: &str) -> Option, ParseError>> { } } if has_num { - process_num_block(&src[num_start..num_end], last_char, &mut chars) + Some(process_num_block( + &src[num_start..num_end], + last_char, + &mut chars, + )) } else { None } @@ -48,11 +52,11 @@ fn process_num_block( src: &str, last_char: char, chars: &mut std::str::CharIndices, -) -> Option, ParseError>> { +) -> Result, ParseError> { let num = match src.parse::() { Ok(n) => n, Err(e) if *e.kind() == std::num::IntErrorKind::PosOverflow => usize::MAX, - _ => return Some(Err(ParseError)), + _ => return Err(ParseError), }; let mut quiet = false; let mut verbose = false; @@ -78,7 +82,7 @@ fn process_num_block( 'k' => multiplier = Some(1024), 'm' => multiplier = Some(1024 * 1024), '\0' => {} - _ => return Some(Err(ParseError)), + _ => return Err(ParseError), } if let Some((_, next)) = chars.next() { c = next; @@ -104,7 +108,7 @@ fn process_num_block( options.push(OsString::from("-n")); options.push(OsString::from(format!("{num}"))); } - Some(Ok(options)) + Ok(options) } /// Parses an -c or -n argument, @@ -134,6 +138,7 @@ mod tests { } } + #[expect(clippy::unnecessary_wraps, reason = "test helper")] fn obsolete_result(src: &[&str]) -> Option, ParseError>> { Some(Ok(src.iter().map(|&s| s.to_string()).collect())) } diff --git a/src/uu/id/src/id.rs b/src/uu/id/src/id.rs index e6ab3a696..591231968 100644 --- a/src/uu/id/src/id.rs +++ b/src/uu/id/src/id.rs @@ -216,22 +216,21 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { for i in 0..=users.len() { let possible_pw = if state.user_specified { - match Passwd::locate(users[i].as_str()) { - Ok(p) => Some(p), - Err(_) => { - show_error!( - "{}", - translate!("id-error-no-such-user", - "user" => users[i].quote() - ) - ); - set_exit_code(1); - if i + 1 >= users.len() { - break; - } - - continue; + if let Ok(p) = Passwd::locate(users[i].as_str()) { + Some(p) + } else { + show_error!( + "{}", + translate!("id-error-no-such-user", + "user" => users[i].quote() + ) + ); + set_exit_code(1); + if i + 1 >= users.len() { + break; } + + continue; } } else { None diff --git a/src/uu/ln/src/ln.rs b/src/uu/ln/src/ln.rs index c20a2fc88..541586625 100644 --- a/src/uu/ln/src/ln.rs +++ b/src/uu/ln/src/ln.rs @@ -319,27 +319,22 @@ fn link_files_in_dir(files: &[PathBuf], target_dir: &Path, settings: &Settings) } } target_dir.to_path_buf() - } else { - match srcpath.as_os_str().to_str() { - Some(name) => { - match Path::new(name).file_name() { - Some(basename) => target_dir.join(basename), - // This can be None only for "." or "..". Trying - // to create a link with such name will fail with - // EEXIST, which agrees with the behavior of GNU - // coreutils. - None => target_dir.join(name), - } - } - None => { - show_error!( - "{}", - translate!("ln-error-cannot-stat", "path" => srcpath.quote()) - ); - all_successful = false; - continue; - } + } else if let Some(name) = srcpath.as_os_str().to_str() { + match Path::new(name).file_name() { + Some(basename) => target_dir.join(basename), + // This can be None only for "." or "..". Trying + // to create a link with such name will fail with + // EEXIST, which agrees with the behavior of GNU + // coreutils. + None => target_dir.join(name), } + } else { + show_error!( + "{}", + translate!("ln-error-cannot-stat", "path" => srcpath.quote()) + ); + all_successful = false; + continue; }; if linked_destinations.contains(&targetpath) { diff --git a/src/uu/logname/src/logname.rs b/src/uu/logname/src/logname.rs index 6684bd9f4..8790fea3c 100644 --- a/src/uu/logname/src/logname.rs +++ b/src/uu/logname/src/logname.rs @@ -26,9 +26,10 @@ fn get_userlogin() -> Option { pub fn uumain(args: impl uucore::Args) -> UResult<()> { let _ = uucore::clap_localization::handle_clap_result(uu_app(), args)?; - match get_userlogin() { - Some(userlogin) => writeln!(stdout(), "{userlogin}")?, - None => show_error!("{}", translate!("logname-error-no-login-name")), + if let Some(userlogin) = get_userlogin() { + writeln!(stdout(), "{userlogin}")?; + } else { + show_error!("{}", translate!("logname-error-no-login-name")); } Ok(()) diff --git a/src/uu/ls/Cargo.toml b/src/uu/ls/Cargo.toml index a96d09108..0b53e8749 100644 --- a/src/uu/ls/Cargo.toml +++ b/src/uu/ls/Cargo.toml @@ -24,8 +24,8 @@ ansi-width = { workspace = true } clap = { workspace = true, features = ["env"] } glob = { workspace = true } hostname = { workspace = true } -fnv = { workspace = true } lscolors = { workspace = true } +rustc-hash = { workspace = true } selinux = { workspace = true, optional = true } terminal_size = { workspace = true } thiserror = { workspace = true } diff --git a/src/uu/ls/locales/en-US.ftl b/src/uu/ls/locales/en-US.ftl index 0ff418309..7de4a17ce 100644 --- a/src/uu/ls/locales/en-US.ftl +++ b/src/uu/ls/locales/en-US.ftl @@ -2,8 +2,13 @@ ls-about = List directory contents. Ignore files and directories starting with a '.' by default dir-about = List directory contents. Ignore files and directories starting with a '.' by default +vdir-about = List directory contents. + Ignore files and directories starting with a '.' by default + + Mandatory arguments to long options are mandatory for short options too. ls-usage = ls [OPTION]... [FILE]... dir-usage = dir [OPTION]... [FILE]... +vdir-usage = vdir [OPTION]... [FILE]... ls-after-help = The TIME_STYLE argument can be full-iso, long-iso, iso, locale or +FORMAT. FORMAT is interpreted like in date. Also the TIME_STYLE environment variable sets the default style to use. # Error messages diff --git a/src/uu/ls/locales/fr-FR.ftl b/src/uu/ls/locales/fr-FR.ftl index a655535f7..ad58bd690 100644 --- a/src/uu/ls/locales/fr-FR.ftl +++ b/src/uu/ls/locales/fr-FR.ftl @@ -2,8 +2,13 @@ ls-about = Lister le contenu des répertoires. Ignorer les fichiers et répertoires commençant par un '.' par défaut dir-about = Lister le contenu des répertoires. Ignorer les fichiers et répertoires commençant par un '.' par défaut +vdir-about = Lister le contenu des répertoires. + Ignorer les fichiers et répertoires commençant par un '.' par défaut + + Les arguments obligatoires pour les options longues le sont aussi pour les options courtes. ls-usage = ls [OPTION]... [FICHIER]... dir-usage = dir [OPTION]... [FICHIER]... +vdir-usage = vdir [OPTION]... [FICHIER]... ls-after-help = L'argument TIME_STYLE peut être full-iso, long-iso, iso, locale ou +FORMAT. FORMAT est interprété comme dans date. De plus, la variable d'environnement TIME_STYLE définit le style par défaut à utiliser. # Messages d'erreur diff --git a/src/uu/ls/src/colors.rs b/src/uu/ls/src/colors.rs index 24b79435b..fa9a8903b 100644 --- a/src/uu/ls/src/colors.rs +++ b/src/uu/ls/src/colors.rs @@ -376,7 +376,7 @@ impl<'a> StyleManager<'a> { } else if file_type.is_dir() { self.indicator_for_directory(path) } else { - self.indicator_for_special_file(file_type) + self.indicator_for_special_file(*file_type) } } @@ -478,7 +478,7 @@ impl<'a> StyleManager<'a> { } #[cfg(unix)] - fn indicator_for_special_file(&self, file_type: &std::fs::FileType) -> Option { + fn indicator_for_special_file(&self, file_type: fs::FileType) -> Option { if file_type.is_fifo() && self.has_indicator_style(Indicator::FIFO) { return Some(Indicator::FIFO); } @@ -495,7 +495,7 @@ impl<'a> StyleManager<'a> { } #[cfg(not(unix))] - fn indicator_for_special_file(&self, _file_type: &std::fs::FileType) -> Option { + fn indicator_for_special_file(&self, _file_type: fs::FileType) -> Option { None } diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index 1bad30023..489fa301b 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -7,8 +7,8 @@ // spell-checker:ignore nohash strtime clocale #[cfg(unix)] -use fnv::FnvHashMap as HashMap; -use fnv::FnvHashSet as HashSet; +use rustc_hash::FxHashMap; +use rustc_hash::FxHashSet; use std::borrow::Cow; use std::cell::RefCell; #[cfg(unix)] @@ -194,9 +194,10 @@ enum LsError { translate!("ls-error-cannot-open-file-permission-denied", "path" => .0.quote()) }, }, - _ => match .1.raw_os_error().unwrap_or(1) { - 9 => translate!("ls-error-cannot-open-directory-bad-descriptor", "path" => .0.quote()), - _ => translate!("ls-error-unknown-io-error", "path" => .0.quote(), "error" => format!("{:?}", .1)), + _ => if 9 == .1.raw_os_error().unwrap_or(1) { + translate!("ls-error-cannot-open-directory-bad-descriptor", "path" => .0.quote()) + } else { + translate!("ls-error-unknown-io-error", "path" => .0.quote(), "error" => format!("{:?}", .1)) }, })] IOErrorContext(PathBuf, std::io::Error, bool), @@ -261,6 +262,7 @@ fn parse_time_style(options: &clap::ArgMatches) -> Result<(String, Option) = ("%b %e %H:%M", Some("%b %e %Y")); // Convert time_styles references to owned String/option. + #[expect(clippy::unnecessary_wraps, reason = "internal result helper")] fn ok((recent, older): (&str, Option<&str>)) -> Result<(String, Option), LsError> { Ok((recent.to_string(), older.map(String::from))) } @@ -827,17 +829,17 @@ fn parse_width(width_match: Option<&String>) -> Result { } }; - let parse_width_from_env = - |columns: OsString| match columns.to_str().and_then(|s| s.parse().ok()) { - Some(columns) => columns, - None => { - show_error!( - "{}", - translate!("ls-invalid-columns-width", "width" => columns.quote()) - ); - DEFAULT_TERM_WIDTH - } - }; + let parse_width_from_env = |columns: OsString| { + if let Some(columns) = columns.to_str().and_then(|s| s.parse().ok()) { + columns + } else { + show_error!( + "{}", + translate!("ls-invalid-columns-width", "width" => columns.quote()) + ); + DEFAULT_TERM_WIDTH + } + }; let calculate_term_size = || match terminal_size::terminal_size() { Some((width, _)) => width.0, @@ -958,8 +960,8 @@ impl Config { let (file_size_block_size, block_size) = if !opt_si && !opt_hr && !raw_block_size.is_empty() { - match parse_size_non_zero_u64(&raw_block_size.to_string_lossy()) { - Ok(size) => match (is_env_var_blocksize, opt_kb) { + if let Ok(size) = parse_size_non_zero_u64(&raw_block_size.to_string_lossy()) { + match (is_env_var_blocksize, opt_kb) { (true, true) => (DEFAULT_FILE_SIZE_BLOCK_SIZE, DEFAULT_BLOCK_SIZE), (true, false) => (DEFAULT_FILE_SIZE_BLOCK_SIZE, size), (false, true) => { @@ -971,20 +973,19 @@ impl Config { } } (false, false) => (size, size), - }, - Err(_) => { - // only fail if invalid block size was specified with --block-size, - // ignore invalid block size from env vars - if let Some(invalid_block_size) = opt_block_size { - return Err(Box::new(LsError::BlockSizeParseError( - invalid_block_size.clone(), - ))); - } - if is_env_var_blocksize { - (DEFAULT_FILE_SIZE_BLOCK_SIZE, DEFAULT_BLOCK_SIZE) - } else { - (DEFAULT_BLOCK_SIZE, DEFAULT_BLOCK_SIZE) - } + } + } else { + // only fail if invalid block size was specified with --block-size, + // ignore invalid block size from env vars + if let Some(invalid_block_size) = opt_block_size { + return Err(Box::new(LsError::BlockSizeParseError( + invalid_block_size.clone(), + ))); + } + if is_env_var_blocksize { + (DEFAULT_FILE_SIZE_BLOCK_SIZE, DEFAULT_BLOCK_SIZE) + } else { + (DEFAULT_BLOCK_SIZE, DEFAULT_BLOCK_SIZE) } } } else if env_var_posixly_correct.is_some() { @@ -1047,14 +1048,13 @@ impl Config { .into_iter() .flatten() { - match parse_glob::from_str(pattern) { - Ok(p) => { - ignore_patterns.push(p); - } - Err(_) => show_warning!( + if let Ok(p) = parse_glob::from_str(pattern) { + ignore_patterns.push(p); + } else { + show_warning!( "{}", translate!("ls-invalid-ignore-pattern", "pattern" => pattern.quote()) - ), + ); } } @@ -1064,14 +1064,13 @@ impl Config { .into_iter() .flatten() { - match parse_glob::from_str(pattern) { - Ok(p) => { - ignore_patterns.push(p); - } - Err(_) => show_warning!( + if let Ok(p) = parse_glob::from_str(pattern) { + ignore_patterns.push(p); + } else { + show_warning!( "{}", translate!("ls-invalid-hide-pattern", "pattern" => pattern.quote()) - ), + ); } } } @@ -2195,9 +2194,9 @@ struct ListState<'a> { // performance was equivalent to BTreeMap. // It's possible a simple vector linear(binary?) search implementation would be even faster. #[cfg(unix)] - uid_cache: HashMap, + uid_cache: FxHashMap, #[cfg(unix)] - gid_cache: HashMap, + gid_cache: FxHashMap, recent_time_range: RangeInclusive, } @@ -2212,9 +2211,9 @@ pub fn list(locs: Vec<&Path>, config: &Config) -> UResult<()> { out: BufWriter::new(stdout()), style_manager: config.color.as_ref().map(StyleManager::new), #[cfg(unix)] - uid_cache: HashMap::default(), + uid_cache: FxHashMap::default(), #[cfg(unix)] - gid_cache: HashMap::default(), + gid_cache: FxHashMap::default(), // Time range for which to use the "recent" format. Anything from 0.5 year in the past to now // (files with modification time in the future use "old" format). // According to GNU a Gregorian year has 365.2425 * 24 * 60 * 60 == 31556952 seconds on the average. @@ -2235,12 +2234,11 @@ pub fn list(locs: Vec<&Path>, config: &Config) -> UResult<()> { continue; } - let show_dir_contents = match path_data.file_type() { - Some(ft) => !config.directory && ft.is_dir(), - None => { - set_exit_code(1); - false - } + let show_dir_contents = if let Some(ft) = path_data.file_type() { + !config.directory && ft.is_dir() + } else { + set_exit_code(1); + false }; if show_dir_contents { @@ -2303,7 +2301,7 @@ pub fn list(locs: Vec<&Path>, config: &Config) -> UResult<()> { writeln!(state.out)?; } } - let mut listed_ancestors = HashSet::default(); + let mut listed_ancestors = FxHashSet::default(); listed_ancestors.insert(FileInformation::from_path( path_data.path(), path_data.must_dereference, @@ -2441,7 +2439,7 @@ fn enter_directory( mut read_dir: ReadDir, config: &Config, state: &mut ListState, - listed_ancestors: &mut HashSet, + listed_ancestors: &mut FxHashSet, dired: &mut DiredOutput, ) -> UResult<()> { // Create vec of entries with initial dot files @@ -2637,7 +2635,7 @@ fn display_additional_leading_info( item: &PathData, padding: &PaddingCollection, config: &Config, -) -> UResult { +) -> String { let mut result = String::new(); #[cfg(unix)] { @@ -2664,7 +2662,8 @@ fn display_additional_leading_info( write!(result, "{} ", pad_left(&s, padding.block_size)).unwrap(); } } - Ok(result) + + result } #[allow(clippy::cognitive_complexity)] @@ -2693,7 +2692,7 @@ fn display_items( let should_display_leading_info = config.alloc_size; if should_display_leading_info { - let more_info = display_additional_leading_info(item, &padding_collection, config)?; + let more_info = display_additional_leading_info(item, &padding_collection, config); write!(state.out, "{more_info}")?; } @@ -2728,7 +2727,7 @@ fn display_items( for i in items { let more_info = if should_display_leading_info { - Some(display_additional_leading_info(i, &padding, config)?) + Some(display_additional_leading_info(i, &padding, config)) } else { None }; @@ -3574,7 +3573,7 @@ fn get_security_context<'a>( // For SMACK, use the path to get the label // If must_dereference is true, we follow the symlink let target_path = if must_dereference { - std::fs::canonicalize(path).unwrap_or_else(|_| path.to_path_buf()) + fs::canonicalize(path).unwrap_or_else(|_| path.to_path_buf()) } else { path.to_path_buf() }; diff --git a/src/uu/md5sum/Cargo.toml b/src/uu/md5sum/Cargo.toml index 70ecfe0cd..23ee431af 100644 --- a/src/uu/md5sum/Cargo.toml +++ b/src/uu/md5sum/Cargo.toml @@ -29,8 +29,6 @@ uucore = { workspace = true, features = [ fluent = { workspace = true } [dev-dependencies] -divan = { workspace = true } -tempfile = { workspace = true } uucore = { workspace = true, features = ["benchmark"] } [[bin]] diff --git a/src/uu/mkdir/src/mkdir.rs b/src/uu/mkdir/src/mkdir.rs index 7ea655352..3d0a9bc33 100644 --- a/src/uu/mkdir/src/mkdir.rs +++ b/src/uu/mkdir/src/mkdir.rs @@ -51,6 +51,10 @@ pub struct Config<'a> { } #[cfg(windows)] +#[expect( + clippy::unnecessary_wraps, + reason = "fn sig must match on all platforms" +)] fn get_mode(_matches: &ArgMatches) -> Result { Ok(DEFAULT_PERM) } @@ -92,7 +96,8 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { set_security_context: set_security_context || context.is_some(), context, }; - exec(dirs, &config) + exec(dirs, &config); + Ok(()) } Err(f) => Err(USimpleError::new(1, f)), } @@ -154,14 +159,13 @@ pub fn uu_app() -> Command { /** * Create the list of new directories */ -fn exec(dirs: ValuesRef, config: &Config) -> UResult<()> { +fn exec(dirs: ValuesRef, config: &Config) { for dir in dirs { let path_buf = PathBuf::from(dir); let path = path_buf.as_path(); show_if_err!(mkdir(path, config)); } - Ok(()) } /// Create directory at a given `path`. diff --git a/src/uu/mkfifo/src/mkfifo.rs b/src/uu/mkfifo/src/mkfifo.rs index 740e8cdb4..a70d140c7 100644 --- a/src/uu/mkfifo/src/mkfifo.rs +++ b/src/uu/mkfifo/src/mkfifo.rs @@ -88,9 +88,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let set_security_context = matches.get_flag(options::SECURITY_CONTEXT); let context = matches.get_one::(options::CONTEXT); if set_security_context || context.is_some() { - uucore::smack::set_smack_label_and_cleanup(&f, context, |p| { - std::fs::remove_file(p) - })?; + uucore::smack::set_smack_label_and_cleanup(&f, context, |p| fs::remove_file(p))?; } } } diff --git a/src/uu/mktemp/src/mktemp.rs b/src/uu/mktemp/src/mktemp.rs index f84f00a00..3efd06901 100644 --- a/src/uu/mktemp/src/mktemp.rs +++ b/src/uu/mktemp/src/mktemp.rs @@ -399,7 +399,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { // Create the temporary file or directory, or simulate creating it. let res = if dry_run { - dry_exec(&tmpdir, &prefix, rand, &suffix) + Ok(dry_exec(&tmpdir, &prefix, rand, &suffix)) } else { exec(&tmpdir, &prefix, rand, &suffix, make_dir) }; @@ -483,7 +483,7 @@ pub fn uu_app() -> Command { ) } -fn dry_exec(tmpdir: &Path, prefix: &str, rand: usize, suffix: &str) -> UResult { +fn dry_exec(tmpdir: &Path, prefix: &str, rand: usize, suffix: &str) -> PathBuf { let len = prefix.len() + suffix.len() + rand; let mut buf = Vec::with_capacity(len); buf.extend(prefix.as_bytes()); @@ -503,8 +503,7 @@ fn dry_exec(tmpdir: &Path, prefix: &str, rand: usize, suffix: &str) -> UResult

UResult { // Create the temporary file or directory, or simulate creating it. if options.dry_run { - dry_exec(&tmpdir, &prefix, rand, &suffix) + Ok(dry_exec(&tmpdir, &prefix, rand, &suffix)) } else { exec(&tmpdir, &prefix, rand, &suffix, options.directory) } diff --git a/src/uu/more/src/more.rs b/src/uu/more/src/more.rs index 6c93dce69..f31521a6e 100644 --- a/src/uu/more/src/more.rs +++ b/src/uu/more/src/more.rs @@ -568,21 +568,20 @@ impl<'a> Pager<'a> { if self.pattern.is_none() { return Ok(()); } - match self.search_pattern_in_file() { - Some(line) => self.upper_mark = line, - None => { - self.pattern = None; - write!( - self.stdout, - "\r{}{} ({}){}", - Attribute::Reverse, - translate!("more-error-pattern-not-found"), - translate!("more-press-return"), - Attribute::Reset, - )?; - self.stdout.flush()?; - self.wait_for_enter_key()?; - } + if let Some(line) = self.search_pattern_in_file() { + self.upper_mark = line; + } else { + self.pattern = None; + write!( + self.stdout, + "\r{}{} ({}){}", + Attribute::Reverse, + translate!("more-error-pattern-not-found"), + translate!("more-press-return"), + Attribute::Reset, + )?; + self.stdout.flush()?; + self.wait_for_enter_key()?; } Ok(()) } diff --git a/src/uu/mv/src/hardlink.rs b/src/uu/mv/src/hardlink.rs index 1402047d5..8c4ea7b33 100644 --- a/src/uu/mv/src/hardlink.rs +++ b/src/uu/mv/src/hardlink.rs @@ -40,9 +40,6 @@ pub struct HardlinkOptions { pub verbose: bool, } -/// Result type for hardlink operations -pub type HardlinkResult = Result; - /// Errors that can occur during hardlink operations #[derive(Debug)] pub enum HardlinkError { @@ -122,7 +119,7 @@ impl HardlinkTracker { dest: &Path, scanner: &HardlinkGroupScanner, options: &HardlinkOptions, - ) -> HardlinkResult> { + ) -> Option { use std::os::unix::fs::MetadataExt; let metadata = match source.metadata() { @@ -132,7 +129,7 @@ impl HardlinkTracker { if options.verbose { eprintln!("warning: cannot get metadata for {}: {}", source.quote(), e); } - return Ok(None); + return None; } }; @@ -154,14 +151,14 @@ impl HardlinkTracker { existing_path.quote() ); } - return Ok(Some(existing_path.clone())); + return Some(existing_path.clone()); } } // This is the first time we see this file, record its destination self.inode_map.insert(key, dest.to_path_buf()); - Ok(None) + None } } @@ -171,13 +168,9 @@ impl HardlinkGroupScanner { } /// Scan files and group them by hardlinks, including recursive directory scanning - pub fn scan_files( - &mut self, - files: &[PathBuf], - options: &HardlinkOptions, - ) -> HardlinkResult<()> { + pub fn scan_files(&mut self, files: &[PathBuf], options: &HardlinkOptions) { if self.scanned { - return Ok(()); + return; } // Store the source files for destination mapping @@ -206,8 +199,6 @@ impl HardlinkGroupScanner { ); } } - - Ok(()) } /// Scan a single path (file or directory) @@ -305,12 +296,11 @@ pub fn with_optional_hardlink_context( where F: FnOnce(&mut HardlinkTracker, &HardlinkGroupScanner) -> R, { - match (tracker, scanner) { - (Some(tracker), Some(scanner)) => operation(tracker, scanner), - _ => { - let (mut dummy_tracker, dummy_scanner) = create_hardlink_context(); - operation(&mut dummy_tracker, &dummy_scanner) - } + if let (Some(tracker), Some(scanner)) = (tracker, scanner) { + operation(tracker, scanner) + } else { + let (mut dummy_tracker, dummy_scanner) = create_hardlink_context(); + operation(&mut dummy_tracker, &dummy_scanner) } } diff --git a/src/uu/mv/src/mv.rs b/src/uu/mv/src/mv.rs index 44540abfa..f7103f78b 100644 --- a/src/uu/mv/src/mv.rs +++ b/src/uu/mv/src/mv.rs @@ -431,7 +431,7 @@ fn handle_two_paths(source: &Path, target: &Path, opts: &Options) -> UResult<()> OverwriteMode::Force => {} OverwriteMode::Default => { let (writable, mode) = is_writable(target); - if !writable && std::io::stdin().is_terminal() { + if !writable && io::stdin().is_terminal() { prompt_overwrite(target, mode)?; } } @@ -587,19 +587,7 @@ fn move_files_into_dir(files: &[PathBuf], target_dir: &Path, options: &Options) }; // Pre-scan files if needed - if let Err(e) = scanner.scan_files(files, &hardlink_options) { - if hardlink_options.verbose { - eprintln!("mv: warning: failed to scan files for hardlinks: {e}"); - eprintln!("mv: continuing without hardlink preservation"); - } else { - // Show warning in non-verbose mode for serious errors - eprintln!( - "mv: warning: hardlink scanning failed, continuing without hardlink preservation" - ); - } - // Continue without hardlink tracking on scan failure - // This provides graceful degradation rather than failing completely - } + scanner.scan_files(files, &hardlink_options); (tracker, scanner) }; @@ -641,12 +629,11 @@ fn move_files_into_dir(files: &[PathBuf], target_dir: &Path, options: &Options) pb.set_message(msg); } - let targetpath = match sourcepath.file_name() { - Some(name) => target_dir.join(name), - None => { - show!(MvError::NoSuchFile(sourcepath.quote().to_string())); - continue; - } + let targetpath = if let Some(name) = sourcepath.file_name() { + target_dir.join(name) + } else { + show!(MvError::NoSuchFile(sourcepath.quote().to_string())); + continue; }; if moved_destinations.contains(&targetpath) && options.backup != BackupMode::Numbered { @@ -683,9 +670,10 @@ fn move_files_into_dir(files: &[PathBuf], target_dir: &Path, options: &Options) let e = e.map_err_context(|| { translate!("mv-error-cannot-move", "source" => sourcepath.quote(), "target" => targetpath.quote()) }); - match display_manager { - Some(ref pb) => pb.suspend(|| show!(e)), - None => show!(e), + if let Some(ref pb) = display_manager { + pb.suspend(|| show!(e)); + } else { + show!(e); } } Ok(()) => (), @@ -741,7 +729,7 @@ fn rename( OverwriteMode::Default => { // GNU mv prompts when stdin is a TTY and target is not writable let (writable, mode) = is_writable(to); - if !writable && std::io::stdin().is_terminal() { + if !writable && io::stdin().is_terminal() { prompt_overwrite(to, mode)?; } } @@ -789,11 +777,10 @@ fn rename( } if opts.verbose { - let message = match backup_path { - Some(path) => { - translate!("mv-verbose-renamed-with-backup", "from" => from.quote(), "to" => to.quote(), "backup" => path.quote()) - } - None => translate!("mv-verbose-renamed", "from" => from.quote(), "to" => to.quote()), + let message = if let Some(path) = backup_path { + translate!("mv-verbose-renamed-with-backup", "from" => from.quote(), "to" => to.quote(), "backup" => path.quote()) + } else { + translate!("mv-verbose-renamed", "from" => from.quote(), "to" => to.quote()) }; match display_manager { @@ -899,6 +886,10 @@ fn rename_fifo_fallback(from: &Path, to: &Path) -> io::Result<()> { } #[cfg(not(unix))] +#[expect( + clippy::unnecessary_wraps, + reason = "fn sig must match on all platforms" +)] fn rename_fifo_fallback(_from: &Path, _to: &Path) -> io::Result<()> { Ok(()) } @@ -1141,7 +1132,7 @@ fn copy_file_with_hardlinks_helper( let hardlink_options = HardlinkOptions::default(); // Create a hardlink instead of copying if let Some(existing_target) = - hardlink_tracker.check_hardlink(from, to, hardlink_scanner, &hardlink_options)? + hardlink_tracker.check_hardlink(from, to, hardlink_scanner, &hardlink_options) { fs::hard_link(&existing_target, to)?; return Ok(()); @@ -1187,7 +1178,7 @@ fn rename_file_fallback( use crate::hardlink::HardlinkOptions; let hardlink_options = HardlinkOptions::default(); if let Some(existing_target) = - tracker.check_hardlink(from, to, scanner, &hardlink_options)? + tracker.check_hardlink(from, to, scanner, &hardlink_options) { // Create a hardlink to the first moved file instead of copying fs::hard_link(&existing_target, to)?; diff --git a/src/uu/nice/src/nice.rs b/src/uu/nice/src/nice.rs index 5c9192c96..1abb7e96f 100644 --- a/src/uu/nice/src/nice.rs +++ b/src/uu/nice/src/nice.rs @@ -119,35 +119,32 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { )); } - let adjustment = match matches.get_one::(options::ADJUSTMENT) { - Some(nstr) => { - if !matches.contains_id(options::COMMAND) { - return Err(UUsageError::new( - 125, - translate!("nice-error-command-required-with-adjustment"), - )); - } - match nstr.parse::() { - Ok(num) => num, - Err(e) => match e.kind() { - IntErrorKind::PosOverflow => NICE_BOUND_NO_OVERFLOW, - IntErrorKind::NegOverflow => -NICE_BOUND_NO_OVERFLOW, - _ => { - return Err(USimpleError::new( - 125, - translate!("nice-error-invalid-number", "value" => nstr.clone(), "error" => e), - )); - } - }, - } + let adjustment = if let Some(nstr) = matches.get_one::(options::ADJUSTMENT) { + if !matches.contains_id(options::COMMAND) { + return Err(UUsageError::new( + 125, + translate!("nice-error-command-required-with-adjustment"), + )); } - None => { - if !matches.contains_id(options::COMMAND) { - writeln!(stdout(), "{niceness}")?; - return Ok(()); - } - 10_i32 + match nstr.parse::() { + Ok(num) => num, + Err(e) => match e.kind() { + IntErrorKind::PosOverflow => NICE_BOUND_NO_OVERFLOW, + IntErrorKind::NegOverflow => -NICE_BOUND_NO_OVERFLOW, + _ => { + return Err(USimpleError::new( + 125, + translate!("nice-error-invalid-number", "value" => nstr.clone(), "error" => e), + )); + } + }, } + } else { + if !matches.contains_id(options::COMMAND) { + writeln!(stdout(), "{niceness}")?; + return Ok(()); + } + 10_i32 }; niceness += adjustment; diff --git a/src/uu/numfmt/src/format.rs b/src/uu/numfmt/src/format.rs index 3b1f41aa9..b6946cdf8 100644 --- a/src/uu/numfmt/src/format.rs +++ b/src/uu/numfmt/src/format.rs @@ -89,11 +89,11 @@ fn find_numeric_beginning(s: &str) -> Option<&str> { } // finds the valid beginning part of an input string, or None. -fn find_valid_number_with_suffix<'a>(s: &'a str, unit: &Unit) -> Option<&'a str> { +fn find_valid_number_with_suffix(s: &str, unit: Unit) -> Option<&str> { let numeric_part = find_numeric_beginning(s)?; - let accepts_suffix = unit != &Unit::None; - let accepts_i = [Unit::Auto, Unit::Iec(true)].contains(unit); + let accepts_suffix = unit != Unit::None; + let accepts_i = [Unit::Auto, Unit::Iec(true)].contains(&unit); let mut characters = s.chars().skip(numeric_part.len()); let potential_suffix = characters.next(); @@ -117,7 +117,7 @@ fn find_valid_number_with_suffix<'a>(s: &'a str, unit: &Unit) -> Option<&'a str> } } -fn detailed_error_message(s: &str, unit: &Unit) -> Option { +fn detailed_error_message(s: &str, unit: Unit) -> Option { if s.is_empty() { return Some(translate!("numfmt-error-invalid-number-empty")); } @@ -144,13 +144,13 @@ fn detailed_error_message(s: &str, unit: &Unit) -> Option { None } -fn parse_suffix(s: &str, unit: &Unit) -> Result<(f64, Option)> { +fn parse_suffix(s: &str, unit: Unit) -> Result<(f64, Option)> { if s.is_empty() { return Err(translate!("numfmt-error-invalid-number-empty")); } let with_i = s.ends_with('i'); - if with_i && ![Unit::Auto, Unit::Iec(true)].contains(unit) { + if with_i && ![Unit::Auto, Unit::Iec(true)].contains(&unit) { return Err(translate!("numfmt-error-invalid-suffix", "input" => s.quote())); } let mut iter = s.chars(); @@ -200,9 +200,9 @@ fn parse_implicit_precision(s: &str) -> usize { } } -fn remove_suffix(i: f64, s: Option, u: &Unit) -> Result { +fn remove_suffix(i: f64, s: Option, u: Unit) -> Result { match (s, u) { - (Some((raw_suffix, false)), &Unit::Auto | &Unit::Si) => match raw_suffix { + (Some((raw_suffix, false)), Unit::Auto | Unit::Si) => match raw_suffix { RawSuffix::K => Ok(i * 1e3), RawSuffix::M => Ok(i * 1e6), RawSuffix::G => Ok(i * 1e9), @@ -214,8 +214,8 @@ fn remove_suffix(i: f64, s: Option, u: &Unit) -> Result { RawSuffix::R => Ok(i * 1e27), RawSuffix::Q => Ok(i * 1e30), }, - (Some((raw_suffix, false)), &Unit::Iec(false)) - | (Some((raw_suffix, true)), &Unit::Auto | &Unit::Iec(true)) => match raw_suffix { + (Some((raw_suffix, false)), Unit::Iec(false)) + | (Some((raw_suffix, true)), Unit::Auto | Unit::Iec(true)) => match raw_suffix { RawSuffix::K => Ok(i * IEC_BASES[1]), RawSuffix::M => Ok(i * IEC_BASES[2]), RawSuffix::G => Ok(i * IEC_BASES[3]), @@ -227,10 +227,10 @@ fn remove_suffix(i: f64, s: Option, u: &Unit) -> Result { RawSuffix::R => Ok(i * IEC_BASES[9]), RawSuffix::Q => Ok(i * IEC_BASES[10]), }, - (Some((raw_suffix, false)), &Unit::Iec(true)) => Err( + (Some((raw_suffix, false)), Unit::Iec(true)) => Err( translate!("numfmt-error-missing-i-suffix", "number" => i, "suffix" => format!("{raw_suffix:?}")), ), - (Some((raw_suffix, with_i)), &Unit::None) => Err( + (Some((raw_suffix, with_i)), Unit::None) => Err( translate!("numfmt-error-rejecting-suffix", "number" => i, "suffix" => format!("{raw_suffix:?}{}", if with_i { "i" } else { "" })), ), (None, _) => Ok(i), @@ -239,11 +239,11 @@ fn remove_suffix(i: f64, s: Option, u: &Unit) -> Result { } fn transform_from(s: &str, opts: &TransformOptions) -> Result { - let (i, suffix) = parse_suffix(s, &opts.from) - .map_err(|original| detailed_error_message(s, &opts.from).unwrap_or(original))?; + let (i, suffix) = parse_suffix(s, opts.from) + .map_err(|original| detailed_error_message(s, opts.from).unwrap_or(original))?; let i = i * (opts.from_unit as f64); - remove_suffix(i, suffix, &opts.from).map(|n| { + remove_suffix(i, suffix, opts.from).map(|n| { // GNU numfmt doesn't round values if no --from argument is provided by the user if opts.from == Unit::None { if n == -0.0 { 0.0 } else { n } @@ -301,7 +301,7 @@ fn round_with_precision(n: f64, method: RoundMethod, precision: usize) -> f64 { fn consider_suffix( n: f64, - u: &Unit, + u: Unit, round_method: RoundMethod, precision: usize, ) -> Result<(f64, Option)> { @@ -310,7 +310,7 @@ fn consider_suffix( let abs_n = n.abs(); let suffixes = [K, M, G, T, P, E, Z, Y, R, Q]; - let (bases, with_i) = match *u { + let (bases, with_i) = match u { Unit::Si => (&SI_BASES, false), Unit::Iec(with_i) => (&IEC_BASES, with_i), Unit::Auto => return Err(translate!("numfmt-error-unit-auto-not-supported-with-to")), @@ -353,7 +353,7 @@ fn transform_to( precision: usize, unit_separator: &str, ) -> Result { - let (i2, s) = consider_suffix(s, &opts.to, round_method, precision)?; + let (i2, s) = consider_suffix(s, opts.to, round_method, precision)?; let i2 = i2 / (opts.to_unit as f64); Ok(match s { None => { @@ -567,7 +567,7 @@ mod tests { #[test] fn test_parse_suffix_q_r_k() { - let result = parse_suffix("1Q", &Unit::Auto); + let result = parse_suffix("1Q", Unit::Auto); assert!(result.is_ok()); let (number, suffix) = result.unwrap(); assert_eq!(number, 1.0); @@ -576,7 +576,7 @@ mod tests { assert_eq!(raw_suffix as i32, RawSuffix::Q as i32); assert!(!with_i); - let result = parse_suffix("2R", &Unit::Auto); + let result = parse_suffix("2R", Unit::Auto); assert!(result.is_ok()); let (number, suffix) = result.unwrap(); assert_eq!(number, 2.0); @@ -585,7 +585,7 @@ mod tests { assert_eq!(raw_suffix as i32, RawSuffix::R as i32); assert!(!with_i); - let result = parse_suffix("3k", &Unit::Auto); + let result = parse_suffix("3k", Unit::Auto); assert!(result.is_ok()); let (number, suffix) = result.unwrap(); assert_eq!(number, 3.0); @@ -594,7 +594,7 @@ mod tests { assert_eq!(raw_suffix as i32, RawSuffix::K as i32); assert!(!with_i); - let result = parse_suffix("4Qi", &Unit::Auto); + let result = parse_suffix("4Qi", Unit::Auto); assert!(result.is_ok()); let (number, suffix) = result.unwrap(); assert_eq!(number, 4.0); @@ -603,7 +603,7 @@ mod tests { assert_eq!(raw_suffix as i32, RawSuffix::Q as i32); assert!(with_i); - let result = parse_suffix("5Ri", &Unit::Auto); + let result = parse_suffix("5Ri", Unit::Auto); assert!(result.is_ok()); let (number, suffix) = result.unwrap(); assert_eq!(number, 5.0); @@ -615,13 +615,13 @@ mod tests { #[test] fn test_parse_suffix_error_messages() { - let result = parse_suffix("foo", &Unit::Auto); + let result = parse_suffix("foo", Unit::Auto); assert!(result.is_err()); let error = result.unwrap_err(); assert!(error.contains("numfmt-error-invalid-number") || error.contains("invalid number")); assert!(!error.contains("invalid suffix")); - let result = parse_suffix("World", &Unit::Auto); + let result = parse_suffix("World", Unit::Auto); assert!(result.is_err()); let error = result.unwrap_err(); assert!(error.contains("numfmt-error-invalid-number") || error.contains("invalid number")); @@ -630,12 +630,12 @@ mod tests { #[test] fn test_detailed_error_message() { - let result = detailed_error_message("123i", &Unit::Auto); + let result = detailed_error_message("123i", Unit::Auto); assert!(result.is_some()); let error = result.unwrap(); assert!(error.contains("numfmt-error-invalid-suffix") || error.contains("invalid suffix")); - let result = detailed_error_message("5MF", &Unit::Auto); + let result = detailed_error_message("5MF", Unit::Auto); assert!(result.is_some()); let error = result.unwrap(); assert!( @@ -643,7 +643,7 @@ mod tests { || error.contains("invalid suffix") ); - let result = detailed_error_message("5KM", &Unit::Auto); + let result = detailed_error_message("5KM", Unit::Auto); assert!(result.is_some()); let error = result.unwrap(); assert!( @@ -656,19 +656,19 @@ mod tests { fn test_remove_suffix_q_r() { use crate::units::Unit; - let result = remove_suffix(1.0, Some((RawSuffix::Q, false)), &Unit::Si); + let result = remove_suffix(1.0, Some((RawSuffix::Q, false)), Unit::Si); assert!(result.is_ok()); assert_eq!(result.unwrap(), 1e30); - let result = remove_suffix(1.0, Some((RawSuffix::R, false)), &Unit::Si); + let result = remove_suffix(1.0, Some((RawSuffix::R, false)), Unit::Si); assert!(result.is_ok()); assert_eq!(result.unwrap(), 1e27); - let result = remove_suffix(1.0, Some((RawSuffix::Q, true)), &Unit::Iec(true)); + let result = remove_suffix(1.0, Some((RawSuffix::Q, true)), Unit::Iec(true)); assert!(result.is_ok()); assert_eq!(result.unwrap(), IEC_BASES[10]); - let result = remove_suffix(1.0, Some((RawSuffix::R, true)), &Unit::Iec(true)); + let result = remove_suffix(1.0, Some((RawSuffix::R, true)), Unit::Iec(true)); assert!(result.is_ok()); assert_eq!(result.unwrap(), IEC_BASES[9]); } @@ -676,67 +676,67 @@ mod tests { #[test] fn test_find_valid_part() { assert_eq!( - find_valid_number_with_suffix("12345KL", &Unit::Auto), + find_valid_number_with_suffix("12345KL", Unit::Auto), Some("12345K") ); assert_eq!( - find_valid_number_with_suffix("12345K", &Unit::Auto), + find_valid_number_with_suffix("12345K", Unit::Auto), Some("12345K") ); assert_eq!( - find_valid_number_with_suffix("12345", &Unit::Auto), + find_valid_number_with_suffix("12345", Unit::Auto), Some("12345") ); assert_eq!( - find_valid_number_with_suffix("asd12345KL", &Unit::Auto), + find_valid_number_with_suffix("asd12345KL", Unit::Auto), None ); assert_eq!( - find_valid_number_with_suffix("8asdf", &Unit::Auto), + find_valid_number_with_suffix("8asdf", Unit::Auto), Some("8") ); - assert_eq!(find_valid_number_with_suffix("5i", &Unit::Si), Some("5")); + assert_eq!(find_valid_number_with_suffix("5i", Unit::Si), Some("5")); assert_eq!( - find_valid_number_with_suffix("5i", &Unit::Iec(true)), + find_valid_number_with_suffix("5i", Unit::Iec(true)), Some("5") ); assert_eq!( - find_valid_number_with_suffix("0.1KL", &Unit::Auto), + find_valid_number_with_suffix("0.1KL", Unit::Auto), Some("0.1K") ); assert_eq!( - find_valid_number_with_suffix("0.1", &Unit::Auto), + find_valid_number_with_suffix("0.1", Unit::Auto), Some("0.1") ); assert_eq!( - find_valid_number_with_suffix("-0.1MT", &Unit::Auto), + find_valid_number_with_suffix("-0.1MT", Unit::Auto), Some("-0.1M") ); assert_eq!( - find_valid_number_with_suffix("-0.1PT", &Unit::Auto), + find_valid_number_with_suffix("-0.1PT", Unit::Auto), Some("-0.1P") ); assert_eq!( - find_valid_number_with_suffix("-0.1PT", &Unit::Auto), + find_valid_number_with_suffix("-0.1PT", Unit::Auto), Some("-0.1P") ); assert_eq!( - find_valid_number_with_suffix("123.4.5", &Unit::Auto), + find_valid_number_with_suffix("123.4.5", Unit::Auto), Some("123.4") ); assert_eq!( - find_valid_number_with_suffix("0.55KiJ", &Unit::Iec(true)), + find_valid_number_with_suffix("0.55KiJ", Unit::Iec(true)), Some("0.55Ki") ); assert_eq!( - find_valid_number_with_suffix("0.55KiJ", &Unit::Iec(false)), + find_valid_number_with_suffix("0.55KiJ", Unit::Iec(false)), Some("0.55K") ); assert_eq!( - find_valid_number_with_suffix("123KICK", &Unit::Auto), + find_valid_number_with_suffix("123KICK", Unit::Auto), Some("123K") ); - assert_eq!(find_valid_number_with_suffix("", &Unit::Auto), None); + assert_eq!(find_valid_number_with_suffix("", Unit::Auto), None); } #[test] @@ -744,7 +744,7 @@ mod tests { use crate::options::RoundMethod; use crate::units::Unit; - let result = consider_suffix(1e27, &Unit::Si, RoundMethod::FromZero, 0); + let result = consider_suffix(1e27, Unit::Si, RoundMethod::FromZero, 0); assert!(result.is_ok()); let (value, suffix) = result.unwrap(); assert!(suffix.is_some()); @@ -752,7 +752,7 @@ mod tests { assert_eq!(raw_suffix as i32, RawSuffix::R as i32); assert_eq!(value, 1.0); - let result = consider_suffix(1e30, &Unit::Si, RoundMethod::FromZero, 0); + let result = consider_suffix(1e30, Unit::Si, RoundMethod::FromZero, 0); assert!(result.is_ok()); let (value, suffix) = result.unwrap(); assert!(suffix.is_some()); @@ -760,7 +760,7 @@ mod tests { assert_eq!(raw_suffix as i32, RawSuffix::Q as i32); assert_eq!(value, 1.0); - let result = consider_suffix(5e30, &Unit::Si, RoundMethod::FromZero, 0); + let result = consider_suffix(5e30, Unit::Si, RoundMethod::FromZero, 0); assert!(result.is_ok()); let (value, suffix) = result.unwrap(); assert!(suffix.is_some()); diff --git a/src/uu/numfmt/src/numfmt.rs b/src/uu/numfmt/src/numfmt.rs index 80528dbd4..69f1257f6 100644 --- a/src/uu/numfmt/src/numfmt.rs +++ b/src/uu/numfmt/src/numfmt.rs @@ -3,25 +3,28 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -use crate::errors::*; +use crate::errors::NumfmtError; use crate::format::{format_and_print_delimited, format_and_print_whitespace}; -use crate::options::*; +use crate::options::{ + DEBUG, DELIMITER, FIELD, FIELD_DEFAULT, FORMAT, FROM, FROM_DEFAULT, FROM_UNIT, + FROM_UNIT_DEFAULT, FormatOptions, HEADER, HEADER_DEFAULT, INVALID, InvalidModes, NUMBER, + NumfmtOptions, PADDING, ROUND, RoundMethod, SUFFIX, TO, TO_DEFAULT, TO_UNIT, TO_UNIT_DEFAULT, + TransformOptions, UNIT_SEPARATOR, ZERO_TERMINATED, +}; use crate::units::{Result, Unit}; use clap::{Arg, ArgAction, ArgMatches, Command, builder::ValueParser, parser::ValueSource}; use std::ffi::OsString; -use std::io::{BufRead, Error, Write}; +use std::io::{BufRead, Error, Write, stderr}; use std::result::Result as StdResult; use std::str::FromStr; use units::{IEC_BASES, SI_BASES}; use uucore::display::Quotable; use uucore::error::UResult; -use uucore::os_str_as_bytes; -use uucore::translate; use uucore::parser::shortcut_value_parser::ShortcutValueParser; use uucore::ranges::Range; -use uucore::{format_usage, show, show_error}; +use uucore::{format_usage, os_str_as_bytes, show, translate, util_name}; pub mod errors; pub mod format; @@ -88,7 +91,7 @@ fn format_and_handle_validation(input_line: &[u8], options: &NumfmtOptions) -> U show!(NumfmtError::FormattingError(error_message)); } InvalidModes::Warn => { - show_error!("{error_message}"); + let _ = writeln!(stderr(), "{}: {error_message}", util_name()); } InvalidModes::Ignore => {} } @@ -282,16 +285,27 @@ fn parse_options(args: &ArgMatches) -> Result { fn print_debug_warnings(options: &NumfmtOptions, matches: &ArgMatches) { // Warn if no conversion option is specified + // 2>/dev/full does not abort if options.transform.from == Unit::None && options.transform.to == Unit::None && options.padding == 0 { - show_error!("{}", translate!("numfmt-debug-no-conversion")); + let _ = writeln!( + stderr(), + "{}: {}", + util_name(), + translate!("numfmt-debug-no-conversion") + ); } // Warn if --header is used with command-line input if options.header > 0 && matches.get_many::(NUMBER).is_some() { - show_error!("{}", translate!("numfmt-debug-header-ignored")); + let _ = writeln!( + stderr(), + "{}: {}", + util_name(), + translate!("numfmt-debug-header-ignored") + ); } } @@ -305,19 +319,16 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { print_debug_warnings(&options, &matches); } - let result = match matches.get_many::(NUMBER) { - Some(values) => { - let byte_args: Vec<&[u8]> = values - .map(|s| os_str_as_bytes(s).map_err(|e| e.to_string())) - .collect::, _>>() - .map_err(NumfmtError::IllegalArgument)?; - handle_args(byte_args.into_iter(), &options) - } - None => { - let stdin = std::io::stdin(); - let mut locked_stdin = stdin.lock(); - handle_buffer(&mut locked_stdin, &options) - } + let result = if let Some(values) = matches.get_many::(NUMBER) { + let byte_args: Vec<&[u8]> = values + .map(|s| os_str_as_bytes(s).map_err(|e| e.to_string())) + .collect::, _>>() + .map_err(NumfmtError::IllegalArgument)?; + handle_args(byte_args.into_iter(), &options) + } else { + let stdin = std::io::stdin(); + let mut locked_stdin = stdin.lock(); + handle_buffer(&mut locked_stdin, &options) }; match result { @@ -330,9 +341,9 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } pub fn uu_app() -> Command { - Command::new(uucore::util_name()) + Command::new(util_name()) .version(uucore::crate_version!()) - .help_template(uucore::localized_help_template(uucore::util_name())) + .help_template(uucore::localized_help_template(util_name())) .about(translate!("numfmt-about")) .after_help(translate!("numfmt-after-help")) .override_usage(format_usage(&translate!("numfmt-usage"))) diff --git a/src/uu/numfmt/src/options.rs b/src/uu/numfmt/src/options.rs index fedcd19c9..8d7b639bb 100644 --- a/src/uu/numfmt/src/options.rs +++ b/src/uu/numfmt/src/options.rs @@ -71,7 +71,7 @@ pub enum RoundMethod { } impl RoundMethod { - pub fn round(&self, f: f64) -> f64 { + pub fn round(self, f: f64) -> f64 { match self { Self::Up => f.ceil(), Self::Down => f.floor(), diff --git a/src/uu/od/src/od.rs b/src/uu/od/src/od.rs index e8f283e7d..4da705aae 100644 --- a/src/uu/od/src/od.rs +++ b/src/uu/od/src/od.rs @@ -591,7 +591,7 @@ fn extract_strings_from_input( // Apply skip_bytes by reading and discarding let mut skipped = 0u64; while skipped < skip_bytes { - let to_skip = std::cmp::min(8192, skip_bytes - skipped); + let to_skip = cmp::min(8192, skip_bytes - skipped); let mut skip_buf = vec![0u8; to_skip as usize]; match mf.read(&mut skip_buf) { Ok(0) => break, // EOF reached diff --git a/src/uu/od/src/parse_formats.rs b/src/uu/od/src/parse_formats.rs index 0edb48474..1103b77ca 100644 --- a/src/uu/od/src/parse_formats.rs +++ b/src/uu/od/src/parse_formats.rs @@ -8,9 +8,16 @@ use uucore::display::Quotable; use uucore::translate; use crate::formatter_item_info::FormatterItemInfo; -use crate::prn_char::*; -use crate::prn_float::*; -use crate::prn_int::*; +use crate::prn_char::{FORMAT_ITEM_A, FORMAT_ITEM_C}; +use crate::prn_float::{ + FORMAT_ITEM_BF16, FORMAT_ITEM_F16, FORMAT_ITEM_F32, FORMAT_ITEM_F64, FORMAT_ITEM_LONG_DOUBLE, +}; +use crate::prn_int::{ + FORMAT_ITEM_DEC8S, FORMAT_ITEM_DEC8U, FORMAT_ITEM_DEC16S, FORMAT_ITEM_DEC16U, + FORMAT_ITEM_DEC32S, FORMAT_ITEM_DEC32U, FORMAT_ITEM_DEC64S, FORMAT_ITEM_DEC64U, + FORMAT_ITEM_HEX8, FORMAT_ITEM_HEX16, FORMAT_ITEM_HEX32, FORMAT_ITEM_HEX64, FORMAT_ITEM_OCT8, + FORMAT_ITEM_OCT16, FORMAT_ITEM_OCT32, FORMAT_ITEM_OCT64, +}; #[derive(Copy, Clone, PartialEq, Eq, Debug)] pub struct ParsedFormatterItemInfo { diff --git a/src/uu/od/src/parse_inputs.rs b/src/uu/od/src/parse_inputs.rs index 8f5e6434b..b6f763b2e 100644 --- a/src/uu/od/src/parse_inputs.rs +++ b/src/uu/od/src/parse_inputs.rs @@ -206,15 +206,14 @@ pub fn parse_offset_operand(s: &str) -> Result { match u64::from_str_radix(&s[start..len], radix) { Ok(i) => { // Check for overflow during multiplication - match i.checked_mul(multiply) { - Some(result) => Ok(result), - None => { - let err = std::io::Error::from_raw_os_error(libc::ERANGE); - let msg = err.to_string(); - // Strip "(os error N)" if present to match Perl's $! - let msg = msg.split(" (os error").next().unwrap_or(&msg).to_string(); - Err(msg) - } + if let Some(result) = i.checked_mul(multiply) { + Ok(result) + } else { + let err = std::io::Error::from_raw_os_error(libc::ERANGE); + let msg = err.to_string(); + // Strip "(os error N)" if present to match Perl's $! + let msg = msg.split(" (os error").next().unwrap_or(&msg).to_string(); + Err(msg) } } Err(e) => { diff --git a/src/uu/od/src/prn_int.rs b/src/uu/od/src/prn_int.rs index c7014c0fa..1ed26b76c 100644 --- a/src/uu/od/src/prn_int.rs +++ b/src/uu/od/src/prn_int.rs @@ -2,7 +2,7 @@ // // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -use crate::formatter_item_info::*; +use crate::formatter_item_info::{FormatWriter, FormatterItemInfo}; /// format string to print octal using `int_writer_unsigned` macro_rules! OCT { diff --git a/src/uu/paste/src/paste.rs b/src/uu/paste/src/paste.rs index 7a8aaab63..5d6ae5ed9 100644 --- a/src/uu/paste/src/paste.rs +++ b/src/uu/paste/src/paste.rs @@ -125,14 +125,12 @@ fn paste( output.clear(); loop { - match input_source.read_until(line_ending_byte, &mut output)? { - 0 => break, - _ => { - remove_trailing_line_ending_byte(line_ending_byte, &mut output); - - delimiter_state.write_delimiter(&mut output); - } + if input_source.read_until(line_ending_byte, &mut output)? == 0 { + break; } + remove_trailing_line_ending_byte(line_ending_byte, &mut output); + + delimiter_state.write_delimiter(&mut output); } delimiter_state.remove_trailing_delimiter(&mut output); diff --git a/src/uu/pinky/src/platform/unix.rs b/src/uu/pinky/src/platform/unix.rs index cbf01b837..486f8b3ca 100644 --- a/src/uu/pinky/src/platform/unix.rs +++ b/src/uu/pinky/src/platform/unix.rs @@ -10,7 +10,7 @@ use crate::options; use crate::uu_app; use uucore::entries::{Locate, Passwd}; -use uucore::error::{FromIo, UResult}; +use uucore::error::UResult; use uucore::libc::S_IWGRP; use uucore::translate; use uucore::utmpx::{self, Utmpx, UtmpxRecord, time}; @@ -96,14 +96,11 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { }; if do_short_format { - match pk.short_pinky() { - Ok(_) => Ok(()), - Err(e) => Err(e.map_err_context(String::new)), - } + pk.short_pinky(); } else { pk.long_pinky(); - Ok(()) } + Ok(()) } struct Pinky { @@ -170,27 +167,24 @@ fn gecos_to_fullname(pw: &Passwd) -> Option { } impl Pinky { - fn print_entry(&self, ut: &UtmpxRecord) -> std::io::Result<()> { + fn print_entry(&self, ut: &UtmpxRecord) { let mut pts_path = PathBuf::from("/dev"); pts_path.push(ut.tty_device().as_str()); let mesg; let last_change; - match pts_path.metadata() { - #[allow(clippy::unnecessary_cast)] - Ok(meta) => { - mesg = if meta.mode() & S_IWGRP as u32 == 0 { - '*' - } else { - ' ' - }; - last_change = meta.atime(); - } - _ => { - mesg = '?'; - last_change = 0; - } + #[allow(clippy::unnecessary_cast)] + if let Ok(meta) = pts_path.metadata() { + mesg = if (meta.mode() & (S_IWGRP as u32)) == 0 { + '*' + } else { + ' ' + }; + last_change = meta.atime(); + } else { + mesg = '?'; + last_change = 0; } print!("{1:<8.0$}", utmpx::UT_NAMESIZE, ut.user()); @@ -233,7 +227,6 @@ impl Pinky { } println!(); - Ok(()) } fn print_heading(&self) { @@ -252,7 +245,7 @@ impl Pinky { println!(); } - fn short_pinky(&self) -> std::io::Result<()> { + fn short_pinky(&self) { if self.include_heading { self.print_heading(); } @@ -260,10 +253,9 @@ impl Pinky { if ut.is_user_process() && (self.names.is_empty() || self.names.iter().any(|n| n.as_str() == ut.user())) { - self.print_entry(&ut)?; + self.print_entry(&ut); } } - Ok(()) } fn long_pinky(&self) { diff --git a/src/uu/pr/src/pr.rs b/src/uu/pr/src/pr.rs index e5faac408..2e6a870f8 100644 --- a/src/uu/pr/src/pr.rs +++ b/src/uu/pr/src/pr.rs @@ -1017,6 +1017,74 @@ fn print_page( Ok(()) } +/// Group the lines of the input file in columns read left-to-right. +fn to_table_across( + content_lines_per_page: usize, + columns: usize, + lines: &[FileLine], +) -> Vec>> { + (0..content_lines_per_page) + .map(|i| (0..columns).map(|j| lines.get(i * columns + j)).collect()) + .collect() +} + +/// Group the lines of the input files in columns, one column per file. +fn to_table_merged( + content_lines_per_page: usize, + columns: usize, + filled_lines: Vec>, +) -> Vec>> { + (0..content_lines_per_page) + .map(|i| { + (0..columns) + .map(|j| { + *filled_lines + .get(content_lines_per_page * j + i) + .unwrap_or(&None) + }) + .collect() + }) + .collect() +} + +/// Group lines of the file in columns, going top-to-bottom then left-to-right. +/// +/// This function should be applied when there are more lines than the +/// total number of cells in the table. +fn to_table( + content_lines_per_page: usize, + columns: usize, + lines: &[FileLine], +) -> Vec>> { + (0..content_lines_per_page) + .map(|i| { + (0..columns) + .map(|j| lines.get(content_lines_per_page * j + i)) + .collect() + }) + .collect() +} + +/// Group lines of the file in columns, going top-to-bottom then left-to-right. +/// +/// This function should be applied when there are fewer lines than the +/// total number of cells in the table. +fn to_table_short_file( + content_lines_per_page: usize, + columns: usize, + lines: &[FileLine], +) -> Vec>> { + let num_rows = lines.len() / columns; + let mut table: Vec> = (0..num_rows) + .map(|i| (0..columns).map(|j| lines.get(num_rows * j + i)).collect()) + .collect(); + // Fill the rest with Nones. + for _ in num_rows..content_lines_per_page { + table.push(vec![None; columns]); + } + table +} + #[allow(clippy::cognitive_complexity)] fn write_columns( lines: &[FileLine], @@ -1043,7 +1111,7 @@ fn write_columns( .as_ref() .is_some_and(|i| i.across_mode); - let mut filled_lines = Vec::new(); + let mut filled_lines: Vec> = Vec::new(); if options.merge_files_print.is_some() { let mut offset = 0; for col in 0..columns { @@ -1063,23 +1131,19 @@ fn write_columns( } } - let table: Vec> = (0..content_lines_per_page) - .map(move |a| { - (0..columns) - .map(|i| { - if across_mode { - lines.get(a * columns + i) - } else if options.merge_files_print.is_some() { - *filled_lines - .get(content_lines_per_page * i + a) - .unwrap_or(&None) - } else { - lines.get(content_lines_per_page * i + a) - } - }) - .collect() - }) - .collect(); + // Group the flat list of lines into a 2-dimensional table of + // cells, where each row will be printed as a single line in the + // output. + let merge = options.merge_files_print.is_some(); + let table = if !merge && (lines.len() < (content_lines_per_page * columns)) { + to_table_short_file(content_lines_per_page, columns, lines) + } else if across_mode { + to_table_across(content_lines_per_page, columns, lines) + } else if merge { + to_table_merged(content_lines_per_page, columns, filled_lines) + } else { + to_table(content_lines_per_page, columns, lines) + }; let blank_line = FileLine::default(); for row in table { diff --git a/src/uu/ptx/src/ptx.rs b/src/uu/ptx/src/ptx.rs index 2bae65115..7749ecc99 100644 --- a/src/uu/ptx/src/ptx.rs +++ b/src/uu/ptx/src/ptx.rs @@ -133,16 +133,14 @@ impl WordFilter { let break_set: Option> = if matches.contains_id(options::BREAK_FILE) && !matches.contains_id(options::WORD_REGEXP) { - let chars = + let mut chars = read_char_filter_file(matches, options::BREAK_FILE).map_err_context(String::new)?; - let mut hs: HashSet = if config.gnu_ext { - HashSet::new() // really only chars found in file - } else { + if !config.gnu_ext { // GNU off means at least these are considered - [' ', '\t', '\n'].iter().copied().collect() - }; - hs.extend(chars); - Some(hs) + chars.extend([' ', '\t', '\n']); + } + // else only chars found in file + Some(chars) } else { // if -W takes precedence or default None diff --git a/src/uu/rm/BENCHMARKING.md b/src/uu/rm/BENCHMARKING.md index 507906d49..154115ab2 100644 --- a/src/uu/rm/BENCHMARKING.md +++ b/src/uu/rm/BENCHMARKING.md @@ -58,4 +58,3 @@ With Cargo Flamegraph you can easily make a flamegraph of `rm`: ```shell cargo flamegraph --cmd coreutils -- rm [additional parameters] ``` - diff --git a/src/uu/rm/locales/en-US.ftl b/src/uu/rm/locales/en-US.ftl index 2d4486ce2..8a9a3513e 100644 --- a/src/uu/rm/locales/en-US.ftl +++ b/src/uu/rm/locales/en-US.ftl @@ -40,6 +40,7 @@ rm-error-cannot-remove-no-such-file = cannot remove {$file}: No such file or dir rm-error-cannot-remove-permission-denied = cannot remove {$file}: Permission denied rm-error-cannot-remove-is-directory = cannot remove {$file}: Is a directory rm-error-dangerous-recursive-operation = it is dangerous to operate recursively on '/' +rm-error-dangerous-recursive-operation-same-as-root = it is dangerous to operate recursively on '{$path}' (same as '/') rm-error-use-no-preserve-root = use --no-preserve-root to override this failsafe rm-error-refusing-to-remove-directory = refusing to remove '.' or '..' directory: skipping {$path} rm-error-cannot-remove = cannot remove {$file} diff --git a/src/uu/rm/locales/fr-FR.ftl b/src/uu/rm/locales/fr-FR.ftl index 52d881d02..6052e2f89 100644 --- a/src/uu/rm/locales/fr-FR.ftl +++ b/src/uu/rm/locales/fr-FR.ftl @@ -40,6 +40,7 @@ rm-error-cannot-remove-no-such-file = impossible de supprimer {$file} : Aucun fi rm-error-cannot-remove-permission-denied = impossible de supprimer {$file} : Permission refusée rm-error-cannot-remove-is-directory = impossible de supprimer {$file} : C'est un répertoire rm-error-dangerous-recursive-operation = il est dangereux d'opérer récursivement sur '/' +rm-error-dangerous-recursive-operation-same-as-root = il est dangereux d'opérer récursivement sur '{$path}' (identique à '/') rm-error-use-no-preserve-root = utilisez --no-preserve-root pour outrepasser cette protection rm-error-refusing-to-remove-directory = refus de supprimer le répertoire '.' ou '..' : ignorer {$path} rm-error-cannot-remove = impossible de supprimer {$file} diff --git a/src/uu/rm/src/rm.rs b/src/uu/rm/src/rm.rs index fa80458fa..b1c7d1ded 100644 --- a/src/uu/rm/src/rm.rs +++ b/src/uu/rm/src/rm.rs @@ -3,7 +3,7 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -// spell-checker:ignore (path) eacces inacc rm-r4 unlinkat fstatat +// spell-checker:ignore (path) eacces inacc rm-r4 unlinkat fstatat rootlink use clap::builder::{PossibleValue, ValueParser}; use clap::{Arg, ArgAction, Command, parser::ValueSource}; @@ -17,7 +17,7 @@ use std::os::unix::ffi::OsStrExt; #[cfg(unix)] use std::os::unix::fs::PermissionsExt; use std::path::MAIN_SEPARATOR; -use std::path::{Path, PathBuf}; +use std::path::Path; use thiserror::Error; use uucore::display::Quotable; use uucore::error::{FromIo, UError, UResult}; @@ -56,7 +56,7 @@ fn verbose_removed_file(path: &Path, options: &Options) { if options.verbose { println!( "{}", - translate!("rm-verbose-removed", "file" => normalize(path).quote()) + translate!("rm-verbose-removed", "file" => uucore::fs::normalize_path(path).quote()) ); } } @@ -66,13 +66,13 @@ fn verbose_removed_directory(path: &Path, options: &Options) { if options.verbose { println!( "{}", - translate!("rm-verbose-removed-directory", "file" => normalize(path).quote()) + translate!("rm-verbose-removed-directory", "file" => uucore::fs::normalize_path(path).quote()) ); } } /// Helper function to show error with context and return error status -fn show_removal_error(error: std::io::Error, path: &Path) -> bool { +fn show_removal_error(error: io::Error, path: &Path) -> bool { if error.kind() == io::ErrorKind::PermissionDenied { show_error!("cannot remove {}: Permission denied", path.quote()); } else { @@ -229,6 +229,9 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { }) }; + let preserve_root = !matches.get_flag(OPT_NO_PRESERVE_ROOT); + let recursive = matches.get_flag(OPT_RECURSIVE); + let options = Options { force: force_flag, interactive: { @@ -245,8 +248,8 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } }, one_fs: matches.get_flag(OPT_ONE_FILE_SYSTEM), - preserve_root: !matches.get_flag(OPT_NO_PRESERVE_ROOT), - recursive: matches.get_flag(OPT_RECURSIVE), + preserve_root, + recursive, dir: matches.get_flag(OPT_DIR), verbose: matches.get_flag(OPT_VERBOSE), progress: matches.get_flag(OPT_PROGRESS), @@ -482,6 +485,19 @@ pub fn remove(files: &[&OsStr], options: &Options) -> bool { for filename in files { let file = Path::new(filename); + // Check if the path (potentially with trailing slash) resolves to root + // This needs to happen before symlink_metadata to catch cases like "rootlink/" + // where rootlink is a symlink to root. + if uucore::fs::path_ends_with_terminator(file) + && options.recursive + && options.preserve_root + && is_root_path(file) + { + show_preserve_root_error(file); + had_err = true; + continue; + } + had_err = match file.symlink_metadata() { Ok(metadata) => { // Create progress bar on first successful file metadata read @@ -668,6 +684,40 @@ fn remove_dir_recursive( } } +/// Check if a path resolves to the root directory. +/// Returns true if the path is root, false otherwise. +fn is_root_path(path: &Path) -> bool { + // Check simple case: literal "/" path + if path.has_root() && path.parent().is_none() { + return true; + } + + // Check if path resolves to "/" after following symlinks + if let Ok(canonical) = path.canonicalize() { + canonical.has_root() && canonical.parent().is_none() + } else { + false + } +} + +/// Show error message for attempting to remove root. +fn show_preserve_root_error(path: &Path) { + let path_looks_like_root = path.has_root() && path.parent().is_none(); + + if path_looks_like_root { + // Path is literally "/" + show_error!("{}", RmError::DangerousRecursiveOperation); + } else { + // Path resolves to root but isn't literally "/" (e.g., symlink to /) + show_error!( + "{}", + translate!("rm-error-dangerous-recursive-operation-same-as-root", + "path" => path.display()) + ); + } + show_error!("{}", RmError::UseNoPreserveRoot); +} + fn handle_dir(path: &Path, options: &Options, progress_bar: Option<&ProgressBar>) -> bool { let mut had_err = false; @@ -680,14 +730,13 @@ fn handle_dir(path: &Path, options: &Options, progress_bar: Option<&ProgressBar> return true; } - let is_root = path.has_root() && path.parent().is_none(); + let is_root = is_root_path(path); if options.recursive && (!is_root || !options.preserve_root) { had_err = remove_dir_recursive(path, options, progress_bar); } else if options.dir && (!is_root || !options.preserve_root) { had_err = remove_dir(path, options, progress_bar).bitor(had_err); } else if options.recursive { - show_error!("{}", RmError::DangerousRecursiveOperation); - show_error!("{}", RmError::UseNoPreserveRoot); + show_preserve_root_error(path); had_err = true; } else { show_error!( @@ -935,14 +984,6 @@ fn prompt_descend(path: &Path) -> bool { prompt_yes!("descend into directory {}?", path.quote()) } -fn normalize(path: &Path) -> PathBuf { - // copied from https://github.com/rust-lang/cargo/blob/2e4cfc2b7d43328b207879228a2ca7d427d188bb/src/cargo/util/paths.rs#L65-L90 - // both projects are MIT https://github.com/rust-lang/cargo/blob/master/LICENSE-MIT - // for std impl progress see rfc https://github.com/rust-lang/rfcs/issues/2208 - // TODO: replace this once that lands - uucore::fs::normalize_path(path) -} - #[cfg(not(windows))] fn is_symlink_dir(_metadata: &Metadata) -> bool { false diff --git a/src/uu/seq/src/seq.rs b/src/uu/seq/src/seq.rs index 29373a511..24f76423b 100644 --- a/src/uu/seq/src/seq.rs +++ b/src/uu/seq/src/seq.rs @@ -154,52 +154,51 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { // If a format was passed on the command line, use that. // If not, use some default format based on parameters precision. - let (format, padding, fast_allowed) = match options.format { - Some(str) => ( + let (format, padding, fast_allowed) = if let Some(str) = options.format { + ( Format::::parse(str)?, 0, false, - ), - None => { - let precision = select_precision(&first, &increment, &last); + ) + } else { + let precision = select_precision(&first, &increment, &last); - let padding = if options.equal_width { - let precision_value = precision.unwrap_or(0); - first - .num_integral_digits - .max(increment.num_integral_digits) - .max(last.num_integral_digits) - + if precision_value > 0 { - precision_value + 1 - } else { - 0 - } - } else { - 0 - }; + let padding = if options.equal_width { + let precision_value = precision.unwrap_or(0); + first + .num_integral_digits + .max(increment.num_integral_digits) + .max(last.num_integral_digits) + + if precision_value > 0 { + precision_value + 1 + } else { + 0 + } + } else { + 0 + }; - let formatter = match precision { - // format with precision: decimal floats and integers - Some(precision) => num_format::Float { - variant: FloatVariant::Decimal, - width: padding, - alignment: num_format::NumberAlignment::RightZero, - precision: Some(precision), - ..Default::default() - }, - // format without precision: hexadecimal floats - None => num_format::Float { - variant: FloatVariant::Shortest, - ..Default::default() - }, - }; - // Allow fast printing if precision is 0 (integer inputs), `print_seq` will do further checks. - ( - Format::from_formatter(formatter), - padding, - precision == Some(0), - ) - } + let formatter = match precision { + // format with precision: decimal floats and integers + Some(precision) => num_format::Float { + variant: FloatVariant::Decimal, + width: padding, + alignment: num_format::NumberAlignment::RightZero, + precision: Some(precision), + ..Default::default() + }, + // format without precision: hexadecimal floats + None => num_format::Float { + variant: FloatVariant::Shortest, + ..Default::default() + }, + }; + // Allow fast printing if precision is 0 (integer inputs), `print_seq` will do further checks. + ( + Format::from_formatter(formatter), + padding, + precision == Some(0), + ) }; let result = print_seq( diff --git a/src/uu/sha1sum/Cargo.toml b/src/uu/sha1sum/Cargo.toml index 001bddd69..7cb6aceab 100644 --- a/src/uu/sha1sum/Cargo.toml +++ b/src/uu/sha1sum/Cargo.toml @@ -29,8 +29,6 @@ uucore = { workspace = true, features = [ fluent = { workspace = true } [dev-dependencies] -divan = { workspace = true } -tempfile = { workspace = true } uucore = { workspace = true, features = ["benchmark"] } [[bin]] diff --git a/src/uu/sha224sum/Cargo.toml b/src/uu/sha224sum/Cargo.toml index 25086ee42..3436c9d2b 100644 --- a/src/uu/sha224sum/Cargo.toml +++ b/src/uu/sha224sum/Cargo.toml @@ -29,8 +29,6 @@ uucore = { workspace = true, features = [ fluent = { workspace = true } [dev-dependencies] -divan = { workspace = true } -tempfile = { workspace = true } uucore = { workspace = true, features = ["benchmark"] } [[bin]] diff --git a/src/uu/sha256sum/Cargo.toml b/src/uu/sha256sum/Cargo.toml index 2ca6204c0..43a5c8577 100644 --- a/src/uu/sha256sum/Cargo.toml +++ b/src/uu/sha256sum/Cargo.toml @@ -29,8 +29,6 @@ uucore = { workspace = true, features = [ fluent = { workspace = true } [dev-dependencies] -divan = { workspace = true } -tempfile = { workspace = true } uucore = { workspace = true, features = ["benchmark"] } [[bin]] diff --git a/src/uu/sha384sum/Cargo.toml b/src/uu/sha384sum/Cargo.toml index 2fb9ca037..ff9d57871 100644 --- a/src/uu/sha384sum/Cargo.toml +++ b/src/uu/sha384sum/Cargo.toml @@ -29,8 +29,6 @@ uucore = { workspace = true, features = [ fluent = { workspace = true } [dev-dependencies] -divan = { workspace = true } -tempfile = { workspace = true } uucore = { workspace = true, features = ["benchmark"] } [[bin]] diff --git a/src/uu/sha512sum/Cargo.toml b/src/uu/sha512sum/Cargo.toml index 0cea1453b..281544adb 100644 --- a/src/uu/sha512sum/Cargo.toml +++ b/src/uu/sha512sum/Cargo.toml @@ -29,8 +29,6 @@ uucore = { workspace = true, features = [ fluent = { workspace = true } [dev-dependencies] -divan = { workspace = true } -tempfile = { workspace = true } uucore = { workspace = true, features = ["benchmark"] } [[bin]] diff --git a/src/uu/shred/src/shred.rs b/src/uu/shred/src/shred.rs index e7a345b4c..0b05a3a35 100644 --- a/src/uu/shred/src/shred.rs +++ b/src/uu/shred/src/shred.rs @@ -549,17 +549,17 @@ fn create_test_compatible_sequence( } } - create_standard_pass_sequence(num_passes) + Ok(create_standard_pass_sequence(num_passes)) } /// Create standard pass sequence with patterns and random passes -fn create_standard_pass_sequence(num_passes: usize) -> UResult> { +fn create_standard_pass_sequence(num_passes: usize) -> Vec { if num_passes == 0 { - return Ok(Vec::new()); + return Vec::new(); } if num_passes <= 3 { - return Ok(vec![PassType::Random; num_passes]); + return vec![PassType::Random; num_passes]; } let mut sequence = Vec::new(); @@ -596,7 +596,7 @@ fn create_standard_pass_sequence(num_passes: usize) -> UResult> { // Final pass is always random sequence.push(PassType::Random); - Ok(sequence) + sequence } /// Create compatible pass sequence using the standard algorithm @@ -609,7 +609,7 @@ fn create_compatible_sequence( create_test_compatible_sequence(num_passes, random_source) } else { // For system random, use standard algorithm - create_standard_pass_sequence(num_passes) + Ok(create_standard_pass_sequence(num_passes)) } } @@ -679,7 +679,7 @@ fn wipe_file( if random_source.is_some() { pass_sequence = create_compatible_sequence(n_passes, random_source)?; } else { - pass_sequence = create_standard_pass_sequence(n_passes)?; + pass_sequence = create_standard_pass_sequence(n_passes); } } @@ -773,7 +773,7 @@ fn do_pass( /// Repeatedly renames the file with strings of decreasing length (most likely all 0s) /// Return the path of the file after its last renaming or None in case of an error -fn wipe_name(orig_path: &Path, verbose: bool, remove_method: RemoveMethod) -> Option { +fn wipe_name(orig_path: &Path, verbose: bool, remove_method: RemoveMethod) -> PathBuf { let file_name_len = orig_path.file_name().unwrap().len(); let mut last_path = PathBuf::from(orig_path); @@ -821,7 +821,7 @@ fn wipe_name(orig_path: &Path, verbose: bool, remove_method: RemoveMethod) -> Op } } - Some(last_path) + last_path } fn do_remove( @@ -838,14 +838,12 @@ fn do_remove( } let remove_path = if remove_method == RemoveMethod::Unlink { - Some(path.with_file_name(orig_filename)) + path.with_file_name(orig_filename) } else { wipe_name(path, verbose, remove_method) }; - if let Some(rp) = remove_path { - fs::remove_file(rp)?; - } + fs::remove_file(remove_path)?; if verbose { show_error!( diff --git a/src/uu/shuf/Cargo.toml b/src/uu/shuf/Cargo.toml index cd2c50072..bfe41d46e 100644 --- a/src/uu/shuf/Cargo.toml +++ b/src/uu/shuf/Cargo.toml @@ -22,11 +22,10 @@ clap = { workspace = true } itoa = { workspace = true } rand = { workspace = true } rand_chacha = { workspace = true } -rand_core = { workspace = true } sha3 = { workspace = true } uucore = { workspace = true } fluent = { workspace = true } -rustc-hash = "2.1.1" +rustc-hash = { workspace = true } [[bin]] name = "shuf" diff --git a/src/uu/shuf/src/random_seed.rs b/src/uu/shuf/src/random_seed.rs index dbc6c728c..621f94e7b 100644 --- a/src/uu/shuf/src/random_seed.rs +++ b/src/uu/shuf/src/random_seed.rs @@ -67,7 +67,7 @@ impl SeededRng { hasher.update(seed.as_bytes()); let seed = hasher.finalize(); let seed = seed.as_slice().try_into().unwrap(); - Self(Box::new(rand_chacha::ChaCha12Rng::from_seed(seed))) + Self(Box::new(ChaCha12Rng::from_seed(seed))) } #[allow(clippy::many_single_char_names)] // use original lemire names for easy comparison diff --git a/src/uu/shuf/src/shuf.rs b/src/uu/shuf/src/shuf.rs index 970a623e2..f0caf445d 100644 --- a/src/uu/shuf/src/shuf.rs +++ b/src/uu/shuf/src/shuf.rs @@ -7,7 +7,7 @@ use std::ffi::{OsStr, OsString}; use std::fs::File; -use std::io::{BufReader, BufWriter, Error, Read, Write, stdin, stdout}; +use std::io::{self, BufReader, BufWriter, Read, Write, stdin, stdout}; use std::ops::RangeInclusive; use std::path::{Path, PathBuf}; use std::str::FromStr; @@ -149,7 +149,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { || translate!("shuf-error-failed-to-open-random-source", "file" => r.quote()), )?; let file = BufReader::new(file); - WrappedRng::File(compat_random_source::RandomSourceAdapter::new(file)) + WrappedRng::File(RandomSourceAdapter::new(file)) } }; @@ -362,24 +362,24 @@ impl Shufable for RangeInclusive { } trait Writable { - fn write_all_to(&self, output: &mut impl OsWrite) -> Result<(), Error>; + fn write_all_to(&self, output: &mut impl OsWrite) -> Result<(), io::Error>; } impl Writable for &[u8] { - fn write_all_to(&self, output: &mut impl OsWrite) -> Result<(), Error> { + fn write_all_to(&self, output: &mut impl OsWrite) -> Result<(), io::Error> { output.write_all(self) } } impl Writable for &OsStr { - fn write_all_to(&self, output: &mut impl OsWrite) -> Result<(), Error> { + fn write_all_to(&self, output: &mut impl OsWrite) -> Result<(), io::Error> { output.write_all_os(self) } } impl Writable for u64 { #[inline] - fn write_all_to(&self, output: &mut impl OsWrite) -> Result<(), Error> { + fn write_all_to(&self, output: &mut impl OsWrite) -> Result<(), io::Error> { // The itoa crate is surprisingly much more efficient than a formatted write. // It speeds up `shuf -r -n1000000 -i1-1024` by 1.8×. let mut buf = itoa::Buffer::new(); @@ -389,7 +389,7 @@ impl Writable for u64 { #[cold] #[inline(never)] -fn handle_write_error(e: std::io::Error) -> Box { +fn handle_write_error(e: io::Error) -> Box { use uucore::error::FromIo; let ctx = translate!("shuf-error-write-failed"); e.map_err_context(move || ctx) diff --git a/src/uu/sleep/locales/fr-FR.ftl b/src/uu/sleep/locales/fr-FR.ftl index 7930b78fe..61393d1e9 100644 --- a/src/uu/sleep/locales/fr-FR.ftl +++ b/src/uu/sleep/locales/fr-FR.ftl @@ -12,4 +12,3 @@ sleep-error-missing-operand = opérande manquant # Messages d'aide sleep-help-number = faire une pause de NOMBRE secondes - diff --git a/src/uu/sort/src/buffer_hint.rs b/src/uu/sort/src/buffer_hint.rs index bb0ea7540..d5baf9efb 100644 --- a/src/uu/sort/src/buffer_hint.rs +++ b/src/uu/sort/src/buffer_hint.rs @@ -6,10 +6,6 @@ //! Heuristics for determining buffer size for external sorting. use std::ffi::OsString; -use crate::{ - FALLBACK_AUTOMATIC_BUF_SIZE, MAX_AUTOMATIC_BUF_SIZE, MIN_AUTOMATIC_BUF_SIZE, STDIN_FILE, -}; - // Heuristics to size the external sort buffer without overcommit memory. pub(crate) fn automatic_buffer_size(files: &[OsString]) -> usize { let file_hint = file_size_hint(files); @@ -20,7 +16,7 @@ pub(crate) fn automatic_buffer_size(files: &[OsString]) -> usize { (Some(file), Some(mem)) => file.min(mem), (Some(file), None) => file, (None, Some(mem)) => mem, - (None, None) => FALLBACK_AUTOMATIC_BUF_SIZE, + (None, None) => crate::FALLBACK_AUTOMATIC_BUF_SIZE, } } @@ -29,7 +25,7 @@ fn file_size_hint(files: &[OsString]) -> Option { let mut total_bytes: u128 = 0; for file in files { - if file == STDIN_FILE { + if file == crate::STDIN_FILE { continue; } @@ -43,7 +39,7 @@ fn file_size_hint(files: &[OsString]) -> Option { total_bytes = total_bytes.saturating_add(metadata.len() as u128); - if total_bytes >= (MAX_AUTOMATIC_BUF_SIZE as u128) * 8 { + if total_bytes >= (crate::MAX_AUTOMATIC_BUF_SIZE as u128) * 8 { break; } } @@ -66,8 +62,8 @@ fn available_memory_hint() -> Option { } fn clamp_hint(bytes: u128) -> usize { - let min = MIN_AUTOMATIC_BUF_SIZE as u128; - let max = MAX_AUTOMATIC_BUF_SIZE as u128; + let min = crate::MIN_AUTOMATIC_BUF_SIZE as u128; + let max = crate::MAX_AUTOMATIC_BUF_SIZE as u128; let clamped = bytes.clamp(min, max); clamped.min(usize::MAX as u128) as usize } @@ -77,7 +73,7 @@ fn desired_file_buffer_bytes(total_bytes: u128) -> u128 { return 0; } - let max = MAX_AUTOMATIC_BUF_SIZE as u128; + let max = crate::MAX_AUTOMATIC_BUF_SIZE as u128; if total_bytes <= max { return total_bytes.saturating_mul(12).clamp(total_bytes, max); diff --git a/src/uu/sort/src/chunks.rs b/src/uu/sort/src/chunks.rs index 61dbef73b..2ff3eacfc 100644 --- a/src/uu/sort/src/chunks.rs +++ b/src/uu/sort/src/chunks.rs @@ -24,7 +24,7 @@ use crate::{ }; const MAX_TOKEN_BUFFER_BYTES: usize = 4 * 1024 * 1024; -const MAX_TOKEN_BUFFER_ELEMS: usize = MAX_TOKEN_BUFFER_BYTES / std::mem::size_of::>(); +const MAX_TOKEN_BUFFER_ELEMS: usize = MAX_TOKEN_BUFFER_BYTES / size_of::>(); self_cell!( /// The chunk that is passed around between threads. diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index a16198d33..ceaf51256 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -191,6 +191,8 @@ impl UError for SortError { } } +// refs are required because this fn is used by thiserror macro +#[expect(clippy::trivially_copy_pass_by_ref)] fn format_disorder(file: &OsString, line_number: &usize, line: &String, silent: &bool) -> String { if *silent { String::new() @@ -304,7 +306,7 @@ impl Default for NumericLocaleSettings { } impl NumericLocaleSettings { - fn num_info_settings(&self, accept_si_units: bool) -> NumInfoParseSettings { + fn num_info_settings(self, accept_si_units: bool) -> NumInfoParseSettings { NumInfoParseSettings { accept_si_units, thousands_separator: self.thousands_sep, @@ -658,7 +660,7 @@ impl<'a> Line<'a> { for (selector, selection) in settings.selectors.iter().map(|selector| { ( selector, - selector.get_selection(line, token_buffer, &settings.numeric_locale), + selector.get_selection(line, token_buffer, settings.numeric_locale), ) }) { match selection { @@ -1186,7 +1188,7 @@ impl FieldSelector { &self, line: &'a [u8], tokens: &[Field], - numeric_locale: &NumericLocaleSettings, + numeric_locale: NumericLocaleSettings, ) -> Selection<'a> { // `get_range` expects `None` when we don't need tokens and would get confused by an empty vector. let tokens = if self.needs_tokens { @@ -2046,25 +2048,14 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { .unwrap_or_default() }; - let mut mode_flags = ModeFlags::default(); - if matches.get_flag(options::modes::HUMAN_NUMERIC) { - mode_flags.human_numeric = true; - } - if matches.get_flag(options::modes::MONTH) { - mode_flags.month = true; - } - if matches.get_flag(options::modes::GENERAL_NUMERIC) { - mode_flags.general_numeric = true; - } - if matches.get_flag(options::modes::NUMERIC) { - mode_flags.numeric = true; - } - if matches.get_flag(options::modes::VERSION) { - mode_flags.version = true; - } - if matches.get_flag(options::modes::RANDOM) { - mode_flags.random = true; - } + let mut mode_flags = ModeFlags { + human_numeric: matches.get_flag(options::modes::HUMAN_NUMERIC), + month: matches.get_flag(options::modes::MONTH), + general_numeric: matches.get_flag(options::modes::GENERAL_NUMERIC), + numeric: matches.get_flag(options::modes::NUMERIC), + version: matches.get_flag(options::modes::VERSION), + random: matches.get_flag(options::modes::RANDOM), + }; if let Some(sort_arg) = matches.get_one::(options::modes::SORT) { match sort_arg.as_str() { "human-numeric" => mode_flags.human_numeric = true, @@ -2306,7 +2297,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { // Initialize locale collation if needed (UTF-8 locales) // This MUST happen before init_precomputed() to avoid the performance regression #[cfg(feature = "i18n-collator")] - let needs_locale_collation = uucore::i18n::collator::init_locale_collation(); + let needs_locale_collation = i18n::collator::init_locale_collation(); #[cfg(not(feature = "i18n-collator"))] let needs_locale_collation = false; diff --git a/src/uu/split/src/filenames.rs b/src/uu/split/src/filenames.rs index 007f817cc..83b43943b 100644 --- a/src/uu/split/src/filenames.rs +++ b/src/uu/split/src/filenames.rs @@ -61,7 +61,7 @@ pub enum SuffixType { impl SuffixType { /// The radix to use when representing the suffix string as digits. - pub fn radix(&self) -> u8 { + pub fn radix(self) -> u8 { match self { Self::Alphabetic => 26, Self::Decimal => 10, diff --git a/src/uu/split/src/number.rs b/src/uu/split/src/number.rs index b81c1e989..fd0256a15 100644 --- a/src/uu/split/src/number.rs +++ b/src/uu/split/src/number.rs @@ -144,7 +144,10 @@ impl Number { pub fn increment(&mut self) -> Result<(), Overflow> { match self { Self::FixedWidth(number) => number.increment(), - Self::DynamicWidth(number) => number.increment(), + Self::DynamicWidth(number) => { + number.increment(); + Ok(()) + } } } } @@ -303,9 +306,8 @@ impl DynamicWidthNumber { } } - fn increment(&mut self) -> Result<(), Overflow> { + fn increment(&mut self) { self.current += 1; - Ok(()) } fn digits(&self) -> Vec { diff --git a/src/uu/split/src/split.rs b/src/uu/split/src/split.rs index fcab096e2..6e1a82279 100644 --- a/src/uu/split/src/split.rs +++ b/src/uu/split/src/split.rs @@ -106,8 +106,8 @@ fn filter_args( if let Some(slice) = os_slice.to_str() { if should_extract_obs_lines( slice, - preceding_long_opt_req_value, - preceding_short_opt_req_value, + *preceding_long_opt_req_value, + *preceding_short_opt_req_value, ) { // start of the short option string // that can have obsolete lines option value in it @@ -137,8 +137,8 @@ fn filter_args( /// and if so, a short option that can contain obsolete lines value fn should_extract_obs_lines( slice: &str, - preceding_long_opt_req_value: &bool, - preceding_short_opt_req_value: &bool, + preceding_long_opt_req_value: bool, + preceding_short_opt_req_value: bool, ) -> bool { slice.starts_with('-') && !slice.starts_with("--") @@ -1156,9 +1156,10 @@ where out_files = OutFiles::init(num_chunks, settings, false)?; } + let buf = &mut Vec::new(); for i in 1_u64..=num_chunks { let chunk_size = chunk_size_base + (chunk_size_reminder > i - 1) as u64; - let buf = &mut Vec::new(); + buf.clear(); if num_bytes > 0 { // Read `chunk_size` bytes from the reader into `buf` // except the last. @@ -1188,18 +1189,15 @@ where } } - match kth_chunk { - Some(chunk_number) => { - if i == chunk_number { - stdout_writer.write_all(buf)?; - break; - } - } - None => { - let idx = (i - 1) as usize; - let writer = out_files.get_writer(idx, settings)?; - writer.write_all(buf)?; + if let Some(chunk_number) = kth_chunk { + if i == chunk_number { + stdout_writer.write_all(buf)?; + break; } + } else { + let idx = (i - 1) as usize; + let writer = out_files.get_writer(idx, settings)?; + writer.write_all(buf)?; } } else { break; @@ -1300,18 +1298,15 @@ where } let bytes = line.as_slice(); - match kth_chunk { - Some(kth) => { - if chunk_number == kth { - stdout_writer.write_all(bytes)?; - } - } - None => { - // Should write into a file - let idx = (chunk_number - 1) as usize; - let writer = out_files.get_writer(idx, settings)?; - custom_write_all(bytes, writer, settings)?; + if let Some(kth) = kth_chunk { + if chunk_number == kth { + stdout_writer.write_all(bytes)?; } + } else { + // Should write into a file + let idx = (chunk_number - 1) as usize; + let writer = out_files.get_writer(idx, settings)?; + custom_write_all(bytes, writer, settings)?; } // Advance to the next chunk if the current one is filled. diff --git a/src/uu/stat/src/stat.rs b/src/uu/stat/src/stat.rs index 1b7f91584..8e044ed77 100644 --- a/src/uu/stat/src/stat.rs +++ b/src/uu/stat/src/stat.rs @@ -351,21 +351,21 @@ fn print_it(output: &OutputType, flags: Flags, width: usize, precision: Precisio // A sign (+ or -) should always be placed before a number produced by a signed conversion. // By default, a sign is used only for negative numbers. // A + overrides a space if both are used. - let padding_char = determine_padding_char(&flags); + let padding_char = determine_padding_char(flags); match output { - OutputType::Str(s) => print_str(s, &flags, width, precision), - OutputType::OsStr(s) => print_os_str(s, &flags, width, precision), - OutputType::Integer(num) => print_integer(*num, &flags, width, precision, padding_char), - OutputType::Unsigned(num) => print_unsigned(*num, &flags, width, precision, padding_char), + OutputType::Str(s) => print_str(s, flags, width, precision), + OutputType::OsStr(s) => print_os_str(s, flags, width, precision), + OutputType::Integer(num) => print_integer(*num, flags, width, precision, padding_char), + OutputType::Unsigned(num) => print_unsigned(*num, flags, width, precision, padding_char), OutputType::UnsignedOct(num) => { - print_unsigned_oct(*num, &flags, width, precision, padding_char); + print_unsigned_oct(*num, flags, width, precision, padding_char); } OutputType::UnsignedHex(num) => { - print_unsigned_hex(*num, &flags, width, precision, padding_char); + print_unsigned_hex(*num, flags, width, precision, padding_char); } OutputType::Float(num) => { - print_float(*num, &flags, width, precision, padding_char); + print_float(*num, flags, width, precision, padding_char); } OutputType::Unknown => print!("?"), } @@ -380,7 +380,7 @@ fn print_it(output: &OutputType, flags: Flags, width: usize, precision: Precisio /// # Returns /// /// * Padding - An instance of the Padding enum representing the padding character. -fn determine_padding_char(flags: &Flags) -> Padding { +fn determine_padding_char(flags: Flags) -> Padding { if flags.zero && !flags.left { Padding::Zero } else { @@ -396,7 +396,7 @@ fn determine_padding_char(flags: &Flags) -> Padding { /// * `flags` - A reference to the Flags struct containing formatting flags. /// * `width` - The width of the field for the printed string. /// * `precision` - How many digits of precision, if any. -fn print_str(s: &str, flags: &Flags, width: usize, precision: Precision) { +fn print_str(s: &str, flags: Flags, width: usize, precision: Precision) { let s = match precision { Precision::Number(p) if p < s.len() => &s[..p], _ => s, @@ -415,7 +415,7 @@ fn print_str(s: &str, flags: &Flags, width: usize, precision: Precision) { /// * `flags` - A reference to the Flags struct containing formatting flags. /// * `width` - The width of the field for the printed string. /// * `precision` - How many digits of precision, if any. -fn print_os_str(s: &OsString, flags: &Flags, width: usize, precision: Precision) { +fn print_os_str(s: &OsString, flags: Flags, width: usize, precision: Precision) { #[cfg(unix)] { use std::os::unix::ffi::OsStrExt; @@ -452,7 +452,7 @@ fn quote_file_name(file_name: &str, quoting_style: &QuotingStyle) -> String { fn get_quoted_file_name( display_name: &str, file: &OsString, - file_type: &FileType, + file_type: FileType, from_user: bool, ) -> Result { let quoting_style = env::var("QUOTING_STYLE") @@ -536,7 +536,7 @@ fn process_token_filesystem(t: &Token, meta: &StatFs, display_name: &str) { /// * `padding_char` - The padding character as determined by `determine_padding_char`. fn print_integer( num: i64, - flags: &Flags, + flags: Flags, width: usize, precision: Precision, padding_char: Padding, @@ -601,7 +601,7 @@ fn precision_trunc(num: f64, precision: Precision) -> String { } } -fn print_float(num: f64, flags: &Flags, width: usize, precision: Precision, padding_char: Padding) { +fn print_float(num: f64, flags: Flags, width: usize, precision: Precision, padding_char: Padding) { let prefix = if flags.sign { "+" } else if flags.space { @@ -625,7 +625,7 @@ fn print_float(num: f64, flags: &Flags, width: usize, precision: Precision, padd /// * `padding_char` - The padding character as determined by `determine_padding_char`. fn print_unsigned( num: u64, - flags: &Flags, + flags: Flags, width: usize, precision: Precision, padding_char: Padding, @@ -655,7 +655,7 @@ fn print_unsigned( /// * `padding_char` - The padding character as determined by `determine_padding_char`. fn print_unsigned_oct( num: u32, - flags: &Flags, + flags: Flags, width: usize, precision: Precision, padding_char: Padding, @@ -680,7 +680,7 @@ fn print_unsigned_oct( /// * `padding_char` - The padding character as determined by `determine_padding_char`. fn print_unsigned_hex( num: u64, - flags: &Flags, + flags: Flags, width: usize, precision: Precision, padding_char: Padding, @@ -1015,7 +1015,7 @@ impl Stater { meta: &Metadata, display_name: &str, file: &OsString, - file_type: &FileType, + file_type: FileType, from_user: bool, #[cfg(feature = "selinux")] follow_symbolic_links: bool, #[cfg(not(feature = "selinux"))] _: bool, @@ -1230,7 +1230,7 @@ impl Stater { &meta, &display_name, &file, - &file_type, + file_type, self.from_user, follow_symbolic_links, ) { diff --git a/src/uu/stdbuf/src/libstdbuf/src/libstdbuf.rs b/src/uu/stdbuf/src/libstdbuf/src/libstdbuf.rs index da0e43fef..d3cc05a31 100644 --- a/src/uu/stdbuf/src/libstdbuf/src/libstdbuf.rs +++ b/src/uu/stdbuf/src/libstdbuf/src/libstdbuf.rs @@ -191,12 +191,9 @@ fn set_buffer(stream: *mut FILE, value: &str) { "0" => (_IONBF, 0_usize), "L" => (_IOLBF, 0_usize), input => { - let buff_size: usize = match input.parse() { - Ok(num) => num, - Err(_) => { - eprintln!("failed to allocate a {value} byte stdio buffer"); - std::process::exit(1); - } + let Ok(buff_size) = input.parse::() else { + eprintln!("failed to allocate a {value} byte stdio buffer"); + std::process::exit(1); }; (_IOFBF, buff_size as size_t) } diff --git a/src/uu/stdbuf/src/stdbuf.rs b/src/uu/stdbuf/src/stdbuf.rs index b18e73d5d..bffe1f7b1 100644 --- a/src/uu/stdbuf/src/stdbuf.rs +++ b/src/uu/stdbuf/src/stdbuf.rs @@ -90,11 +90,19 @@ enum ProgramOptionsError { target_os = "openbsd", target_os = "dragonfly" ))] +#[expect( + clippy::unnecessary_wraps, + reason = "fn sig must match on all platforms" +)] fn preload_strings() -> UResult<(&'static str, &'static str)> { Ok(("LD_PRELOAD", "so")) } #[cfg(target_vendor = "apple")] +#[expect( + clippy::unnecessary_wraps, + reason = "fn sig must match on all platforms" +)] fn preload_strings() -> UResult<(&'static str, &'static str)> { Ok(("DYLD_LIBRARY_PATH", "dylib")) } diff --git a/src/uu/stty/src/stty.rs b/src/uu/stty/src/stty.rs index 1d40a1a1a..af54db3e9 100644 --- a/src/uu/stty/src/stty.rs +++ b/src/uu/stty/src/stty.rs @@ -428,7 +428,7 @@ fn stty(opts: &Options) -> UResult<()> { print_special_setting(setting, opts.file.as_raw_fd())?; } ArgOptions::SavedState(state) => { - apply_saved_state(&mut termios, state)?; + apply_saved_state(&mut termios, state); } } } @@ -485,10 +485,12 @@ fn invalid_speed(arg: &str, speed: &str) -> Result> { /// GNU uses different error messages if values overflow or underflow a u8, /// this function returns the appropriate error message in the case of overflow or underflow, or u8 on success fn parse_u8_or_err(arg: &str) -> Result { - arg.parse::().map_err(|e| match e.kind() { - IntErrorKind::PosOverflow => translate!("stty-error-invalid-integer-argument-value-too-large", "value" => format!("'{arg}'")), - _ => translate!("stty-error-invalid-integer-argument", - "value" => format!("'{arg}'")), + arg.parse::().map_err(|e| { + if let IntErrorKind::PosOverflow = e.kind() { + translate!("stty-error-invalid-integer-argument-value-too-large", "value" => format!("'{arg}'")) + } else { + translate!("stty-error-invalid-integer-argument", "value" => format!("'{arg}'")) + } }) } @@ -567,16 +569,15 @@ impl WrappedPrinter { /// If term_size is None (typically when output is piped), falls back to /// the COLUMNS environment variable or a default width of 80 columns. fn new(term_size: Option<&TermSize>) -> Self { - let columns = match term_size { - Some(term_size) => term_size.columns, - None => { - const DEFAULT_TERM_WIDTH: u16 = 80; + let columns = if let Some(term_size) = term_size { + term_size.columns + } else { + const DEFAULT_TERM_WIDTH: u16 = 80; - std::env::var_os("COLUMNS") - .and_then(|s| s.to_str()?.parse().ok()) - .filter(|&c| c > 0) - .unwrap_or(DEFAULT_TERM_WIDTH) - } + std::env::var_os("COLUMNS") + .and_then(|s| s.to_str()?.parse().ok()) + .filter(|&c| c > 0) + .unwrap_or(DEFAULT_TERM_WIDTH) }; Self { @@ -612,6 +613,10 @@ impl WrappedPrinter { } } +#[allow( + clippy::unnecessary_wraps, + reason = "needed for some platform-specific code" +)] fn print_terminal_size( termios: &Termios, opts: &Options, @@ -985,10 +990,10 @@ fn apply_char_mapping(termios: &mut Termios, mapping: &(S, u8)) { /// /// If state has fewer than 4 elements, no changes are applied. This is a defensive /// check that should never trigger since `parse_saved_state` rejects such states. -fn apply_saved_state(termios: &mut Termios, state: &[u32]) -> nix::Result<()> { +fn apply_saved_state(termios: &mut Termios, state: &[u32]) { // Require at least 4 elements for the flags (defensive check) if state.len() < 4 { - return Ok(()); // No-op for invalid state (already validated by parser) + return; // No-op for invalid state (already validated by parser) } // Apply the four flag groups, done (as _) for MacOS size compatibility @@ -1003,8 +1008,6 @@ fn apply_saved_state(termios: &mut Termios, state: &[u32]) -> nix::Result<()> { termios.control_chars[i] = cc_val as u8; } } - - Ok(()) } fn apply_special_setting( diff --git a/src/uu/sync/locales/en-US.ftl b/src/uu/sync/locales/en-US.ftl index c1cc10bea..d0b520bcb 100644 --- a/src/uu/sync/locales/en-US.ftl +++ b/src/uu/sync/locales/en-US.ftl @@ -10,6 +10,9 @@ sync-error-data-needs-argument = --data needs at least one argument sync-error-opening-file = error opening { $file } sync-error-no-such-file = error opening { $file }: No such file or directory +# Warning messages +sync-warning-fcntl-failed = warning: failed to reset O_NONBLOCK flag for { $file }: { $error } + # Windows-specific error messages sync-error-flush-file-buffer = failed to flush file buffer sync-error-create-volume-handle = failed to create volume handle diff --git a/src/uu/sync/locales/fr-FR.ftl b/src/uu/sync/locales/fr-FR.ftl index 7ab9e8119..f0d00db03 100644 --- a/src/uu/sync/locales/fr-FR.ftl +++ b/src/uu/sync/locales/fr-FR.ftl @@ -10,6 +10,9 @@ sync-error-data-needs-argument = --data nécessite au moins un argument sync-error-opening-file = erreur lors de l'ouverture de { $file } sync-error-no-such-file = erreur lors de l'ouverture de { $file } : Aucun fichier ou répertoire de ce type +# Messages d'avertissement +sync-warning-fcntl-failed = avertissement : échec de la réinitialisation du drapeau O_NONBLOCK pour { $file } : { $error } + # Messages d'erreur spécifiques à Windows sync-error-flush-file-buffer = échec du vidage du tampon de fichier sync-error-create-volume-handle = échec de la création du handle de volume diff --git a/src/uu/sync/src/sync.rs b/src/uu/sync/src/sync.rs index de79a957b..0e5b864b9 100644 --- a/src/uu/sync/src/sync.rs +++ b/src/uu/sync/src/sync.rs @@ -38,20 +38,41 @@ mod platform { use std::fs::File; #[cfg(any(target_os = "linux", target_os = "android"))] use uucore::error::FromIo; + #[cfg(any(target_os = "linux", target_os = "android"))] + use uucore::translate; use uucore::error::UResult; + #[expect( + clippy::unnecessary_wraps, + reason = "fn sig must match on all platforms" + )] pub fn do_sync() -> UResult<()> { sync(); Ok(()) } + /// Opens a file and resets its O_NONBLOCK flag to match GNU behavior. + /// Returns the opened file or an error if opening fails. + /// Logs a warning if fcntl fails but doesn't abort the operation. + #[cfg(any(target_os = "linux", target_os = "android"))] + fn open_and_reset_nonblock(path: &str) -> UResult { + let f = File::open(path).map_err_context(|| path.to_string())?; + // Reset O_NONBLOCK flag if it was set (matches GNU behavior) + // This is non-critical, so we log errors but don't fail + if let Err(e) = fcntl(&f, FcntlArg::F_SETFL(OFlag::empty())) { + eprintln!( + "sync: {}", + translate!("sync-warning-fcntl-failed", "file" => path, "error" => e.to_string()) + ); + } + Ok(f) + } + #[cfg(any(target_os = "linux", target_os = "android"))] pub fn do_syncfs(files: Vec) -> UResult<()> { for path in files { - let f = File::open(&path).map_err_context(|| path.clone())?; - // Reset O_NONBLOCK flag if it was set (matches GNU behavior) - let _ = fcntl(&f, FcntlArg::F_SETFL(OFlag::empty())); + let f = open_and_reset_nonblock(&path)?; syncfs(f)?; } Ok(()) @@ -60,9 +81,7 @@ mod platform { #[cfg(any(target_os = "linux", target_os = "android"))] pub fn do_fdatasync(files: Vec) -> UResult<()> { for path in files { - let f = File::open(&path).map_err_context(|| path.clone())?; - // Reset O_NONBLOCK flag if it was set (matches GNU behavior) - let _ = fcntl(&f, FcntlArg::F_SETFL(OFlag::empty())); + let f = open_and_reset_nonblock(&path)?; fdatasync(f)?; } Ok(()) diff --git a/src/uu/tail/src/follow/watch.rs b/src/uu/tail/src/follow/watch.rs index 363449ae7..06a355b22 100644 --- a/src/uu/tail/src/follow/watch.rs +++ b/src/uu/tail/src/follow/watch.rs @@ -298,7 +298,9 @@ impl Observer { event: ¬ify::Event, settings: &Settings, ) -> UResult> { - use notify::event::*; + use notify::event::{ + CreateKind, DataChange, EventKind, MetadataKind, ModifyKind, RemoveKind, RenameMode, + }; let event_path = event.paths.first().unwrap(); let mut paths: Vec = vec![]; diff --git a/src/uu/tail/src/tail.rs b/src/uu/tail/src/tail.rs index 0ae60a08d..ad568db76 100644 --- a/src/uu/tail/src/tail.rs +++ b/src/uu/tail/src/tail.rs @@ -210,7 +210,7 @@ fn tail_file( /// After opening, we clear O_NONBLOCK so subsequent reads block normally. /// Without `--pid`, FIFOs block on open() until a writer connects (GNU behavior). #[cfg(unix)] -fn open_file(path: &Path, use_nonblock_for_fifo: bool) -> std::io::Result { +fn open_file(path: &Path, use_nonblock_for_fifo: bool) -> io::Result { use nix::fcntl::{FcntlArg, OFlag, fcntl}; use std::fs::OpenOptions; use std::os::fd::AsFd; @@ -277,47 +277,44 @@ fn tail_stdin( return Ok(()); } - match input.resolve() { + if let Some(path) = input.resolve() { // fifo - Some(path) => { - let mut stdin_offset = 0; - if cfg!(unix) { - // Save the current seek position/offset of a stdin redirected file. - // This is needed to pass "gnu/tests/tail-2/start-middle.sh" - if let Ok(mut stdin_handle) = Handle::stdin() { - if let Ok(offset) = stdin_handle.as_file_mut().stream_position() { - stdin_offset = offset; - } + let mut stdin_offset = 0; + if cfg!(unix) { + // Save the current seek position/offset of a stdin redirected file. + // This is needed to pass "gnu/tests/tail-2/start-middle.sh" + if let Ok(mut stdin_handle) = Handle::stdin() { + if let Ok(offset) = stdin_handle.as_file_mut().stream_position() { + stdin_offset = offset; } } - tail_file( - settings, - header_printer, - input, - &path, - observer, - stdin_offset, - )?; } + tail_file( + settings, + header_printer, + input, + &path, + observer, + stdin_offset, + )?; + } else { // pipe - None => { - header_printer.print_input(input); - if paths::stdin_is_bad_fd() { - set_exit_code(1); + header_printer.print_input(input); + if paths::stdin_is_bad_fd() { + set_exit_code(1); + show_error!( + "{}", + translate!("tail-error-cannot-fstat", "file" => translate!("tail-stdin-header"), "error" => translate!("tail-bad-fd")) + ); + if settings.follow.is_some() { show_error!( "{}", - translate!("tail-error-cannot-fstat", "file" => translate!("tail-stdin-header"), "error" => translate!("tail-bad-fd")) + translate!("tail-error-reading-file", "file" => translate!("tail-stdin-header"), "error" => translate!("tail-bad-fd")) ); - if settings.follow.is_some() { - show_error!( - "{}", - translate!("tail-error-reading-file", "file" => translate!("tail-stdin-header"), "error" => translate!("tail-bad-fd")) - ); - } - } else { - let mut reader = BufReader::new(stdin()); - unbounded_tail(&mut reader, settings)?; } + } else { + let mut reader = BufReader::new(stdin()); + unbounded_tail(&mut reader, settings)?; } } diff --git a/src/uu/test/locales/en-US.ftl b/src/uu/test/locales/en-US.ftl index 86871637f..66d7c0a83 100644 --- a/src/uu/test/locales/en-US.ftl +++ b/src/uu/test/locales/en-US.ftl @@ -3,7 +3,7 @@ test-usage = test EXPRESSION test {"[ EXPRESSION ]"} {"[ ]"} - {"[ OPTION ]"} + {"[ OPTION"} test-after-help = Exit with the status determined by EXPRESSION. An omitted EXPRESSION defaults to false. diff --git a/src/uu/test/locales/fr-FR.ftl b/src/uu/test/locales/fr-FR.ftl index 78cae9b44..36729d3f7 100644 --- a/src/uu/test/locales/fr-FR.ftl +++ b/src/uu/test/locales/fr-FR.ftl @@ -3,7 +3,7 @@ test-usage = test EXPRESSION test {"[ EXPRESSION ]"} {"[ ]"} - {"[ OPTION ]"} + {"[ OPTION"} test-after-help = Quitter avec le statut déterminé par EXPRESSION. Une EXPRESSION omise vaut false par défaut. diff --git a/src/uu/test/src/parser.rs b/src/uu/test/src/parser.rs index c1c06e4c5..f39175251 100644 --- a/src/uu/test/src/parser.rs +++ b/src/uu/test/src/parser.rs @@ -341,16 +341,15 @@ impl Parser { .map(|token| Symbol::new(Some(token))) .collect(); - match peek4.as_slice() { + if let [Symbol::Literal(_), Symbol::BoolOp(_), Symbol::Literal(_)] = + peek4.as_slice() + { // we peeked ahead 4 but there were only 3 tokens left - [Symbol::Literal(_), Symbol::BoolOp(_), Symbol::Literal(_)] => { - self.expr()?; - self.stack.push(Symbol::Bang); - } - _ => { - self.term()?; - self.stack.push(Symbol::Bang); - } + self.expr()?; + self.stack.push(Symbol::Bang); + } else { + self.term()?; + self.stack.push(Symbol::Bang); } } } diff --git a/src/uu/timeout/src/timeout.rs b/src/uu/timeout/src/timeout.rs index cac487036..6944794f1 100644 --- a/src/uu/timeout/src/timeout.rs +++ b/src/uu/timeout/src/timeout.rs @@ -188,7 +188,7 @@ fn install_sigchld() { /// We should terminate child process when receiving termination signals. static SIGNALED: AtomicBool = AtomicBool::new(false); /// Track which signal was received (0 = none/timeout expired naturally). -static RECEIVED_SIGNAL: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(0); +static RECEIVED_SIGNAL: atomic::AtomicI32 = atomic::AtomicI32::new(0); /// Install signal handlers for termination signals. fn install_signal_handlers(term_signal: usize) { diff --git a/src/uu/touch/src/touch.rs b/src/uu/touch/src/touch.rs index 3c1bab7a8..e50686417 100644 --- a/src/uu/touch/src/touch.rs +++ b/src/uu/touch/src/touch.rs @@ -702,7 +702,7 @@ fn prepend_century(s: &str) -> UResult { /// then cc is 20 for years in the range 0 … 68, and 19 for years in 69 … 99. /// in order to be compatible with GNU `touch`. fn parse_timestamp(s: &str) -> UResult { - use format::*; + use format::{YYYYMMDDHHMM, YYYYMMDDHHMM_DOT_SS}; let current_year = || Timestamp::now().to_zoned(TimeZone::system()).year(); @@ -760,6 +760,7 @@ fn parse_timestamp(s: &str) -> UResult { /// /// On Windows, uses `GetFinalPathNameByHandleW` to attempt to get the path /// from the stdout handle. +#[cfg_attr(not(windows), expect(clippy::unnecessary_wraps))] fn pathbuf_from_stdout() -> Result { #[cfg(all(unix, not(target_os = "android")))] { diff --git a/src/uu/tr/BENCHMARKING.md b/src/uu/tr/BENCHMARKING.md index 2bdd16f75..0b6e723d7 100644 --- a/src/uu/tr/BENCHMARKING.md +++ b/src/uu/tr/BENCHMARKING.md @@ -63,4 +63,3 @@ hyperfine 'tr -d aeiou < mixed_input > /dev/null' './target/release/tr -d aeiou - Uses lookup tables instead of hash maps for O(1) operations - 32KB I/O buffers for improved throughput - Should be competitive with GNU `tr` for most operations - diff --git a/src/uu/tr/src/operation.rs b/src/uu/tr/src/operation.rs index 1984f18b9..0c62840e0 100644 --- a/src/uu/tr/src/operation.rs +++ b/src/uu/tr/src/operation.rs @@ -105,8 +105,8 @@ impl Display for BadSequence { ) } Self::BackwardsRange { end, start } => { - fn end_or_start_to_string(ut: &u32) -> String { - match char::from_u32(*ut) { + fn end_or_start_to_string(ut: u32) -> String { + match char::from_u32(ut) { Some(ch @ '\x20'..='\x7E') => ch.escape_default().to_string(), _ => { format!("\\{ut:03o}") @@ -116,7 +116,7 @@ impl Display for BadSequence { write!( f, "{}", - translate!("tr-error-backwards-range", "start" => end_or_start_to_string(start), "end" => end_or_start_to_string(end)) + translate!("tr-error-backwards-range", "start" => end_or_start_to_string(*start), "end" => end_or_start_to_string(*end)) ) } Self::MultipleCharInEquivalence(s) => write!( diff --git a/src/uu/true/src/true.rs b/src/uu/true/src/true.rs index e19b26408..fe8c762e0 100644 --- a/src/uu/true/src/true.rs +++ b/src/uu/true/src/true.rs @@ -9,31 +9,30 @@ use uucore::error::{UResult, set_exit_code}; use uucore::translate; #[uucore::main] +// TODO: modify proc macro to allow no-result uumain +#[expect(clippy::unnecessary_wraps, reason = "proc macro requires UResult")] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let mut command = uu_app(); - let args: Vec = args.collect(); - if args.len() > 2 { + if args.len() != 2 { return Ok(()); } - if let Err(e) = command.try_get_matches_from_mut(args) { - let error = match e.kind() { - clap::error::ErrorKind::DisplayHelp => command.print_help(), - clap::error::ErrorKind::DisplayVersion => { - write!(std::io::stdout(), "{}", command.render_version()) - } - _ => Ok(()), - }; + // args[0] is the name of the binary. + let error = if args[1] == "--help" { + uu_app().print_help() + } else if args[1] == "--version" { + write!(std::io::stdout(), "{}", uu_app().render_version()) + } else { + Ok(()) + }; - if let Err(print_fail) = error { - // Try to display this error. - let _ = writeln!(std::io::stderr(), "{}: {print_fail}", uucore::util_name()); - // Mirror GNU options. When failing to print warnings or version flags, then we exit - // with FAIL. This avoids allocation some error information which may result in yet - // other types of failure. - set_exit_code(1); - } + if let Err(print_fail) = error { + // Try to display this error. + let _ = writeln!(std::io::stderr(), "{}: {print_fail}", uucore::util_name()); + // Mirror GNU options. When failing to print warnings or version flags, then we exit + // with FAIL. This avoids allocation some error information which may result in yet + // other types of failure. + set_exit_code(1); } Ok(()) diff --git a/src/uu/tsort/BENCHMARKING.md b/src/uu/tsort/BENCHMARKING.md index 52b9dfc57..8470212f9 100644 --- a/src/uu/tsort/BENCHMARKING.md +++ b/src/uu/tsort/BENCHMARKING.md @@ -1,12 +1,12 @@ # Benchmarking `tsort` -Much of what makes `tsort` fast is the efficiency of its algorithm and implementation for topological sorting. -Our implementation of `tsort` also outputs a cycle whenever such ordering does not exist, just like GNU `tsort`. +Much of what makes `tsort` fast is the efficiency of its algorithm and implementation for topological sorting. +Our implementation of `tsort` also outputs a cycle whenever such ordering does not exist, just like GNU `tsort`. ## Strategies -To test `tsort`'s performance for its nominal use case, we need to test it with a DAG. One of the worst cases is when all nodes are just representing a succession of independent steps. -We should also test cycle detection for good measure. +To test `tsort`'s performance for its nominal use case, we need to test it with a DAG. One of the worst cases is when all nodes are just representing a succession of independent steps. +We should also test cycle detection for good measure. ### Random acyclic graph (DAG) @@ -25,16 +25,16 @@ for i in range(100*N): ### Random graph with cycles -The following will output a graph with multiples edges, it also allows some degree of tuning to test different cases. +The following will output a graph with multiples edges, it also allows some degree of tuning to test different cases. ```python import random # Parameters for the graph -num_nodes = 100 -num_edges = 150 -cycle_percentage = 0.10 -max_cycle_size = 6 +num_nodes = 100 +num_edges = 150 +cycle_percentage = 0.10 +max_cycle_size = 6 num_cycles = int(num_edges * cycle_percentage) @@ -52,7 +52,7 @@ for _ in range(num_cycles): ``` ## Running Benchmarks -The above scripts will output the generated graphs to the standard output. They can therefore be used directly as tests. In order to run a Benchmark, the output should be redirected to a file. +The above scripts will output the generated graphs to the standard output. They can therefore be used directly as tests. In order to run a Benchmark, the output should be redirected to a file. Use [`hyperfine`](https://github.com/sharkdp/hyperfine) to compare the performance of different `tsort` versions. For example, you can compare the performance of GNU `tsort` and another implementation with the following command: ```sh diff --git a/src/uu/tsort/Cargo.toml b/src/uu/tsort/Cargo.toml index 8819596e0..74209a62f 100644 --- a/src/uu/tsort/Cargo.toml +++ b/src/uu/tsort/Cargo.toml @@ -24,6 +24,7 @@ fluent = { workspace = true } string-interner = { workspace = true } thiserror = { workspace = true } uucore = { workspace = true } +rustc-hash = { workspace = true } [target.'cfg(unix)'.dependencies] nix = { workspace = true, features = ["fs"] } diff --git a/src/uu/tsort/src/tsort.rs b/src/uu/tsort/src/tsort.rs index 713c2f5c9..a689cd05b 100644 --- a/src/uu/tsort/src/tsort.rs +++ b/src/uu/tsort/src/tsort.rs @@ -5,8 +5,9 @@ //spell-checker:ignore TAOCP indegree fadvise FADV //spell-checker:ignore (libs) interner uclibc use clap::{Arg, ArgAction, Command}; +use rustc_hash::FxHashMap; +use std::collections::VecDeque; use std::collections::hash_map::Entry; -use std::collections::{HashMap, VecDeque}; use std::ffi::OsString; use std::fs::File; use std::io::{self, BufRead, BufReader}; @@ -19,7 +20,7 @@ use uucore::{format_usage, show, translate}; // short types for switching interning behavior on the fly. type Sym = string_interner::symbol::SymbolUsize; -type Interner = StringInterner>; +type Interner = StringInterner, rustc_hash::FxBuildHasher>; mod options { pub const FILE: &str = "file"; @@ -150,7 +151,7 @@ enum TsortError { /// Wrapper for bubbling up IO errors #[error("{0}")] - IO(#[from] std::io::Error), + IO(#[from] io::Error), } // Auxiliary struct, just for printing loop nodes via show! macro @@ -224,18 +225,18 @@ impl Node { struct Graph { name_sym: Sym, - nodes: HashMap, + nodes: FxHashMap, interner: Interner, } impl Graph { fn new(name: String) -> Self { - let mut interner = Interner::new(); + let mut interner = Interner::with_hasher(rustc_hash::FxBuildHasher); let name_sym = interner.get_or_intern(name); Self { name_sym, interner, - nodes: HashMap::default(), + nodes: FxHashMap::default(), } } @@ -357,7 +358,7 @@ impl Graph { let mut nodes: Vec<_> = self.nodes.keys().copied().collect(); nodes.sort_unstable_by(|a, b| self.get_node_name(*a).cmp(self.get_node_name(*b))); - let mut visited = HashMap::new(); + let mut visited = FxHashMap::default(); let mut stack = Vec::with_capacity(self.nodes.len()); for &node in &nodes { if self.dfs(node, &mut visited, &mut stack) { @@ -376,7 +377,7 @@ impl Graph { fn dfs<'a>( &'a self, node: Sym, - visited: &mut HashMap, + visited: &mut FxHashMap, stack: &mut Vec<(Sym, &'a [Sym])>, ) -> bool { stack.push(( diff --git a/src/uu/tty/src/tty.rs b/src/uu/tty/src/tty.rs index 5bf5199a0..e2c541cff 100644 --- a/src/uu/tty/src/tty.rs +++ b/src/uu/tty/src/tty.rs @@ -41,12 +41,11 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let name = nix::unistd::ttyname(std::io::stdin()); - let write_result = match name { - Ok(name) => stdout.write_all_os(name.as_os_str()), - Err(_) => { - set_exit_code(1); - writeln!(stdout, "{}", translate!("tty-not-a-tty")) - } + let write_result = if let Ok(name) = name { + stdout.write_all_os(name.as_os_str()) + } else { + set_exit_code(1); + writeln!(stdout, "{}", translate!("tty-not-a-tty")) }; if write_result.is_err() || stdout.flush().is_err() { diff --git a/src/uu/uname/src/uname.rs b/src/uu/uname/src/uname.rs index c35e9d51a..862257339 100644 --- a/src/uu/uname/src/uname.rs +++ b/src/uu/uname/src/uname.rs @@ -8,7 +8,7 @@ use std::ffi::{OsStr, OsString}; use clap::{Arg, ArgAction, Command}; -use platform_info::*; +use platform_info::{PlatformInfo, PlatformInfoAPI, UNameAPI}; use uucore::display::println_verbatim; use uucore::translate; use uucore::{ diff --git a/src/uu/uniq/src/uniq.rs b/src/uu/uniq/src/uniq.rs index a968e6bf0..3288241e4 100644 --- a/src/uu/uniq/src/uniq.rs +++ b/src/uu/uniq/src/uniq.rs @@ -387,16 +387,16 @@ fn filter_args( if let Some(slice) = os_slice.to_str() { if should_extract_obs_skip_fields( slice, - preceding_long_opt_req_value, - preceding_short_opt_req_value, + *preceding_long_opt_req_value, + *preceding_short_opt_req_value, ) { // start of the short option string // that can have obsolete skip fields option value in it filter = handle_extract_obs_skip_fields(slice, skip_fields_old); } else if should_extract_obs_skip_chars( slice, - preceding_long_opt_req_value, - preceding_short_opt_req_value, + *preceding_long_opt_req_value, + *preceding_short_opt_req_value, ) { // the obsolete skip chars option filter = handle_extract_obs_skip_chars(slice, skip_chars_old); @@ -436,8 +436,8 @@ fn filter_args( /// and if so, a short option that can contain obsolete skip fields value fn should_extract_obs_skip_fields( slice: &str, - preceding_long_opt_req_value: &bool, - preceding_short_opt_req_value: &bool, + preceding_long_opt_req_value: bool, + preceding_short_opt_req_value: bool, ) -> bool { slice.starts_with('-') && !slice.starts_with("--") @@ -452,8 +452,8 @@ fn should_extract_obs_skip_fields( /// Checks if the slice is a true obsolete skip chars short option fn should_extract_obs_skip_chars( slice: &str, - preceding_long_opt_req_value: &bool, - preceding_short_opt_req_value: &bool, + preceding_long_opt_req_value: bool, + preceding_short_opt_req_value: bool, ) -> bool { slice.starts_with('+') && posix_version().is_some_and(|v| v <= OBSOLETE) diff --git a/src/uu/uptime/src/uptime.rs b/src/uu/uptime/src/uptime.rs index d8da60654..936725d52 100644 --- a/src/uu/uptime/src/uptime.rs +++ b/src/uu/uptime/src/uptime.rs @@ -14,7 +14,10 @@ use thiserror::Error; use uucore::error::{UError, UResult}; use uucore::libc::time_t; use uucore::translate; -use uucore::uptime::*; +use uucore::uptime::{ + OutputFormat, format_nusers, get_formatted_loadavg, get_formatted_nusers, get_formatted_time, + get_formatted_uptime, get_uptime, +}; use clap::{Arg, ArgAction, Command, ValueHint, builder::ValueParser}; @@ -22,7 +25,7 @@ use uucore::format_usage; #[cfg(unix)] #[cfg(not(target_os = "openbsd"))] -use uucore::utmpx::*; +use uucore::utmpx::{BOOT_TIME, USER_PROCESS, Utmpx}; pub mod options { pub static SINCE: &str = "since"; diff --git a/src/uu/vdir/locales/en-US.ftl b/src/uu/vdir/locales/en-US.ftl deleted file mode 100644 index f0d63274e..000000000 --- a/src/uu/vdir/locales/en-US.ftl +++ /dev/null @@ -1,5 +0,0 @@ -vdir-about = List directory contents. - Ignore files and directories starting with a '.' by default. - - Mandatory arguments to long options are mandatory for short options too. -vdir-usage = vdir [OPTION]... [FILE]... diff --git a/src/uu/vdir/src/vdir.rs b/src/uu/vdir/src/vdir.rs index affc8d47e..7d098d158 100644 --- a/src/uu/vdir/src/vdir.rs +++ b/src/uu/vdir/src/vdir.rs @@ -9,6 +9,7 @@ use std::path::Path; use uu_ls::{Config, Format, options}; use uucore::error::UResult; use uucore::quoting_style::QuotingStyle; +use uucore::{format_usage, translate}; #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { @@ -61,4 +62,6 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { // an uu_app function, so we need this dummy one. pub fn uu_app() -> Command { uu_ls::uu_app() + .override_usage(format_usage(&translate!("vdir-usage"))) + .about(translate!("vdir-about")) } diff --git a/src/uu/wc/src/utf8/mod.rs b/src/uu/wc/src/utf8/mod.rs index 5407a06fa..399a9f364 100644 --- a/src/uu/wc/src/utf8/mod.rs +++ b/src/uu/wc/src/utf8/mod.rs @@ -33,7 +33,7 @@ impl Incomplete { } } - pub fn is_empty(&self) -> bool { + pub fn is_empty(self) -> bool { self.buffer_len == 0 } @@ -76,20 +76,15 @@ impl Incomplete { let consumed = valid_up_to.checked_sub(initial_buffer_len).unwrap(); self.buffer_len = valid_up_to as u8; (consumed, Some(Ok(()))) + } else if let Some(invalid_sequence_length) = error.error_len() { + let consumed = invalid_sequence_length + .checked_sub(initial_buffer_len) + .unwrap(); + self.buffer_len = invalid_sequence_length as u8; + (consumed, Some(Err(()))) } else { - match error.error_len() { - Some(invalid_sequence_length) => { - let consumed = invalid_sequence_length - .checked_sub(initial_buffer_len) - .unwrap(); - self.buffer_len = invalid_sequence_length as u8; - (consumed, Some(Err(()))) - } - None => { - self.buffer_len = spliced.len() as u8; - (copied_from_input, None) - } - } + self.buffer_len = spliced.len() as u8; + (copied_from_input, None) } } } diff --git a/src/uu/wc/src/utf8/read.rs b/src/uu/wc/src/utf8/read.rs index 518248bda..6364c45df 100644 --- a/src/uu/wc/src/utf8/read.rs +++ b/src/uu/wc/src/utf8/read.rs @@ -3,7 +3,7 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. // spell-checker:ignore bytestream -use super::*; +use super::{Incomplete, str}; use std::io::{self, BufRead}; use thiserror::Error; use uucore::translate; @@ -79,17 +79,13 @@ impl BufReadDecoder { if valid_up_to > 0 { break (BytesSource::BufRead(valid_up_to), Ok(())); } - match error.error_len() { - Some(invalid_sequence_length) => { - break (BytesSource::BufRead(invalid_sequence_length), Err(())); - } - None => { - self.bytes_consumed = buf.len(); - self.incomplete = Incomplete::new(buf); - // need more input bytes - continue; - } + if let Some(invalid_sequence_length) = error.error_len() { + break (BytesSource::BufRead(invalid_sequence_length), Err(())); } + self.bytes_consumed = buf.len(); + self.incomplete = Incomplete::new(buf); + // need more input bytes + continue; } } } else { diff --git a/src/uu/wc/src/wc.rs b/src/uu/wc/src/wc.rs index 866f213ff..3c762353c 100644 --- a/src/uu/wc/src/wc.rs +++ b/src/uu/wc/src/wc.rs @@ -335,7 +335,7 @@ impl> From for TotalWhen { } impl TotalWhen { - fn is_total_row_visible(&self, num_inputs: usize) -> bool { + fn is_total_row_visible(self, num_inputs: usize) -> bool { match self { Self::Auto => num_inputs > 1, Self::Always | Self::Only => true, @@ -846,14 +846,14 @@ fn hardware_feature_label(feature: HardwareFeature) -> &'static str { } } -fn is_simd_runtime_feature(feature: &HardwareFeature) -> bool { +fn is_simd_runtime_feature(feature: HardwareFeature) -> bool { matches!( feature, HardwareFeature::Avx2 | HardwareFeature::Sse2 | HardwareFeature::Asimd ) } -fn is_simd_debug_feature(feature: &HardwareFeature) -> bool { +fn is_simd_debug_feature(feature: HardwareFeature) -> bool { matches!( feature, HardwareFeature::Avx512 @@ -872,16 +872,16 @@ struct WcSimdFeatures { fn wc_simd_features(policy: &SimdPolicy) -> WcSimdFeatures { let enabled = policy .iter_features() - .filter(is_simd_runtime_feature) + .filter(|v| is_simd_runtime_feature(*v)) .collect(); let mut disabled = Vec::new(); let mut disabled_runtime = Vec::new(); for feature in policy.disabled_features() { - if is_simd_debug_feature(&feature) { + if is_simd_debug_feature(feature) { disabled.push(feature); } - if is_simd_runtime_feature(&feature) { + if is_simd_runtime_feature(feature) { disabled_runtime.push(feature); } } @@ -895,12 +895,10 @@ fn wc_simd_features(policy: &SimdPolicy) -> WcSimdFeatures { pub(crate) fn wc_simd_allowed(policy: &SimdPolicy) -> bool { let disabled_features = policy.disabled_features(); - if disabled_features.iter().any(is_simd_runtime_feature) { + if disabled_features.into_iter().any(is_simd_runtime_feature) { return false; } - policy - .iter_features() - .any(|feature| is_simd_runtime_feature(&feature)) + policy.iter_features().any(is_simd_runtime_feature) } fn wc(inputs: &Inputs, settings: &Settings) -> UResult<()> { diff --git a/src/uu/who/src/platform/unix.rs b/src/uu/who/src/platform/unix.rs index 5cd27f26b..5db18f23e 100644 --- a/src/uu/who/src/platform/unix.rs +++ b/src/uu/who/src/platform/unix.rs @@ -354,23 +354,20 @@ impl Who { p.push(ut.tty_device().as_str()); let mesg; let last_change; - match p.metadata() { - Ok(meta) => { - #[cfg(all( - not(target_os = "android"), - not(target_os = "freebsd"), - not(target_vendor = "apple") - ))] - let iwgrp = S_IWGRP; - #[cfg(any(target_os = "android", target_os = "freebsd", target_vendor = "apple"))] - let iwgrp = S_IWGRP as u32; - mesg = if meta.mode() & iwgrp == 0 { '-' } else { '+' }; - last_change = meta.atime(); - } - _ => { - mesg = '?'; - last_change = 0; - } + if let Ok(meta) = p.metadata() { + #[cfg(all( + not(target_os = "android"), + not(target_os = "freebsd"), + not(target_vendor = "apple") + ))] + let iwgrp = S_IWGRP; + #[cfg(any(target_os = "android", target_os = "freebsd", target_vendor = "apple"))] + let iwgrp = S_IWGRP as u32; + mesg = if meta.mode() & iwgrp == 0 { '-' } else { '+' }; + last_change = meta.atime(); + } else { + mesg = '?'; + last_change = 0; } let idle = if last_change == 0 { diff --git a/src/uu/yes/src/yes.rs b/src/uu/yes/src/yes.rs index 92527221b..22c84a1a2 100644 --- a/src/uu/yes/src/yes.rs +++ b/src/uu/yes/src/yes.rs @@ -52,6 +52,7 @@ pub fn uu_app() -> Command { } /// Copies words from `i` into `buf`, separated by spaces. +#[allow(clippy::unnecessary_wraps, reason = "needed on some platforms")] fn args_into_buffer<'a>( buf: &mut Vec, i: Option>, diff --git a/src/uucore/build.rs b/src/uucore/build.rs index 6285e2438..15068f28a 100644 --- a/src/uucore/build.rs +++ b/src/uucore/build.rs @@ -6,7 +6,7 @@ use std::env; use std::fs::File; use std::io::Write; -use std::path::Path; +use std::path::{Path, PathBuf}; pub fn main() -> Result<(), Box> { let out_dir = env::var("OUT_DIR")?; @@ -63,9 +63,9 @@ pub fn main() -> Result<(), Box> { /// /// Returns an error if the `CARGO_MANIFEST_DIR` environment variable is not set /// or if the current directory structure does not allow determining the project root. -fn project_root() -> Result> { +fn project_root() -> Result> { let manifest_dir = env::var("CARGO_MANIFEST_DIR")?; - let uucore_path = std::path::Path::new(&manifest_dir); + let uucore_path = Path::new(&manifest_dir); // Navigate from src/uucore to project root let project_root = uucore_path @@ -100,7 +100,7 @@ fn detect_target_utility() -> Option { // Check for a build configuration file in the target directory if let Ok(target_dir) = env::var("CARGO_TARGET_DIR") { - let config_path = std::path::Path::new(&target_dir).join("uucore_target_util.txt"); + let config_path = Path::new(&target_dir).join("uucore_target_util.txt"); if let Ok(content) = fs::read_to_string(&config_path) { let util_name = content.trim(); if !util_name.is_empty() && util_name != "multicall" { @@ -131,7 +131,7 @@ fn detect_target_utility() -> Option { /// Returns an error if the locales for `util_name` or `uucore` cannot be found /// or if writing to the `embedded_file` fails. fn embed_single_utility_locale( - embedded_file: &mut std::fs::File, + embedded_file: &mut File, project_root: &Path, util_name: &str, locales_to_embed: &(String, Option), @@ -168,7 +168,7 @@ fn embed_single_utility_locale( /// Returns an error if the `src/uu` directory cannot be read, if any utility /// locales cannot be embedded, or if flushing the `embedded_file` fails. fn embed_all_utility_locales( - embedded_file: &mut std::fs::File, + embedded_file: &mut File, project_root: &Path, locales_to_embed: &(String, Option), ) -> Result<(), Box> { @@ -219,7 +219,7 @@ fn embed_all_utility_locales( /// Returns an error if the directory containing the crate cannot be read or /// if writing to the `embedded_file` fails. fn embed_static_utility_locales( - embedded_file: &mut std::fs::File, + embedded_file: &mut File, locales_to_embed: &(String, Option), ) -> Result<(), Box> { use std::env; @@ -312,7 +312,7 @@ where /// Returns an error if the file at `locale_path` cannot be read or if /// writing to `embedded_file` fails. fn embed_locale_file( - embedded_file: &mut std::fs::File, + embedded_file: &mut File, locale_path: &Path, locale_key: &str, locale: &str, @@ -348,13 +348,13 @@ fn embed_locale_file( /// Returns an error if `for_each_locale` fails, which typically happens if /// reading a locale file or writing to the `embedded_file` fails. fn embed_component_locales( - embedded_file: &mut std::fs::File, + embedded_file: &mut File, locales: &(String, Option), component_name: &str, path_builder: F, ) -> Result<(), Box> where - F: Fn(&str) -> std::path::PathBuf, + F: Fn(&str) -> PathBuf, { for_each_locale(locales, |locale| { let locale_path = path_builder(locale); diff --git a/src/uucore/src/lib/features/backup_control.rs b/src/uucore/src/lib/features/backup_control.rs index dd5f6b610..6c26b9d58 100644 --- a/src/uucore/src/lib/features/backup_control.rs +++ b/src/uucore/src/lib/features/backup_control.rs @@ -211,7 +211,7 @@ pub mod arguments { clap::Arg::new(OPT_BACKUP) .long("backup") .help("make a backup of each existing destination file") - .action(clap::ArgAction::Set) + .action(ArgAction::Set) .require_equals(true) .num_args(0..=1) .value_name("CONTROL") @@ -231,7 +231,7 @@ pub mod arguments { .short('S') .long("suffix") .help("override the usual backup suffix") - .action(clap::ArgAction::Set) + .action(ArgAction::Set) .value_name("SUFFIX") .allow_hyphen_values(true) } @@ -245,7 +245,7 @@ pub mod arguments { /// 2. From the "SIMPLE_BACKUP_SUFFIX" environment variable, if present /// 3. By using the default '~' if none of the others apply /// -/// This function directly takes [`clap::ArgMatches`] as argument and looks for +/// This function directly takes [`ArgMatches`] as argument and looks for /// the '-S' and '--suffix' arguments itself. pub fn determine_backup_suffix(matches: &ArgMatches) -> String { let supplied_suffix = matches.get_one::(arguments::OPT_SUFFIX); @@ -261,7 +261,7 @@ pub fn determine_backup_suffix(matches: &ArgMatches) -> String { /// Parses the backup options according to the [GNU manual][1], and converts /// them to an instance of `BackupMode` for further processing. /// -/// Takes [`clap::ArgMatches`] as argument which **must** contain the options +/// Takes [`ArgMatches`] as argument which **must** contain the options /// from [`arguments::backup()`] and [`arguments::backup_no_args()`]. Otherwise /// the `NoBackup` mode is returned unconditionally. /// @@ -505,7 +505,7 @@ mod tests { // Environment variable for "VERSION_CONTROL" static ENV_VERSION_CONTROL: &str = "VERSION_CONTROL"; - fn make_app() -> clap::Command { + fn make_app() -> Command { Command::new("command") .arg(arguments::backup()) .arg(arguments::backup_no_args()) diff --git a/src/uucore/src/lib/features/benchmark.rs b/src/uucore/src/lib/features/benchmark.rs index 29f2c1a59..e2dfac1e7 100644 --- a/src/uucore/src/lib/features/benchmark.rs +++ b/src/uucore/src/lib/features/benchmark.rs @@ -22,7 +22,7 @@ pub fn create_test_file(data: &[u8], temp_dir: &Path) -> PathBuf { writer.write_all(data).unwrap(); writer.flush().unwrap(); // Ensure data is fully written to disk before returning - std::mem::drop(writer); + drop(writer); File::open(&file_path).unwrap().sync_all().unwrap(); file_path } @@ -173,7 +173,7 @@ pub mod text_data { pub fn generate_ascii_data_simple(num_lines: usize) -> Vec { let mut data = Vec::new(); for i in 0..num_lines { - let line = format!("line_{:06}\n", (num_lines - i - 1)); + let line = format!("line_{:06}\n", num_lines - i - 1); data.extend_from_slice(line.as_bytes()); } data diff --git a/src/uucore/src/lib/features/checksum/compute.rs b/src/uucore/src/lib/features/checksum/compute.rs index c5b0cf6e4..4a51f81d7 100644 --- a/src/uucore/src/lib/features/checksum/compute.rs +++ b/src/uucore/src/lib/features/checksum/compute.rs @@ -10,7 +10,9 @@ use std::fs::File; use std::io::{self, BufReader, Read, Write}; use std::path::Path; -use crate::checksum::{AlgoKind, ChecksumError, SizedAlgoKind, digest_reader, escape_filename}; +use crate::checksum::{ + AlgoKind, ChecksumError, ReadingMode, SizedAlgoKind, digest_reader, escape_filename, +}; use crate::error::{FromIo, UResult, USimpleError}; use crate::line_ending::LineEnding; use crate::sum::DigestOutput; @@ -36,33 +38,6 @@ pub struct ChecksumComputeOptions { pub line_ending: LineEnding, } -/// Reading mode used to compute digest. -/// -/// On most linux systems, this is irrelevant, as there is no distinction -/// between text and binary files. Refer to GNU's cksum documentation for more -/// information. -/// -/// As discussed in #9168, we decide to ignore the reading mode to compute the -/// digest, both on Windows and UNIX. The reason for that is that this is a -/// legacy feature that is poorly documented and used. This enum is kept -/// nonetheless to still take into account the flags passed to cksum when -/// generating untagged lines. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum ReadingMode { - Binary, - Text, -} - -impl ReadingMode { - #[inline] - fn as_char(&self) -> char { - match self { - Self::Binary => '*', - Self::Text => ' ', - } - } -} - /// Whether to write the digest as hexadecimal or encoded in base64. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum DigestFormat { @@ -72,8 +47,8 @@ pub enum DigestFormat { impl DigestFormat { #[inline] - fn is_base64(&self) -> bool { - *self == Self::Base64 + fn is_base64(self) -> bool { + self == Self::Base64 } } @@ -181,7 +156,7 @@ fn print_legacy_checksum( filename: &OsStr, sum: &DigestOutput, size: usize, -) -> UResult<()> { +) { debug_assert!(options.algo_kind.is_legacy()); debug_assert!(matches!(sum, DigestOutput::U16(_) | DigestOutput::Crc(_))); @@ -216,15 +191,9 @@ fn print_legacy_checksum( print!(" "); let _dropped_result = io::stdout().write_all(escaped_filename.as_bytes()); } - - Ok(()) } -fn print_tagged_checksum( - options: &ChecksumComputeOptions, - filename: &OsStr, - sum: &String, -) -> UResult<()> { +fn print_tagged_checksum(options: &ChecksumComputeOptions, filename: &OsStr, sum: &String) { let (escaped_filename, prefix) = if options.line_ending == LineEnding::Nul { (filename.to_string_lossy().to_string(), "") } else { @@ -239,8 +208,6 @@ fn print_tagged_checksum( // Print closing parenthesis and sum print!(") = {sum}"); - - Ok(()) } fn print_untagged_checksum( @@ -248,7 +215,7 @@ fn print_untagged_checksum( filename: &OsStr, sum: &String, reading_mode: ReadingMode, -) -> UResult<()> { +) { let (escaped_filename, prefix) = if options.line_ending == LineEnding::Nul { (filename.to_string_lossy().to_string(), "") } else { @@ -260,8 +227,6 @@ fn print_untagged_checksum( // Print filename let _dropped_result = io::stdout().write_all(escaped_filename.as_bytes()); - - Ok(()) } /// Calculate checksum @@ -315,7 +280,7 @@ where // Always compute the "binary" version of the digest, i.e. on Windows, // never handle CRLFs specifically. - let (digest_output, sz) = digest_reader(&mut digest, &mut file, /* binary: */ true) + let (digest_output, sz) = digest_reader(&mut digest, &mut file, ReadingMode::Binary) .map_err_context(|| translate!("checksum-error-failed-to-read-input"))?; // Encodes the sum if df is Base64, leaves as-is otherwise. @@ -334,14 +299,14 @@ where return Ok(()); } OutputFormat::Legacy => { - print_legacy_checksum(&options, filename, &digest_output, sz)?; + print_legacy_checksum(&options, filename, &digest_output, sz); } OutputFormat::Tagged(digest_format) => { print_tagged_checksum( &options, filename, &encode_sum(digest_output, digest_format)?, - )?; + ); } OutputFormat::Untagged(digest_format, reading_mode) => { print_untagged_checksum( @@ -349,7 +314,7 @@ where filename, &encode_sum(digest_output, digest_format)?, reading_mode, - )?; + ); } } diff --git a/src/uucore/src/lib/features/checksum/mod.rs b/src/uucore/src/lib/features/checksum/mod.rs index 7ae4c775b..0c6dc54f3 100644 --- a/src/uucore/src/lib/features/checksum/mod.rs +++ b/src/uucore/src/lib/features/checksum/mod.rs @@ -417,10 +417,37 @@ impl UError for ChecksumError { } } +/// Reading mode used to compute digest. +/// +/// On most linux systems, this is irrelevant, as there is no distinction +/// between text and binary files. Refer to GNU's cksum documentation for more +/// information. +/// +/// As discussed in #9168, we decide to ignore the reading mode to compute the +/// digest, both on Windows and UNIX. The reason for that is that this is a +/// legacy feature that is poorly documented and used. This enum is kept +/// nonetheless to still take into account the flags passed to cksum when +/// generating untagged lines. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ReadingMode { + Binary, + Text, +} + +impl ReadingMode { + #[inline] + fn as_char(self) -> char { + match self { + Self::Binary => '*', + Self::Text => ' ', + } + } +} + pub fn digest_reader( digest: &mut Box, reader: &mut T, - binary: bool, + mode: ReadingMode, ) -> io::Result<(DigestOutput, usize)> { digest.reset(); @@ -436,8 +463,8 @@ pub fn digest_reader( // `DigestWriter` and only written if the following character is // "\n". But when "\r" is the last character read, we need to force // it to be written.) - let mut digest_writer = DigestWriter::new(digest, binary); - let output_size = std::io::copy(reader, &mut digest_writer)? as usize; + let mut digest_writer = DigestWriter::new(digest, mode == ReadingMode::Binary); + let output_size = io::copy(reader, &mut digest_writer)? as usize; digest_writer.finalize(); Ok((digest.result(), output_size)) diff --git a/src/uucore/src/lib/features/checksum/validate.rs b/src/uucore/src/lib/features/checksum/validate.rs index 43fcd95e9..7fede1c49 100644 --- a/src/uucore/src/lib/features/checksum/validate.rs +++ b/src/uucore/src/lib/features/checksum/validate.rs @@ -14,8 +14,10 @@ use std::io::{self, BufReader, Read, Write, stderr, stdin}; use os_display::Quotable; -use crate::checksum::{AlgoKind, ChecksumError, SizedAlgoKind, digest_reader, unescape_filename}; -use crate::error::{FromIo, UError, UResult, USimpleError}; +use crate::checksum::{ + AlgoKind, ChecksumError, ReadingMode, SizedAlgoKind, digest_reader, unescape_filename, +}; +use crate::error::{FromIo, UError, UIoError, UResult, USimpleError}; use crate::quoting_style::{QuotingStyle, locale_aware_escape_name}; use crate::sum::DigestOutput; use crate::{ @@ -215,7 +217,7 @@ impl FileChecksumResult { /// The cli options might prevent to display on the outcome of the /// comparison on STDOUT. - fn can_display(&self, verbose: ChecksumVerbose) -> bool { + fn can_display(self, verbose: ChecksumVerbose) -> bool { match self { Self::Ok => verbose.over_quiet(), Self::Failed => verbose.over_status(), @@ -535,7 +537,7 @@ fn get_file_to_check( let filename_bytes = os_str_as_bytes(filename).map_err(|e| LineCheckError::UError(e.into()))?; if filename == "-" { - Ok(Box::new(io::stdin())) // Use stdin if "-" is specified in the checksum file + Ok(Box::new(stdin())) // Use stdin if "-" is specified in the checksum file } else { let failed_open = || { write_file_report( @@ -679,28 +681,28 @@ fn compute_and_check_digest_from_file( // Read the file and calculate the checksum let mut digest = algo.create_digest(); - // TODO: improve function signature to use ReadingMode instead of binary bool // Set binary to false because --binary is not supported with --check - let (calculated_checksum, _) = match digest_reader(&mut digest, &mut file_reader, false) { - Ok(result) => result, - Err(err) => { - show!(err.map_err_context(|| { - locale_aware_escape_name(&real_filename_to_check, QuotingStyle::SHELL_ESCAPE) - .to_string_lossy() - .to_string() - })); + let (calculated_checksum, _) = + match digest_reader(&mut digest, &mut file_reader, ReadingMode::Text) { + Ok(result) => result, + Err(err) => { + show!(err.map_err_context(|| { + locale_aware_escape_name(&real_filename_to_check, QuotingStyle::SHELL_ESCAPE) + .to_string_lossy() + .to_string() + })); - write_file_report( - std::io::stdout(), - filename, - FileChecksumResult::CantOpen, - prefix, - opts.verbose, - ); - return Err(LineCheckError::CantOpenFile); - } - }; + write_file_report( + io::stdout(), + filename, + FileChecksumResult::CantOpen, + prefix, + opts.verbose, + ); + return Err(LineCheckError::CantOpenFile); + } + }; // Do the checksum validation let checksum_correct = match calculated_checksum { @@ -709,7 +711,7 @@ fn compute_and_check_digest_from_file( DigestOutput::U16(n) => n.to_be_bytes() == expected_checksum, }; write_file_report( - std::io::stdout(), + io::stdout(), filename, FileChecksumResult::from_bool(checksum_correct), prefix, @@ -851,7 +853,6 @@ fn process_checksum_file( }; let reader = BufReader::new(file); - let lines = read_os_string_lines(reader).collect::>(); // cached_line_format is used to ensure that several non algo-based checksum line // will use the same parser. @@ -861,9 +862,16 @@ fn process_checksum_file( // Behavior tested in gnu_cksum_c::test_warn let mut last_algo = None; - for (i, line) in lines.iter().enumerate() { + for (i, line_res) in read_os_string_lines(reader).enumerate() { + let line = line_res.map_err(|e| { + USimpleError::new( + UIoError::from(e).code(), + format!("{}: read error", filename_input.maybe_quote()), + ) + })?; + let line_result = process_checksum_line( - line, + &line, i, cli_algo_kind, cli_algo_length, diff --git a/src/uucore/src/lib/features/format/argument.rs b/src/uucore/src/lib/features/format/argument.rs index 3561bd4a7..b4eea0313 100644 --- a/src/uucore/src/lib/features/format/argument.rs +++ b/src/uucore/src/lib/features/format/argument.rs @@ -73,7 +73,7 @@ impl<'a> FormatArguments<'a> { self.next_arg_position = self.current_offset; } - pub fn next_char(&mut self, position: &ArgumentLocation) -> u8 { + pub fn next_char(&mut self, position: ArgumentLocation) -> u8 { match self.next_arg(position) { Some(FormatArgument::Char(c)) => *c as u8, Some(FormatArgument::Unparsed(os)) => match os_str_as_bytes(os) { @@ -84,14 +84,14 @@ impl<'a> FormatArguments<'a> { } } - pub fn next_string(&mut self, position: &ArgumentLocation) -> &'a OsStr { + pub fn next_string(&mut self, position: ArgumentLocation) -> &'a OsStr { match self.next_arg(position) { Some(FormatArgument::Unparsed(os) | FormatArgument::String(os)) => os, _ => "".as_ref(), } } - pub fn next_i64(&mut self, position: &ArgumentLocation) -> i64 { + pub fn next_i64(&mut self, position: ArgumentLocation) -> i64 { match self.next_arg(position) { Some(FormatArgument::SignedInt(n)) => *n, Some(FormatArgument::Unparsed(os)) => Self::get_num::(os), @@ -99,7 +99,7 @@ impl<'a> FormatArguments<'a> { } } - pub fn next_u64(&mut self, position: &ArgumentLocation) -> u64 { + pub fn next_u64(&mut self, position: ArgumentLocation) -> u64 { match self.next_arg(position) { Some(FormatArgument::UnsignedInt(n)) => *n, Some(FormatArgument::Unparsed(os)) => Self::get_num::(os), @@ -107,7 +107,7 @@ impl<'a> FormatArguments<'a> { } } - pub fn next_extended_big_decimal(&mut self, position: &ArgumentLocation) -> ExtendedBigDecimal { + pub fn next_extended_big_decimal(&mut self, position: ArgumentLocation) -> ExtendedBigDecimal { match self.next_arg(position) { Some(FormatArgument::Float(n)) => n.clone(), Some(FormatArgument::Unparsed(os)) => Self::get_num::(os), @@ -188,14 +188,14 @@ impl<'a> FormatArguments<'a> { self.args.get(pos) } - fn next_arg(&mut self, position: &ArgumentLocation) -> Option<&'a FormatArgument> { + fn next_arg(&mut self, position: ArgumentLocation) -> Option<&'a FormatArgument> { match position { ArgumentLocation::NextArgument => { let arg = self.args.get(self.next_arg_position); self.next_arg_position += 1; arg } - ArgumentLocation::Position(pos) => self.get_at_relative_position(*pos), + ArgumentLocation::Position(pos) => self.get_at_relative_position(pos), } } } @@ -257,7 +257,7 @@ mod tests { assert!(!args.is_exhausted()); assert_eq!(Some(&FormatArgument::Char('a')), args.peek_arg()); assert!(!args.is_exhausted()); // Peek shouldn't consume - assert_eq!(b'a', args.next_char(&ArgumentLocation::NextArgument)); + assert_eq!(b'a', args.next_char(ArgumentLocation::NextArgument)); args.start_next_batch(); assert!(args.is_exhausted()); // After batch, exhausted with a single arg assert_eq!(None, args.peek_arg()); @@ -278,26 +278,26 @@ mod tests { ]); // First batch - two sequential calls - assert_eq!(b'z', args.next_char(&ArgumentLocation::NextArgument)); - assert_eq!(b'y', args.next_char(&ArgumentLocation::NextArgument)); + assert_eq!(b'z', args.next_char(ArgumentLocation::NextArgument)); + assert_eq!(b'y', args.next_char(ArgumentLocation::NextArgument)); args.start_next_batch(); assert!(!args.is_exhausted()); // Second batch - same pattern - assert_eq!(b'x', args.next_char(&ArgumentLocation::NextArgument)); - assert_eq!(b'w', args.next_char(&ArgumentLocation::NextArgument)); + assert_eq!(b'x', args.next_char(ArgumentLocation::NextArgument)); + assert_eq!(b'w', args.next_char(ArgumentLocation::NextArgument)); args.start_next_batch(); assert!(!args.is_exhausted()); // Third batch - same pattern - assert_eq!(b'v', args.next_char(&ArgumentLocation::NextArgument)); - assert_eq!(b'u', args.next_char(&ArgumentLocation::NextArgument)); + assert_eq!(b'v', args.next_char(ArgumentLocation::NextArgument)); + assert_eq!(b'u', args.next_char(ArgumentLocation::NextArgument)); args.start_next_batch(); assert!(!args.is_exhausted()); // Fourth batch - same pattern (last batch) - assert_eq!(b't', args.next_char(&ArgumentLocation::NextArgument)); - assert_eq!(b's', args.next_char(&ArgumentLocation::NextArgument)); + assert_eq!(b't', args.next_char(ArgumentLocation::NextArgument)); + assert_eq!(b's', args.next_char(ArgumentLocation::NextArgument)); args.start_next_batch(); assert!(args.is_exhausted()); } @@ -316,20 +316,20 @@ mod tests { let mut args = FormatArguments::new(&args); // First batch - next_char followed by next_string - assert_eq!(b'a', args.next_char(&ArgumentLocation::NextArgument)); - assert_eq!("hello", args.next_string(&ArgumentLocation::NextArgument)); + assert_eq!(b'a', args.next_char(ArgumentLocation::NextArgument)); + assert_eq!("hello", args.next_string(ArgumentLocation::NextArgument)); args.start_next_batch(); assert!(!args.is_exhausted()); // Second batch - same pattern - assert_eq!(b'1', args.next_char(&ArgumentLocation::NextArgument)); // First byte of 123 - assert_eq!("world", args.next_string(&ArgumentLocation::NextArgument)); + assert_eq!(b'1', args.next_char(ArgumentLocation::NextArgument)); // First byte of 123 + assert_eq!("world", args.next_string(ArgumentLocation::NextArgument)); args.start_next_batch(); assert!(!args.is_exhausted()); // Third batch - same pattern (last batch) - assert_eq!(b'z', args.next_char(&ArgumentLocation::NextArgument)); - assert_eq!("test", args.next_string(&ArgumentLocation::NextArgument)); + assert_eq!(b'z', args.next_char(ArgumentLocation::NextArgument)); + assert_eq!("test", args.next_string(ArgumentLocation::NextArgument)); args.start_next_batch(); assert!(args.is_exhausted()); } @@ -354,23 +354,23 @@ mod tests { ]); // First batch - positional access - assert_eq!(b'b', args.next_char(&non_zero_pos(2))); // Position 2 - assert_eq!(b'a', args.next_char(&non_zero_pos(1))); // Position 1 - assert_eq!(b'c', args.next_char(&non_zero_pos(3))); // Position 3 + assert_eq!(b'b', args.next_char(non_zero_pos(2))); // Position 2 + assert_eq!(b'a', args.next_char(non_zero_pos(1))); // Position 1 + assert_eq!(b'c', args.next_char(non_zero_pos(3))); // Position 3 args.start_next_batch(); assert!(!args.is_exhausted()); // Second batch - same positional pattern - assert_eq!(b'e', args.next_char(&non_zero_pos(2))); // Position 2 - assert_eq!(b'd', args.next_char(&non_zero_pos(1))); // Position 1 - assert_eq!(b'f', args.next_char(&non_zero_pos(3))); // Position 3 + assert_eq!(b'e', args.next_char(non_zero_pos(2))); // Position 2 + assert_eq!(b'd', args.next_char(non_zero_pos(1))); // Position 1 + assert_eq!(b'f', args.next_char(non_zero_pos(3))); // Position 3 args.start_next_batch(); assert!(!args.is_exhausted()); // Third batch - same positional pattern (last batch) - assert_eq!(b'h', args.next_char(&non_zero_pos(2))); // Position 2 - assert_eq!(b'g', args.next_char(&non_zero_pos(1))); // Position 1 - assert_eq!(b'i', args.next_char(&non_zero_pos(3))); // Position 3 + assert_eq!(b'h', args.next_char(non_zero_pos(2))); // Position 2 + assert_eq!(b'g', args.next_char(non_zero_pos(1))); // Position 1 + assert_eq!(b'i', args.next_char(non_zero_pos(3))); // Position 3 args.start_next_batch(); assert!(args.is_exhausted()); } @@ -390,20 +390,20 @@ mod tests { ]); // First batch - mix of sequential and positional - assert_eq!(b'a', args.next_char(&ArgumentLocation::NextArgument)); // Sequential - assert_eq!(b'c', args.next_char(&non_zero_pos(3))); // Positional + assert_eq!(b'a', args.next_char(ArgumentLocation::NextArgument)); // Sequential + assert_eq!(b'c', args.next_char(non_zero_pos(3))); // Positional args.start_next_batch(); assert!(!args.is_exhausted()); // Second batch - same mixed pattern - assert_eq!(b'd', args.next_char(&ArgumentLocation::NextArgument)); // Sequential - assert_eq!(b'f', args.next_char(&non_zero_pos(3))); // Positional + assert_eq!(b'd', args.next_char(ArgumentLocation::NextArgument)); // Sequential + assert_eq!(b'f', args.next_char(non_zero_pos(3))); // Positional args.start_next_batch(); assert!(!args.is_exhausted()); // Last batch - same mixed pattern - assert_eq!(b'g', args.next_char(&ArgumentLocation::NextArgument)); // Sequential - assert_eq!(b'\0', args.next_char(&non_zero_pos(3))); // Out of bounds + assert_eq!(b'g', args.next_char(ArgumentLocation::NextArgument)); // Sequential + assert_eq!(b'\0', args.next_char(non_zero_pos(3))); // Out of bounds args.start_next_batch(); assert!(args.is_exhausted()); } @@ -422,17 +422,17 @@ mod tests { let mut args = FormatArguments::new(&args); // First batch - i64, u64, decimal - assert_eq!(10, args.next_i64(&ArgumentLocation::NextArgument)); - assert_eq!(20, args.next_u64(&ArgumentLocation::NextArgument)); - let result = args.next_extended_big_decimal(&ArgumentLocation::NextArgument); + assert_eq!(10, args.next_i64(ArgumentLocation::NextArgument)); + assert_eq!(20, args.next_u64(ArgumentLocation::NextArgument)); + let result = args.next_extended_big_decimal(ArgumentLocation::NextArgument); assert_eq!(ExtendedBigDecimal::zero(), result); args.start_next_batch(); assert!(!args.is_exhausted()); // Second batch - same pattern - assert_eq!(30, args.next_i64(&ArgumentLocation::NextArgument)); - assert_eq!(40, args.next_u64(&ArgumentLocation::NextArgument)); - let result = args.next_extended_big_decimal(&ArgumentLocation::NextArgument); + assert_eq!(30, args.next_i64(ArgumentLocation::NextArgument)); + assert_eq!(40, args.next_u64(ArgumentLocation::NextArgument)); + let result = args.next_extended_big_decimal(ArgumentLocation::NextArgument); assert_eq!(ExtendedBigDecimal::zero(), result); args.start_next_batch(); assert!(args.is_exhausted()); @@ -450,14 +450,14 @@ mod tests { let mut args = FormatArguments::new(&args); // First batch - string, number - assert_eq!("hello", args.next_string(&ArgumentLocation::NextArgument)); - assert_eq!(123, args.next_i64(&ArgumentLocation::NextArgument)); + assert_eq!("hello", args.next_string(ArgumentLocation::NextArgument)); + assert_eq!(123, args.next_i64(ArgumentLocation::NextArgument)); args.start_next_batch(); assert!(!args.is_exhausted()); // Second batch - same pattern - assert_eq!("hello", args.next_string(&ArgumentLocation::NextArgument)); - assert_eq!(456, args.next_i64(&ArgumentLocation::NextArgument)); + assert_eq!("hello", args.next_string(ArgumentLocation::NextArgument)); + assert_eq!(456, args.next_i64(ArgumentLocation::NextArgument)); args.start_next_batch(); assert!(args.is_exhausted()); } @@ -476,16 +476,16 @@ mod tests { let mut args = FormatArguments::new(&args); // First batch - positional access of different types - assert_eq!(b'a', args.next_char(&non_zero_pos(1))); - assert_eq!("test", args.next_string(&non_zero_pos(2))); - assert_eq!(42, args.next_u64(&non_zero_pos(3))); + assert_eq!(b'a', args.next_char(non_zero_pos(1))); + assert_eq!("test", args.next_string(non_zero_pos(2))); + assert_eq!(42, args.next_u64(non_zero_pos(3))); args.start_next_batch(); assert!(!args.is_exhausted()); // Second batch - same pattern - assert_eq!(b'b', args.next_char(&non_zero_pos(1))); - assert_eq!("more", args.next_string(&non_zero_pos(2))); - assert_eq!(99, args.next_u64(&non_zero_pos(3))); + assert_eq!(b'b', args.next_char(non_zero_pos(1))); + assert_eq!("more", args.next_string(non_zero_pos(2))); + assert_eq!(99, args.next_u64(non_zero_pos(3))); args.start_next_batch(); assert!(args.is_exhausted()); } @@ -502,14 +502,14 @@ mod tests { ]); // First batch - assert_eq!(b'a', args.next_char(&ArgumentLocation::NextArgument)); - assert_eq!(b'c', args.next_char(&non_zero_pos(3))); + assert_eq!(b'a', args.next_char(ArgumentLocation::NextArgument)); + assert_eq!(b'c', args.next_char(non_zero_pos(3))); args.start_next_batch(); assert!(!args.is_exhausted()); // Second batch (partial) - assert_eq!(b'd', args.next_char(&ArgumentLocation::NextArgument)); - assert_eq!(b'\0', args.next_char(&non_zero_pos(3))); // Out of bounds + assert_eq!(b'd', args.next_char(ArgumentLocation::NextArgument)); + assert_eq!(b'\0', args.next_char(non_zero_pos(3))); // Out of bounds args.start_next_batch(); assert!(args.is_exhausted()); } diff --git a/src/uucore/src/lib/features/format/escape.rs b/src/uucore/src/lib/features/format/escape.rs index cba03a8a6..2c6a0283a 100644 --- a/src/uucore/src/lib/features/format/escape.rs +++ b/src/uucore/src/lib/features/format/escape.rs @@ -33,21 +33,21 @@ enum Base { } impl Base { - fn as_base(&self) -> u8 { + fn as_base(self) -> u8 { match self { Self::Oct(_) => 8, Self::Hex => 16, } } - fn max_digits(&self) -> u8 { + fn max_digits(self) -> u8 { match self { - Self::Oct(parsing) => *parsing as u8, + Self::Oct(parsing) => parsing as u8, Self::Hex => 2, } } - fn convert_digit(&self, c: u8) -> Option { + fn convert_digit(self, c: u8) -> Option { match self { Self::Oct(_) => { if matches!(c, b'0'..=b'7') { diff --git a/src/uucore/src/lib/features/format/num_format.rs b/src/uucore/src/lib/features/format/num_format.rs index 1fa07977e..9a09b3f7d 100644 --- a/src/uucore/src/lib/features/format/num_format.rs +++ b/src/uucore/src/lib/features/format/num_format.rs @@ -257,8 +257,8 @@ impl Formatter<&ExtendedBigDecimal> for Float { let mut alignment = self.alignment; - let s = match abs { - ExtendedBigDecimal::BigDecimal(bd) => match self.variant { + let s = if let ExtendedBigDecimal::BigDecimal(bd) = abs { + match self.variant { FloatVariant::Decimal => { format_float_decimal(&bd, self.precision, self.force_decimal) } @@ -271,14 +271,13 @@ impl Formatter<&ExtendedBigDecimal> for Float { FloatVariant::Hexadecimal => { format_float_hexadecimal(&bd, self.precision, self.case, self.force_decimal) } - }, - _ => { - // Pad non-finite numbers with spaces, not zeros. - if alignment == NumberAlignment::RightZero { - alignment = NumberAlignment::RightSpace; - } - format_float_non_finite(&abs, self.case) } + } else { + // Pad non-finite numbers with spaces, not zeros. + if alignment == NumberAlignment::RightZero { + alignment = NumberAlignment::RightSpace; + } + format_float_non_finite(&abs, self.case) }; let sign_indicator = get_sign_indicator(self.positive_sign, negative); @@ -1128,11 +1127,11 @@ mod test { ForceDecimal::No, ) }; - assert_eq!(f(0.1171875), "0.117188"); - assert_eq!(f(0.01171875), "0.0117188"); - assert_eq!(f(0.001171875), "0.00117187"); - assert_eq!(f(0.0001171875), "0.000117187"); - assert_eq!(f(0.001171875001), "0.00117188"); + assert_eq!(f(0.117_187_5), "0.117188"); + assert_eq!(f(0.011_718_75), "0.0117188"); + assert_eq!(f(0.001_171_875), "0.00117187"); + assert_eq!(f(0.000_117_187_5), "0.000117187"); + assert_eq!(f(0.001_171_875_001), "0.00117188"); } #[test] @@ -1149,7 +1148,7 @@ mod test { assert_eq!(f(0.001), "0.001"); assert_eq!(f(0.0001), "0.0001"); assert_eq!(f(0.00001), "1e-05"); - assert_eq!(f(0.000001), "1e-06"); + assert_eq!(f(0.000_001), "1e-06"); } /// Wrapper function to get a string out of Format.fmt() diff --git a/src/uucore/src/lib/features/format/spec.rs b/src/uucore/src/lib/features/format/spec.rs index 3bef0fbb1..3587816b7 100644 --- a/src/uucore/src/lib/features/format/spec.rs +++ b/src/uucore/src/lib/features/format/spec.rs @@ -357,7 +357,7 @@ impl Spec { let (width, neg_width) = resolve_asterisk_width(*width, args).unwrap_or_default(); write_padded( writer, - &[args.next_char(position)], + &[args.next_char(*position)], width, *align_left || neg_width, ) @@ -377,7 +377,7 @@ impl Spec { // TODO: We need to not use Rust's formatting for aligning the output, // so that we can just write bytes to stdout without panicking. let precision = resolve_asterisk_precision(*precision, args); - let os_str = args.next_string(position); + let os_str = args.next_string(*position); let bytes = os_str_as_bytes(os_str)?; let truncated = match precision { @@ -387,7 +387,7 @@ impl Spec { write_padded(writer, truncated, width, *align_left || neg_width) } Self::EscapedString { position } => { - let os_str = args.next_string(position); + let os_str = args.next_string(*position); let bytes = os_str_as_bytes(os_str)?; let mut parsed = Vec::::new(); @@ -404,7 +404,7 @@ impl Spec { } Self::QuotedString { position } => { let s = locale_aware_escape_name( - args.next_string(position), + args.next_string(*position), QuotingStyle::SHELL_ESCAPE, ); let bytes = os_str_as_bytes(&s)?; @@ -419,7 +419,7 @@ impl Spec { } => { let (width, neg_width) = resolve_asterisk_width(*width, args).unwrap_or((0, false)); let precision = resolve_asterisk_precision(*precision, args).unwrap_or_default(); - let i = args.next_i64(position); + let i = args.next_i64(*position); if precision as u64 > i32::MAX as u64 { return Err(FormatError::InvalidPrecision(precision.to_string())); @@ -447,7 +447,7 @@ impl Spec { } => { let (width, neg_width) = resolve_asterisk_width(*width, args).unwrap_or((0, false)); let precision = resolve_asterisk_precision(*precision, args).unwrap_or_default(); - let i = args.next_u64(position); + let i = args.next_u64(*position); if precision as u64 > i32::MAX as u64 { return Err(FormatError::InvalidPrecision(precision.to_string())); @@ -478,7 +478,7 @@ impl Spec { } => { let (width, neg_width) = resolve_asterisk_width(*width, args).unwrap_or((0, false)); let precision = resolve_asterisk_precision(*precision, args); - let f: ExtendedBigDecimal = args.next_extended_big_decimal(position); + let f: ExtendedBigDecimal = args.next_extended_big_decimal(*position); if precision.is_some_and(|p| p as u64 > i32::MAX as u64) { return Err(FormatError::InvalidPrecision( @@ -515,7 +515,7 @@ fn resolve_asterisk_width( match option { None => None, Some(CanAsterisk::Asterisk(loc)) => { - let nb = args.next_i64(&loc); + let nb = args.next_i64(loc); if nb < 0 { Some((usize::try_from(-(nb as isize)).ok().unwrap_or(0), true)) } else { @@ -534,7 +534,7 @@ fn resolve_asterisk_precision( ) -> Option { match option { None => None, - Some(CanAsterisk::Asterisk(loc)) => match args.next_i64(&loc) { + Some(CanAsterisk::Asterisk(loc)) => match args.next_i64(loc) { v if v >= 0 => usize::try_from(v).ok(), v if v < 0 => Some(0usize), _ => None, diff --git a/src/uucore/src/lib/features/fs.rs b/src/uucore/src/lib/features/fs.rs index 94ed5d1dc..615316238 100644 --- a/src/uucore/src/lib/features/fs.rs +++ b/src/uucore/src/lib/features/fs.rs @@ -843,7 +843,7 @@ pub fn make_fifo(path: &Path) -> std::io::Result<()> { let name = CString::new(path.to_str().unwrap()).unwrap(); let err = unsafe { mkfifo(name.as_ptr(), 0o666) }; if err == -1 { - Err(std::io::Error::from_raw_os_error(err)) + Err(Error::from_raw_os_error(err)) } else { Ok(()) } @@ -957,8 +957,7 @@ mod tests { let path = Path::new(test.path); let normalized = normalize_path(path); assert_eq!( - test.test - .replace('/', std::path::MAIN_SEPARATOR.to_string().as_str()), + test.test.replace('/', MAIN_SEPARATOR.to_string().as_str()), normalized.to_str().expect("Path is not valid utf-8!") ); } @@ -1153,7 +1152,7 @@ mod tests { assert!(make_fifo(&path).is_ok()); // Check that it is indeed a FIFO. - assert!(std::fs::metadata(&path).unwrap().file_type().is_fifo()); + assert!(fs::metadata(&path).unwrap().file_type().is_fifo()); // Check that we can write to it and read from it. // @@ -1161,7 +1160,7 @@ mod tests { // otherwise `write` would block indefinitely while waiting // for the `read`. let path2 = path.clone(); - std::thread::spawn(move || assert!(std::fs::write(&path2, b"foo").is_ok())); - assert_eq!(std::fs::read(&path).unwrap(), b"foo"); + std::thread::spawn(move || assert!(fs::write(&path2, b"foo").is_ok())); + assert_eq!(fs::read(&path).unwrap(), b"foo"); } } diff --git a/src/uucore/src/lib/features/fsext.rs b/src/uucore/src/lib/features/fsext.rs index c31b82725..173fe918f 100644 --- a/src/uucore/src/lib/features/fsext.rs +++ b/src/uucore/src/lib/features/fsext.rs @@ -143,6 +143,10 @@ impl From<&str> for MetadataTimeField { } #[cfg(unix)] +#[expect( + clippy::unnecessary_wraps, + reason = "fn sig must match on all platforms" +)] fn metadata_get_change_time(md: &Metadata) -> Option { let mut st = UNIX_EPOCH; let (secs, nsecs) = (md.ctime(), md.ctime_nsec()); @@ -586,8 +590,7 @@ impl FsUsage { blocks: statvfs.f_blocks, bfree: statvfs.f_bfree, bavail: statvfs.f_bavail.try_into().unwrap(), - bavail_top_bit_set: ((std::convert::TryInto::::try_into(statvfs.f_bavail) - .unwrap()) + bavail_top_bit_set: ((TryInto::::try_into(statvfs.f_bavail).unwrap()) & (1u64.rotate_right(1))) != 0, files: statvfs.f_files, @@ -599,8 +602,7 @@ impl FsUsage { blocks: statvfs.f_blocks, bfree: statvfs.f_bfree, bavail: statvfs.f_bavail.try_into().unwrap(), - bavail_top_bit_set: ((std::convert::TryInto::::try_into(statvfs.f_bavail) - .unwrap()) + bavail_top_bit_set: ((TryInto::::try_into(statvfs.f_bavail).unwrap()) & (1u64.rotate_right(1))) != 0, files: statvfs.f_files, @@ -902,15 +904,14 @@ pub fn statfs(path: &OsStr) -> Result { Ok(p) => { let mut buffer: StatFs = unsafe { mem::zeroed() }; unsafe { - match statfs_fn(p.as_ptr(), &raw mut buffer) { - 0 => Ok(buffer), - _ => { - let errno = IOError::last_os_error().raw_os_error().unwrap_or(0); - Err(CStr::from_ptr(strerror(errno)) - .to_str() - .map_err(|_| "Error message contains invalid UTF-8".to_owned())? - .to_owned()) - } + if statfs_fn(p.as_ptr(), &raw mut buffer) == 0 { + Ok(buffer) + } else { + let errno = IOError::last_os_error().raw_os_error().unwrap_or(0); + Err(CStr::from_ptr(strerror(errno)) + .to_str() + .map_err(|_| "Error message contains invalid UTF-8".to_owned())? + .to_owned()) } } } diff --git a/src/uucore/src/lib/features/mode.rs b/src/uucore/src/lib/features/mode.rs index d562f1fe0..5424e8985 100644 --- a/src/uucore/src/lib/features/mode.rs +++ b/src/uucore/src/lib/features/mode.rs @@ -18,7 +18,7 @@ pub fn parse_numeric(fperm: u32, mut mode: &str, considering_dir: bool) -> Resul u32::from_str_radix(mode, 8).map_err(|e| e.to_string())? }; if change > 0o7777 { - Err(format!("mode is too large ({change} > 7777")) + Err(format!("mode is too large ({change:o} > 7777)")) } else { Ok(match op { Some('+') => fperm | change, diff --git a/src/uucore/src/lib/features/parser/num_parser.rs b/src/uucore/src/lib/features/parser/num_parser.rs index b23f51fb5..f87be3052 100644 --- a/src/uucore/src/lib/features/parser/num_parser.rs +++ b/src/uucore/src/lib/features/parser/num_parser.rs @@ -35,7 +35,7 @@ enum Base { impl Base { /// Return the digit value of a character in the given base - fn digit(&self, c: char) -> Option { + fn digit(self, c: char) -> Option { fn from_decimal(c: char) -> u64 { u64::from(c) - u64::from('0') } @@ -53,7 +53,7 @@ impl Base { /// Greedily parse as many digits as possible from the string /// Returns parsed digits (if any), and the rest of the string. - fn parse_digits<'a>(&self, str: &'a str) -> (Option, &'a str) { + fn parse_digits(self, str: &str) -> (Option, &str) { let (digits, _, rest) = self.parse_digits_count(str, None); (digits, rest) } @@ -61,11 +61,11 @@ impl Base { /// Greedily parse as many digits as possible from the string, adding to already parsed digits. /// This is meant to be used (directly) for the part after a decimal point. /// Returns parsed digits (if any), the number of parsed digits, and the rest of the string. - fn parse_digits_count<'a>( - &self, - str: &'a str, + fn parse_digits_count( + self, + str: &str, digits: Option, - ) -> (Option, i64, &'a str) { + ) -> (Option, i64, &str) { let mut digits: Option = digits; let mut count: i64 = 0; let mut rest = str; @@ -77,9 +77,9 @@ impl Base { let mut mul_tmp: u64 = 1; while let Some(d) = rest.chars().next().and_then(|c| self.digit(c)) { (digits_tmp, count_tmp, mul_tmp) = ( - digits_tmp * *self as u64 + d, + digits_tmp * self as u64 + d, count_tmp + 1, - mul_tmp * *self as u64, + mul_tmp * self as u64, ); rest = &rest[1..]; // In base 16, we parse 4 bits at a time, so we can parse 16 digits at most in a u64. @@ -583,7 +583,7 @@ mod tests { u64::extended_parse("-9223372036854775808") // i64::MIN ); assert_eq!( - Ok(1123372036854675616), + Ok(1_123_372_036_854_675_616), u64::extended_parse("-17323372036854876000") // 2*i64::MIN ); assert_eq!(Ok(1), u64::extended_parse("-18446744073709551615")); // -u64::MAX @@ -679,10 +679,10 @@ mod tests { assert_eq!(Ok(123.15), f64::extended_parse("0123.15")); assert_eq!(Ok(123.15), f64::extended_parse("+0123.15")); assert_eq!(Ok(-123.15), f64::extended_parse("-0123.15")); - assert_eq!(Ok(12315000.0), f64::extended_parse("123.15e5")); - assert_eq!(Ok(-12315000.0), f64::extended_parse("-123.15e5")); - assert_eq!(Ok(12315000.0), f64::extended_parse("123.15E+5")); - assert_eq!(Ok(0.0012315), f64::extended_parse("123.15E-5")); + assert_eq!(Ok(12_315_000.0), f64::extended_parse("123.15e5")); + assert_eq!(Ok(-12_315_000.0), f64::extended_parse("-123.15e5")); + assert_eq!(Ok(12_315_000.0), f64::extended_parse("123.15E+5")); + assert_eq!(Ok(0.001_231_5), f64::extended_parse("123.15E-5")); assert_eq!( Ok(0.15), f64::extended_parse(".150000000000000000000000000231313") @@ -933,7 +933,7 @@ mod tests { // We cannot really check that 'e' is not a valid exponent indicator for hex floats... // but we can check that the number still gets parsed properly: 0x0.8e5 is 0x8e5 / 16**3 - assert_eq!(Ok(0.555908203125), f64::extended_parse("0x0.8e5")); + assert_eq!(Ok(0.555_908_203_125), f64::extended_parse("0x0.8e5")); assert_eq!( f64::extended_parse("0x0.1p"), diff --git a/src/uucore/src/lib/features/parser/parse_time.rs b/src/uucore/src/lib/features/parser/parse_time.rs index 4abf85a1d..5435bd823 100644 --- a/src/uucore/src/lib/features/parser/parse_time.rs +++ b/src/uucore/src/lib/features/parser/parse_time.rs @@ -87,7 +87,7 @@ pub fn from_str(string: &str, allow_suffixes: bool) -> Result return Ok(Duration::MAX); } // early return if number is too small (< 1 ns) - if !bd.is_zero() && bd < bigdecimal::BigDecimal::from_f64(0.0000000001).unwrap() { + if !bd.is_zero() && bd < bigdecimal::BigDecimal::from_f64(0.000_000_000_1).unwrap() { return Ok(NANOSECOND_DURATION); } bd diff --git a/src/uucore/src/lib/features/perms.rs b/src/uucore/src/lib/features/perms.rs index d1e351ee3..e9aaf875d 100644 --- a/src/uucore/src/lib/features/perms.rs +++ b/src/uucore/src/lib/features/perms.rs @@ -890,7 +890,7 @@ pub fn chown_base( .action(clap::ArgAction::Append) .required(true) .num_args(1..) - .value_parser(clap::value_parser!(std::ffi::OsString)), + .value_parser(clap::value_parser!(OsString)), ); let matches = crate::clap_localization::handle_clap_result(command, args)?; diff --git a/src/uucore/src/lib/features/proc_info.rs b/src/uucore/src/lib/features/proc_info.rs index d36f5d010..0d99cb8a8 100644 --- a/src/uucore/src/lib/features/proc_info.rs +++ b/src/uucore/src/lib/features/proc_info.rs @@ -479,7 +479,7 @@ mod tests { #[test] fn test_thread_ids() { - let main_tid = unsafe { crate::libc::gettid() }; + let main_tid = unsafe { libc::gettid() }; std::thread::spawn(move || { let mut pid_entry = ProcessInformation::try_new( PathBuf::from_str(&format!("/proc/{}", current_pid())).unwrap(), @@ -489,7 +489,7 @@ mod tests { assert!(thread_ids.contains(&(main_tid as usize))); - let new_thread_tid = unsafe { crate::libc::gettid() }; + let new_thread_tid = unsafe { libc::gettid() }; assert!(thread_ids.contains(&(new_thread_tid as usize))); }) .join() diff --git a/src/uucore/src/lib/features/safe_traversal.rs b/src/uucore/src/lib/features/safe_traversal.rs index 3b1ac7067..05c456ad4 100644 --- a/src/uucore/src/lib/features/safe_traversal.rs +++ b/src/uucore/src/lib/features/safe_traversal.rs @@ -345,15 +345,15 @@ impl FileType { } } - pub fn is_directory(&self) -> bool { + pub fn is_directory(self) -> bool { matches!(self, Self::Directory) } - pub fn is_regular_file(&self) -> bool { + pub fn is_regular_file(self) -> bool { matches!(self, Self::RegularFile) } - pub fn is_symlink(&self) -> bool { + pub fn is_symlink(self) -> bool { matches!(self, Self::Symlink) } } @@ -780,7 +780,7 @@ mod tests { #[test] fn test_path_with_null_byte() { - let path_with_null = std::ffi::OsString::from_vec(b"test\0file".to_vec()); + let path_with_null = OsString::from_vec(b"test\0file".to_vec()); let temp_dir = TempDir::new().unwrap(); let dir_fd = DirFd::open(temp_dir.path()).unwrap(); diff --git a/src/uucore/src/lib/features/selinux.rs b/src/uucore/src/lib/features/selinux.rs index 04d6e4464..ed79be414 100644 --- a/src/uucore/src/lib/features/selinux.rs +++ b/src/uucore/src/lib/features/selinux.rs @@ -274,8 +274,8 @@ pub fn contexts_differ(from_path: &Path, to_path: &Path) -> bool { // Check if SELinux contexts differ match ( - selinux::SecurityContext::of_path(from_path, false, false), - selinux::SecurityContext::of_path(to_path, false, false), + SecurityContext::of_path(from_path, false, false), + SecurityContext::of_path(to_path, false, false), ) { (Ok(Some(from_ctx)), Ok(Some(to_ctx))) => { // Convert contexts to CString and compare diff --git a/src/uucore/src/lib/features/sum.rs b/src/uucore/src/lib/features/sum.rs index 6d190edbe..5272ab3c1 100644 --- a/src/uucore/src/lib/features/sum.rs +++ b/src/uucore/src/lib/features/sum.rs @@ -34,7 +34,7 @@ pub enum DigestOutput { } impl DigestOutput { - pub fn write_raw(&self, mut w: impl std::io::Write) -> io::Result<()> { + pub fn write_raw(&self, mut w: impl Write) -> io::Result<()> { match self { Self::Vec(buf) => w.write_all(buf), // For legacy outputs, print them in big endian @@ -175,10 +175,10 @@ impl Crc { crc_fast::CrcParams::new( "CRC-32/CKSUM", // Name 32, // Width - 0x04c11db7, // Polynomial - 0x00000000, // Initial CRC value: 0 (not 0xffffffff) + 0x04c1_1db7, // Polynomial + 0x0000_0000, // Initial CRC value: 0 (not 0xffffffff) false, // No input reflection (refin) - 0xffffffff, // XOR output with 0xffffffff (xorout) + 0xffff_ffff, // XOR output with 0xffffffff (xorout) 0, // Check value (not used) ) } @@ -245,7 +245,7 @@ impl Digest for CRC32B { let result = self.digest.finalize(); // crc_fast returns a 64-bit value, but CRC32B should be 32-bit // Take the lower 32 bits and convert to big-endian bytes - let crc32_value = (result & 0xffffffff) as u32; + let crc32_value = (result & 0xffff_ffff) as u32; out.copy_from_slice(&crc32_value.to_be_bytes()); } @@ -468,13 +468,13 @@ impl<'a> DigestWriter<'a> { impl Write for DigestWriter<'_> { #[cfg(not(windows))] - fn write(&mut self, buf: &[u8]) -> std::io::Result { + fn write(&mut self, buf: &[u8]) -> io::Result { self.digest.hash_update(buf); Ok(buf.len()) } #[cfg(windows)] - fn write(&mut self, buf: &[u8]) -> std::io::Result { + fn write(&mut self, buf: &[u8]) -> io::Result { if self.binary { self.digest.hash_update(buf); return Ok(buf.len()); @@ -524,7 +524,7 @@ impl Write for DigestWriter<'_> { Ok(n) } - fn flush(&mut self) -> std::io::Result<()> { + fn flush(&mut self) -> io::Result<()> { Ok(()) } } @@ -601,7 +601,7 @@ mod tests { assert_ne!(empty_result, test_result); // Test known value: "test" should give 3076352578 - assert_eq!(test_result, 3076352578); + assert_eq!(test_result, 3_076_352_578); } #[test] @@ -653,9 +653,9 @@ mod tests { // Test against our CRC implementation values // Note: These are the correct values for our POSIX cksum implementation let test_cases = [ - ("", 4294967295_u64), - ("a", 1220704766_u64), - ("abc", 1219131554_u64), + ("", 4_294_967_295_u64), + ("a", 1_220_704_766_u64), + ("abc", 1_219_131_554_u64), ]; for (input, expected) in test_cases { diff --git a/src/uucore/src/lib/features/systemd_logind.rs b/src/uucore/src/lib/features/systemd_logind.rs index 5869eb69e..9efd46a39 100644 --- a/src/uucore/src/lib/features/systemd_logind.rs +++ b/src/uucore/src/lib/features/systemd_logind.rs @@ -17,7 +17,6 @@ use std::mem::MaybeUninit; use std::time::{SystemTime, UNIX_EPOCH}; use crate::error::{UResult, USimpleError}; -use crate::utmpx; /// FFI bindings for libsystemd login and D-Bus functions mod ffi { @@ -304,18 +303,18 @@ impl SystemdLoginRecord { } /// Get login time as time::OffsetDateTime compatible with utmpx - pub fn login_time_offset(&self) -> utmpx::time::OffsetDateTime { + pub fn login_time_offset(&self) -> time::OffsetDateTime { let duration = self .login_time .duration_since(UNIX_EPOCH) .unwrap_or_default(); let ts_nanos: i128 = (duration.as_nanos()).try_into().unwrap_or(0); - let local_offset = utmpx::time::OffsetDateTime::now_local() - .map_or_else(|_| utmpx::time::UtcOffset::UTC, |v| v.offset()); - utmpx::time::OffsetDateTime::from_unix_timestamp_nanos(ts_nanos) + let local_offset = + time::OffsetDateTime::now_local().map_or_else(|_| time::UtcOffset::UTC, |v| v.offset()); + time::OffsetDateTime::from_unix_timestamp_nanos(ts_nanos) .unwrap_or_else(|_| { - utmpx::time::OffsetDateTime::now_local() - .unwrap_or_else(|_| utmpx::time::OffsetDateTime::now_utc()) + time::OffsetDateTime::now_local() + .unwrap_or_else(|_| time::OffsetDateTime::now_utc()) }) .to_offset(local_offset) } @@ -566,7 +565,7 @@ impl SystemdUtmpxCompat { } /// Login time - pub fn login_time(&self) -> utmpx::time::OffsetDateTime { + pub fn login_time(&self) -> time::OffsetDateTime { self.record.login_time_offset() } @@ -581,10 +580,10 @@ impl SystemdUtmpxCompat { } /// Canonical host name - pub fn canon_host(&self) -> std::io::Result { + pub fn canon_host(&self) -> String { // Simple implementation - just return the host as-is // Could be enhanced with DNS lookup like the original - Ok(self.record.host.clone()) + self.record.host.clone() } } @@ -681,7 +680,7 @@ mod tests { seat_or_tty: "tty1".to_string(), raw_device: "tty1".to_string(), host: "host1".to_string(), - login_time: std::time::UNIX_EPOCH, + login_time: UNIX_EPOCH, pid: 1234, session_leader_pid: 1234, record_type: SystemdRecordType::UserProcess, @@ -692,7 +691,7 @@ mod tests { seat_or_tty: "pts/0".to_string(), raw_device: "pts/0".to_string(), host: "host2".to_string(), - login_time: std::time::UNIX_EPOCH, + login_time: UNIX_EPOCH, pid: 5678, session_leader_pid: 5678, record_type: SystemdRecordType::UserProcess, @@ -729,7 +728,7 @@ mod tests { seat_or_tty: "tty1".to_string(), raw_device: "tty1".to_string(), host: "host1".to_string(), - login_time: std::time::UNIX_EPOCH, + login_time: UNIX_EPOCH, pid: 1234, session_leader_pid: 1234, record_type: SystemdRecordType::UserProcess, @@ -753,7 +752,7 @@ mod tests { seat_or_tty: "seat0".to_string(), raw_device: "seat0".to_string(), host: "localhost".to_string(), - login_time: std::time::UNIX_EPOCH + std::time::Duration::from_secs(1000), + login_time: UNIX_EPOCH + std::time::Duration::from_secs(1000), pid: 9999, session_leader_pid: 9999, record_type: SystemdRecordType::UserProcess, diff --git a/src/uucore/src/lib/features/time.rs b/src/uucore/src/lib/features/time.rs index d6f44434f..bc5d9ec66 100644 --- a/src/uucore/src/lib/features/time.rs +++ b/src/uucore/src/lib/features/time.rs @@ -57,33 +57,32 @@ pub fn format_system_time( mode: FormatSystemTimeFallback, ) -> UResult<()> { let zoned: Result = time.try_into(); - match zoned { - Ok(zoned) => format_zoned(out, zoned, fmt), - Err(_) => { - // Assume that if we cannot build a Zoned element, the timestamp is - // out of reasonable range, just print it then. - // TODO: The range allowed by jiff is different from what GNU accepts, - // but it still far enough in the future/past to be unlikely to matter: - // jiff: Year between -9999 to 9999 (UTC) [-377705023201..=253402207200] - // GNU: Year fits in signed 32 bits (timezone dependent) - let (mut secs, mut nsecs) = system_time_to_sec(time); - match mode { - FormatSystemTimeFallback::Integer => out.write_all(secs.to_string().as_bytes())?, - FormatSystemTimeFallback::IntegerError => { - let str = secs.to_string(); - show_error!("time '{str}' is out of range"); - out.write_all(str.as_bytes())?; + if let Ok(zoned) = zoned { + format_zoned(out, zoned, fmt) + } else { + // Assume that if we cannot build a Zoned element, the timestamp is + // out of reasonable range, just print it then. + // TODO: The range allowed by jiff is different from what GNU accepts, + // but it still far enough in the future/past to be unlikely to matter: + // jiff: Year between -9999 to 9999 (UTC) [-377705023201..=253402207200] + // GNU: Year fits in signed 32 bits (timezone dependent) + let (mut secs, mut nsecs) = system_time_to_sec(time); + match mode { + FormatSystemTimeFallback::Integer => out.write_all(secs.to_string().as_bytes())?, + FormatSystemTimeFallback::IntegerError => { + let str = secs.to_string(); + show_error!("time '{str}' is out of range"); + out.write_all(str.as_bytes())?; + } + FormatSystemTimeFallback::Float => { + if secs < 0 && nsecs != 0 { + secs -= 1; + nsecs = 1_000_000_000 - nsecs; } - FormatSystemTimeFallback::Float => { - if secs < 0 && nsecs != 0 { - secs -= 1; - nsecs = 1_000_000_000 - nsecs; - } - out.write_fmt(format_args!("{secs}.{nsecs:09}"))?; - } + out.write_fmt(format_args!("{secs}.{nsecs:09}"))?; } - Ok(()) } + Ok(()) } } diff --git a/src/uucore/src/lib/features/update_control.rs b/src/uucore/src/lib/features/update_control.rs index d7c4b4f16..5d5a58275 100644 --- a/src/uucore/src/lib/features/update_control.rs +++ b/src/uucore/src/lib/features/update_control.rs @@ -83,7 +83,7 @@ pub mod arguments { .default_missing_value("older") .require_equals(true) .overrides_with("update") - .action(clap::ArgAction::Set) + .action(ArgAction::Set) } /// `-u` argument diff --git a/src/uucore/src/lib/features/utmpx.rs b/src/uucore/src/lib/features/utmpx.rs index 3c3664389..8a6d03fd5 100644 --- a/src/uucore/src/lib/features/utmpx.rs +++ b/src/uucore/src/lib/features/utmpx.rs @@ -72,7 +72,7 @@ pub unsafe extern "C" fn utmpxname(_file: *const libc::c_char) -> libc::c_int { 0 } -use crate::*; // import macros from `../../macros.rs` +use crate::{LazyLock, libc}; // import macros from `../../macros.rs` // In case the c_char array doesn't end with NULL macro_rules! chars2string { @@ -521,7 +521,7 @@ impl UtmpxRecord { match self { Self::Traditional(utmpx) => utmpx.canon_host(), #[cfg(feature = "feat_systemd_logind")] - Self::Systemd(systemd) => systemd.canon_host(), + Self::Systemd(systemd) => Ok(systemd.canon_host()), } } } diff --git a/src/uucore/src/lib/lib.rs b/src/uucore/src/lib/lib.rs index 228ca3ede..5b8982928 100644 --- a/src/uucore/src/lib/lib.rs +++ b/src/uucore/src/lib/lib.rs @@ -171,8 +171,8 @@ pub fn get_canonical_util_name(util_name: &str) -> &str { match util_name { // uu_test aliases - '[' is an alias for test "[" => "test", - - "dir" => "ls", // dir is an alias for ls + "dir" => "ls", // dir is an alias for ls + "vdir" => "ls", // vdir is an alias for ls // Default case - return the util name as is _ => util_name, @@ -239,7 +239,7 @@ macro_rules! crate_version { /// as `{0}`. pub fn format_usage(s: &str) -> String { let s = s.replace('\n', &format!("\n{}", " ".repeat(7))); - s.replace("{}", crate::execution_phrase()) + s.replace("{}", execution_phrase()) } /// Creates a localized help template for clap commands. @@ -288,7 +288,7 @@ pub fn localized_help_template_with_colors( use std::fmt::Write; // Ensure localization is initialized for this utility - let _ = crate::locale::setup_localization(util_name); + let _ = locale::setup_localization(util_name); // Get the localized "Usage" label let usage_label = crate::locale::translate!("common-usage"); @@ -319,13 +319,13 @@ pub fn localized_help_template_with_colors( /// Used to check if the utility is the second argument. /// Used to check if we were called as a multicall binary (`coreutils `) pub fn get_utility_is_second_arg() -> bool { - crate::macros::UTILITY_IS_SECOND_ARG.load(Ordering::SeqCst) + macros::UTILITY_IS_SECOND_ARG.load(Ordering::SeqCst) } /// Change the value of `UTILITY_IS_SECOND_ARG` to true /// Used to specify that the utility is the second argument. pub fn set_utility_is_second_arg() { - crate::macros::UTILITY_IS_SECOND_ARG.store(true, Ordering::SeqCst); + macros::UTILITY_IS_SECOND_ARG.store(true, Ordering::SeqCst); } // args_os() can be expensive to call, it copies all of argv before iterating. @@ -434,6 +434,7 @@ impl error::UError for NonUtf8OsStrError {} /// /// This always succeeds on unix platforms, /// and fails on other platforms if the string can't be coerced to UTF-8. +#[cfg_attr(unix, expect(clippy::unnecessary_wraps))] pub fn os_str_as_bytes(os_string: &OsStr) -> Result<&[u8], NonUtf8OsStrError> { #[cfg(unix)] return Ok(os_string.as_bytes()); @@ -467,13 +468,14 @@ pub fn os_str_as_bytes_lossy(os_string: &OsStr) -> Cow<'_, [u8]> { /// /// This always succeeds on unix platforms, /// and fails on other platforms if the bytes can't be parsed as UTF-8. -pub fn os_str_from_bytes(bytes: &[u8]) -> mods::error::UResult> { +#[cfg_attr(unix, expect(clippy::unnecessary_wraps))] +pub fn os_str_from_bytes(bytes: &[u8]) -> error::UResult> { #[cfg(unix)] return Ok(Cow::Borrowed(OsStr::from_bytes(bytes))); #[cfg(not(unix))] Ok(Cow::Owned(OsString::from(str::from_utf8(bytes).map_err( - |_| mods::error::UUsageError::new(1, "Unable to transform bytes into OsStr"), + |_| error::UUsageError::new(1, "Unable to transform bytes into OsStr"), )?))) } @@ -481,13 +483,14 @@ pub fn os_str_from_bytes(bytes: &[u8]) -> mods::error::UResult> { /// /// This always succeeds on unix platforms, /// and fails on other platforms if the bytes can't be parsed as UTF-8. -pub fn os_string_from_vec(vec: Vec) -> mods::error::UResult { +#[cfg_attr(unix, expect(clippy::unnecessary_wraps))] +pub fn os_string_from_vec(vec: Vec) -> error::UResult { #[cfg(unix)] return Ok(OsString::from_vec(vec)); #[cfg(not(unix))] Ok(OsString::from(String::from_utf8(vec).map_err(|_| { - mods::error::UUsageError::new(1, "invalid UTF-8 was detected in one or more arguments") + error::UUsageError::new(1, "invalid UTF-8 was detected in one or more arguments") })?)) } @@ -495,14 +498,15 @@ pub fn os_string_from_vec(vec: Vec) -> mods::error::UResult { /// /// This always succeeds on unix platforms, /// and fails on other platforms if the bytes can't be parsed as UTF-8. -pub fn os_string_to_vec(s: OsString) -> mods::error::UResult> { +#[cfg_attr(unix, expect(clippy::unnecessary_wraps))] +pub fn os_string_to_vec(s: OsString) -> error::UResult> { #[cfg(unix)] let v = s.into_vec(); #[cfg(not(unix))] let v = s .into_string() .map_err(|_| { - mods::error::UUsageError::new(1, "invalid UTF-8 was detected in one or more arguments") + error::UUsageError::new(1, "invalid UTF-8 was detected in one or more arguments") })? .into(); @@ -513,24 +517,25 @@ pub fn os_string_to_vec(s: OsString) -> mods::error::UResult> { /// which avoids panicking on non UTF-8 input. pub fn read_byte_lines( mut buf_reader: BufReader, -) -> impl Iterator> { +) -> impl Iterator>> { iter::from_fn(move || { let mut buf = Vec::with_capacity(256); - let size = buf_reader.read_until(b'\n', &mut buf).ok()?; - if size == 0 { - return None; - } + match buf_reader.read_until(b'\n', &mut buf) { + Ok(0) => None, + Err(e) => Some(Err(e)), + Ok(_) => { + // Trim (\r)\n + if buf.ends_with(b"\n") { + buf.pop(); + if buf.ends_with(b"\r") { + buf.pop(); + } + } - // Trim (\r)\n - if buf.ends_with(b"\n") { - buf.pop(); - if buf.ends_with(b"\r") { - buf.pop(); + Some(Ok(buf)) } } - - Some(buf) }) } @@ -539,8 +544,9 @@ pub fn read_byte_lines( /// but it still will on Windows. pub fn read_os_string_lines( buf_reader: BufReader, -) -> impl Iterator { - read_byte_lines(buf_reader).map(|byte_line| os_string_from_vec(byte_line).expect("UTF-8 error")) +) -> impl Iterator> { + read_byte_lines(buf_reader) + .map(|byte_line_res| byte_line_res.map(|bl| os_string_from_vec(bl).expect("UTF-8 error"))) } /// Prompt the user with a formatted string and returns `true` if they reply `'y'` or `'Y'` diff --git a/src/uucore/src/lib/macros.rs b/src/uucore/src/lib/macros.rs index 068c06519..bd234768e 100644 --- a/src/uucore/src/lib/macros.rs +++ b/src/uucore/src/lib/macros.rs @@ -91,7 +91,8 @@ macro_rules! show( use $crate::error::UError; let e = $err; $crate::error::set_exit_code(e.code()); - eprintln!("{}: {e}", $crate::util_name()); + use std::io::Write as _; + let _ = writeln!(std::io::stderr().lock(), "{}: {e}", $crate::util_name()); }) ); @@ -151,8 +152,10 @@ macro_rules! show_if_err( #[macro_export] macro_rules! show_error( ($($args:tt)+) => ({ - eprint!("{}: ", $crate::util_name()); - eprintln!($($args)+); + use std::io::Write as _; + let mut error = std::io::stderr().lock(); + let _ = write!(error, "{}: ", $crate::util_name()); + let _ = writeln!(error, $($args)+); }) ); @@ -174,8 +177,10 @@ macro_rules! show_error( #[macro_export] macro_rules! show_warning( ($($args:tt)+) => ({ - eprint!("{}: warning: ", $crate::util_name()); - eprintln!($($args)+); + use std::io::Write as _; + let mut error = std::io::stderr().lock(); + let _ = write!(error, "{}: warning: ", $crate::util_name()); + let _ = writeln!(error, $($args)+); }) ); @@ -183,7 +188,9 @@ macro_rules! show_warning( #[macro_export] macro_rules! show_warning_caps( ($($args:tt)+) => ({ - eprint!("{}: WARNING: ", $crate::util_name()); - eprintln!($($args)+); + use std::io::Write as _; + let mut error = std::io::stderr().lock(); + let _ = write!(error, "{}: WARNING: ", $crate::util_name()); + let _ = writeln!(error, $($args)+); }) ); diff --git a/src/uucore/src/lib/mods/locale.rs b/src/uucore/src/lib/mods/locale.rs index 776cf3168..b670f8976 100644 --- a/src/uucore/src/lib/mods/locale.rs +++ b/src/uucore/src/lib/mods/locale.rs @@ -141,11 +141,11 @@ fn create_bundle( // Disable Unicode directional isolate characters bundle.set_use_isolating(false); - let mut try_add_resource_from = |dir_opt: Option| { + let mut try_add_resource_from = |dir_opt: Option| { if let Some(resource) = dir_opt .map(|dir| dir.join(format!("{locale}.ftl"))) .and_then(|locale_path| fs::read_to_string(locale_path).ok()) - .and_then(|ftl| fluent_bundle::FluentResource::try_new(ftl).ok()) + .and_then(|ftl| FluentResource::try_new(ftl).ok()) { bundle.add_resource_overriding(resource); } @@ -412,24 +412,21 @@ pub fn setup_localization(p: &str) -> Result<(), LocalizationError> { }); // Load common strings along with utility-specific strings - match get_locales_dir(p) { - Ok(locales_dir) => { - // Load both utility-specific and common strings - init_localization(&locale, &locales_dir, p) - } - Err(_) => { - // No locales directory found, use embedded English with common strings directly - let default_locale = LanguageIdentifier::from_str(DEFAULT_LOCALE) - .expect("Default locale should always be valid"); - let english_bundle = create_english_bundle_from_embedded(&default_locale, p)?; - let localizer = Localizer::new(english_bundle); + if let Ok(locales_dir) = get_locales_dir(p) { + // Load both utility-specific and common strings + init_localization(&locale, &locales_dir, p) + } else { + // No locales directory found, use embedded English with common strings directly + let default_locale = LanguageIdentifier::from_str(DEFAULT_LOCALE) + .expect("Default locale should always be valid"); + let english_bundle = create_english_bundle_from_embedded(&default_locale, p)?; + let localizer = Localizer::new(english_bundle); - LOCALIZER.with(|lock| { - lock.set(localizer) - .map_err(|_| LocalizationError::Bundle("Localizer already initialized".into())) - })?; - Ok(()) - } + LOCALIZER.with(|lock| { + lock.set(localizer) + .map_err(|_| LocalizationError::Bundle("Localizer already initialized".into())) + })?; + Ok(()) } } @@ -1339,7 +1336,7 @@ invalid-syntax = This is { $missing std::thread::spawn(|| { // Force English locale for this test unsafe { - std::env::set_var("LANG", "en-US"); + env::set_var("LANG", "en-US"); } // Test with a utility name that has embedded locales diff --git a/tests/by-util/test_cat.rs b/tests/by-util/test_cat.rs index 7cb824f60..3bb623eb1 100644 --- a/tests/by-util/test_cat.rs +++ b/tests/by-util/test_cat.rs @@ -32,7 +32,7 @@ fn test_cat_broken_pipe_nonzero_and_message() { // Close the read end to simulate a broken pipe on stdout let read_end = File::from_raw_fd(fds[0]); // Explicitly drop the read-end so writers see EPIPE instead of blocking on a full pipe - std::mem::drop(read_end); + drop(read_end); let write_end = File::from_raw_fd(fds[1]); let content = (0..10000).map(|_| "x").collect::(); @@ -592,7 +592,7 @@ fn test_dev_full_show_all() { #[cfg(target_os = "linux")] fn test_write_fast_fallthrough_uses_flush() { const PROC_INIT_CMDLINE: &str = "/proc/1/cmdline"; - let cmdline = std::fs::read_to_string(PROC_INIT_CMDLINE).unwrap(); + let cmdline = read_to_string(PROC_INIT_CMDLINE).unwrap(); new_ucmd!() .args(&[PROC_INIT_CMDLINE, "alpha.txt"]) diff --git a/tests/by-util/test_chmod.rs b/tests/by-util/test_chmod.rs index 18c180bd0..a75324b7c 100644 --- a/tests/by-util/test_chmod.rs +++ b/tests/by-util/test_chmod.rs @@ -283,6 +283,34 @@ fn test_chmod_error_permissions() { ); } +#[test] +fn test_chmod_permissions_too_large() { + let scenario = TestScenario::new(util_name!()); + let at = &scenario.fixtures; + + at.touch("file"); + + scenario + .ucmd() + .args(&["10777", "file"]) + .fails_with_code(1) + .stderr_is( + // spell-checker:disable-next-line + "chmod: mode is too large (10777 > 7777)\n", + ); + // test around the boundary of the acceptable octal mode + scenario + .ucmd() + .args(&["10000", "file"]) + .fails_with_code(1) + .stderr_is( + // spell-checker:disable-next-line + "chmod: mode is too large (10000 > 7777)\n", + ); + at.mkdir("dir"); + scenario.ucmd().args(&["7777", "dir"]).succeeds(); +} + #[test] #[allow(clippy::unreadable_literal)] fn test_chmod_ugo_copy() { diff --git a/tests/by-util/test_cksum.rs b/tests/by-util/test_cksum.rs index a6b77a2d2..daa69047f 100644 --- a/tests/by-util/test_cksum.rs +++ b/tests/by-util/test_cksum.rs @@ -2,7 +2,7 @@ // // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -// spell-checker:ignore (words) asdf algo algos asha mgmt xffname hexa GFYEQ HYQK Yqxb dont +// spell-checker:ignore (words) asdf algo algos asha mgmt xffname hexa GFYEQ HYQK Yqxb dont checkfile use uutests::at_and_ucmd; use uutests::new_ucmd; @@ -2524,9 +2524,33 @@ mod gnu_cksum_c { } #[test] - #[ignore = "todo"] fn test_signed_checksums() { - todo!() + let ts = TestScenario::new(util_name!()); + let at = &ts.fixtures; + + let filename = "test_file"; + at.touch(filename); + + let checksum_result = ts.ucmd().arg("-a").arg("sha256").arg(filename).succeeds(); + let valid_checksum_line = checksum_result.stdout_str().trim(); + + let signed_content = format!( + "-----BEGIN PGP SIGNED MESSAGE-----\n\ + Hash: SHA256\n\ + \n\ + # This is a comment that should be ignored\n\ + {valid_checksum_line}\n\ + -----BEGIN PGP SIGNATURE-----\n\ + \n\ + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n\ + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n\ + =PlaceHolder\n\ + -----END PGP SIGNATURE-----" + ); + + at.write("signed_CHECKSUMS", &signed_content); + + ts.ucmd().arg("--check").arg("signed_CHECKSUMS").succeeds(); } #[test] @@ -3060,3 +3084,17 @@ fn test_check_file_with_io_error() { .stderr_contains("Input/output error") .stdout_contains("FAILED open or read"); } + +#[test] +#[cfg(all(target_os = "linux", not(target_env = "musl")))] +fn test_check_checkfile_with_io_error() { + // /proc/self/mem causes EIO when read without proper seeking + new_ucmd!() + .arg("-a") + .arg("md5") + .arg("--check") + .arg("/proc/self/mem") + .fails() + .stderr_contains("/proc/self/mem: read error") + .no_stdout(); +} diff --git a/tests/by-util/test_cp.rs b/tests/by-util/test_cp.rs index dd77ddd61..f01619af2 100644 --- a/tests/by-util/test_cp.rs +++ b/tests/by-util/test_cp.rs @@ -7502,7 +7502,7 @@ fn test_cp_to_existing_file_permissions() { let mut src_permissions = std::fs::metadata(&src_path).unwrap().permissions(); src_permissions.set_readonly(true); - std::fs::set_permissions(&src_path, src_permissions).unwrap(); + set_permissions(&src_path, src_permissions).unwrap(); let dst_mode = std::fs::metadata(&dst_path).unwrap().permissions().mode(); @@ -7531,7 +7531,7 @@ fn test_cp_xattr_enotsup_handling() { } // Check if /dev/shm exists - if !std::path::Path::new("/dev/shm").exists() { + if !Path::new("/dev/shm").exists() { return; // Skip: /dev/shm not available } diff --git a/tests/by-util/test_dd.rs b/tests/by-util/test_dd.rs index ce0eec3d1..828a1e043 100644 --- a/tests/by-util/test_dd.rs +++ b/tests/by-util/test_dd.rs @@ -25,7 +25,8 @@ use std::path::PathBuf; not(target_os = "freebsd"), feature = "printf" ))] -use std::process::{Command, Stdio}; +use std::process::Command; +use std::process::Stdio; #[cfg(not(windows))] use std::thread::sleep; #[cfg(not(windows))] @@ -674,7 +675,6 @@ fn test_skip_beyond_file() { fn test_skip_beyond_file_seekable_stdin() { // When stdin is a seekable file, dd should use seek to skip bytes. // This tests that skipping beyond the file size issues a warning. - use std::process::Stdio; // Test cases: (bs, skip) pairs that skip beyond a 4-byte file let test_cases = [ @@ -1478,7 +1478,7 @@ fn test_sparse() { // On common Linux filesystems, setting the length to one megabyte // should cause the file to become a sparse file, but it depends // on the system. - std::fs::File::create(at.plus("infile")) + File::create(at.plus("infile")) .unwrap() .set_len(1024 * 1024) .unwrap(); @@ -1734,7 +1734,7 @@ fn test_iflag_directory_fails_when_file_is_passed_via_std_in() { let filename = at.plus_as_string("input"); new_ucmd!() .args(&["iflag=directory", "count=0"]) - .set_stdin(std::process::Stdio::from(File::open(filename).unwrap())) + .set_stdin(Stdio::from(File::open(filename).unwrap())) .fails() .stderr_only("dd: setting flags for 'standard input': Not a directory\n"); } @@ -1744,7 +1744,7 @@ fn test_iflag_directory_fails_when_file_is_passed_via_std_in() { fn test_iflag_directory_passes_when_dir_is_redirected() { new_ucmd!() .args(&["iflag=directory", "count=0"]) - .set_stdin(std::process::Stdio::from(File::open(".").unwrap())) + .set_stdin(Stdio::from(File::open(".").unwrap())) .succeeds(); } @@ -1760,8 +1760,6 @@ fn test_iflag_directory_fails_when_file_is_piped_via_std_in() { #[test] fn test_stdin_stdout_not_rewound_even_when_connected_to_seekable_file() { - use std::process::Stdio; - let ts = TestScenario::new(util_name!()); let at = &ts.fixtures; @@ -1821,8 +1819,6 @@ fn test_wrong_number_err_msg() { #[test] #[cfg(unix)] fn test_no_dropped_writes() { - use std::process::Stdio; - const BLK_SIZE: usize = 0x4000; const COUNT: usize = 1000; const NUM_BYTES: usize = BLK_SIZE * COUNT; @@ -1905,7 +1901,7 @@ fn test_skip_overflow() { #[cfg(target_os = "linux")] fn test_nocache_eof() { let (at, mut ucmd) = at_and_ucmd!(); - at.write_bytes("in.f", &vec![0u8; 1234567]); + at.write_bytes("in.f", &vec![0u8; 1_234_567]); ucmd.args(&[ "if=in.f", "of=out.f", @@ -1914,7 +1910,7 @@ fn test_nocache_eof() { "status=noxfer", ]) .succeeds(); - assert_eq!(at.read_bytes("out.f").len(), 1234567); + assert_eq!(at.read_bytes("out.f").len(), 1_234_567); } #[test] @@ -1922,7 +1918,7 @@ fn test_nocache_eof() { fn test_nocache_eof_fadvise_zero_length() { use std::process::Command; let (at, _ucmd) = at_and_ucmd!(); - at.write_bytes("in.f", &vec![0u8; 1234567]); + at.write_bytes("in.f", &vec![0u8; 1_234_567]); let strace_file = at.plus_as_string("strace.out"); let result = Command::new("strace") diff --git a/tests/by-util/test_df.rs b/tests/by-util/test_df.rs index 4754acbfe..c1cf50d25 100644 --- a/tests/by-util/test_df.rs +++ b/tests/by-util/test_df.rs @@ -15,6 +15,8 @@ use std::collections::HashSet; #[cfg(not(any(target_os = "freebsd", target_os = "windows")))] use uutests::at_and_ucmd; use uutests::new_ucmd; +#[cfg(target_os = "linux")] +use uutests::util::TestScenario; #[test] fn test_invalid_arg() { @@ -1091,3 +1093,62 @@ fn test_df_hides_binfmt_misc_by_default() { } // If binfmt_misc is not mounted, skip the test silently } + +/// Run df inside a mount namespace where /proc is masked with tmpfs. +/// Returns (success, stdout, stderr). +#[cfg(target_os = "linux")] +fn run_df_with_masked_proc(args: &str) -> Option<(bool, String, String)> { + use std::process::Command; + + // Check if user namespaces are available + if !Command::new("unshare") + .args(["-rm", "true"]) + .status() + .is_ok_and(|s| s.success()) + { + return None; + } + + let df_path = TestScenario::new("df").bin_path.clone(); + let output = Command::new("unshare") + .args(["-rm", "sh", "-c"]) + .arg(format!( + "mount -t tmpfs tmpfs /proc && {} df {args}", + df_path.display() + )) + .output() + .ok()?; + + Some(( + output.status.success(), + String::from_utf8_lossy(&output.stdout).to_string(), + String::from_utf8_lossy(&output.stderr).to_string(), + )) +} + +/// Test df fallback when /proc is masked - should work with path, fail without or with filters. +#[test] +#[cfg(target_os = "linux")] +fn test_df_masked_proc_fallback() { + if let Some((ok, stdout, stderr)) = run_df_with_masked_proc(".") { + assert!(ok, "df . should succeed: {stderr}"); + assert!(stderr.contains("cannot read table of mounted file systems")); + assert!(stdout.contains("Filesystem")); + } + + if let Some((ok, _, _)) = run_df_with_masked_proc("") { + assert!(!ok, "df without args should fail when /proc is masked"); + } + + for args in ["-a .", "-l .", "-t ext4 .", "-x tmpfs ."] { + if let Some((ok, _, _)) = run_df_with_masked_proc(args) { + assert!(!ok, "df {args} should fail when /proc is masked"); + } + } + + for args in ["-i .", "-T .", "--total ."] { + if let Some((ok, _, stderr)) = run_df_with_masked_proc(args) { + assert!(ok, "df {args} should succeed: {stderr}"); + } + } +} diff --git a/tests/by-util/test_du.rs b/tests/by-util/test_du.rs index c89cd4667..2be6ef02c 100644 --- a/tests/by-util/test_du.rs +++ b/tests/by-util/test_du.rs @@ -2058,3 +2058,96 @@ fn test_du_symlinks_multiple_links_in_args() { result.stdout_contains("dir1/file"); result.stdout_does_not_contain("dir1/link"); } + +#[test] +fn test_block_size_args_override() { + let ts = TestScenario::new(util_name!()); + let at = &ts.fixtures; + let dir = "override_args_dir"; + + at.mkdir(dir); + let fpath = at.plus(format!("{dir}/file")); + std::fs::File::create(fpath) + .expect("cannot create test file") + .set_len(100_000_000) + .expect("cannot set file size"); + + let fpath2 = at.plus(format!("{dir}/file_2")); + std::fs::File::create(fpath2) + .expect("cannot create test file") + .set_len(100_000_000) + .expect("cannot set file size"); + + let test_cases = [ + (["-sk", "-m"], "-sm"), + (["-sk", "-b"], "-sb"), + (["-sm", "-k"], "-sk"), + ]; + + for (idx, (overwriting_args, expected)) in test_cases.into_iter().enumerate() { + let overridden_args = ts + .ucmd() + .arg(dir) + .args(&overwriting_args) + .succeeds() + .stdout_move_str(); + + let single_args = ts + .ucmd() + .arg(dir) + .arg(expected) + .succeeds() + .stdout_move_str(); + + assert_eq!( + overridden_args, single_args, + "The last argument of m, k and b should overwrite. Run: {idx}" + ); + } +} + +#[test] +fn test_block_override_b_still_has_apparent_size() { + let ts = TestScenario::new(util_name!()); + let at = &ts.fixtures; + let dir = "override_args_dir"; + + at.mkdir(dir); + let fpath = at.plus(format!("{dir}/file")); + std::fs::File::create(fpath) + .expect("cannot create test file") + .set_len(100_000_000) + .expect("cannot set file size"); + + let fpath2 = at.plus(format!("{dir}/file_2")); + std::fs::File::create(fpath2) + .expect("cannot create test file") + .set_len(100_000_000) + .expect("cannot set file size"); + + let test_cases = [ + (["-sb", "-m"], ["-sm", "--apparent-size"]), + (["-sb", "-k"], ["-sk", "--apparent-size"]), + ]; + + for (idx, (overwriting_args, expected)) in test_cases.into_iter().enumerate() { + let overridden_args = ts + .ucmd() + .arg(dir) + .args(&overwriting_args) + .succeeds() + .stdout_move_str(); + + let single_args = ts + .ucmd() + .arg(dir) + .args(&expected) + .succeeds() + .stdout_move_str(); + + assert_eq!( + overridden_args, single_args, + "Overwriting the b flag should still leave --apparent-size active. Run: {idx}" + ); + } +} diff --git a/tests/by-util/test_expand.rs b/tests/by-util/test_expand.rs index 78a0f6ae5..cd84a82d2 100644 --- a/tests/by-util/test_expand.rs +++ b/tests/by-util/test_expand.rs @@ -451,3 +451,13 @@ fn test_expand_non_utf8_paths() { .succeeds() .stdout_is("hello world\ntest line\n"); } + +#[test] +fn test_buffered_reads_new_line_no_tabs_in_first_chunk() { + // test includes tabs after 1 full chunk of no tabs with new line + // checks that the tabstop calculation is done to correct column + new_ucmd!() + .args(&["new_line_in_chunk.txt"]) + .succeeds() + .stdout_is_fixture("new_line_in_chunk_expected.txt"); +} diff --git a/tests/by-util/test_false.rs b/tests/by-util/test_false.rs index 48b05c5f7..cc0874256 100644 --- a/tests/by-util/test_false.rs +++ b/tests/by-util/test_false.rs @@ -34,6 +34,13 @@ fn test_short_options() { } } +#[test] +fn test_extra_args() { + for option in ["--help", "--version"] { + new_ucmd!().args(&[option, "test"]).fails().no_output(); + } +} + #[test] fn test_conflict() { new_ucmd!() diff --git a/tests/by-util/test_fold.rs b/tests/by-util/test_fold.rs index c6ae6b56d..c371ba4ca 100644 --- a/tests/by-util/test_fold.rs +++ b/tests/by-util/test_fold.rs @@ -2,7 +2,7 @@ // // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -// spell-checker:ignore fullwidth +// spell-checker:ignore fullwidth refgh tefgh nefgh use bytecount::count; use unicode_width::UnicodeWidthChar; @@ -958,3 +958,37 @@ fn test_fullwidth_characters() { .succeeds() .stdout_is(format!("{e_fullwidth}\n{e_fullwidth}")); } + +#[test] +fn test_character_mode_special_chars() { + for (args, input, expected) in [ + // backspace decreases column + (&["-c", "-w", "5"][..], "abcde\x08fg\n", "abcde\x08f\ng\n"), + // carriage return resets column + (&["-c", "-w", "5"], "abcd\refgh\n", "abcd\refgh\n"), + // tab at start exceeds width + (&["-c", "-w", "4"], "\tabc\n", "\t\nabc\n"), + // multiple tabs + (&["-c", "-w", "10"], "a\tb\tc\n", "a\tb\n\tc\n"), + // basic folding + (&["-c", "-w", "3"], "abcdef\n", "abc\ndef\n"), + // preserves empty lines + (&["-c", "-w", "5"], "abc\n\ndef\n", "abc\n\ndef\n"), + // word boundary with -s + (&["-c", "-s", "-w", "5"], "ab cd ef\n", "ab \ncd ef\n"), + // tab as word boundary + (&["-c", "-s", "-w", "10"], "abcd\tefgh\n", "abcd\t\nefgh\n"), + // wide chars count as 1 in -c mode + ( + &["-c", "-w", "3"], + "\u{FF1A}\u{FF1A}\u{FF1A}\u{FF1A}\n", + "\u{FF1A}\u{FF1A}\u{FF1A}\n\u{FF1A}\n", + ), + ] { + new_ucmd!() + .args(args) + .pipe_in(input) + .succeeds() + .stdout_is(expected); + } +} diff --git a/tests/by-util/test_install.rs b/tests/by-util/test_install.rs index 7a2ccb875..32cdbee8c 100644 --- a/tests/by-util/test_install.rs +++ b/tests/by-util/test_install.rs @@ -11,7 +11,7 @@ use std::fs; use std::os::unix::ffi::OsStringExt; use std::os::unix::fs::{MetadataExt, PermissionsExt}; #[cfg(not(windows))] -use std::process::Command; +use std::process; #[cfg(any(target_os = "linux", target_os = "android"))] use std::thread::sleep; use uucore::process::{getegid, geteuid}; @@ -777,7 +777,7 @@ fn test_install_and_strip() { .succeeds() .no_stderr(); - let output = Command::new(SYMBOL_DUMP_PROGRAM) + let output = process::Command::new(SYMBOL_DUMP_PROGRAM) .arg("-t") .arg(at.plus(STRIP_TARGET_FILE)) .output() @@ -804,7 +804,7 @@ fn test_install_and_strip_with_program() { .succeeds() .no_stderr(); - let output = Command::new(SYMBOL_DUMP_PROGRAM) + let output = process::Command::new(SYMBOL_DUMP_PROGRAM) .arg("-t") .arg(at.plus(STRIP_TARGET_FILE)) .output() @@ -1937,7 +1937,7 @@ fn test_install_compare_group_ownership() { at.write(source, "test content"); - let user_group = std::process::Command::new("id") + let user_group = process::Command::new("id") .arg("-nrg") .output() .map_or_else( @@ -2498,7 +2498,7 @@ fn test_install_non_utf8_paths() { let source_filename = std::ffi::OsString::from_vec(vec![0xFF, 0xFE]); let dest_dir = "target_dir"; - std::fs::write(at.plus(&source_filename), b"test content").unwrap(); + fs::write(at.plus(&source_filename), b"test content").unwrap(); at.mkdir(dest_dir); ucmd.arg(&source_filename).arg(dest_dir).succeeds(); diff --git a/tests/by-util/test_join.rs b/tests/by-util/test_join.rs index a0a061b6b..b19b16199 100644 --- a/tests/by-util/test_join.rs +++ b/tests/by-util/test_join.rs @@ -550,8 +550,8 @@ fn test_join_non_utf8_paths() { #[cfg(unix)] { use std::os::unix::ffi::OsStrExt; - let file1_name = std::ffi::OsStr::from_bytes(file1_bytes); - let file2_name = std::ffi::OsStr::from_bytes(file2_bytes); + let file1_name = OsStr::from_bytes(file1_bytes); + let file2_name = OsStr::from_bytes(file2_bytes); let mut file1 = File::create(test_dir.join(file1_name)).unwrap(); file1.write_all(b"a 1\n").unwrap(); diff --git a/tests/by-util/test_mv.rs b/tests/by-util/test_mv.rs index d756cea7d..761f62b5a 100644 --- a/tests/by-util/test_mv.rs +++ b/tests/by-util/test_mv.rs @@ -1921,7 +1921,7 @@ fn test_move_should_not_fallback_to_copy() { #[cfg(target_os = "linux")] mod inter_partition_copying { - use std::fs::{read_to_string, set_permissions, write}; + use std::fs::{self, set_permissions, write}; use std::os::unix::fs::{PermissionsExt, symlink}; use tempfile::TempDir; use uutests::util::TestScenario; @@ -1964,13 +1964,13 @@ mod inter_partition_copying { // make sure that file contents in other_fs_file didn't change. assert_eq!( - read_to_string(&other_fs_file_path).expect("Unable to read other_fs_file"), + fs::read_to_string(&other_fs_file_path).expect("Unable to read other_fs_file"), "other fs file contents" ); // make sure that src file contents got copied into new file created in symlink_path assert_eq!( - read_to_string(&symlink_path).expect("Unable to read other_fs_file"), + fs::read_to_string(&symlink_path).expect("Unable to read other_fs_file"), "src contents" ); } @@ -2011,7 +2011,7 @@ mod inter_partition_copying { #[test] #[cfg(unix)] pub(crate) fn test_mv_preserves_hardlinks_across_partitions() { - use std::fs::metadata; + use std::fs; use std::os::unix::fs::MetadataExt; use tempfile::TempDir; use uutests::util::TestScenario; @@ -2022,8 +2022,8 @@ mod inter_partition_copying { at.write("file1", "test content"); at.hard_link("file1", "file2"); - let metadata1 = metadata(at.plus("file1")).expect("Failed to get metadata for file1"); - let metadata2 = metadata(at.plus("file2")).expect("Failed to get metadata for file2"); + let metadata1 = fs::metadata(at.plus("file1")).expect("Failed to get metadata for file1"); + let metadata2 = fs::metadata(at.plus("file2")).expect("Failed to get metadata for file2"); assert_eq!( metadata1.ino(), metadata2.ino(), @@ -2055,9 +2055,9 @@ mod inter_partition_copying { assert!(moved_file2.exists(), "file2 should exist in destination"); let moved_metadata1 = - metadata(&moved_file1).expect("Failed to get metadata for moved file1"); + fs::metadata(&moved_file1).expect("Failed to get metadata for moved file1"); let moved_metadata2 = - metadata(&moved_file2).expect("Failed to get metadata for moved file2"); + fs::metadata(&moved_file2).expect("Failed to get metadata for moved file2"); assert_eq!( moved_metadata1.ino(), @@ -2072,11 +2072,11 @@ mod inter_partition_copying { // Verify content is preserved assert_eq!( - std::fs::read_to_string(&moved_file1).expect("Failed to read moved file1"), + fs::read_to_string(&moved_file1).expect("Failed to read moved file1"), "test content" ); assert_eq!( - std::fs::read_to_string(&moved_file2).expect("Failed to read moved file2"), + fs::read_to_string(&moved_file2).expect("Failed to read moved file2"), "test content" ); } @@ -2186,24 +2186,12 @@ mod inter_partition_copying { "Single file should still have nlink=1" ); + assert_eq!(fs::read_to_string(&moved_g1f1).unwrap(), "content group 1"); + assert_eq!(fs::read_to_string(&moved_g1f2).unwrap(), "content group 1"); + assert_eq!(fs::read_to_string(&moved_g2f1).unwrap(), "content group 2"); + assert_eq!(fs::read_to_string(&moved_g2f2).unwrap(), "content group 2"); assert_eq!( - std::fs::read_to_string(&moved_g1f1).unwrap(), - "content group 1" - ); - assert_eq!( - std::fs::read_to_string(&moved_g1f2).unwrap(), - "content group 1" - ); - assert_eq!( - std::fs::read_to_string(&moved_g2f1).unwrap(), - "content group 2" - ); - assert_eq!( - std::fs::read_to_string(&moved_g2f2).unwrap(), - "content group 2" - ); - assert_eq!( - std::fs::read_to_string(&moved_single).unwrap(), + fs::read_to_string(&moved_single).unwrap(), "single file content" ); } @@ -2296,14 +2284,14 @@ mod inter_partition_copying { "a/1 should have nlink=2 after move" ); - assert_eq!(std::fs::read_to_string(&moved_f).unwrap(), "file content"); - assert_eq!(std::fs::read_to_string(&moved_g).unwrap(), "file content"); + assert_eq!(fs::read_to_string(&moved_f).unwrap(), "file content"); + assert_eq!(fs::read_to_string(&moved_g).unwrap(), "file content"); assert_eq!( - std::fs::read_to_string(&moved_dir_a_file).unwrap(), + fs::read_to_string(&moved_dir_a_file).unwrap(), "directory file content" ); assert_eq!( - std::fs::read_to_string(&moved_dir_second_file).unwrap(), + fs::read_to_string(&moved_dir_second_file).unwrap(), "directory file content" ); } @@ -2436,26 +2424,23 @@ mod inter_partition_copying { "nested file group should still have nlink=2" ); - assert_eq!(std::fs::read_to_string(&moved_file_a).unwrap(), "content A"); + assert_eq!(fs::read_to_string(&moved_file_a).unwrap(), "content A"); assert_eq!( - std::fs::read_to_string(&moved_file_a_link1).unwrap(), + fs::read_to_string(&moved_file_a_link1).unwrap(), "content A" ); assert_eq!( - std::fs::read_to_string(&moved_file_a_link2).unwrap(), + fs::read_to_string(&moved_file_a_link2).unwrap(), "content A" ); - assert_eq!(std::fs::read_to_string(&moved_file_b).unwrap(), "content B"); + assert_eq!(fs::read_to_string(&moved_file_b).unwrap(), "content B"); assert_eq!( - std::fs::read_to_string(&moved_file_b_hardlink).unwrap(), + fs::read_to_string(&moved_file_b_hardlink).unwrap(), "content B" ); + assert_eq!(fs::read_to_string(&moved_nested).unwrap(), "nested content"); assert_eq!( - std::fs::read_to_string(&moved_nested).unwrap(), - "nested content" - ); - assert_eq!( - std::fs::read_to_string(&moved_nested_link).unwrap(), + fs::read_to_string(&moved_nested_link).unwrap(), "nested content" ); } diff --git a/tests/by-util/test_pr.rs b/tests/by-util/test_pr.rs index 1beed2305..9c8c4264e 100644 --- a/tests/by-util/test_pr.rs +++ b/tests/by-util/test_pr.rs @@ -7,8 +7,8 @@ use jiff::{Timestamp, ToSpan}; use regex::Regex; use std::fs::metadata; -use uutests::new_ucmd; use uutests::util::UCommand; +use uutests::{at_and_ucmd, new_ucmd}; const DATE_TIME_FORMAT_DEFAULT: &str = "%Y-%m-%d %H:%M"; @@ -666,3 +666,93 @@ fn test_form_feed_followed_by_new_line() { .succeeds() .stdout_matches(®ex); } + +#[test] +fn test_columns() { + let whitespace = " ".repeat(50); + let datetime_pattern = r"\d\d\d\d-\d\d-\d\d \d\d:\d\d"; + let header = format!("\n\n{datetime_pattern}{whitespace}Page 1\n\n\n"); + // TODO Our output still does not match the behavior of GNU + // pr. The correct output should be: + // + // "a\t\t\t\t b\n"; + // + let data = "a \tb \n"; + let blank_lines_60 = "\n".repeat(60); + let pattern = format!("{header}{data}{blank_lines_60}"); + let regex = Regex::new(&pattern).unwrap(); + + // Command line: `printf "a\nb\n" | pr -2`. + new_ucmd!() + .arg("-2") + .pipe_in("a\nb\n") + .succeeds() + .stdout_matches(®ex); +} + +#[test] +fn test_merge() { + // Create the two files to merge. + let (at, mut ucmd) = at_and_ucmd!(); + at.write("f", "a\n"); + at.write("g", "b\n"); + + let whitespace = " ".repeat(50); + let datetime_pattern = r"\d\d\d\d-\d\d-\d\d \d\d:\d\d"; + let header = format!("\n\n{datetime_pattern}{whitespace}Page 1\n\n\n"); + // TODO Our output still does not match the behavior of GNU + // pr. The correct output should be: + // + // "a\t\t\t\t b\n"; + // + // and the blank lines should actually be empty lines. + let data = "a \tb \n"; + let blank_lines_55 = + " \t \n".repeat(55); + let footer = "\n".repeat(5); + let pattern = format!("{header}{data}{blank_lines_55}{footer}"); + let regex = Regex::new(&pattern).unwrap(); + + // Command line: `(echo "a" > f; echo "b" > g; pr -m f g)`. + ucmd.args(&["-m", "f", "g"]) + .succeeds() + .stdout_matches(®ex); +} + +#[test] +fn test_merge_one_long_one_short() { + // Create the two files to merge. + let (at, mut ucmd) = at_and_ucmd!(); + at.write("f", "a\na\n"); + at.write("g", "b\n"); + + // Page 1 should have the first line of `f` and the first line of + // `b` side-by-side. + let whitespace = " ".repeat(50); + let datetime_pattern = r"\d\d\d\d-\d\d-\d\d \d\d:\d\d"; + let header = format!("\n\n{datetime_pattern}{whitespace}Page 1\n\n\n"); + let data = "a \tb \n"; + let footer = "\n".repeat(5); + let page1 = format!("{header}{data}{footer}"); + + // Page 2 should have just the second line of `f`. + let header = format!("\n\n{datetime_pattern}{whitespace}Page 2\n\n\n"); + let data = "a \t \n"; + let page2 = format!("{header}{data}{footer}"); + + let pattern = format!("{page1}{page2}"); + let regex = Regex::new(&pattern).unwrap(); + + // Command line: + // + // printf "a\na\n" > f + // printf "b\n" > g + // pr -l11 -m f g + // + // The line length of 11 leaves room for a 5-line header, a 5-line + // footer, and one line of data from the input files. The extra + // line from the file `f` will be on the second page. + ucmd.args(&["-l", "11", "-m", "f", "g"]) + .succeeds() + .stdout_matches(®ex); +} diff --git a/tests/by-util/test_rm.rs b/tests/by-util/test_rm.rs index e262e9612..c303a4bab 100644 --- a/tests/by-util/test_rm.rs +++ b/tests/by-util/test_rm.rs @@ -2,6 +2,7 @@ // // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. +// spell-checker:ignore rootlink #![allow(clippy::stable_sort_primitive)] use std::process::Stdio; @@ -1290,3 +1291,73 @@ fn test_symlink_to_readonly_no_prompt() { assert!(!at.symlink_exists("bar")); } + +/// Test that --preserve-root properly detects symlinks pointing to root. +#[cfg(unix)] +#[test] +fn test_preserve_root_symlink_to_root() { + let (at, mut ucmd) = at_and_ucmd!(); + + // Create a symlink pointing to the root directory + at.symlink_dir("/", "rootlink"); + + // Attempting to recursively delete through this symlink should fail + // because it resolves to the same device/inode as "/" + ucmd.arg("-rf") + .arg("--preserve-root") + .arg("rootlink/") + .fails() + .stderr_contains("it is dangerous to operate recursively on") + .stderr_contains("(same as '/')"); + + // The symlink itself should still exist (we didn't delete it) + assert!(at.symlink_exists("rootlink")); +} + +/// Test that --preserve-root properly detects nested symlinks pointing to root. +#[cfg(unix)] +#[test] +fn test_preserve_root_nested_symlink_to_root() { + let (at, mut ucmd) = at_and_ucmd!(); + + // Create a symlink pointing to the root directory + at.symlink_dir("/", "rootlink"); + // Create another symlink pointing to the first symlink + at.symlink_dir("rootlink", "rootlink2"); + + // Attempting to recursively delete through nested symlinks should also fail + ucmd.arg("-rf") + .arg("--preserve-root") + .arg("rootlink2/") + .fails() + .stderr_contains("it is dangerous to operate recursively on") + .stderr_contains("(same as '/')"); +} + +/// Test that removing the symlink itself (not the target) still works. +#[cfg(unix)] +#[test] +fn test_preserve_root_symlink_removal_without_trailing_slash() { + let (at, mut ucmd) = at_and_ucmd!(); + + // Create a symlink pointing to the root directory + at.symlink_dir("/", "rootlink"); + + // Removing the symlink itself (without trailing slash) should succeed + // because we're removing the link, not traversing through it + ucmd.arg("--preserve-root").arg("rootlink").succeeds(); + + assert!(!at.symlink_exists("rootlink")); +} + +/// Test that literal "/" is still properly protected. +#[test] +fn test_preserve_root_literal_root() { + new_ucmd!() + .arg("-rf") + .arg("--preserve-root") + .arg("/") + .fails() + .stderr_contains("it is dangerous to operate recursively on '/'") + .stderr_contains("use --no-preserve-root to override this failsafe"); +} diff --git a/tests/by-util/test_shred.rs b/tests/by-util/test_shred.rs index a0aa7b505..81f69fb5c 100644 --- a/tests/by-util/test_shred.rs +++ b/tests/by-util/test_shred.rs @@ -339,7 +339,7 @@ fn test_shred_non_utf8_paths() { fn test_gnu_shred_passes_20() { let (at, mut ucmd) = at_and_ucmd!(); - let us_data = vec![0x55; 102400]; // 100K of 'U' bytes + let us_data = vec![0x55; 102_400]; // 100K of 'U' bytes at.write_bytes("Us", &us_data); let file = "f"; @@ -397,7 +397,7 @@ fn test_gnu_shred_passes_20() { fn test_gnu_shred_passes_different_counts() { let (at, mut ucmd) = at_and_ucmd!(); - let us_data = vec![0x55; 102400]; + let us_data = vec![0x55; 102_400]; at.write_bytes("Us", &us_data); let file = "f"; diff --git a/tests/by-util/test_shuf.rs b/tests/by-util/test_shuf.rs index 948b3ed07..01c53c2e1 100644 --- a/tests/by-util/test_shuf.rs +++ b/tests/by-util/test_shuf.rs @@ -329,7 +329,7 @@ fn test_echo_multi() { .stdout_str() .split('\n') .filter(|x| !x.is_empty()) - .map(std::convert::Into::into) + .map(Into::into) .collect(); result_seq.sort_unstable(); assert_eq!(result_seq, ["a", "b", "c"], "Output is not a permutation"); @@ -344,7 +344,7 @@ fn test_echo_postfix() { .stdout_str() .split('\n') .filter(|x| !x.is_empty()) - .map(std::convert::Into::into) + .map(Into::into) .collect(); result_seq.sort_unstable(); assert_eq!(result_seq, ["a", "b", "c"], "Output is not a permutation"); diff --git a/tests/by-util/test_split.rs b/tests/by-util/test_split.rs index 497559aca..d9dac54ca 100644 --- a/tests/by-util/test_split.rs +++ b/tests/by-util/test_split.rs @@ -14,7 +14,7 @@ use std::env; use std::os::unix::ffi::OsStringExt; use std::path::Path; use std::{ - fs::{File, read_dir}, + fs::{self, File}, io::{BufWriter, Read, Write}, }; use uutests::util::{AtPath, TestScenario}; @@ -48,7 +48,7 @@ impl Glob { /// Get all files in `self.directory` that match `self.regex` fn collect(&self) -> Vec { - read_dir(Path::new(&self.directory.subdir)) + fs::read_dir(Path::new(&self.directory.subdir)) .unwrap() .filter_map(|entry| { let path = entry.unwrap().path(); @@ -2011,7 +2011,7 @@ fn test_split_non_utf8_paths() { let (at, mut ucmd) = at_and_ucmd!(); let filename = std::ffi::OsString::from_vec(vec![0xFF, 0xFE]); - std::fs::write(at.plus(&filename), b"line1\nline2\nline3\nline4\nline5\n").unwrap(); + fs::write(at.plus(&filename), b"line1\nline2\nline3\nline4\nline5\n").unwrap(); ucmd.arg(&filename).succeeds(); @@ -2032,7 +2032,7 @@ fn test_split_non_utf8_prefix() { // Check that split files were created (functionality works) // The actual filename may be converted due to lossy conversion, but the command should succeed - let entries: Vec<_> = std::fs::read_dir(at.as_string()).unwrap().collect(); + let entries: Vec<_> = fs::read_dir(at.as_string()).unwrap().collect(); let split_files = entries .iter() .filter_map(|e| e.as_ref().ok()) @@ -2063,7 +2063,7 @@ fn test_split_non_utf8_additional_suffix() { // Check that split files were created (functionality works) // The actual filename may be converted due to lossy conversion, but the command should succeed - let entries: Vec<_> = std::fs::read_dir(at.as_string()).unwrap().collect(); + let entries: Vec<_> = fs::read_dir(at.as_string()).unwrap().collect(); let split_files = entries .iter() .filter_map(|e| e.as_ref().ok()) diff --git a/tests/by-util/test_stty.rs b/tests/by-util/test_stty.rs index c2b8a77e5..79f9c1283 100644 --- a/tests/by-util/test_stty.rs +++ b/tests/by-util/test_stty.rs @@ -1237,7 +1237,7 @@ fn test_saved_state_invalid_formats() { let (path, _controller, _replica) = pty_path(); let (_at, ts) = at_and_ts!(); - let num_cc = nix::libc::NCCS; + let num_cc = libc::NCCS; // Build test strings with platform-specific counts let cc_zeros = vec!["0"; num_cc].join(":"); @@ -1540,7 +1540,7 @@ fn test_saved_state_with_control_chars() { let (_at, ts) = at_and_ts!(); // Build a valid saved state with platform-specific number of control characters - let num_cc = nix::libc::NCCS; + let num_cc = libc::NCCS; let cc_values: Vec = (1..=num_cc).map(|_| format!("{:x}", 0)).collect(); let saved_state = format!("500:5:4bf:8a3b:{}", cc_values.join(":")); diff --git a/tests/by-util/test_tail.rs b/tests/by-util/test_tail.rs index efbd13596..f2875e7b1 100644 --- a/tests/by-util/test_tail.rs +++ b/tests/by-util/test_tail.rs @@ -5049,7 +5049,7 @@ fn test_child_when_run_with_stderr_to_stdout() { fn test_failed_write_is_reported() { new_ucmd!() .pipe_in("hello") - .set_stdout(std::fs::File::create("/dev/full").unwrap()) + .set_stdout(File::create("/dev/full").unwrap()) .fails() .stderr_is("tail: No space left on device\n"); } diff --git a/tests/by-util/test_tee.rs b/tests/by-util/test_tee.rs index 4a3e16912..8d03328b6 100644 --- a/tests/by-util/test_tee.rs +++ b/tests/by-util/test_tee.rs @@ -198,8 +198,9 @@ mod linux_only { use std::os::unix::io::FromRawFd; let mut fds: [c_int; 2] = [0, 0]; - assert!( - (unsafe { libc::pipe(std::ptr::from_mut::(&mut fds[0])) } == 0), + assert_eq!( + unsafe { libc::pipe(std::ptr::from_mut::(&mut fds[0])) }, + 0, "Failed to create pipe" ); @@ -215,8 +216,9 @@ mod linux_only { use std::os::unix::io::FromRawFd; let mut fds: [c_int; 2] = [0, 0]; - assert!( - (unsafe { libc::pipe(std::ptr::from_mut::(&mut fds[0])) } == 0), + assert_eq!( + unsafe { libc::pipe(std::ptr::from_mut::(&mut fds[0])) }, + 0, "Failed to create pipe" ); diff --git a/tests/by-util/test_true.rs b/tests/by-util/test_true.rs index a8a9cf047..23c947fb2 100644 --- a/tests/by-util/test_true.rs +++ b/tests/by-util/test_true.rs @@ -37,6 +37,13 @@ fn test_short_options() { } } +#[test] +fn test_extra_args() { + for option in ["--help", "--version"] { + new_ucmd!().args(&[option, "test"]).succeeds().no_output(); + } +} + #[test] fn test_conflict() { new_ucmd!() diff --git a/tests/by-util/test_vdir.rs b/tests/by-util/test_vdir.rs index a68e3b543..3e3633054 100644 --- a/tests/by-util/test_vdir.rs +++ b/tests/by-util/test_vdir.rs @@ -56,3 +56,21 @@ fn test_column_output() { fn test_invalid_option_exit_code() { new_ucmd!().arg("-/").fails().code_is(2); } + +#[test] +fn test_help_shows_vdir_not_ls() { + let result = new_ucmd!().arg("--help").succeeds(); + let output = result.stdout_str(); + + // Verify help text contains "vdir" in the usage line + assert!( + output.contains("vdir [OPTION]"), + "Help should show 'vdir [OPTION]'" + ); + + // Verify help text does not incorrectly show "ls" + assert!( + !output.contains("ls [OPTION]"), + "Help should not show 'ls [OPTION]'" + ); +} diff --git a/tests/fixtures/.gitattributes b/tests/fixtures/.gitattributes index 2156bf6bd..42e422926 100644 --- a/tests/fixtures/.gitattributes +++ b/tests/fixtures/.gitattributes @@ -1 +1 @@ -* -text diff +* -text diff diff --git a/tests/fixtures/expand/new_line_in_chunk.txt b/tests/fixtures/expand/new_line_in_chunk.txt new file mode 100644 index 000000000..9654789dc --- /dev/null +++ b/tests/fixtures/expand/new_line_in_chunk.txt @@ -0,0 +1,9 @@ +000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +000000000000000000 0 000 000 00 0 0 0 0 + Lorem Ipsum test file + + + + +hello diff --git a/tests/fixtures/expand/new_line_in_chunk_expected.txt b/tests/fixtures/expand/new_line_in_chunk_expected.txt new file mode 100644 index 000000000..3997d70b6 --- /dev/null +++ b/tests/fixtures/expand/new_line_in_chunk_expected.txt @@ -0,0 +1,9 @@ +000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +000000000000000000 0 000 000 00 0 0 0 0 + Lorem Ipsum test file + + + + +hello diff --git a/tests/fixtures/nohup/is_a_tty.sh b/tests/fixtures/nohup/is_a_tty.sh old mode 100644 new mode 100755 diff --git a/tests/fixtures/util/is_a_tty.sh b/tests/fixtures/util/is_a_tty.sh old mode 100644 new mode 100755 diff --git a/tests/test_util_name.rs b/tests/test_util_name.rs index 12309a6e3..745e6814b 100644 --- a/tests/test_util_name.rs +++ b/tests/test_util_name.rs @@ -20,7 +20,7 @@ pub const TESTS_BINARY: &str = env!("CARGO_BIN_EXE_coreutils"); fn init() { // No need for unsafe here unsafe { - std::env::set_var("UUTESTS_BINARY_PATH", TESTS_BINARY); + env::set_var("UUTESTS_BINARY_PATH", TESTS_BINARY); } // Print for debugging eprintln!("Setting UUTESTS_BINARY_PATH={TESTS_BINARY}"); @@ -109,7 +109,7 @@ fn util_name_single() { #[test] #[cfg(unix)] fn util_invalid_name_help() { - use std::process::{Command, Stdio}; + use std::process::Command; let scenario = TestScenario::new("invalid_name"); if !scenario.bin_path.exists() { @@ -117,22 +117,12 @@ fn util_invalid_name_help() { return; } symlink_file(&scenario.bin_path, scenario.fixtures.plus("invalid_name")).unwrap(); - let child = Command::new(scenario.fixtures.plus("invalid_name")) + let code = Command::new(scenario.fixtures.plus("invalid_name")) .arg("--help") - .stdin(Stdio::piped()) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .spawn() - .unwrap(); - let output = child.wait_with_output().unwrap(); - assert_eq!(output.status.code(), Some(0)); - assert_eq!(output.stderr, b""); - let output_str = String::from_utf8(output.stdout).unwrap(); - assert!(output_str.contains("(multi-call binary)"), "{output_str:?}"); - assert!( - output_str.contains("Usage: invalid_name [function "), - "{output_str:?}" - ); + .status() + .unwrap() + .code(); + assert_eq!(code, Some(1)); } #[test] @@ -177,7 +167,7 @@ fn util_non_utf8_name_help() { #[test] #[cfg(unix)] fn util_invalid_name_invalid_command() { - use std::process::{Command, Stdio}; + use std::process::Command; let scenario = TestScenario::new("invalid_name"); symlink_file(&scenario.bin_path, scenario.fixtures.plus("invalid_name")).unwrap(); @@ -186,20 +176,11 @@ fn util_invalid_name_invalid_command() { return; } - let child = Command::new(scenario.fixtures.plus("invalid_name")) - .arg("definitely_invalid") - .stdin(Stdio::piped()) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .spawn() - .unwrap(); - let output = child.wait_with_output().unwrap(); - assert_eq!(output.status.code(), Some(1)); - assert_eq!(output.stdout, b""); - assert_eq!( - output.stderr, - b"definitely_invalid: function/utility not found\n" - ); + let code = Command::new(scenario.fixtures.plus("invalid_name")) + .status() + .unwrap() + .code(); + assert_eq!(code, Some(1)); } #[test] @@ -223,7 +204,7 @@ fn util_version() { assert_eq!(output.status.code(), Some(0)); assert_eq!(output.stderr, b""); let output_str = String::from_utf8(output.stdout).unwrap(); - let ver = std::env::var("CARGO_PKG_VERSION").unwrap(); + let ver = env::var("CARGO_PKG_VERSION").unwrap(); assert_eq!(format!("coreutils {ver} (multi-call binary)\n"), output_str); } } diff --git a/tests/test_uudoc.rs b/tests/test_uudoc.rs new file mode 100644 index 000000000..b4f12c099 --- /dev/null +++ b/tests/test_uudoc.rs @@ -0,0 +1,122 @@ +//! Tests on the `uudoc` binary. +//! +//! To run the uudoc +//! ``` +//! cargo run --bin uudoc --features uudoc +//! ``` +//! +//! To run the tests +//! ``` +//! cargo test --features uudoc +//! ``` +#![cfg(feature = "uudoc")] + +use std::env; +pub const TESTS_BINARY: &str = env!("CARGO_BIN_EXE_uudoc"); + +// Set the environment variable for any tests + +// Use the ctor attribute to run this function before any tests +#[ctor::ctor] +fn init() { + // No need for unsafe here + unsafe { + env::set_var("UUTESTS_BINARY_PATH", TESTS_BINARY); + } + // Print for debugging + eprintln!("Setting UUTESTS_BINARY_PATH={TESTS_BINARY}"); +} + +/// Run the `uudoc` command and return the output as a vector of strings. +/// # Errors +/// If the command fails to execute or if the output cannot be converted to UTF-8, an `io::Error` is returned. +fn run_write_doc() -> Vec { + use std::process::Command; + use uutests::util::TestScenario; + + let scenario = TestScenario::new(""); + let output = Command::new(&scenario.bin_path).output().unwrap(); + assert!( + output.status.success(), + "uudoc command failed: {}", + String::from_utf8_lossy(&output.stderr) + ); + + String::from_utf8(output.stdout) + .unwrap() + .lines() + .map(String::from) + .filter(|line| line.starts_with("Wrote")) + .collect::>() +} + +fn get_doc_file_from_output(output: &str) -> (String, String) { + let correct_path_test = output + .strip_prefix("Wrote to '") + .unwrap() + .strip_suffix("'") + .unwrap() + .to_string(); + let content = std::fs::read_to_string(&correct_path_test); + let content = match content { + Ok(content) => content, + Err(e) => { + panic!( + "Failed to read file {}: {} from {:?}", + correct_path_test, + e, + env::current_dir() + ); + } + }; + (correct_path_test, content) +} + +#[test] +fn uudoc_check_test() { + let pages = run_write_doc(); + // assert wrote to the correct file + let path_test = pages.iter().find(|line| line.contains("test.md")).unwrap(); + let (correct_path, content) = get_doc_file_from_output(path_test); + + // open the file + assert!( + content.contains( + "``` +test EXPRESSION +test +[ EXPRESSION ] +[ ] +[ OPTION +``` +", + ), + "{correct_path} does not contains the required text" + ); +} + +#[test] +fn uudoc_check_sums() { + let pages = run_write_doc(); + let sums = [ + "md5sum", + "sha1sum", + "sha224sum", + "sha256sum", + "sha384sum", + "sha512sum", + "b2sum", + ]; + for one_sum in sums { + let output_path = pages + .iter() + .find(|one_line| one_line.contains(one_sum)) + .unwrap(); + let (correct_path, content) = get_doc_file_from_output(output_path); + let formatted = format!("```\n{one_sum} [OPTIONS] [FILE]...\n```"); + assert!( + content.contains(&formatted), + "Content of {correct_path} does not contain the expected format: {formatted}", + ); + } +} diff --git a/tests/tests.rs b/tests/tests.rs index b731ad465..08c9bfb3a 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -12,7 +12,7 @@ pub const TESTS_BINARY: &str = env!("CARGO_BIN_EXE_coreutils"); fn init() { unsafe { // Necessary for uutests to be able to find the binary - std::env::set_var("UUTESTS_BINARY_PATH", TESTS_BINARY); + env::set_var("UUTESTS_BINARY_PATH", TESTS_BINARY); } } diff --git a/tests/uutests/Cargo.toml b/tests/uutests/Cargo.toml index 57eea11ae..457930b4e 100644 --- a/tests/uutests/Cargo.toml +++ b/tests/uutests/Cargo.toml @@ -15,6 +15,9 @@ version.workspace = true [package.metadata.docs.rs] all-features = true +[lints] +workspace = true + [lib] path = "src/lib/lib.rs" diff --git a/tests/uutests/src/lib/util.rs b/tests/uutests/src/lib/util.rs index 0c4bd2553..eaa9c868d 100644 --- a/tests/uutests/src/lib/util.rs +++ b/tests/uutests/src/lib/util.rs @@ -97,7 +97,7 @@ const DEFAULT_ENV: [(&str, &str); 2] = [("LC_ALL", "C"), ("TZ", "UTC")]; /// Test if the program is running under CI pub fn is_ci() -> bool { - std::env::var("CI").is_ok_and(|s| s.eq_ignore_ascii_case("true")) + env::var("CI").is_ok_and(|s| s.eq_ignore_ascii_case("true")) } /// Read a test scenario fixture, returning its bytes @@ -942,7 +942,7 @@ pub fn get_root_path() -> &'static str { /// /// `true` if both paths have the same set of extended attributes, `false` otherwise. #[cfg(all(unix, not(any(target_os = "macos", target_os = "openbsd"))))] -pub fn compare_xattrs>(path1: P, path2: P) -> bool { +pub fn compare_xattrs>(path1: P, path2: P) -> bool { let get_sorted_xattrs = |path: P| { xattr::list(path) .map(|attrs| { @@ -1031,13 +1031,13 @@ impl AtPath { pub fn write(&self, name: &str, contents: &str) { log_info("write(default)", self.plus_as_string(name)); - std::fs::write(self.plus(name), contents) + fs::write(self.plus(name), contents) .unwrap_or_else(|e| panic!("Couldn't write {name}: {e}")); } pub fn write_bytes(&self, name: &str, contents: &[u8]) { log_info("write(default)", self.plus_as_string(name)); - std::fs::write(self.plus(name), contents) + fs::write(self.plus(name), contents) .unwrap_or_else(|e| panic!("Couldn't write {name}: {e}")); } @@ -1079,23 +1079,37 @@ impl AtPath { pub fn rename(&self, source: &str, target: &str) { let source = self.plus(source); let target = self.plus(target); - log_info("rename", format!("{source:?} {target:?}")); - std::fs::rename(&source, &target) - .unwrap_or_else(|e| panic!("Couldn't rename {source:?} -> {target:?}: {e}")); + log_info( + "rename", + format!("{} {}", source.display(), target.display()), + ); + fs::rename(&source, &target).unwrap_or_else(|e| { + panic!( + "Couldn't rename {} -> {}: {e}", + source.display(), + target.display() + ) + }); } pub fn remove(&self, source: &str) { let source = self.plus(source); - log_info("remove", format!("{source:?}")); - std::fs::remove_file(&source).unwrap_or_else(|e| panic!("Couldn't remove {source:?}: {e}")); + log_info("remove", format!("{}", source.display())); + remove_file(&source) + .unwrap_or_else(|e| panic!("Couldn't remove {}: {e}", source.display())); } pub fn copy(&self, source: &str, target: &str) { let source = self.plus(source); let target = self.plus(target); - log_info("copy", format!("{source:?} {target:?}")); - std::fs::copy(&source, &target) - .unwrap_or_else(|e| panic!("Couldn't copy {source:?} -> {target:?}: {e}")); + log_info("copy", format!("{} {}", source.display(), target.display())); + fs::copy(&source, &target).unwrap_or_else(|e| { + panic!( + "Couldn't copy {} -> {}: {e}", + source.display(), + target.display() + ) + }); } pub fn rmdir(&self, dir: &str) { @@ -1308,9 +1322,9 @@ impl AtPath { #[cfg(not(windows))] pub fn set_mode(&self, filename: &str, mode: u32) { let path = self.plus(filename); - let mut perms = std::fs::metadata(&path).unwrap().permissions(); + let mut perms = fs::metadata(&path).unwrap().permissions(); perms.set_mode(mode); - std::fs::set_permissions(&path, perms).unwrap(); + fs::set_permissions(&path, perms).unwrap(); } } @@ -1691,11 +1705,11 @@ impl UCommand { } #[cfg(unix)] - fn read_from_pty(pty_fd: std::os::fd::OwnedFd, out: File) { - let read_file = std::fs::File::from(pty_fd); - let mut reader = std::io::BufReader::new(read_file); - let mut writer = std::io::BufWriter::new(out); - let result = std::io::copy(&mut reader, &mut writer); + fn read_from_pty(pty_fd: OwnedFd, out: File) { + let read_file = File::from(pty_fd); + let mut reader = io::BufReader::new(read_file); + let mut writer = BufWriter::new(out); + let result = io::copy(&mut reader, &mut writer); match result { Ok(_) => {} // Input/output error (os error 5) is returned due to pipe closes. Buffer gets content anyway. @@ -1716,7 +1730,7 @@ impl UCommand { if let Some(mut captured_output_i) = captured_output { let fd = captured_output_i.try_clone().unwrap(); - let handle = std::thread::Builder::new() + let handle = thread::Builder::new() .name(name) .spawn(move || { Self::read_from_pty(pty_fd_master, fd); @@ -1788,7 +1802,7 @@ impl UCommand { { self.args.push_front(c_arg); } - }; + } // unwrap is safe here because we have set `self.bin_path` before let mut command = Command::new(self.bin_path.as_ref().unwrap()); @@ -1875,7 +1889,7 @@ impl UCommand { .stdin(self.stdin.take().unwrap_or_else(Stdio::null)) .stdout(stdout) .stderr(stderr); - }; + } #[cfg(unix)] if let Some(simulated_terminal) = &self.terminal_simulation { @@ -2050,7 +2064,7 @@ impl std::fmt::Display for UCommand { struct CapturedOutput { current_file: File, output: tempfile::NamedTempFile, // drop last - reader_thread_handle: Option>, + reader_thread_handle: Option>, } impl CapturedOutput { @@ -2064,7 +2078,7 @@ impl CapturedOutput { } /// Try to clone the file pointer. - fn try_clone(&mut self) -> io::Result { + fn try_clone(&mut self) -> Result { self.output.as_file().try_clone() } @@ -2265,7 +2279,7 @@ pub struct UChild { stdin_pty: Option, ignore_stdin_write_error: bool, stderr_to_stdout: bool, - join_handle: Option>>, + join_handle: Option>>, timeout: Option, tmpd: Option>, // drop last } @@ -2341,7 +2355,7 @@ impl UChild { /// /// If [`Child::kill`] returned an error or if the child process could not be terminated within /// `self.timeout` or the default of 60s. - pub fn try_kill(&mut self) -> io::Result<()> { + pub fn try_kill(&mut self) -> Result<()> { let start = Instant::now(); self.raw.kill()?; @@ -2402,10 +2416,7 @@ impl UChild { /// If [`Child::kill`] returned an error or if the child process could not be terminated within /// `self.timeout` or the default of 60s. #[cfg(unix)] - pub fn try_kill_with_custom_signal( - &mut self, - signal_name: sys::signal::Signal, - ) -> io::Result<()> { + pub fn try_kill_with_custom_signal(&mut self, signal_name: sys::signal::Signal) -> Result<()> { let start = Instant::now(); sys::signal::kill( nix::unistd::Pid::from_raw(self.raw.id().try_into().unwrap()), @@ -2462,7 +2473,7 @@ impl UChild { /// # Errors /// /// Returns the error from the call to `wait_with_output` if any - pub fn wait(self) -> io::Result { + pub fn wait(self) -> Result { let (bin_path, util_name, tmpd) = ( self.bin_path.clone(), self.util_name.clone(), @@ -2491,7 +2502,7 @@ impl UChild { /// /// If `self.timeout` is reached while waiting or [`Child::wait_with_output`] returned an /// error. - fn wait_with_output(mut self) -> io::Result { + fn wait_with_output(mut self) -> Result { // some apps do not stop execution until their stdin gets closed. // to prevent a endless waiting here, we close the stdin. self.join(); // ensure that all pending async input is piped in @@ -2533,7 +2544,7 @@ impl UChild { .join() .expect("Error joining with the piping stdin thread") .unwrap(); - }; + } if let Some(stdout) = self.captured_stdout.as_mut() { if let Some(handle) = stdout.reader_thread_handle.take() { @@ -2754,7 +2765,7 @@ impl UChild { } let mut writer = self.take_stdin_as_writer(); - let join_handle = std::thread::Builder::new() + let join_handle = thread::Builder::new() .name("pipe_in".to_string()) .spawn( move || match writer.write_all(&content).and_then(|()| writer.flush()) { @@ -2797,7 +2808,7 @@ impl UChild { /// /// # Errors /// If [`std::process::ChildStdin::write_all`] or [`std::process::ChildStdin::flush`] returned an error - pub fn try_write_in>>(&mut self, data: T) -> io::Result<()> { + pub fn try_write_in>>(&mut self, data: T) -> Result<()> { let ignore_stdin_write_error = self.ignore_stdin_write_error; let mut writer = self.access_stdin_as_writer(); @@ -2867,8 +2878,8 @@ pub fn whoami() -> String { // Use environment variable to get current user instead of // invoking `whoami` and fall back to user "nobody" on error. - std::env::var("USER") - .or_else(|_| std::env::var("USERNAME")) + env::var("USER") + .or_else(|_| env::var("USERNAME")) .unwrap_or_else(|e| { println!("{UUTILS_WARNING}: {e}, using \"nobody\" instead"); "nobody".to_string() @@ -3182,7 +3193,7 @@ mod tests { #[ctor::ctor] fn init() { unsafe { - std::env::set_var("UUTESTS_BINARY_PATH", ""); + env::set_var("UUTESTS_BINARY_PATH", ""); } } @@ -3429,7 +3440,7 @@ mod tests { match check_coreutil_version("id", VERSION_MIN) { Ok(s) => assert!(s.starts_with("uutils-tests-")), Err(s) => assert!(s.starts_with("uutils-tests-warning")), - }; + } #[cfg(target_os = "linux")] std::assert_eq!( check_coreutil_version("no test name", VERSION_MIN), diff --git a/util/analyze-gnu-results.py b/util/analyze-gnu-results.py old mode 100644 new mode 100755 diff --git a/util/android-scripts/collect-info.sh b/util/android-scripts/collect-info.sh old mode 100644 new mode 100755 diff --git a/util/android-scripts/run-tests.sh b/util/android-scripts/run-tests.sh old mode 100644 new mode 100755 diff --git a/util/compare_test_results.py b/util/compare_test_results.py old mode 100644 new mode 100755 diff --git a/util/deps.nu b/util/deps.nu index 368048fff..4a0ea354a 100644 --- a/util/deps.nu +++ b/util/deps.nu @@ -88,7 +88,7 @@ export def all_dep_info [] { let features = [unix, feat_selinux] let lock = open Cargo.lock | from toml | get package - + $lock # Add number of versions | join ($lock | group-by name | transpose | update column1 { length } | rename name num_versions) name diff --git a/util/dwr.sh b/util/dwr.sh old mode 100644 new mode 100755 diff --git a/util/fetch-gnu.sh b/util/fetch-gnu.sh index 7765bf544..a242b6606 100755 --- a/util/fetch-gnu.sh +++ b/util/fetch-gnu.sh @@ -16,6 +16,8 @@ sed -i -e 's/no-mtab-status.sh/no-mtab-status-masked-proc.sh/' -e 's/nproc-quota curl -L ${repo}/raw/refs/heads/master/tests/df/no-mtab-status-masked-proc.sh > tests/df/no-mtab-status-masked-proc.sh curl -L ${repo}/raw/refs/heads/master/tests/nproc/nproc-quota-systemd.sh > tests/nproc/nproc-quota-systemd.sh curl -L ${repo}/raw/refs/heads/master/tests/stty/bad-speed.sh > tests/stty/bad-speed.sh +# symlink to /bin/false should fail with --help. Freeze commit to avoid regression. +curl -L https://raw.githubusercontent.com/coreutils/coreutils/d5164f3d216917005003877faeb1abe7cae5d765/tests/misc/coreutils.sh > tests/misc/coreutils.sh # Better support for single binary curl -L ${repo}/raw/refs/heads/master/tests/env/env.sh > tests/env/env.sh # Avoid incorrect PASS diff --git a/util/gnu-patches/tests_cksum_base64.patch b/util/gnu-patches/tests_cksum_base64.patch index ea6bf92e1..c5cbcab34 100644 --- a/util/gnu-patches/tests_cksum_base64.patch +++ b/util/gnu-patches/tests_cksum_base64.patch @@ -4,7 +4,7 @@ Index: gnu/tests/cksum/cksum-base64.pl +++ gnu/tests/cksum/cksum-base64.pl @@ -92,8 +92,8 @@ my $prog = 'cksum'; my $fail = run_tests ($program_name, $prog, \@Tests, $save_temps, $verbose); - + # Ensure hash names from cksum --help match those in @pairs above. -my $help_algs = join ' ', map { m{^ ([[:alpha:]]\S+)} } - grep { m{^ ([[:alpha:]]\S+)} } split ('\n', `cksum --help`); @@ -12,4 +12,3 @@ Index: gnu/tests/cksum/cksum-base64.pl + grep { m{^\s*-\s*([[:alpha:]]\S+):} } split ('\n', `cksum --help`); my $test_algs = join ' ', map {$_->[0]} @pairs; $help_algs eq $test_algs or die "$help_algs not equal to\n$test_algs"; - diff --git a/util/gnu-patches/tests_cut_error_msg.patch b/util/gnu-patches/tests_cut_error_msg.patch index 1b1673fef..63e3c3813 100644 --- a/util/gnu-patches/tests_cut_error_msg.patch +++ b/util/gnu-patches/tests_cut_error_msg.patch @@ -3,7 +3,7 @@ Index: gnu/tests/cut/cut.pl --- gnu.orig/tests/cut/cut.pl +++ gnu/tests/cut/cut.pl @@ -29,13 +29,15 @@ my $mb_locale = $ENV{LOCALE_FR_UTF8}; - + my $prog = 'cut'; my $try = "Try '$prog --help' for more information.\n"; -my $from_field1 = "$prog: fields are numbered from 1\n$try"; @@ -22,21 +22,21 @@ Index: gnu/tests/cut/cut.pl +my $inval_pos = "$prog: range '--' was invalid: failed to parse range\n"; +my $no_endpoint = "$prog: range '-' was invalid: invalid range with no endpoint\n"; +my $nofield = "$prog: invalid input: The '--delimiter' ('-d') option only usable if printing a sequence of fields\n"; - + my @Tests = ( @@ -44,16 +46,16 @@ my @Tests = - + # This failed (as it should) even before coreutils-6.9.90, # but cut from 6.9.90 produces a more useful diagnostic. - ['zero-1', '-b0', {ERR=>$from_pos1}, {EXIT => 1} ], + ['zero-1', '-b0', {ERR=>$from_field_0}, {EXIT => 1} ], - + # Up to coreutils-6.9, specifying a range of 0-2 was not an error. # It was treated just like "-2". - ['zero-2', '-f0-2', {ERR=>$from_field1}, {EXIT => 1} ], + ['zero-2', '-f0-2', {ERR=>$from_field_0_2}, {EXIT => 1} ], - + # Up to coreutils-8.20, specifying a range of 0- was not an error. - ['zero-3b', '-b0-', {ERR=>$from_pos1}, {EXIT => 1} ], - ['zero-3c', '-c0-', {ERR=>$from_pos1}, {EXIT => 1} ], @@ -44,7 +44,7 @@ Index: gnu/tests/cut/cut.pl + ['zero-3b', '-b0-', {ERR=>$from_field_0_dash}, {EXIT => 1} ], + ['zero-3c', '-c0-', {ERR=>$from_field_0_dash}, {EXIT => 1} ], + ['zero-3f', '-f0-', {ERR=>$from_field_0_dash}, {EXIT => 1} ], - + ['1', '-d:', '-f1,3-', {IN=>"a:b:c\n"}, {OUT=>"a:c\n"}], ['2', '-d:', '-f1,3-', {IN=>"a:b:c\n"}, {OUT=>"a:c\n"}], @@ -96,11 +98,10 @@ my @Tests = @@ -62,7 +62,7 @@ Index: gnu/tests/cut/cut.pl # Empty field list ['empty-fl', qw(-f ''), {IN=>":\n"}, {OUT=>""}, {EXIT=>1}, @@ -199,7 +200,7 @@ my @Tests = - + # None of the following invalid ranges provoked an error up to coreutils-6.9. ['inval1', qw(-f 2-0), {IN=>''}, {OUT=>''}, {EXIT=>1}, - {ERR=>"$prog: invalid decreasing range\n$try"}], diff --git a/util/gnu-patches/tests_du_move_dir_while_traversing.patch b/util/gnu-patches/tests_du_move_dir_while_traversing.patch index 12b7e5c36..c6c594d68 100644 --- a/util/gnu-patches/tests_du_move_dir_while_traversing.patch +++ b/util/gnu-patches/tests_du_move_dir_while_traversing.patch @@ -5,12 +5,12 @@ Index: gnu/tests/du/move-dir-while-traversing.sh @@ -91,9 +91,7 @@ retry_delay_ nonempty .1 5 || fail=1 # Before coreutils-8.10, du would abort. returns_ 1 du -a $t d2 2> err || fail=1 - + -# check for the new diagnostic -printf "du: fts_read failed: $t/3/a/b: No such file or directory\n" > exp \ - || fail=1 -compare exp err || fail=1 +# check that it doesn't crash +grep -Pq "^du: cannot read directory '$t/3/a/b.*': No such file or directory" err || fail=1 - + Exit $fail diff --git a/util/gnu-patches/tests_invalid_opt.patch b/util/gnu-patches/tests_invalid_opt.patch index c23476674..2819071cc 100644 --- a/util/gnu-patches/tests_invalid_opt.patch +++ b/util/gnu-patches/tests_invalid_opt.patch @@ -5,7 +5,7 @@ Index: gnu/tests/misc/invalid-opt.pl @@ -74,23 +74,13 @@ foreach my $prog (@built_programs) defined $out or $out = ''; - + - my $err = $expected_err{$prog}; - defined $err - or $err = $x == 0 ? '' : "$prog: invalid option -- /\n$try"; @@ -22,11 +22,11 @@ Index: gnu/tests/misc/invalid-opt.pl + # Strip all stderr output + # Our output is better and more consistent + my $err_subst = 's/(.|\n)*//ms'; - + my @Tests = (["$prog-invalid-opt", '-/', {OUT=>$out}, {ERR_SUBST => $err_subst}, - {EXIT=>$x}, {ERR=>$err}]); + {EXIT=>$x}]); - + my $save_temps = $ENV{DEBUG}; my $verbose = $ENV{VERBOSE}; diff --git a/util/gnu-patches/tests_ls_no_cap.patch b/util/gnu-patches/tests_ls_no_cap.patch index c2f48ca21..62b836f79 100644 --- a/util/gnu-patches/tests_ls_no_cap.patch +++ b/util/gnu-patches/tests_ls_no_cap.patch @@ -4,17 +4,17 @@ index 99f0563bc..f7b9e7885 100755 +++ b/tests/ls/no-cap.sh @@ -27,11 +27,11 @@ setcap 'cap_net_bind_service=ep' file || skip_ "setcap doesn't work" - + LS_COLORS=ca=1; export LS_COLORS -strace -e capget ls --color=always > /dev/null 2> out || fail=1 -$EGREP 'capget\(' out || skip_ "your ls doesn't call capget" +strace -e listxattr ls --color=always > /dev/null 2> out || fail=1 +$EGREP 'listxattr\(' out || skip_ "your ls doesn't call listxattr" - + LS_COLORS=ca=:; export LS_COLORS -strace -e capget ls --color=always > /dev/null 2> out || fail=1 -$EGREP 'capget\(' out && fail=1 +strace -e listxattr ls --color=always > /dev/null 2> out || fail=1 +$EGREP 'listxattr\(' out && fail=1 - + Exit $fail diff --git a/util/gnu-patches/tests_sort_merge.pl.patch b/util/gnu-patches/tests_sort_merge.pl.patch index d6db2e09c..3a5982af1 100644 --- a/util/gnu-patches/tests_sort_merge.pl.patch +++ b/util/gnu-patches/tests_sort_merge.pl.patch @@ -8,25 +8,25 @@ Index: gnu/tests/sort/sort-merge.pl {ERR=>"$prog: invalid --batch-size argument '0'\n". - "$prog: minimum --batch-size argument is '2'\n"}, {EXIT=>2}], + "$prog: minimum --batch-size argument is '2'\nTry 'sort --help' for more information.\n"}, {EXIT=>2}], - + ['nmerge-1', "-m --batch-size=1", @inputs, {ERR=>"$prog: invalid --batch-size argument '1'\n". - "$prog: minimum --batch-size argument is '2'\n"}, {EXIT=>2}], + "$prog: minimum --batch-size argument is '2'\nTry 'sort --help' for more information.\n"}, {EXIT=>2}], - + ['nmerge-neg', "-m --batch-size=-1", @inputs, - {ERR=>"$prog: invalid --batch-size argument '-1'\n"}, {EXIT=>2}], + {ERR=>"$prog: invalid --batch-size argument '-1'\nTry 'sort --help' for more information.\n"}, {EXIT=>2}], - + ['nmerge-nan', "-m --batch-size=a", @inputs, - {ERR=>"$prog: invalid --batch-size argument 'a'\n"}, {EXIT=>2}], + {ERR=>"$prog: invalid --batch-size argument 'a'\nTry 'sort --help' for more information.\n"}, {EXIT=>2}], - + ['nmerge-big', "-m --batch-size=$bigint", @inputs, {ERR_SUBST=>'s/(current rlimit is) \d+/$1/'}, {ERR=>"$prog: --batch-size argument '$bigint' too large\n". - "$prog: maximum --batch-size argument with current rlimit is\n"}, + "$prog: maximum --batch-size argument with current rlimit is\nTry 'sort --help' for more information.\n"}, {EXIT=>2}], - + # This should work since nmerge >= the number of input files diff --git a/util/show-utils.BAT b/util/show-utils.BAT index 92f618160..00e9df5a1 100644 --- a/util/show-utils.BAT +++ b/util/show-utils.BAT @@ -1,32 +1,32 @@ -@setLocal -@echo off - -@rem ::# spell-checker:ignore (CMD) ERRORLEVEL -@rem ::# spell-checker:ignore (utils) cksum coreutils dircolors mkdir mktemp printenv printf readlink realpath rmdir shuf tsort unexpand -@rem ::# spell-checker:ignore (jq) deps startswith - -set "ME=%~0" -set "ME_dir=%~dp0." -set "ME_parent_dir=%~dp0.\.." - -@rem refs: , - -@rem :: default ("Tier 1" cross-platform) utility list -set "default_utils=base32 base64 basename cat cksum comm cp cut date dircolors dirname echo env expand expr factor false fmt fold head join link ln ls mkdir mktemp more mv nl od paste printenv printf ptx pwd readlink realpath rm rmdir seq shred shuf sleep sort split sum tac tail tee test tr true truncate tsort unexpand uniq wc yes" - -set "project_dir=%ME_parent_dir%" -cd "%project_dir%" - -@:: `jq` available? -set "JQ=" -set "ERRORLEVEL=" -jq --version 1>NUL 2>&1 -if NOT ERRORLEVEL 1 ( set "JQ=jq" ) - -if NOT DEFINED JQ ( - echo WARN: missing `jq` ^(install with `scoop install jq`^)^; falling back to default ^(only fully cross-platform^) util list 1>&2 - echo %default_utils% -) else ( - cargo metadata %* --format-version 1 | jq -r "[.resolve.nodes[] | { id: .id, deps: [.deps[] | { name:.name, pkg:.pkg }] }] | .[] | select(.id|startswith(\"coreutils\")) | [.deps[] | select((.name|startswith(\"uu_\")) or (.pkg|startswith(\"uu_\")))] | [.[].pkg | match(\"^^\\w+\";\"g\")] | [.[].string | sub(\"^uu_\"; \"\")] | sort | join(\" \")" - REM cargo metadata %* --format-version 1 | jq -r "[.resolve.nodes[] | { id: .id, deps: [.deps[] | { name:.name, pkg:.pkg }] }] | .[] | select(.id|startswith(\"coreutils\")) | [.deps[] | select((.name|startswith(\"uu_\")) or (.pkg|startswith(\"uu_\")))] | [.[].pkg | match(\"^^\\w+\";\"g\")] | [.[].string] | sort | join(\" \")" -) +@setLocal +@echo off + +@rem ::# spell-checker:ignore (CMD) ERRORLEVEL +@rem ::# spell-checker:ignore (utils) cksum coreutils dircolors mkdir mktemp printenv printf readlink realpath rmdir shuf tsort unexpand +@rem ::# spell-checker:ignore (jq) deps startswith + +set "ME=%~0" +set "ME_dir=%~dp0." +set "ME_parent_dir=%~dp0.\.." + +@rem refs: , + +@rem :: default ("Tier 1" cross-platform) utility list +set "default_utils=base32 base64 basename cat cksum comm cp cut date dircolors dirname echo env expand expr factor false fmt fold head join link ln ls mkdir mktemp more mv nl od paste printenv printf ptx pwd readlink realpath rm rmdir seq shred shuf sleep sort split sum tac tail tee test tr true truncate tsort unexpand uniq wc yes" + +set "project_dir=%ME_parent_dir%" +cd "%project_dir%" + +@:: `jq` available? +set "JQ=" +set "ERRORLEVEL=" +jq --version 1>NUL 2>&1 +if NOT ERRORLEVEL 1 ( set "JQ=jq" ) + +if NOT DEFINED JQ ( + echo WARN: missing `jq` ^(install with `scoop install jq`^)^; falling back to default ^(only fully cross-platform^) util list 1>&2 + echo %default_utils% +) else ( + cargo metadata %* --format-version 1 | jq -r "[.resolve.nodes[] | { id: .id, deps: [.deps[] | { name:.name, pkg:.pkg }] }] | .[] | select(.id|startswith(\"coreutils\")) | [.deps[] | select((.name|startswith(\"uu_\")) or (.pkg|startswith(\"uu_\")))] | [.[].pkg | match(\"^^\\w+\";\"g\")] | [.[].string | sub(\"^uu_\"; \"\")] | sort | join(\" \")" + REM cargo metadata %* --format-version 1 | jq -r "[.resolve.nodes[] | { id: .id, deps: [.deps[] | { name:.name, pkg:.pkg }] }] | .[] | select(.id|startswith(\"coreutils\")) | [.deps[] | select((.name|startswith(\"uu_\")) or (.pkg|startswith(\"uu_\")))] | [.[].pkg | match(\"^^\\w+\";\"g\")] | [.[].string] | sort | join(\" \")" +) diff --git a/util/size-experiment.py b/util/size-experiment.py old mode 100644 new mode 100755 diff --git a/util/test-repo-whitespace.BAT b/util/test-repo-whitespace.BAT index 6e2fce557..eb6f110c9 100644 --- a/util/test-repo-whitespace.BAT +++ b/util/test-repo-whitespace.BAT @@ -1,93 +1,93 @@ -@setLocal -@echo off - -:: `test-repo-whitespace [DIR]...` v2022.01.01 -:: style inspector ~ whitespace: find nonconforming files in repository - -:: Copyright (C) 2016-2022 ~ Roy Ivy III -:: License: MIT/Apache-2.0 (see https://opensource.org/licenses/Apache-2.0 , https://opensource.org/licenses/MIT) -:: * this software is provided for free, WITHOUT ANY EXPRESS OR IMPLIED WARRANTY (see the license for details) - -:: spell-checker:ignore (shell/cmd) COMSPEC ERRORLEVEL -:: spell-checker:ignore () CTYPE POSIX Tval Tvar akefile makefile makefiles multiline testdata - -:config -set "_exclude_dirs_rx=(?i)[_.#]build|[.]git|[.]gpg|[.]obj|[.]vs|fixtures|node_modules|target|testdata|test-resources|vendor" -set "_exclude_files_rx=(?i)[.](cache|dll|exe|gif|gz|ico|png|zip)$" -set "_crlf_files_rx=(?i)[.](bat|cmd|csv)$" -set "_tabbed_files_rx=(?i)^^(.*[.]go|go[.](mod|sum)|(GNU)?[Mm]akefile([.].*)?)$" -:config_done - -set _dp0=%~dp0. -set _nx0=%~nx0 -set dirs=%* -if NOT DEFINED dirs if EXIST "%CD%\repo" ( set dirs="%CD%\repo" ) -if NOT DEFINED dirs if EXIST "%_dp0%\..\.git" ( set dirs="%_dp0%\.." ) -if NOT DEFINED dirs ( set "dirs=." ) - -set PCREGREP=pcregrep -if EXIST "%_dp0%\pcregrep.EXE" ( set "PCREGREP=%_dp0%\pcregrep.EXE" ) -"%PCREGREP%" --version >NUL 2>NUL || ( echo ERR!: Missing required `pcregrep` [try `scoop install pcregrep`] 1>&2 & goto _undefined_ 2>NUL || "%COMSPEC%" /d/c exit 1 ) - -echo [ %dirs% ] - -if /i "%LC_CTYPE%"=="posix" (set "LC_CTYPE=C") &:: `pcregrep` doesn't understand the common "POSIX", replace with the equivalent "C" - -set "ERRORLEVEL=" -set "ERROR=" -:: 1. Test for TABs within leading whitespace (except go files makefiles) -"%PCREGREP%" -I --exclude-dir "%_exclude_dirs_rx%" --exclude "%_exclude_files_rx%" --exclude "%_tabbed_files_rx%" --count --files-with-matches --recursive "^\s*\t" %dirs% -if NOT "%ERRORLEVEL%" == "1" ( set ERROR=1 & echo ERROR: files found with TABs within leading whitespace [file:#lines_matched]) - -:: 2. Test for lines with internal TABs -"%PCREGREP%" -I --exclude-dir "%_exclude_dirs_rx%" --exclude "%_exclude_files_rx%" --count --files-with-matches --recursive "^.*[^\t]\t" %dirs% -if NOT "%ERRORLEVEL%" == "1" ( set ERROR=1 & echo ERROR: files found with lines containing internal TABs [file:#lines_matched]) - -:: 3. Test that makefiles have ONLY initial-TAB leading whitespace -"%PCREGREP%" -I --exclude-dir "%_exclude_dirs_rx%" --include "(GNU)?[Mm]akefile([.].*)?" --exclude "[.](to|y)ml$" --recursive --line-number --invert-match "^([\t]\s*\S|\S|$)" %dirs% -if NOT "%ERRORLEVEL%" == "1" ( set ERROR=1 & echo ERROR: Makefiles found with lines having non-TAB leading whitespace [file:line_number]) - -:: 4. Test for non-LF line endings -set "HAVE_NonLF_ERROR=" -"%PCREGREP%" --buffer-size=1M -I --exclude-dir "%_exclude_dirs_rx%" --exclude "%_exclude_files_rx%" -NLF --files-with-matches --multiline --recursive "\r[^\n]" %dirs% -if NOT "%ERRORLEVEL%" == "1" ( set HAVE_NonLF_ERROR=1 & echo ## files found with CR line endings) -"%PCREGREP%" --buffer-size=1M -I --exclude-dir "%_exclude_dirs_rx%" --exclude "%_exclude_files_rx%" --exclude "%_crlf_files_rx%" -NLF --files-with-matches --multiline --recursive "\r\n" %dirs% -if NOT "%ERRORLEVEL%" == "1" ( set HAVE_NonLF_ERROR=1 & echo ## files found with CRLF line endings) -if DEFINED HAVE_NonLF_ERROR ( set ERROR=1 & echo ERROR: files found with non-LF line endings) - -:: 5. Test for files without trailing newline -:: "%PCREGREP%" -I --exclude-dir "%_exclude_dirs_rx%" --exclude "%_exclude_files_rx%" --files-without-match --multiline --recursive "\r?[\r\n]\z" %dirs% -"%PCREGREP%" -I --exclude-dir "%_exclude_dirs_rx%" --exclude "%_exclude_files_rx%" --files-with-matches --multiline --recursive "\z" %dirs% -if NOT "%ERRORLEVEL%" == "1" ( set ERROR=1 & echo ERROR: files found without trailing newline) - -:: 6. Test for files with lines having trailing whitespace -"%PCREGREP%" -I --exclude-dir "%_exclude_dirs_rx%" --exclude "%_exclude_files_rx%" --recursive --line-number "\s$" %dirs% -if NOT "%ERRORLEVEL%" == "1" ( set ERROR=1 & echo ERROR: files found with lines having trailing whitespace [file:line_number]) - -:: 7. Test for files with BOM -"%PCREGREP%" -I --exclude-dir "%_exclude_dirs_rx%" --exclude "%_exclude_files_rx%" --files-with-matches --multiline --recursive "\A[\xEF][\xBB][\xBF]" %dirs% -if NOT "%ERRORLEVEL%" == "1" ( set ERROR=1 & echo ERROR: files found with leading BOM) - -:script_done -if NOT DEFINED ERROR ( - echo success: no file errors found - ) -call :#exit__title __exit_title "%COMSPEC%" -set "__exit_title=" && title %__exit_title% && set "ERRORLEVEL=" & goto _undefined_ 2>NUL || "%COMSPEC%" /d/c "exit %ERROR%" - -@rem:::: -@rem:: FUNCTIONS -@goto :EOF - -:: -:#exit__title ( ref_RETURN PATH ) -:: RETURN == name of PATH -setLocal -set "_RETvar=%~1" -set "_RETval=%~2" -if NOT DEFINED _RETval ( set "_RETval=cmd" & goto :#exit__title_RETURN ) -set "_RETval=%~n2" -:#exit__title_RETURN -endLocal & set %_RETvar%^=%_RETval% -goto :EOF -:: +@setLocal +@echo off + +:: `test-repo-whitespace [DIR]...` v2022.01.01 +:: style inspector ~ whitespace: find nonconforming files in repository + +:: Copyright (C) 2016-2022 ~ Roy Ivy III +:: License: MIT/Apache-2.0 (see https://opensource.org/licenses/Apache-2.0 , https://opensource.org/licenses/MIT) +:: * this software is provided for free, WITHOUT ANY EXPRESS OR IMPLIED WARRANTY (see the license for details) + +:: spell-checker:ignore (shell/cmd) COMSPEC ERRORLEVEL +:: spell-checker:ignore () CTYPE POSIX Tval Tvar akefile makefile makefiles multiline testdata + +:config +set "_exclude_dirs_rx=(?i)[_.#]build|[.]git|[.]gpg|[.]obj|[.]vs|fixtures|node_modules|target|testdata|test-resources|vendor" +set "_exclude_files_rx=(?i)[.](cache|dll|exe|gif|gz|ico|png|zip)$" +set "_crlf_files_rx=(?i)[.](bat|cmd|csv)$" +set "_tabbed_files_rx=(?i)^^(.*[.]go|go[.](mod|sum)|(GNU)?[Mm]akefile([.].*)?)$" +:config_done + +set _dp0=%~dp0. +set _nx0=%~nx0 +set dirs=%* +if NOT DEFINED dirs if EXIST "%CD%\repo" ( set dirs="%CD%\repo" ) +if NOT DEFINED dirs if EXIST "%_dp0%\..\.git" ( set dirs="%_dp0%\.." ) +if NOT DEFINED dirs ( set "dirs=." ) + +set PCREGREP=pcregrep +if EXIST "%_dp0%\pcregrep.EXE" ( set "PCREGREP=%_dp0%\pcregrep.EXE" ) +"%PCREGREP%" --version >NUL 2>NUL || ( echo ERR!: Missing required `pcregrep` [try `scoop install pcregrep`] 1>&2 & goto _undefined_ 2>NUL || "%COMSPEC%" /d/c exit 1 ) + +echo [ %dirs% ] + +if /i "%LC_CTYPE%"=="posix" (set "LC_CTYPE=C") &:: `pcregrep` doesn't understand the common "POSIX", replace with the equivalent "C" + +set "ERRORLEVEL=" +set "ERROR=" +:: 1. Test for TABs within leading whitespace (except go files makefiles) +"%PCREGREP%" -I --exclude-dir "%_exclude_dirs_rx%" --exclude "%_exclude_files_rx%" --exclude "%_tabbed_files_rx%" --count --files-with-matches --recursive "^\s*\t" %dirs% +if NOT "%ERRORLEVEL%" == "1" ( set ERROR=1 & echo ERROR: files found with TABs within leading whitespace [file:#lines_matched]) + +:: 2. Test for lines with internal TABs +"%PCREGREP%" -I --exclude-dir "%_exclude_dirs_rx%" --exclude "%_exclude_files_rx%" --count --files-with-matches --recursive "^.*[^\t]\t" %dirs% +if NOT "%ERRORLEVEL%" == "1" ( set ERROR=1 & echo ERROR: files found with lines containing internal TABs [file:#lines_matched]) + +:: 3. Test that makefiles have ONLY initial-TAB leading whitespace +"%PCREGREP%" -I --exclude-dir "%_exclude_dirs_rx%" --include "(GNU)?[Mm]akefile([.].*)?" --exclude "[.](to|y)ml$" --recursive --line-number --invert-match "^([\t]\s*\S|\S|$)" %dirs% +if NOT "%ERRORLEVEL%" == "1" ( set ERROR=1 & echo ERROR: Makefiles found with lines having non-TAB leading whitespace [file:line_number]) + +:: 4. Test for non-LF line endings +set "HAVE_NonLF_ERROR=" +"%PCREGREP%" --buffer-size=1M -I --exclude-dir "%_exclude_dirs_rx%" --exclude "%_exclude_files_rx%" -NLF --files-with-matches --multiline --recursive "\r[^\n]" %dirs% +if NOT "%ERRORLEVEL%" == "1" ( set HAVE_NonLF_ERROR=1 & echo ## files found with CR line endings) +"%PCREGREP%" --buffer-size=1M -I --exclude-dir "%_exclude_dirs_rx%" --exclude "%_exclude_files_rx%" --exclude "%_crlf_files_rx%" -NLF --files-with-matches --multiline --recursive "\r\n" %dirs% +if NOT "%ERRORLEVEL%" == "1" ( set HAVE_NonLF_ERROR=1 & echo ## files found with CRLF line endings) +if DEFINED HAVE_NonLF_ERROR ( set ERROR=1 & echo ERROR: files found with non-LF line endings) + +:: 5. Test for files without trailing newline +:: "%PCREGREP%" -I --exclude-dir "%_exclude_dirs_rx%" --exclude "%_exclude_files_rx%" --files-without-match --multiline --recursive "\r?[\r\n]\z" %dirs% +"%PCREGREP%" -I --exclude-dir "%_exclude_dirs_rx%" --exclude "%_exclude_files_rx%" --files-with-matches --multiline --recursive "\z" %dirs% +if NOT "%ERRORLEVEL%" == "1" ( set ERROR=1 & echo ERROR: files found without trailing newline) + +:: 6. Test for files with lines having trailing whitespace +"%PCREGREP%" -I --exclude-dir "%_exclude_dirs_rx%" --exclude "%_exclude_files_rx%" --recursive --line-number "\s$" %dirs% +if NOT "%ERRORLEVEL%" == "1" ( set ERROR=1 & echo ERROR: files found with lines having trailing whitespace [file:line_number]) + +:: 7. Test for files with BOM +"%PCREGREP%" -I --exclude-dir "%_exclude_dirs_rx%" --exclude "%_exclude_files_rx%" --files-with-matches --multiline --recursive "\A[\xEF][\xBB][\xBF]" %dirs% +if NOT "%ERRORLEVEL%" == "1" ( set ERROR=1 & echo ERROR: files found with leading BOM) + +:script_done +if NOT DEFINED ERROR ( + echo success: no file errors found + ) +call :#exit__title __exit_title "%COMSPEC%" +set "__exit_title=" && title %__exit_title% && set "ERRORLEVEL=" & goto _undefined_ 2>NUL || "%COMSPEC%" /d/c "exit %ERROR%" + +@rem:::: +@rem:: FUNCTIONS +@goto :EOF + +:: +:#exit__title ( ref_RETURN PATH ) +:: RETURN == name of PATH +setLocal +set "_RETvar=%~1" +set "_RETval=%~2" +if NOT DEFINED _RETval ( set "_RETval=cmd" & goto :#exit__title_RETURN ) +set "_RETval=%~n2" +:#exit__title_RETURN +endLocal & set %_RETvar%^=%_RETval% +goto :EOF +:: diff --git a/util/test_compare_test_results.py b/util/test_compare_test_results.py old mode 100644 new mode 100755