mirror of
https://github.com/uutils/coreutils
synced 2024-11-05 14:21:32 +00:00
Merge branch 'main' into dd-seconds-precision-3
This commit is contained in:
commit
59d34ce667
47 changed files with 1068 additions and 649 deletions
129
.github/workflows/CICD.yml
vendored
129
.github/workflows/CICD.yml
vendored
|
@ -37,8 +37,6 @@ jobs:
|
|||
## ToDO: [2021-11-10; rivy] 'Style/deps' needs more informative output and better integration of results into the GHA dashboard
|
||||
name: Style/deps
|
||||
runs-on: ${{ matrix.job.os }}
|
||||
# env:
|
||||
# STYLE_FAIL_ON_FAULT: false # overrides workflow default
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
|
@ -73,7 +71,6 @@ jobs:
|
|||
run: |
|
||||
rustup toolchain install nightly --no-self-update --profile minimal
|
||||
rustup default nightly
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: Install `cargo-udeps`
|
||||
run: cargo install cargo-udeps
|
||||
env:
|
||||
|
@ -93,8 +90,6 @@ jobs:
|
|||
style_format:
|
||||
name: Style/format
|
||||
runs-on: ${{ matrix.job.os }}
|
||||
# env:
|
||||
# STYLE_FAIL_ON_FAULT: false # overrides workflow default
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
|
@ -124,7 +119,6 @@ jobs:
|
|||
## Install `rust` toolchain
|
||||
rustup toolchain install stable --no-self-update -c rustfmt --profile minimal
|
||||
rustup default stable
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: "`cargo fmt` testing"
|
||||
shell: bash
|
||||
run: |
|
||||
|
@ -151,7 +145,7 @@ jobs:
|
|||
- name: Install `cargo-fuzz`
|
||||
run: cargo install cargo-fuzz
|
||||
- name: Run fuzz_date for XX seconds
|
||||
# TODO: fix the issues
|
||||
# TODO: fix https://github.com/uutils/coreutils/issues/4494
|
||||
continue-on-error: true
|
||||
shell: bash
|
||||
run: |
|
||||
|
@ -159,8 +153,6 @@ jobs:
|
|||
cd fuzz
|
||||
cargo +nightly fuzz run fuzz_date -- -max_total_time=${{ env.RUN_FOR }} -detect_leaks=0
|
||||
- name: Run fuzz_parse_glob for XX seconds
|
||||
# TODO: fix the issues
|
||||
continue-on-error: true
|
||||
shell: bash
|
||||
run: |
|
||||
## Run it
|
||||
|
@ -173,8 +165,6 @@ jobs:
|
|||
cd fuzz
|
||||
cargo +nightly fuzz run fuzz_parse_size -- -max_total_time=${{ env.RUN_FOR }} -detect_leaks=0
|
||||
- name: Run fuzz_parse_time for XX seconds
|
||||
# TODO: fix the issues
|
||||
continue-on-error: true
|
||||
shell: bash
|
||||
run: |
|
||||
## Run it
|
||||
|
@ -184,8 +174,9 @@ jobs:
|
|||
style_lint:
|
||||
name: Style/lint
|
||||
runs-on: ${{ matrix.job.os }}
|
||||
# env:
|
||||
# STYLE_FAIL_ON_FAULT: false # overrides workflow default
|
||||
env:
|
||||
SCCACHE_GHA_ENABLED: "true"
|
||||
RUSTC_WRAPPER: "sccache"
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
|
@ -195,6 +186,11 @@ jobs:
|
|||
- { os: windows-latest , features: feat_os_windows }
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: Run sccache-cache
|
||||
uses: mozilla-actions/sccache-action@v0.0.2
|
||||
with:
|
||||
version: "v0.4.0-pre.11"
|
||||
- name: Initialize workflow variables
|
||||
id: vars
|
||||
shell: bash
|
||||
|
@ -229,7 +225,6 @@ jobs:
|
|||
## Install `rust` toolchain
|
||||
rustup toolchain install stable --no-self-update -c clippy --profile minimal
|
||||
rustup default stable
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: "`cargo clippy` lint testing"
|
||||
shell: bash
|
||||
run: |
|
||||
|
@ -244,15 +239,12 @@ jobs:
|
|||
style_spellcheck:
|
||||
name: Style/spelling
|
||||
runs-on: ${{ matrix.job.os }}
|
||||
# env:
|
||||
# STYLE_FAIL_ON_FAULT: false # overrides workflow default
|
||||
strategy:
|
||||
matrix:
|
||||
job:
|
||||
- { os: ubuntu-latest , features: feat_os_unix }
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: Initialize workflow variables
|
||||
id: vars
|
||||
shell: bash
|
||||
|
@ -292,6 +284,9 @@ jobs:
|
|||
doc_warnings:
|
||||
name: Documentation/warnings
|
||||
runs-on: ${{ matrix.job.os }}
|
||||
env:
|
||||
SCCACHE_GHA_ENABLED: "true"
|
||||
RUSTC_WRAPPER: "sccache"
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
|
@ -304,6 +299,11 @@ jobs:
|
|||
# - { os: windows-latest , features: feat_os_windows }
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: Run sccache-cache
|
||||
uses: mozilla-actions/sccache-action@v0.0.2
|
||||
with:
|
||||
version: "v0.4.0-pre.11"
|
||||
- name: Initialize workflow variables
|
||||
id: vars
|
||||
shell: bash
|
||||
|
@ -331,7 +331,6 @@ jobs:
|
|||
## Install `rust` toolchain
|
||||
rustup toolchain install stable --no-self-update -c clippy --profile minimal
|
||||
rustup default stable
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: "`cargo doc` with warnings"
|
||||
shell: bash
|
||||
run: |
|
||||
|
@ -347,12 +346,20 @@ jobs:
|
|||
min_version:
|
||||
name: MinRustV # Minimum supported rust version (aka, MinSRV or MSRV)
|
||||
runs-on: ${{ matrix.job.os }}
|
||||
env:
|
||||
SCCACHE_GHA_ENABLED: "true"
|
||||
RUSTC_WRAPPER: "sccache"
|
||||
strategy:
|
||||
matrix:
|
||||
job:
|
||||
- { os: ubuntu-latest , features: feat_os_unix }
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: Run sccache-cache
|
||||
uses: mozilla-actions/sccache-action@v0.0.2
|
||||
with:
|
||||
version: "v0.4.0-pre.11"
|
||||
- name: Initialize workflow variables
|
||||
id: vars
|
||||
shell: bash
|
||||
|
@ -369,7 +376,6 @@ jobs:
|
|||
## Install `rust` toolchain (v${{ env.RUST_MIN_SRV }})
|
||||
rustup toolchain install --no-self-update ${{ env.RUST_MIN_SRV }} --profile minimal
|
||||
rustup default ${{ env.RUST_MIN_SRV }}
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: Confirm MinSRV compatible 'Cargo.lock'
|
||||
shell: bash
|
||||
run: |
|
||||
|
@ -424,7 +430,6 @@ jobs:
|
|||
## Install `rust` toolchain
|
||||
rustup toolchain install stable --no-self-update --profile minimal
|
||||
rustup default stable
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: "`cargo update` testing"
|
||||
shell: bash
|
||||
run: |
|
||||
|
@ -436,6 +441,9 @@ jobs:
|
|||
name: Build/Makefile
|
||||
needs: [ min_version, deps ]
|
||||
runs-on: ${{ matrix.job.os }}
|
||||
env:
|
||||
SCCACHE_GHA_ENABLED: "true"
|
||||
RUSTC_WRAPPER: "sccache"
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
|
@ -443,12 +451,16 @@ jobs:
|
|||
- { os: ubuntu-latest , features: feat_os_unix }
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: Run sccache-cache
|
||||
uses: mozilla-actions/sccache-action@v0.0.2
|
||||
with:
|
||||
version: "v0.4.0-pre.11"
|
||||
- name: Install `rust` toolchain
|
||||
run: |
|
||||
## Install `rust` toolchain
|
||||
rustup toolchain install stable --no-self-update --profile minimal
|
||||
rustup default stable
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: "`make build`"
|
||||
shell: bash
|
||||
run: |
|
||||
|
@ -470,12 +482,14 @@ jobs:
|
|||
env:
|
||||
RUST_BACKTRACE: "1"
|
||||
|
||||
|
||||
build_rust_stable:
|
||||
name: Build/stable
|
||||
needs: [ min_version, deps ]
|
||||
runs-on: ${{ matrix.job.os }}
|
||||
timeout-minutes: 90
|
||||
env:
|
||||
SCCACHE_GHA_ENABLED: "true"
|
||||
RUSTC_WRAPPER: "sccache"
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
|
@ -485,12 +499,16 @@ jobs:
|
|||
- { os: windows-latest , features: feat_os_windows }
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: Run sccache-cache
|
||||
uses: mozilla-actions/sccache-action@v0.0.2
|
||||
with:
|
||||
version: "v0.4.0-pre.11"
|
||||
- name: Install `rust` toolchain
|
||||
run: |
|
||||
## Install `rust` toolchain
|
||||
rustup toolchain install stable --no-self-update --profile minimal
|
||||
rustup default stable
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: Test
|
||||
run: cargo test ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }}
|
||||
env:
|
||||
|
@ -501,6 +519,9 @@ jobs:
|
|||
needs: [ min_version, deps ]
|
||||
runs-on: ${{ matrix.job.os }}
|
||||
timeout-minutes: 90
|
||||
env:
|
||||
SCCACHE_GHA_ENABLED: "true"
|
||||
RUSTC_WRAPPER: "sccache"
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
|
@ -510,12 +531,16 @@ jobs:
|
|||
- { os: windows-latest , features: feat_os_windows }
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: Run sccache-cache
|
||||
uses: mozilla-actions/sccache-action@v0.0.2
|
||||
with:
|
||||
version: "v0.4.0-pre.11"
|
||||
- name: Install `rust` toolchain
|
||||
run: |
|
||||
## Install `rust` toolchain
|
||||
rustup toolchain install nightly --no-self-update --profile minimal
|
||||
rustup default nightly
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: Test
|
||||
run: cargo test ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }}
|
||||
env:
|
||||
|
@ -525,6 +550,9 @@ jobs:
|
|||
name: Binary sizes
|
||||
needs: [ min_version, deps ]
|
||||
runs-on: ${{ matrix.job.os }}
|
||||
env:
|
||||
SCCACHE_GHA_ENABLED: "true"
|
||||
RUSTC_WRAPPER: "sccache"
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
|
@ -532,6 +560,11 @@ jobs:
|
|||
- { os: ubuntu-latest , features: feat_os_unix }
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: Run sccache-cache
|
||||
uses: mozilla-actions/sccache-action@v0.0.2
|
||||
with:
|
||||
version: "v0.4.0-pre.11"
|
||||
- name: Install dependencies
|
||||
shell: bash
|
||||
run: |
|
||||
|
@ -543,7 +576,6 @@ jobs:
|
|||
## Install `rust` toolchain
|
||||
rustup toolchain install stable --no-self-update --profile minimal
|
||||
rustup default stable
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: "`make install`"
|
||||
shell: bash
|
||||
run: |
|
||||
|
@ -579,6 +611,8 @@ jobs:
|
|||
timeout-minutes: 90
|
||||
env:
|
||||
DOCKER_OPTS: '--volume /etc/passwd:/etc/passwd --volume /etc/group:/etc/group'
|
||||
SCCACHE_GHA_ENABLED: "true"
|
||||
RUSTC_WRAPPER: "sccache"
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
|
@ -605,6 +639,11 @@ jobs:
|
|||
- { os: windows-latest , target: x86_64-pc-windows-msvc , features: feat_os_windows }
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: Run sccache-cache
|
||||
uses: mozilla-actions/sccache-action@v0.0.2
|
||||
with:
|
||||
version: "v0.4.0-pre.11"
|
||||
- name: Initialize workflow variables
|
||||
id: vars
|
||||
shell: bash
|
||||
|
@ -732,9 +771,6 @@ jobs:
|
|||
## rust toolchain ~ install
|
||||
rustup toolchain install --no-self-update ${{ env.RUST_MIN_SRV }} -t ${{ matrix.job.target }} --profile minimal
|
||||
rustup default ${{ env.RUST_MIN_SRV }}
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
key: ${{ matrix.job.os }}-${{ matrix.job.target }}
|
||||
- name: Initialize toolchain-dependent workflow variables
|
||||
id: dep_vars
|
||||
shell: bash
|
||||
|
@ -844,6 +880,9 @@ jobs:
|
|||
name: Tests/BusyBox test suite
|
||||
needs: [ min_version, deps ]
|
||||
runs-on: ${{ matrix.job.os }}
|
||||
env:
|
||||
SCCACHE_GHA_ENABLED: "true"
|
||||
RUSTC_WRAPPER: "sccache"
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
|
@ -858,6 +897,10 @@ jobs:
|
|||
echo "TEST_SUMMARY_FILE=busybox-result.json" >> $GITHUB_OUTPUT
|
||||
- uses: actions/checkout@v3
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: Run sccache-cache
|
||||
uses: mozilla-actions/sccache-action@v0.0.2
|
||||
with:
|
||||
version: "v0.4.0-pre.11"
|
||||
- name: Install/setup prerequisites
|
||||
shell: bash
|
||||
run: |
|
||||
|
@ -917,6 +960,9 @@ jobs:
|
|||
name: Tests/Toybox test suite
|
||||
needs: [ min_version, deps ]
|
||||
runs-on: ${{ matrix.job.os }}
|
||||
env:
|
||||
SCCACHE_GHA_ENABLED: "true"
|
||||
RUSTC_WRAPPER: "sccache"
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
|
@ -933,6 +979,10 @@ jobs:
|
|||
outputs TEST_SUMMARY_FILE
|
||||
- uses: actions/checkout@v3
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: Run sccache-cache
|
||||
uses: mozilla-actions/sccache-action@v0.0.2
|
||||
with:
|
||||
version: "v0.4.0-pre.11"
|
||||
- name: rust toolchain ~ install
|
||||
run: |
|
||||
## rust toolchain ~ install
|
||||
|
@ -1010,8 +1060,15 @@ jobs:
|
|||
arch: [x86] # , arm64-v8a
|
||||
env:
|
||||
TERMUX: v0.118.0
|
||||
SCCACHE_GHA_ENABLED: "true"
|
||||
RUSTC_WRAPPER: "sccache"
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: Run sccache-cache
|
||||
uses: mozilla-actions/sccache-action@v0.0.2
|
||||
with:
|
||||
version: "v0.4.0-pre.11"
|
||||
- name: AVD cache
|
||||
uses: actions/cache@v3
|
||||
id: avd-cache
|
||||
|
@ -1065,9 +1122,15 @@ jobs:
|
|||
- { os: macos-12 , features: unix } ## GHA MacOS-11.0 VM won't have VirtualBox; refs: <https://github.com/actions/virtual-environments/issues/4060> , <https://github.com/actions/virtual-environments/pull/4010>
|
||||
env:
|
||||
mem: 4096
|
||||
SCCACHE_GHA_ENABLED: "true"
|
||||
RUSTC_WRAPPER: "sccache"
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: Run sccache-cache
|
||||
uses: mozilla-actions/sccache-action@v0.0.2
|
||||
with:
|
||||
version: "v0.4.0-pre.11"
|
||||
- name: Prepare, build and test
|
||||
## spell-checker:ignore (ToDO) sshfs usesh vmactions
|
||||
uses: vmactions/freebsd-vm@v0.3.0
|
||||
|
@ -1132,6 +1195,9 @@ jobs:
|
|||
name: Code Coverage
|
||||
runs-on: ${{ matrix.job.os }}
|
||||
timeout-minutes: 90
|
||||
env:
|
||||
SCCACHE_GHA_ENABLED: "true"
|
||||
RUSTC_WRAPPER: "sccache"
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
|
@ -1141,6 +1207,11 @@ jobs:
|
|||
- { os: windows-latest , features: windows }
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: Run sccache-cache
|
||||
uses: mozilla-actions/sccache-action@v0.0.2
|
||||
with:
|
||||
version: "v0.4.0-pre.11"
|
||||
# - name: Reattach HEAD ## may be needed for accurate code coverage info
|
||||
# run: git checkout ${{ github.head_ref }}
|
||||
- name: Initialize workflow variables
|
||||
|
@ -1194,7 +1265,6 @@ jobs:
|
|||
## rust toolchain ~ install
|
||||
rustup toolchain install ${{ steps.vars.outputs.TOOLCHAIN }} --no-self-update --profile minimal
|
||||
rustup default ${{ steps.vars.outputs.TOOLCHAIN }}
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: Initialize toolchain-dependent workflow variables
|
||||
id: dep_vars
|
||||
shell: bash
|
||||
|
@ -1260,3 +1330,4 @@ jobs:
|
|||
flags: ${{ steps.vars.outputs.CODECOV_FLAGS }}
|
||||
name: codecov-umbrella
|
||||
fail_ci_if_error: false
|
||||
|
||||
|
|
|
@ -20,6 +20,7 @@ exacl
|
|||
filetime
|
||||
formatteriteminfo
|
||||
fsext
|
||||
fundu
|
||||
getopts
|
||||
getrandom
|
||||
globset
|
||||
|
|
288
CONTRIBUTING.md
288
CONTRIBUTING.md
|
@ -1,20 +1,12 @@
|
|||
<!-- spell-checker:ignore reimplementing toybox RUNTEST -->
|
||||
|
||||
# Contributing to coreutils
|
||||
|
||||
Contributions are very welcome, and should target Rust's main branch until the
|
||||
standard libraries are stabilized. You may *claim* an item on the to-do list by
|
||||
following these steps:
|
||||
|
||||
1. Open an issue named "Implement [the utility of your choice]", e.g. "Implement
|
||||
ls".
|
||||
1. State that you are working on this utility.
|
||||
1. Develop the utility.
|
||||
1. Add integration tests.
|
||||
1. Add the reference to your utility into Cargo.toml and Makefile.
|
||||
1. Remove utility from the to-do list in the README.
|
||||
1. Submit a pull request and close the issue.
|
||||
|
||||
The steps above imply that, before starting to work on a utility, you should
|
||||
search the issues to make sure no one else is working on it.
|
||||
Contributions are very welcome via Pull Requests. If you don't know where to
|
||||
start, take a look at the
|
||||
[`good-first-issues`](https://github.com/uutils/coreutils/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22).
|
||||
If you have any questions, feel free to ask them in the issues or on
|
||||
[Discord](https://discord.gg/wQVJbvJ).
|
||||
|
||||
## Best practices
|
||||
|
||||
|
@ -38,36 +30,240 @@ search the issues to make sure no one else is working on it.
|
|||
|
||||
## Platforms
|
||||
|
||||
We take pride in supporting many operating systems and architectures.
|
||||
We take pride in supporting many operating systems and architectures. Any code
|
||||
you contribute must at least compile without warnings for all platforms in the
|
||||
CI. However, you can use `#[cfg(...)]` attributes to create platform dependent features.
|
||||
|
||||
**Tip:**
|
||||
For Windows, Microsoft provides some images (VMWare, Hyper-V, VirtualBox and Parallels)
|
||||
for development:
|
||||
**Tip:** For Windows, Microsoft provides some images (VMWare, Hyper-V,
|
||||
VirtualBox and Parallels) for development:
|
||||
<https://developer.microsoft.com/windows/downloads/virtual-machines/>
|
||||
|
||||
## Tools
|
||||
|
||||
We have an extensive CI that will check your code before it can be merged. This
|
||||
section explains how to run those checks locally to avoid waiting for the CI.
|
||||
|
||||
### pre-commit hooks
|
||||
|
||||
A configuration for `pre-commit` is provided in the repository. It allows
|
||||
automatically checking every git commit you make to ensure it compiles, and
|
||||
passes `clippy` and `rustfmt` without warnings.
|
||||
|
||||
To use the provided hook:
|
||||
|
||||
1. [Install `pre-commit`](https://pre-commit.com/#install)
|
||||
1. Run `pre-commit install` while in the repository directory
|
||||
|
||||
Your git commits will then automatically be checked. If a check fails, an error
|
||||
message will explain why, and your commit will be canceled. You can then make
|
||||
the suggested changes, and run `git commit ...` again.
|
||||
|
||||
### clippy
|
||||
|
||||
```shell
|
||||
cargo clippy --all-targets --all-features
|
||||
```
|
||||
|
||||
The `msrv` key in the clippy configuration file `clippy.toml` is used to disable
|
||||
lints pertaining to newer features by specifying the minimum supported Rust
|
||||
version (MSRV).
|
||||
|
||||
### rustfmt
|
||||
|
||||
```shell
|
||||
cargo fmt --all
|
||||
```
|
||||
|
||||
### cargo-deny
|
||||
|
||||
This project uses [cargo-deny](https://github.com/EmbarkStudios/cargo-deny/) to
|
||||
detect duplicate dependencies, checks licenses, etc. To run it locally, first
|
||||
install it and then run with:
|
||||
|
||||
```
|
||||
cargo deny --all-features check all
|
||||
```
|
||||
|
||||
### Markdown linter
|
||||
|
||||
We use [markdownlint](https://github.com/DavidAnson/markdownlint) to lint the
|
||||
Markdown files in the repository.
|
||||
|
||||
### Spell checker
|
||||
|
||||
We use `cspell` as spell checker for all files in the project. If you are using
|
||||
VS Code, you can install the
|
||||
[code spell checker](https://marketplace.visualstudio.com/items?itemName=streetsidesoftware.code-spell-checker)
|
||||
extension to enable spell checking within your editor. Otherwise, you can
|
||||
install [cspell](https://cspell.org/) separately.
|
||||
|
||||
If you want to make the spell checker ignore a word, you can add
|
||||
|
||||
```rust
|
||||
// spell-checker:ignore word_to_ignore
|
||||
```
|
||||
|
||||
at the top of the file.
|
||||
|
||||
## Testing
|
||||
|
||||
Testing can be done using either Cargo or `make`.
|
||||
|
||||
### Testing with Cargo
|
||||
|
||||
Just like with building, we follow the standard procedure for testing using
|
||||
Cargo:
|
||||
|
||||
```shell
|
||||
cargo test
|
||||
```
|
||||
|
||||
By default, `cargo test` only runs the common programs. To run also platform
|
||||
specific tests, run:
|
||||
|
||||
```shell
|
||||
cargo test --features unix
|
||||
```
|
||||
|
||||
If you would prefer to test a select few utilities:
|
||||
|
||||
```shell
|
||||
cargo test --features "chmod mv tail" --no-default-features
|
||||
```
|
||||
|
||||
If you also want to test the core utilities:
|
||||
|
||||
```shell
|
||||
cargo test -p uucore -p coreutils
|
||||
```
|
||||
|
||||
To debug:
|
||||
|
||||
```shell
|
||||
gdb --args target/debug/coreutils ls
|
||||
(gdb) b ls.rs:79
|
||||
(gdb) run
|
||||
```
|
||||
|
||||
### Testing with GNU Make
|
||||
|
||||
To simply test all available utilities:
|
||||
|
||||
```shell
|
||||
make test
|
||||
```
|
||||
|
||||
To test all but a few of the available utilities:
|
||||
|
||||
```shell
|
||||
make SKIP_UTILS='UTILITY_1 UTILITY_2' test
|
||||
```
|
||||
|
||||
To test only a few of the available utilities:
|
||||
|
||||
```shell
|
||||
make UTILS='UTILITY_1 UTILITY_2' test
|
||||
```
|
||||
|
||||
To include tests for unimplemented behavior:
|
||||
|
||||
```shell
|
||||
make UTILS='UTILITY_1 UTILITY_2' SPEC=y test
|
||||
```
|
||||
|
||||
### Run Busybox Tests
|
||||
|
||||
This testing functionality is only available on *nix operating systems and
|
||||
requires `make`.
|
||||
|
||||
To run busybox tests for all utilities for which busybox has tests
|
||||
|
||||
```shell
|
||||
make busytest
|
||||
```
|
||||
|
||||
To run busybox tests for a few of the available utilities
|
||||
|
||||
```shell
|
||||
make UTILS='UTILITY_1 UTILITY_2' busytest
|
||||
```
|
||||
|
||||
To pass an argument like "-v" to the busybox test runtime
|
||||
|
||||
```shell
|
||||
make UTILS='UTILITY_1 UTILITY_2' RUNTEST_ARGS='-v' busytest
|
||||
```
|
||||
|
||||
### Comparing with GNU
|
||||
|
||||
To run uutils against the GNU test suite locally, run the following commands:
|
||||
|
||||
```shell
|
||||
bash util/build-gnu.sh
|
||||
bash util/run-gnu-test.sh
|
||||
# To run a single test:
|
||||
bash util/run-gnu-test.sh tests/touch/not-owner.sh # for example
|
||||
# To run several tests:
|
||||
bash util/run-gnu-test.sh tests/touch/not-owner.sh tests/rm/no-give-up.sh # for example
|
||||
# If this is a perl (.pl) test, to run in debug:
|
||||
DEBUG=1 bash util/run-gnu-test.sh tests/misc/sm3sum.pl
|
||||
```
|
||||
|
||||
Note that it relies on individual utilities (not the multicall binary).
|
||||
|
||||
### Improving the GNU compatibility
|
||||
|
||||
The Python script `./util/remaining-gnu-error.py` shows the list of failing
|
||||
tests in the CI.
|
||||
|
||||
To improve the GNU compatibility, the following process is recommended:
|
||||
|
||||
1. Identify a test (the smaller, the better) on a program that you understand or
|
||||
is easy to understand. You can use the `./util/remaining-gnu-error.py` script
|
||||
to help with this decision.
|
||||
1. Build both the GNU and Rust coreutils using: `bash util/build-gnu.sh`
|
||||
1. Run the test with `bash util/run-gnu-test.sh <your test>`
|
||||
1. Start to modify `<your test>` to understand what is wrong. Examples:
|
||||
1. Add `set -v` to have the bash verbose mode
|
||||
1. Add `echo $?` where needed
|
||||
1. When the variable `fail` is used in the test, `echo $fail` to see when the
|
||||
test started to fail
|
||||
1. Bump the content of the output (ex: `cat err`)
|
||||
1. ...
|
||||
1. Or, if the test is simple, extract the relevant information to create a new
|
||||
test case running both GNU & Rust implementation
|
||||
1. Start to modify the Rust implementation to match the expected behavior
|
||||
1. Add a test to make sure that we don't regress (our test suite is super quick)
|
||||
|
||||
## Commit messages
|
||||
|
||||
To help the project maintainers review pull requests from contributors across
|
||||
numerous utilities, the team has settled on conventions for commit messages.
|
||||
|
||||
From <http://git-scm.com/book/ch5-2.html>:
|
||||
From <https://git-scm.com/book/ch5-2.html>:
|
||||
|
||||
```
|
||||
Short (50 chars or less) summary of changes
|
||||
Capitalized, short (50 chars or less) summary
|
||||
|
||||
More detailed explanatory text, if necessary. Wrap it to about 72
|
||||
characters or so. In some contexts, the first line is treated as the
|
||||
subject of an email and the rest of the text as the body. The blank
|
||||
line separating the summary from the body is critical (unless you omit
|
||||
the body entirely); tools like rebase can get confused if you run the
|
||||
the body entirely); tools like rebase will confuse you if you run the
|
||||
two together.
|
||||
|
||||
Write your commit message in the imperative: "Fix bug" and not "Fixed bug"
|
||||
or "Fixes bug." This convention matches up with commit messages generated
|
||||
by commands like git merge and git revert.
|
||||
|
||||
Further paragraphs come after blank lines.
|
||||
|
||||
- Bullet points are okay, too
|
||||
|
||||
- Typically a hyphen or asterisk is used for the bullet, preceded by a
|
||||
- Typically a hyphen or asterisk is used for the bullet, followed by a
|
||||
single space, with blank lines in between, but conventions vary here
|
||||
|
||||
- Use a hanging indent
|
||||
```
|
||||
|
||||
Furthermore, here are a few examples for a summary line:
|
||||
|
@ -103,15 +299,49 @@ uutils: add new utility
|
|||
gitignore: add temporary files
|
||||
```
|
||||
|
||||
## cargo-deny
|
||||
## Code coverage
|
||||
|
||||
This project uses [cargo-deny](https://github.com/EmbarkStudios/cargo-deny/) to
|
||||
detect duplicate dependencies, checks licenses, etc. To run it locally, first
|
||||
install it and then run with:
|
||||
<!-- spell-checker:ignore (flags) Ccodegen Coverflow Cpanic Zinstrument Zpanic -->
|
||||
|
||||
Code coverage report can be generated using [grcov](https://github.com/mozilla/grcov).
|
||||
|
||||
### Using Nightly Rust
|
||||
|
||||
To generate [gcov-based](https://github.com/mozilla/grcov#example-how-to-generate-gcda-files-for-a-rust-project) coverage report
|
||||
|
||||
```shell
|
||||
export CARGO_INCREMENTAL=0
|
||||
export RUSTFLAGS="-Zprofile -Ccodegen-units=1 -Copt-level=0 -Clink-dead-code -Coverflow-checks=off -Zpanic_abort_tests -Cpanic=abort"
|
||||
export RUSTDOCFLAGS="-Cpanic=abort"
|
||||
cargo build <options...> # e.g., --features feat_os_unix
|
||||
cargo test <options...> # e.g., --features feat_os_unix test_pathchk
|
||||
grcov . -s . --binary-path ./target/debug/ -t html --branch --ignore-not-existing --ignore build.rs --excl-br-line "^\s*((debug_)?assert(_eq|_ne)?\#\[derive\()" -o ./target/debug/coverage/
|
||||
# open target/debug/coverage/index.html in browser
|
||||
```
|
||||
cargo deny --all-features check all
|
||||
```
|
||||
|
||||
if changes are not reflected in the report then run `cargo clean` and run the above commands.
|
||||
|
||||
### Using Stable Rust
|
||||
|
||||
If you are using stable version of Rust that doesn't enable code coverage instrumentation by default
|
||||
then add `-Z-Zinstrument-coverage` flag to `RUSTFLAGS` env variable specified above.
|
||||
|
||||
|
||||
## Other implementations
|
||||
|
||||
The Coreutils have different implementations, with different levels of completions:
|
||||
|
||||
* [GNU's](https://git.savannah.gnu.org/gitweb/?p=coreutils.git)
|
||||
* [OpenBSD](https://github.com/openbsd/src/tree/master/bin)
|
||||
* [Busybox](https://github.com/mirror/busybox/tree/master/coreutils)
|
||||
* [Toybox (Android)](https://github.com/landley/toybox/tree/master/toys/posix)
|
||||
* [V lang](https://github.com/vlang/coreutils)
|
||||
* [SerenityOS](https://github.com/SerenityOS/serenity/tree/master/Userland/Utilities)
|
||||
* [Initial Unix](https://github.com/dspinellis/unix-history-repo)
|
||||
|
||||
However, when reimplementing the tools/options in Rust, don't read their source codes
|
||||
when they are using reciprocal licenses (ex: GNU GPL, GNU LGPL, etc).
|
||||
|
||||
|
||||
## Licensing
|
||||
|
||||
|
|
67
Cargo.lock
generated
67
Cargo.lock
generated
|
@ -89,9 +89,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "bindgen"
|
||||
version = "0.62.0"
|
||||
version = "0.63.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c6720a8b7b2d39dd533285ed438d458f65b31b5c257e6ac7bb3d7e82844dd722"
|
||||
checksum = "36d860121800b2a9a94f9b5604b332d5cffb234ce17609ea479d723dbc9d3885"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cexpr",
|
||||
|
@ -870,9 +870,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "fts-sys"
|
||||
version = "0.2.3"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32bd98333d10742c0b048272ebf4cb05336d415423b853961c92ccb398966a03"
|
||||
checksum = "9a66c0a21e344f20c87b4ca12643cf4f40a7018f132c98d344e989b959f49dd1"
|
||||
dependencies = [
|
||||
"bindgen",
|
||||
"libc",
|
||||
|
@ -880,9 +880,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "fundu"
|
||||
version = "0.3.0"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "925250bc259498d4008ee072bf16586083ab2c491aa4b06b3c4d0a6556cebd74"
|
||||
checksum = "da58c38fe7b706cead98429d8a8535261addbe55fd531c7d7c7d770346464010"
|
||||
|
||||
[[package]]
|
||||
name = "futures"
|
||||
|
@ -1139,12 +1139,6 @@ dependencies = [
|
|||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "io-lifetimes"
|
||||
version = "0.7.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "59ce5ef949d49ee85593fc4d3f3f95ad61657076395cbbce23e2121fc5542074"
|
||||
|
||||
[[package]]
|
||||
name = "io-lifetimes"
|
||||
version = "1.0.5"
|
||||
|
@ -1162,8 +1156,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "22e18b0a45d56fe973d6db23972bf5bc46f988a4a2385deac9cc29572f09daef"
|
||||
dependencies = [
|
||||
"hermit-abi 0.3.1",
|
||||
"io-lifetimes 1.0.5",
|
||||
"rustix 0.36.8",
|
||||
"io-lifetimes",
|
||||
"rustix",
|
||||
"windows-sys 0.45.0",
|
||||
]
|
||||
|
||||
|
@ -1257,12 +1251,6 @@ dependencies = [
|
|||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.0.46"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d4d2456c373231a208ad294c33dc5bff30051eafd954cd4caae83a712b12854d"
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.1.4"
|
||||
|
@ -1719,15 +1707,15 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "procfs"
|
||||
version = "0.14.1"
|
||||
version = "0.15.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2dfb6451c91904606a1abe93e83a8ec851f45827fa84273f256ade45dc095818"
|
||||
checksum = "943ca7f9f29bab5844ecd8fdb3992c5969b6622bb9609b9502fef9b4310e3f1f"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"byteorder",
|
||||
"hex",
|
||||
"lazy_static",
|
||||
"rustix 0.35.13",
|
||||
"rustix",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1921,20 +1909,6 @@ dependencies = [
|
|||
"semver",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.35.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "727a1a6d65f786ec22df8a81ca3121107f235970dc1705ed681d3e6e8b9cd5f9"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"errno",
|
||||
"io-lifetimes 0.7.5",
|
||||
"libc",
|
||||
"linux-raw-sys 0.0.46",
|
||||
"windows-sys 0.42.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.36.8"
|
||||
|
@ -1943,9 +1917,9 @@ checksum = "f43abb88211988493c1abb44a70efa56ff0ce98f233b7b276146f1f3f7ba9644"
|
|||
dependencies = [
|
||||
"bitflags",
|
||||
"errno",
|
||||
"io-lifetimes 1.0.5",
|
||||
"io-lifetimes",
|
||||
"libc",
|
||||
"linux-raw-sys 0.1.4",
|
||||
"linux-raw-sys",
|
||||
"windows-sys 0.45.0",
|
||||
]
|
||||
|
||||
|
@ -1992,9 +1966,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "selinux-sys"
|
||||
version = "0.6.1"
|
||||
version = "0.6.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e4c02c5c6e2db8a78b3ffffc666f75fcda5bbd7068ba3c0f560e5504f4d88443"
|
||||
checksum = "806d381649bb85347189d2350728817418138d11d738e2482cb644ec7f3c755d"
|
||||
dependencies = [
|
||||
"bindgen",
|
||||
"cc",
|
||||
|
@ -2185,7 +2159,7 @@ dependencies = [
|
|||
"cfg-if",
|
||||
"fastrand",
|
||||
"redox_syscall",
|
||||
"rustix 0.36.8",
|
||||
"rustix",
|
||||
"windows-sys 0.42.0",
|
||||
]
|
||||
|
||||
|
@ -2209,12 +2183,12 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "terminal_size"
|
||||
version = "0.2.2"
|
||||
version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "40ca90c434fd12083d1a6bdcbe9f92a14f96c8a1ba600ba451734ac334521f7a"
|
||||
checksum = "4c9afddd2cec1c0909f06b00ef33f94ab2cc0578c4a610aa208ddfec8aa2b43a"
|
||||
dependencies = [
|
||||
"rustix 0.35.13",
|
||||
"windows-sys 0.42.0",
|
||||
"rustix",
|
||||
"windows-sys 0.45.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -3041,6 +3015,7 @@ name = "uu_sleep"
|
|||
version = "0.0.17"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"fundu",
|
||||
"uucore",
|
||||
]
|
||||
|
||||
|
|
|
@ -282,7 +282,7 @@ filetime = "0.2"
|
|||
fnv = "1.0.7"
|
||||
fs_extra = "1.1.0"
|
||||
fts-sys = "0.2"
|
||||
fundu = "0.3.0"
|
||||
fundu = "0.4.2"
|
||||
gcd = "2.2"
|
||||
glob = "0.3.0"
|
||||
half = "2.1"
|
||||
|
@ -320,7 +320,7 @@ strum = "0.24.1"
|
|||
strum_macros = "0.24.2"
|
||||
tempfile = "3.4.0"
|
||||
term_grid = "0.1.5"
|
||||
terminal_size = "0.2.2"
|
||||
terminal_size = "0.2.5"
|
||||
textwrap = { version="0.16.0", features=["terminal_size"] }
|
||||
thiserror = "1.0"
|
||||
time = { version="0.3" }
|
||||
|
@ -493,7 +493,7 @@ hex-literal = "0.3.1"
|
|||
rstest = "0.16.0"
|
||||
|
||||
[target.'cfg(any(target_os = "linux", target_os = "android"))'.dev-dependencies]
|
||||
procfs = { version = "0.14", default-features = false }
|
||||
procfs = { version = "0.15", default-features = false }
|
||||
rlimit = "0.9.1"
|
||||
|
||||
[target.'cfg(unix)'.dev-dependencies]
|
||||
|
|
|
@ -1,68 +0,0 @@
|
|||
# Documentation
|
||||
|
||||
The source of the documentation is available on:
|
||||
|
||||
<https://uutils.github.io/dev/coreutils/>
|
||||
|
||||
The documentation is updated everyday on this repository:
|
||||
|
||||
<https://github.com/uutils/uutils.github.io/>
|
||||
|
||||
## Running GNU tests
|
||||
|
||||
<!-- spell-checker:ignore gnulib -->
|
||||
|
||||
- Check out <https://github.com/coreutils/coreutils> next to your fork as gnu
|
||||
- Check out <https://github.com/coreutils/gnulib> next to your fork as gnulib
|
||||
- Rename the checkout of your fork to uutils
|
||||
|
||||
At the end you should have uutils, gnu and gnulib checked out next to each other.
|
||||
|
||||
- Run `cd uutils && ./util/build-gnu.sh && cd ..` to get everything ready (this may take a while)
|
||||
- Finally, you can run tests with `bash uutils/util/run-gnu-test.sh <tests>`. Instead of `<tests>` insert the tests you want to run, e.g. `tests/misc/wc-proc.sh`.
|
||||
|
||||
## Code Coverage Report Generation
|
||||
|
||||
<!-- spell-checker:ignore (flags) Ccodegen Coverflow Cpanic Zinstrument Zpanic -->
|
||||
|
||||
Code coverage report can be generated using [grcov](https://github.com/mozilla/grcov).
|
||||
|
||||
### Using Nightly Rust
|
||||
|
||||
To generate [gcov-based](https://github.com/mozilla/grcov#example-how-to-generate-gcda-files-for-a-rust-project) coverage report
|
||||
|
||||
```shell
|
||||
export CARGO_INCREMENTAL=0
|
||||
export RUSTFLAGS="-Zprofile -Ccodegen-units=1 -Copt-level=0 -Clink-dead-code -Coverflow-checks=off -Zpanic_abort_tests -Cpanic=abort"
|
||||
export RUSTDOCFLAGS="-Cpanic=abort"
|
||||
cargo build <options...> # e.g., --features feat_os_unix
|
||||
cargo test <options...> # e.g., --features feat_os_unix test_pathchk
|
||||
grcov . -s . --binary-path ./target/debug/ -t html --branch --ignore-not-existing --ignore build.rs --excl-br-line "^\s*((debug_)?assert(_eq|_ne)?\#\[derive\()" -o ./target/debug/coverage/
|
||||
# open target/debug/coverage/index.html in browser
|
||||
```
|
||||
|
||||
if changes are not reflected in the report then run `cargo clean` and run the above commands.
|
||||
|
||||
### Using Stable Rust
|
||||
|
||||
If you are using stable version of Rust that doesn't enable code coverage instrumentation by default
|
||||
then add `-Z-Zinstrument-coverage` flag to `RUSTFLAGS` env variable specified above.
|
||||
|
||||
## pre-commit hooks
|
||||
|
||||
A configuration for `pre-commit` is provided in the repository. It allows automatically checking every git commit you make to ensure it compiles, and passes `clippy` and `rustfmt` without warnings.
|
||||
|
||||
To use the provided hook:
|
||||
|
||||
1. [Install `pre-commit`](https://pre-commit.com/#install)
|
||||
1. Run `pre-commit install` while in the repository directory
|
||||
|
||||
Your git commits will then automatically be checked. If a check fails, an error message will explain why, and your commit will be canceled. You can then make the suggested changes, and run `git commit ...` again.
|
||||
|
||||
## Using Clippy
|
||||
|
||||
The `msrv` key in the clippy configuration file `clippy.toml` is used to disable lints pertaining to newer features by specifying the minimum supported Rust version (MSRV). However, this key is only supported on `nightly`. To invoke clippy without errors, use `cargo +nightly clippy`. In order to also check tests and non-default crate features, use `cargo +nightly clippy --all-targets --all-features`.
|
||||
|
||||
## Markdown linter
|
||||
|
||||
We use <https://github.com/DavidAnson/markdownlint> to lint the Markdown files.
|
325
README.md
325
README.md
|
@ -1,3 +1,10 @@
|
|||
<!-- markdownlint-disable MD033 MD041 MD002 -->
|
||||
<!-- markdownlint-disable commands-show-output no-duplicate-heading -->
|
||||
<!-- spell-checker:ignore markdownlint ; (options) DESTDIR UTILNAME manpages reimplementation -->
|
||||
<div align="center">
|
||||
|
||||
![uutils logo](docs/src/logo.svg)
|
||||
|
||||
# uutils coreutils
|
||||
|
||||
[![Crates.io](https://img.shields.io/crates/v/coreutils.svg)](https://crates.io/crates/coreutils)
|
||||
|
@ -9,15 +16,14 @@
|
|||
[![CodeCov](https://codecov.io/gh/uutils/coreutils/branch/master/graph/badge.svg)](https://codecov.io/gh/uutils/coreutils)
|
||||
![MSRV](https://img.shields.io/badge/MSRV-1.64.0-brightgreen)
|
||||
|
||||
-----------------------------------------------
|
||||
</div>
|
||||
|
||||
<!-- markdownlint-disable commands-show-output no-duplicate-heading -->
|
||||
<!-- spell-checker:ignore markdownlint ; (options) DESTDIR RUNTEST UTILNAME manpages -->
|
||||
---
|
||||
|
||||
uutils is an attempt at writing universal (as in cross-platform) CLI
|
||||
utilities in [Rust](http://www.rust-lang.org).
|
||||
While all programs have been implemented, some options might be missing
|
||||
or different behavior might be experienced.
|
||||
|
||||
uutils coreutils is a cross-platform reimplementation of the GNU coreutils in
|
||||
[Rust](http://www.rust-lang.org). While all programs have been implemented, some
|
||||
options might be missing or different behavior might be experienced.
|
||||
|
||||
To install it:
|
||||
|
||||
|
@ -27,13 +33,15 @@ cargo install coreutils
|
|||
```
|
||||
|
||||
<!-- markdownlint-disable-next-line MD026 -->
|
||||
## Why?
|
||||
|
||||
uutils aims to work on as many platforms as possible, to be able to use the
|
||||
same utils on Linux, Mac, Windows and other platforms. This ensures, for
|
||||
example, that scripts can be easily transferred between platforms. Rust was
|
||||
chosen not only because it is fast and safe, but is also excellent for
|
||||
writing cross-platform code.
|
||||
## Goals
|
||||
|
||||
uutils aims to be a drop-in replacement for the GNU utils. Differences with GNU
|
||||
are treated as bugs.
|
||||
|
||||
uutils aims to work on as many platforms as possible, to be able to use the same
|
||||
utils on Linux, Mac, Windows and other platforms. This ensures, for example,
|
||||
that scripts can be easily transferred between platforms.
|
||||
|
||||
## Documentation
|
||||
|
||||
|
@ -42,10 +50,11 @@ uutils has both user and developer documentation available:
|
|||
- [User Manual](https://uutils.github.io/user/)
|
||||
- [Developer Documentation](https://uutils.github.io/dev/coreutils/)
|
||||
|
||||
Both can also be generated locally, the instructions for that can be found in the
|
||||
[coreutils docs](https://github.com/uutils/uutils.github.io) repository.
|
||||
Both can also be generated locally, the instructions for that can be found in
|
||||
the [coreutils docs](https://github.com/uutils/uutils.github.io) repository.
|
||||
|
||||
<!-- ANCHOR: build (this mark is needed for mdbook) -->
|
||||
|
||||
## Requirements
|
||||
|
||||
- Rust (`cargo`, `rustc`)
|
||||
|
@ -53,13 +62,13 @@ Both can also be generated locally, the instructions for that can be found in th
|
|||
|
||||
### Rust Version
|
||||
|
||||
uutils follows Rust's release channels and is tested against stable, beta and nightly.
|
||||
The current Minimum Supported Rust Version (MSRV) is `1.64.0`.
|
||||
uutils follows Rust's release channels and is tested against stable, beta and
|
||||
nightly. The current Minimum Supported Rust Version (MSRV) is `1.64.0`.
|
||||
|
||||
## Building
|
||||
|
||||
There are currently two methods to build the uutils binaries: either Cargo
|
||||
or GNU Make.
|
||||
There are currently two methods to build the uutils binaries: either Cargo or
|
||||
GNU Make.
|
||||
|
||||
> Building the full package, including all documentation, requires both Cargo
|
||||
> and Gnu Make on a Unix platform.
|
||||
|
@ -73,8 +82,8 @@ cd coreutils
|
|||
|
||||
### Cargo
|
||||
|
||||
Building uutils using Cargo is easy because the process is the same as for
|
||||
every other Rust program:
|
||||
Building uutils using Cargo is easy because the process is the same as for every
|
||||
other Rust program:
|
||||
|
||||
```shell
|
||||
cargo build --release
|
||||
|
@ -83,9 +92,9 @@ cargo build --release
|
|||
This command builds the most portable common core set of uutils into a multicall
|
||||
(BusyBox-type) binary, named 'coreutils', on most Rust-supported platforms.
|
||||
|
||||
Additional platform-specific uutils are often available. Building these
|
||||
expanded sets of uutils for a platform (on that platform) is as simple as
|
||||
specifying it as a feature:
|
||||
Additional platform-specific uutils are often available. Building these expanded
|
||||
sets of uutils for a platform (on that platform) is as simple as specifying it
|
||||
as a feature:
|
||||
|
||||
```shell
|
||||
cargo build --release --features macos
|
||||
|
@ -96,18 +105,18 @@ cargo build --release --features unix
|
|||
```
|
||||
|
||||
If you don't want to build every utility available on your platform into the
|
||||
final binary, you can also specify which ones you want to build manually.
|
||||
For example:
|
||||
final binary, you can also specify which ones you want to build manually. For
|
||||
example:
|
||||
|
||||
```shell
|
||||
cargo build --features "base32 cat echo rm" --no-default-features
|
||||
```
|
||||
|
||||
If you don't want to build the multicall binary and would prefer to build
|
||||
the utilities as individual binaries, that is also possible. Each utility
|
||||
is contained in its own package within the main repository, named
|
||||
"uu_UTILNAME". To build individual utilities, use cargo to build just the
|
||||
specific packages (using the `--package` [aka `-p`] option). For example:
|
||||
If you don't want to build the multicall binary and would prefer to build the
|
||||
utilities as individual binaries, that is also possible. Each utility is
|
||||
contained in its own package within the main repository, named "uu_UTILNAME". To
|
||||
build individual utilities, use cargo to build just the specific packages (using
|
||||
the `--package` [aka `-p`] option). For example:
|
||||
|
||||
```shell
|
||||
cargo build -p uu_base32 -p uu_cat -p uu_echo -p uu_rm
|
||||
|
@ -148,13 +157,15 @@ make UTILS='UTILITY_1 UTILITY_2'
|
|||
Likewise, installing can simply be done using:
|
||||
|
||||
```shell
|
||||
cargo install --path .
|
||||
cargo install --path . --locked
|
||||
```
|
||||
|
||||
This command will install uutils into Cargo's *bin* folder (*e.g.* `$HOME/.cargo/bin`).
|
||||
This command will install uutils into Cargo's _bin_ folder (_e.g._
|
||||
`$HOME/.cargo/bin`).
|
||||
|
||||
This does not install files necessary for shell completion or manpages.
|
||||
For manpages or shell completion to work, use `GNU Make` or see `Manually install shell completions`/`Manually install manpages`.
|
||||
This does not install files necessary for shell completion or manpages. For
|
||||
manpages or shell completion to work, use `GNU Make` or see
|
||||
`Manually install shell completions`/`Manually install manpages`.
|
||||
|
||||
### Install with GNU Make
|
||||
|
||||
|
@ -207,8 +218,8 @@ be generated; See `Manually install shell completions`.
|
|||
|
||||
### Manually install shell completions
|
||||
|
||||
The `coreutils` binary can generate completions for the `bash`, `elvish`, `fish`, `powershell`
|
||||
and `zsh` shells. It prints the result to stdout.
|
||||
The `coreutils` binary can generate completions for the `bash`, `elvish`,
|
||||
`fish`, `powershell` and `zsh` shells. It prints the result to stdout.
|
||||
|
||||
The syntax is:
|
||||
|
||||
|
@ -216,8 +227,8 @@ The syntax is:
|
|||
cargo run completion <utility> <shell>
|
||||
```
|
||||
|
||||
So, to install completions for `ls` on `bash` to `/usr/local/share/bash-completion/completions/ls`,
|
||||
run:
|
||||
So, to install completions for `ls` on `bash` to
|
||||
`/usr/local/share/bash-completion/completions/ls`, run:
|
||||
|
||||
```shell
|
||||
cargo run completion ls bash > /usr/local/share/bash-completion/completions/ls
|
||||
|
@ -226,12 +237,12 @@ cargo run completion ls bash > /usr/local/share/bash-completion/completions/ls
|
|||
### Manually install manpages
|
||||
|
||||
To generate manpages, the syntax is:
|
||||
|
||||
```bash
|
||||
cargo run manpage <utility>
|
||||
```
|
||||
|
||||
So, to install the manpage for `ls` to `/usr/local/share/man/man1/ls.1`
|
||||
run:
|
||||
So, to install the manpage for `ls` to `/usr/local/share/man/man1/ls.1` run:
|
||||
|
||||
```bash
|
||||
cargo run manpage ls > /usr/local/share/man/man1/ls.1
|
||||
|
@ -280,245 +291,21 @@ make PREFIX=/my/path uninstall
|
|||
|
||||
<!-- ANCHOR_END: build (this mark is needed for mdbook) -->
|
||||
|
||||
## Testing
|
||||
|
||||
Testing can be done using either Cargo or `make`.
|
||||
|
||||
### Testing with Cargo
|
||||
|
||||
Just like with building, we follow the standard procedure for testing using
|
||||
Cargo:
|
||||
|
||||
```shell
|
||||
cargo test
|
||||
```
|
||||
|
||||
By default, `cargo test` only runs the common programs. To run also platform
|
||||
specific tests, run:
|
||||
|
||||
```shell
|
||||
cargo test --features unix
|
||||
```
|
||||
|
||||
If you would prefer to test a select few utilities:
|
||||
|
||||
```shell
|
||||
cargo test --features "chmod mv tail" --no-default-features
|
||||
```
|
||||
|
||||
If you also want to test the core utilities:
|
||||
|
||||
```shell
|
||||
cargo test -p uucore -p coreutils
|
||||
```
|
||||
|
||||
To debug:
|
||||
|
||||
```shell
|
||||
gdb --args target/debug/coreutils ls
|
||||
(gdb) b ls.rs:79
|
||||
(gdb) run
|
||||
```
|
||||
|
||||
### Testing with GNU Make
|
||||
|
||||
To simply test all available utilities:
|
||||
|
||||
```shell
|
||||
make test
|
||||
```
|
||||
|
||||
To test all but a few of the available utilities:
|
||||
|
||||
```shell
|
||||
make SKIP_UTILS='UTILITY_1 UTILITY_2' test
|
||||
```
|
||||
|
||||
To test only a few of the available utilities:
|
||||
|
||||
```shell
|
||||
make UTILS='UTILITY_1 UTILITY_2' test
|
||||
```
|
||||
|
||||
To include tests for unimplemented behavior:
|
||||
|
||||
```shell
|
||||
make UTILS='UTILITY_1 UTILITY_2' SPEC=y test
|
||||
```
|
||||
|
||||
### Run Busybox Tests
|
||||
|
||||
This testing functionality is only available on *nix operating systems and
|
||||
requires `make`.
|
||||
|
||||
To run busybox tests for all utilities for which busybox has tests
|
||||
|
||||
```shell
|
||||
make busytest
|
||||
```
|
||||
|
||||
To run busybox tests for a few of the available utilities
|
||||
|
||||
```shell
|
||||
make UTILS='UTILITY_1 UTILITY_2' busytest
|
||||
```
|
||||
|
||||
To pass an argument like "-v" to the busybox test runtime
|
||||
|
||||
```shell
|
||||
make UTILS='UTILITY_1 UTILITY_2' RUNTEST_ARGS='-v' busytest
|
||||
```
|
||||
|
||||
### Comparing with GNU
|
||||
## GNU test suite compatibility
|
||||
|
||||
Below is the evolution of how many GNU tests uutils passes. A more detailed
|
||||
breakdown of the GNU test results of the main branch can be found
|
||||
[in the user manual](https://uutils.github.io/user/test_coverage.html).
|
||||
|
||||
See <https://github.com/uutils/coreutils/issues/3336> for the main meta bugs
|
||||
(many are missing).
|
||||
|
||||
![Evolution over time](https://github.com/uutils/coreutils-tracking/blob/main/gnu-results.png?raw=true)
|
||||
|
||||
To run locally:
|
||||
|
||||
```shell
|
||||
bash util/build-gnu.sh
|
||||
bash util/run-gnu-test.sh
|
||||
# To run a single test:
|
||||
bash util/run-gnu-test.sh tests/touch/not-owner.sh # for example
|
||||
# To run several tests:
|
||||
bash util/run-gnu-test.sh tests/touch/not-owner.sh tests/rm/no-give-up.sh # for example
|
||||
# If this is a perl (.pl) test, to run in debug:
|
||||
DEBUG=1 bash util/run-gnu-test.sh tests/misc/sm3sum.pl
|
||||
```
|
||||
|
||||
Note that it relies on individual utilities (not the multicall binary).
|
||||
|
||||
### Improving the GNU compatibility
|
||||
|
||||
The Python script `./util/remaining-gnu-error.py` shows the list of failing tests in the CI.
|
||||
|
||||
To improve the GNU compatibility, the following process is recommended:
|
||||
|
||||
1. Identify a test (the smaller, the better) on a program that you understand or is easy to understand. You can use the `./util/remaining-gnu-error.py` script to help with this decision.
|
||||
1. Build both the GNU and Rust coreutils using: `bash util/build-gnu.sh`
|
||||
1. Run the test with `bash util/run-gnu-test.sh <your test>`
|
||||
1. Start to modify `<your test>` to understand what is wrong. Examples:
|
||||
1. Add `set -v` to have the bash verbose mode
|
||||
1. Add `echo $?` where needed
|
||||
1. When the variable `fail` is used in the test, `echo $fail` to see when the test started to fail
|
||||
1. Bump the content of the output (ex: `cat err`)
|
||||
1. ...
|
||||
1. Or, if the test is simple, extract the relevant information to create a new test case running both GNU & Rust implementation
|
||||
1. Start to modify the Rust implementation to match the expected behavior
|
||||
1. Add a test to make sure that we don't regress (our test suite is super quick)
|
||||
|
||||
## Contributing
|
||||
|
||||
To contribute to uutils, please see [CONTRIBUTING](CONTRIBUTING.md).
|
||||
|
||||
## Utilities
|
||||
|
||||
Please note that this is not fully accurate:
|
||||
|
||||
- Some new options can be added / removed in the GNU implementation;
|
||||
- Some error management might be missing;
|
||||
- Some behaviors might be different.
|
||||
|
||||
See <https://github.com/uutils/coreutils/issues/3336> for the main meta bugs
|
||||
(many are missing).
|
||||
|
||||
| Done | WIP |
|
||||
|-----------|-----------|
|
||||
| arch | cp |
|
||||
| base32 | date |
|
||||
| base64 | dd |
|
||||
| basename | df |
|
||||
| basenc | expr |
|
||||
| cat | install |
|
||||
| chcon | ls |
|
||||
| chgrp | more |
|
||||
| chmod | numfmt |
|
||||
| chown | od (`--strings` and 128-bit data types missing) |
|
||||
| chroot | pr |
|
||||
| cksum | printf |
|
||||
| comm | sort |
|
||||
| csplit | split |
|
||||
| cut | tac |
|
||||
| dircolors | test |
|
||||
| dirname | dir |
|
||||
| du | vdir |
|
||||
| echo | stty |
|
||||
| env | |
|
||||
| expand | |
|
||||
| factor | |
|
||||
| false | |
|
||||
| fmt | |
|
||||
| fold | |
|
||||
| groups | |
|
||||
| hashsum | |
|
||||
| head | |
|
||||
| hostid | |
|
||||
| hostname | |
|
||||
| id | |
|
||||
| join | |
|
||||
| kill | |
|
||||
| link | |
|
||||
| ln | |
|
||||
| logname | |
|
||||
| ~~md5sum~~ (replaced by [hashsum](https://github.com/uutils/coreutils/blob/main/src/uu/hashsum/src/hashsum.rs)) | |
|
||||
| ~~sha1sum~~ (replaced by [hashsum](https://github.com/uutils/coreutils/blob/main/src/uu/hashsum/src/hashsum.rs)) | |
|
||||
| ~~sha224sum~~ (replaced by [hashsum](https://github.com/uutils/coreutils/blob/main/src/uu/hashsum/src/hashsum.rs)) | |
|
||||
| ~~sha256sum~~ (replaced by [hashsum](https://github.com/uutils/coreutils/blob/main/src/uu/hashsum/src/hashsum.rs)) | |
|
||||
| ~~sha384sum~~ (replaced by [hashsum](https://github.com/uutils/coreutils/blob/main/src/uu/hashsum/src/hashsum.rs)) | |
|
||||
| ~~sha512sum~~ (replaced by [hashsum](https://github.com/uutils/coreutils/blob/main/src/uu/hashsum/src/hashsum.rs)) | |
|
||||
| mkdir | |
|
||||
| mkfifo | |
|
||||
| mknod | |
|
||||
| mktemp | |
|
||||
| mv | |
|
||||
| nice | |
|
||||
| nl | |
|
||||
| nohup | |
|
||||
| nproc | |
|
||||
| paste | |
|
||||
| pathchk | |
|
||||
| pinky | |
|
||||
| printenv | |
|
||||
| ptx | |
|
||||
| pwd | |
|
||||
| readlink | |
|
||||
| realpath | |
|
||||
| relpath | |
|
||||
| rm | |
|
||||
| rmdir | |
|
||||
| runcon | |
|
||||
| seq | |
|
||||
| shred | |
|
||||
| shuf | |
|
||||
| sleep | |
|
||||
| stat | |
|
||||
| stdbuf | |
|
||||
| sum | |
|
||||
| sync | |
|
||||
| tail | |
|
||||
| tee | |
|
||||
| timeout | |
|
||||
| touch | |
|
||||
| tr | |
|
||||
| true | |
|
||||
| truncate | |
|
||||
| tsort | |
|
||||
| tty | |
|
||||
| uname | |
|
||||
| unexpand | |
|
||||
| uniq | |
|
||||
| unlink | |
|
||||
| uptime | |
|
||||
| users | |
|
||||
| wc | |
|
||||
| who | |
|
||||
| whoami | |
|
||||
| yes | |
|
||||
|
||||
## License
|
||||
|
||||
uutils is licensed under the MIT License - see the `LICENSE` file for details
|
||||
|
|
2
build.rs
2
build.rs
|
@ -20,6 +20,8 @@ pub fn main() {
|
|||
for (key, val) in env::vars() {
|
||||
if val == "1" && key.starts_with(ENV_FEATURE_PREFIX) {
|
||||
let krate = key[ENV_FEATURE_PREFIX.len()..].to_lowercase();
|
||||
// Allow this as we have a bunch of info in the comments
|
||||
#[allow(clippy::match_same_arms)]
|
||||
match krate.as_ref() {
|
||||
"default" | "macos" | "unix" | "windows" | "selinux" | "zip" => continue, // common/standard feature names
|
||||
"nightly" | "test_unimplemented" => continue, // crate-local custom features
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
// spell-checker:ignore (ToDO) Chmoder cmode fmode fperm fref ugoa RFILE RFILE's
|
||||
|
||||
use clap::{crate_version, Arg, ArgAction, Command};
|
||||
use std::ffi::OsString;
|
||||
use std::fs;
|
||||
use std::os::unix::fs::{MetadataExt, PermissionsExt};
|
||||
use std::path::Path;
|
||||
|
@ -35,14 +36,64 @@ mod options {
|
|||
pub const FILE: &str = "FILE";
|
||||
}
|
||||
|
||||
/// Extract negative modes (starting with '-') from the rest of the arguments.
|
||||
///
|
||||
/// This is mainly required for GNU compatibility, where "non-positional negative" modes are used
|
||||
/// as the actual positional MODE. Some examples of these cases are:
|
||||
/// * "chmod -w -r file", which is the same as "chmod -w,-r file"
|
||||
/// * "chmod -w file -r", which is the same as "chmod -w,-r file"
|
||||
///
|
||||
/// These can currently not be handled by clap.
|
||||
/// Therefore it might be possible that a pseudo MODE is inserted to pass clap parsing.
|
||||
/// The pseudo MODE is later replaced by the extracted (and joined) negative modes.
|
||||
fn extract_negative_modes(mut args: impl uucore::Args) -> (Option<String>, Vec<OsString>) {
|
||||
// we look up the args until "--" is found
|
||||
// "-mode" will be extracted into parsed_cmode_vec
|
||||
let (parsed_cmode_vec, pre_double_hyphen_args): (Vec<OsString>, Vec<OsString>) =
|
||||
args.by_ref().take_while(|a| a != "--").partition(|arg| {
|
||||
let arg = if let Some(arg) = arg.to_str() {
|
||||
arg.to_string()
|
||||
} else {
|
||||
return false;
|
||||
};
|
||||
arg.len() >= 2
|
||||
&& arg.starts_with('-')
|
||||
&& matches!(
|
||||
arg.chars().nth(1).unwrap(),
|
||||
'r' | 'w' | 'x' | 'X' | 's' | 't' | 'u' | 'g' | 'o' | '0'..='7'
|
||||
)
|
||||
});
|
||||
|
||||
let mut clean_args = Vec::new();
|
||||
if !parsed_cmode_vec.is_empty() {
|
||||
// we need a pseudo cmode for clap, which won't be used later.
|
||||
// this is required because clap needs the default "chmod MODE FILE" scheme.
|
||||
clean_args.push("w".into());
|
||||
}
|
||||
clean_args.extend(pre_double_hyphen_args);
|
||||
|
||||
if let Some(arg) = args.next() {
|
||||
// as there is still something left in the iterator, we previously consumed the "--"
|
||||
// -> add it to the args again
|
||||
clean_args.push("--".into());
|
||||
clean_args.push(arg);
|
||||
}
|
||||
clean_args.extend(args);
|
||||
|
||||
let parsed_cmode = Some(
|
||||
parsed_cmode_vec
|
||||
.iter()
|
||||
.map(|s| s.to_str().unwrap())
|
||||
.collect::<Vec<&str>>()
|
||||
.join(","),
|
||||
)
|
||||
.filter(|s| !s.is_empty());
|
||||
(parsed_cmode, clean_args)
|
||||
}
|
||||
|
||||
#[uucore::main]
|
||||
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||
let mut args = args.collect_lossy();
|
||||
|
||||
// Before we can parse 'args' with clap (and previously getopts),
|
||||
// a possible MODE prefix '-' needs to be removed (e.g. "chmod -x FILE").
|
||||
let mode_had_minus_prefix = mode::strip_minus_from_mode(&mut args);
|
||||
|
||||
let (parsed_cmode, args) = extract_negative_modes(args.skip(1)); // skip binary name
|
||||
let matches = uu_app().after_help(LONG_USAGE).try_get_matches_from(args)?;
|
||||
|
||||
let changes = matches.get_flag(options::CHANGES);
|
||||
|
@ -62,13 +113,14 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
|||
},
|
||||
None => None,
|
||||
};
|
||||
let modes = matches.get_one::<String>(options::MODE).unwrap(); // should always be Some because required
|
||||
let cmode = if mode_had_minus_prefix {
|
||||
// clap parsing is finished, now put prefix back
|
||||
format!("-{modes}")
|
||||
|
||||
let modes = matches.get_one::<String>(options::MODE);
|
||||
let cmode = if let Some(parsed_cmode) = parsed_cmode {
|
||||
parsed_cmode
|
||||
} else {
|
||||
modes.to_string()
|
||||
modes.unwrap().to_string() // modes is required
|
||||
};
|
||||
// FIXME: enable non-utf8 paths
|
||||
let mut files: Vec<String> = matches
|
||||
.get_many::<String>(options::FILE)
|
||||
.map(|v| v.map(ToString::to_string).collect())
|
||||
|
@ -107,6 +159,7 @@ pub fn uu_app() -> Command {
|
|||
.override_usage(format_usage(USAGE))
|
||||
.args_override_self(true)
|
||||
.infer_long_args(true)
|
||||
.no_binary_name(true)
|
||||
.arg(
|
||||
Arg::new(options::CHANGES)
|
||||
.long(options::CHANGES)
|
||||
|
@ -376,3 +429,34 @@ impl Chmoder {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_extract_negative_modes() {
|
||||
// "chmod -w -r file" becomes "chmod -w,-r file". clap does not accept "-w,-r" as MODE.
|
||||
// Therefore, "w" is added as pseudo mode to pass clap.
|
||||
let (c, a) = extract_negative_modes(vec!["-w", "-r", "file"].iter().map(OsString::from));
|
||||
assert_eq!(c, Some("-w,-r".to_string()));
|
||||
assert_eq!(a, vec!["w", "file"]);
|
||||
|
||||
// "chmod -w file -r" becomes "chmod -w,-r file". clap does not accept "-w,-r" as MODE.
|
||||
// Therefore, "w" is added as pseudo mode to pass clap.
|
||||
let (c, a) = extract_negative_modes(vec!["-w", "file", "-r"].iter().map(OsString::from));
|
||||
assert_eq!(c, Some("-w,-r".to_string()));
|
||||
assert_eq!(a, vec!["w", "file"]);
|
||||
|
||||
// "chmod -w -- -r file" becomes "chmod -w -r file", where "-r" is interpreted as file.
|
||||
// Again, "w" is needed as pseudo mode.
|
||||
let (c, a) = extract_negative_modes(vec!["-w", "--", "-r", "f"].iter().map(OsString::from));
|
||||
assert_eq!(c, Some("-w".to_string()));
|
||||
assert_eq!(a, vec!["w", "--", "-r", "f"]);
|
||||
|
||||
// "chmod -- -r file" becomes "chmod -r file".
|
||||
let (c, a) = extract_negative_modes(vec!["--", "-r", "file"].iter().map(OsString::from));
|
||||
assert_eq!(c, None);
|
||||
assert_eq!(a, vec!["--", "-r", "file"]);
|
||||
}
|
||||
}
|
||||
|
|
23
src/uu/cksum/cksum.md
Normal file
23
src/uu/cksum/cksum.md
Normal file
|
@ -0,0 +1,23 @@
|
|||
# cksum
|
||||
|
||||
```
|
||||
cksum [OPTIONS] [FILE]...
|
||||
```
|
||||
|
||||
Print CRC and size for each file
|
||||
|
||||
## After Help
|
||||
|
||||
DIGEST determines the digest algorithm and default output format:
|
||||
|
||||
- `-a=sysv`: (equivalent to sum -s)
|
||||
- `-a=bsd`: (equivalent to sum -r)
|
||||
- `-a=crc`: (equivalent to cksum)
|
||||
- `-a=md5`: (equivalent to md5sum)
|
||||
- `-a=sha1`: (equivalent to sha1sum)
|
||||
- `-a=sha224`: (equivalent to sha224sum)
|
||||
- `-a=sha256`: (equivalent to sha256sum)
|
||||
- `-a=sha384`: (equivalent to sha384sum)
|
||||
- `-a=sha512`: (equivalent to sha512sum)
|
||||
- `-a=blake2b`: (equivalent to b2sum)
|
||||
- `-a=sm3`: (only available through cksum)
|
|
@ -15,15 +15,16 @@ use std::iter;
|
|||
use std::path::Path;
|
||||
use uucore::{
|
||||
error::{FromIo, UResult},
|
||||
format_usage,
|
||||
format_usage, help_about, help_section, help_usage,
|
||||
sum::{
|
||||
div_ceil, Blake2b, Digest, DigestWriter, Md5, Sha1, Sha224, Sha256, Sha384, Sha512, Sm3,
|
||||
BSD, CRC, SYSV,
|
||||
},
|
||||
};
|
||||
|
||||
const USAGE: &str = "{} [OPTIONS] [FILE]...";
|
||||
const ABOUT: &str = "Print CRC and size for each file";
|
||||
const USAGE: &str = help_usage!("cksum.md");
|
||||
const ABOUT: &str = help_about!("cksum.md");
|
||||
const AFTER_HELP: &str = help_section!("after help", "cksum.md");
|
||||
|
||||
const ALGORITHM_OPTIONS_SYSV: &str = "sysv";
|
||||
const ALGORITHM_OPTIONS_BSD: &str = "bsd";
|
||||
|
@ -205,21 +206,6 @@ mod options {
|
|||
pub static ALGORITHM: &str = "algorithm";
|
||||
}
|
||||
|
||||
const ALGORITHM_HELP_DESC: &str =
|
||||
"DIGEST determines the digest algorithm and default output format:\n\
|
||||
\n\
|
||||
-a=sysv: (equivalent to sum -s)\n\
|
||||
-a=bsd: (equivalent to sum -r)\n\
|
||||
-a=crc: (equivalent to cksum)\n\
|
||||
-a=md5: (equivalent to md5sum)\n\
|
||||
-a=sha1: (equivalent to sha1sum)\n\
|
||||
-a=sha224: (equivalent to sha224sum)\n\
|
||||
-a=sha256: (equivalent to sha256sum)\n\
|
||||
-a=sha384: (equivalent to sha384sum)\n\
|
||||
-a=sha512: (equivalent to sha512sum)\n\
|
||||
-a=blake2b: (equivalent to b2sum)\n\
|
||||
-a=sm3: (only available through cksum)\n";
|
||||
|
||||
#[uucore::main]
|
||||
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||
let args = args.collect_ignore();
|
||||
|
@ -278,5 +264,5 @@ pub fn uu_app() -> Command {
|
|||
ALGORITHM_OPTIONS_SM3,
|
||||
]),
|
||||
)
|
||||
.after_help(ALGORITHM_HELP_DESC)
|
||||
.after_help(AFTER_HELP)
|
||||
}
|
||||
|
|
|
@ -22,7 +22,7 @@ use uucore::display::Quotable;
|
|||
#[cfg(not(any(target_os = "macos", target_os = "redox")))]
|
||||
use uucore::error::FromIo;
|
||||
use uucore::error::{UResult, USimpleError};
|
||||
use uucore::{format_usage, help_about, help_usage, show_error};
|
||||
use uucore::{format_usage, help_about, help_usage, show};
|
||||
#[cfg(windows)]
|
||||
use windows_sys::Win32::{Foundation::SYSTEMTIME, System::SystemInformation::SetSystemTime};
|
||||
|
||||
|
@ -114,8 +114,8 @@ impl<'a> From<&'a str> for Iso8601Format {
|
|||
SECONDS | SECOND => Self::Seconds,
|
||||
NS => Self::Ns,
|
||||
DATE => Self::Date,
|
||||
// Should be caught by clap
|
||||
_ => panic!("Invalid format: {s}"),
|
||||
// Note: This is caught by clap via `possible_values`
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -203,9 +203,6 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
|||
|
||||
return set_system_datetime(date);
|
||||
} else {
|
||||
// Declare a file here because it needs to outlive the `dates` iterator.
|
||||
let file: File;
|
||||
|
||||
// Get the current time, either in the local time zone or UTC.
|
||||
let now: DateTime<FixedOffset> = if settings.utc {
|
||||
let now = Utc::now();
|
||||
|
@ -222,12 +219,19 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
|||
let iter = std::iter::once(date);
|
||||
Box::new(iter)
|
||||
}
|
||||
DateSource::File(ref path) => {
|
||||
file = File::open(path).unwrap();
|
||||
DateSource::File(ref path) => match File::open(path) {
|
||||
Ok(file) => {
|
||||
let lines = BufReader::new(file).lines();
|
||||
let iter = lines.filter_map(Result::ok).map(parse_date);
|
||||
Box::new(iter)
|
||||
}
|
||||
Err(_err) => {
|
||||
return Err(USimpleError::new(
|
||||
2,
|
||||
format!("{}: No such file or directory", path.display()),
|
||||
));
|
||||
}
|
||||
},
|
||||
DateSource::Now => {
|
||||
let iter = std::iter::once(Ok(now));
|
||||
Box::new(iter)
|
||||
|
@ -257,7 +261,10 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
|||
.replace("%f", "%N");
|
||||
println!("{formatted}");
|
||||
}
|
||||
Err((input, _err)) => show_error!("invalid date {}", input.quote()),
|
||||
Err((input, _err)) => show!(USimpleError::new(
|
||||
1,
|
||||
format!("invalid date {}", input.quote())
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -291,6 +298,9 @@ pub fn uu_app() -> Command {
|
|||
.short('I')
|
||||
.long(OPT_ISO_8601)
|
||||
.value_name("FMT")
|
||||
.value_parser([DATE, HOUR, HOURS, MINUTE, MINUTES, SECOND, SECONDS, NS])
|
||||
.num_args(0..=1)
|
||||
.default_missing_value(OPT_DATE)
|
||||
.help(ISO_8601_HELP_STRING),
|
||||
)
|
||||
.arg(
|
||||
|
@ -304,6 +314,7 @@ pub fn uu_app() -> Command {
|
|||
Arg::new(OPT_RFC_3339)
|
||||
.long(OPT_RFC_3339)
|
||||
.value_name("FMT")
|
||||
.value_parser([DATE, SECOND, SECONDS, NS])
|
||||
.help(RFC_3339_HELP_STRING),
|
||||
)
|
||||
.arg(
|
||||
|
|
|
@ -27,11 +27,14 @@ use std::cmp;
|
|||
use std::env;
|
||||
use std::ffi::OsString;
|
||||
use std::fs::{File, OpenOptions};
|
||||
use std::io::{self, Read, Seek, SeekFrom, Stdin, Stdout, Write};
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::fs::FileTypeExt;
|
||||
use std::io::{self, Read, Seek, SeekFrom, Stdout, Write};
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
use std::os::unix::fs::OpenOptionsExt;
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::{
|
||||
fs::FileTypeExt,
|
||||
io::{AsRawFd, FromRawFd},
|
||||
};
|
||||
use std::path::Path;
|
||||
use std::sync::mpsc;
|
||||
use std::thread;
|
||||
|
@ -93,21 +96,44 @@ impl Num {
|
|||
}
|
||||
|
||||
/// Data sources.
|
||||
///
|
||||
/// Use [`Source::stdin_as_file`] if available to enable more
|
||||
/// fine-grained access to reading from stdin.
|
||||
enum Source {
|
||||
/// Input from stdin.
|
||||
Stdin(Stdin),
|
||||
#[cfg(not(unix))]
|
||||
Stdin(io::Stdin),
|
||||
|
||||
/// Input from a file.
|
||||
File(File),
|
||||
|
||||
/// Input from stdin, opened from its file descriptor.
|
||||
#[cfg(unix)]
|
||||
StdinFile(File),
|
||||
|
||||
/// Input from a named pipe, also known as a FIFO.
|
||||
#[cfg(unix)]
|
||||
Fifo(File),
|
||||
}
|
||||
|
||||
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
|
||||
/// `Source::Stdin` to allow reading from stdin without consuming
|
||||
/// the entire contents of stdin when this process terminates.
|
||||
#[cfg(unix)]
|
||||
fn stdin_as_file() -> Self {
|
||||
let fd = io::stdin().as_raw_fd();
|
||||
let f = unsafe { File::from_raw_fd(fd) };
|
||||
Self::StdinFile(f)
|
||||
}
|
||||
|
||||
fn skip(&mut self, n: u64) -> io::Result<u64> {
|
||||
match self {
|
||||
#[cfg(not(unix))]
|
||||
Self::Stdin(stdin) => match io::copy(&mut stdin.take(n), &mut io::sink()) {
|
||||
Ok(m) if m < n => {
|
||||
show_error!("'standard input': cannot skip to specified offset");
|
||||
|
@ -116,6 +142,15 @@ impl Source {
|
|||
Ok(m) => Ok(m),
|
||||
Err(e) => Err(e),
|
||||
},
|
||||
#[cfg(unix)]
|
||||
Self::StdinFile(f) => match io::copy(&mut f.take(n), &mut io::sink()) {
|
||||
Ok(m) if m < n => {
|
||||
show_error!("'standard input': cannot skip to specified offset");
|
||||
Ok(m)
|
||||
}
|
||||
Ok(m) => Ok(m),
|
||||
Err(e) => Err(e),
|
||||
},
|
||||
Self::File(f) => f.seek(io::SeekFrom::Start(n)),
|
||||
#[cfg(unix)]
|
||||
Self::Fifo(f) => io::copy(&mut f.take(n), &mut io::sink()),
|
||||
|
@ -126,9 +161,12 @@ impl Source {
|
|||
impl Read for Source {
|
||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||
match self {
|
||||
#[cfg(not(unix))]
|
||||
Self::Stdin(stdin) => stdin.read(buf),
|
||||
Self::File(f) => f.read(buf),
|
||||
#[cfg(unix)]
|
||||
Self::StdinFile(f) => f.read(buf),
|
||||
#[cfg(unix)]
|
||||
Self::Fifo(f) => f.read(buf),
|
||||
}
|
||||
}
|
||||
|
@ -151,7 +189,10 @@ struct Input<'a> {
|
|||
impl<'a> Input<'a> {
|
||||
/// Instantiate this struct with stdin as a source.
|
||||
fn new_stdin(settings: &'a Settings) -> UResult<Self> {
|
||||
#[cfg(not(unix))]
|
||||
let mut src = Source::Stdin(io::stdin());
|
||||
#[cfg(unix)]
|
||||
let mut src = Source::stdin_as_file();
|
||||
if settings.skip > 0 {
|
||||
src.skip(settings.skip)?;
|
||||
}
|
||||
|
|
8
src/uu/fold/fold.md
Normal file
8
src/uu/fold/fold.md
Normal file
|
@ -0,0 +1,8 @@
|
|||
# fold
|
||||
|
||||
```
|
||||
fold [OPTION]... [FILE]...
|
||||
```
|
||||
|
||||
Writes each file (or standard input if no files are given)
|
||||
to standard output whilst breaking long lines
|
|
@ -13,13 +13,12 @@ use std::io::{stdin, BufRead, BufReader, Read};
|
|||
use std::path::Path;
|
||||
use uucore::display::Quotable;
|
||||
use uucore::error::{FromIo, UResult, USimpleError};
|
||||
use uucore::format_usage;
|
||||
use uucore::{format_usage, help_about, help_usage};
|
||||
|
||||
const TAB_WIDTH: usize = 8;
|
||||
|
||||
static USAGE: &str = "{} [OPTION]... [FILE]...";
|
||||
static ABOUT: &str = "Writes each file (or standard input if no files are given)
|
||||
to standard output whilst breaking long lines";
|
||||
const USAGE: &str = help_usage!("fold.md");
|
||||
const ABOUT: &str = help_about!("fold.md");
|
||||
|
||||
mod options {
|
||||
pub const BYTES: &str = "bytes";
|
||||
|
|
7
src/uu/mkfifo/mkfifo.md
Normal file
7
src/uu/mkfifo/mkfifo.md
Normal file
|
@ -0,0 +1,7 @@
|
|||
# mkfifo
|
||||
|
||||
```
|
||||
mkfifo [OPTION]... NAME...
|
||||
```
|
||||
|
||||
Create a FIFO with the given name.
|
|
@ -10,10 +10,10 @@ use libc::mkfifo;
|
|||
use std::ffi::CString;
|
||||
use uucore::display::Quotable;
|
||||
use uucore::error::{UResult, USimpleError};
|
||||
use uucore::{format_usage, show};
|
||||
use uucore::{format_usage, help_about, help_usage, show};
|
||||
|
||||
static USAGE: &str = "{} [OPTION]... NAME...";
|
||||
static ABOUT: &str = "Create a FIFO with the given name.";
|
||||
static USAGE: &str = help_usage!("mkfifo.md");
|
||||
static ABOUT: &str = help_about!("mkfifo.md");
|
||||
|
||||
mod options {
|
||||
pub static MODE: &str = "mode";
|
||||
|
|
25
src/uu/mknod/mknod.md
Normal file
25
src/uu/mknod/mknod.md
Normal file
|
@ -0,0 +1,25 @@
|
|||
# mknod
|
||||
|
||||
```
|
||||
mknod [OPTION]... NAME TYPE [MAJOR MINOR]
|
||||
```
|
||||
|
||||
Create the special file NAME of the given TYPE.
|
||||
|
||||
## After Help
|
||||
|
||||
Mandatory arguments to long options are mandatory for short options too.
|
||||
`-m`, `--mode=MODE` set file permission bits to `MODE`, not `a=rw - umask`
|
||||
|
||||
Both `MAJOR` and `MINOR` must be specified when `TYPE` is `b`, `c`, or `u`, and they
|
||||
must be omitted when `TYPE` is `p`. If `MAJOR` or `MINOR` begins with `0x` or `0X`,
|
||||
it is interpreted as hexadecimal; otherwise, if it begins with 0, as octal;
|
||||
otherwise, as decimal. `TYPE` may be:
|
||||
|
||||
* `b` create a block (buffered) special file
|
||||
* `c`, `u` create a character (unbuffered) special file
|
||||
* `p` create a FIFO
|
||||
|
||||
NOTE: your shell may have its own version of mknod, which usually supersedes
|
||||
the version described here. Please refer to your shell's documentation
|
||||
for details about the options it supports.
|
|
@ -14,28 +14,11 @@ use std::ffi::CString;
|
|||
|
||||
use uucore::display::Quotable;
|
||||
use uucore::error::{set_exit_code, UResult, USimpleError, UUsageError};
|
||||
use uucore::format_usage;
|
||||
use uucore::{format_usage, help_about, help_section, help_usage};
|
||||
|
||||
static ABOUT: &str = "Create the special file NAME of the given TYPE.";
|
||||
static USAGE: &str = "{} [OPTION]... NAME TYPE [MAJOR MINOR]";
|
||||
static LONG_HELP: &str = "Mandatory arguments to long options are mandatory for short options too.
|
||||
-m, --mode=MODE set file permission bits to MODE, not a=rw - umask
|
||||
--help display this help and exit
|
||||
--version output version information and exit
|
||||
|
||||
Both MAJOR and MINOR must be specified when TYPE is b, c, or u, and they
|
||||
must be omitted when TYPE is p. If MAJOR or MINOR begins with 0x or 0X,
|
||||
it is interpreted as hexadecimal; otherwise, if it begins with 0, as octal;
|
||||
otherwise, as decimal. TYPE may be:
|
||||
|
||||
b create a block (buffered) special file
|
||||
c, u create a character (unbuffered) special file
|
||||
p create a FIFO
|
||||
|
||||
NOTE: your shell may have its own version of mknod, which usually supersedes
|
||||
the version described here. Please refer to your shell's documentation
|
||||
for details about the options it supports.
|
||||
";
|
||||
const ABOUT: &str = help_about!("mknod.md");
|
||||
const USAGE: &str = help_usage!("mknod.md");
|
||||
const AFTER_HELP: &str = help_section!("after help", "mknod.md");
|
||||
|
||||
const MODE_RW_UGO: mode_t = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH;
|
||||
|
||||
|
@ -142,7 +125,7 @@ pub fn uu_app() -> Command {
|
|||
Command::new(uucore::util_name())
|
||||
.version(crate_version!())
|
||||
.override_usage(format_usage(USAGE))
|
||||
.after_help(LONG_HELP)
|
||||
.after_help(AFTER_HELP)
|
||||
.about(ABOUT)
|
||||
.infer_long_args(true)
|
||||
.arg(
|
||||
|
|
7
src/uu/mktemp/mktemp.md
Normal file
7
src/uu/mktemp/mktemp.md
Normal file
|
@ -0,0 +1,7 @@
|
|||
# mktemp
|
||||
|
||||
```
|
||||
mktemp [OPTION]... [TEMPLATE]
|
||||
```
|
||||
|
||||
Create a temporary file or directory.
|
|
@ -11,7 +11,7 @@
|
|||
use clap::{crate_version, Arg, ArgAction, ArgMatches, Command};
|
||||
use uucore::display::{println_verbatim, Quotable};
|
||||
use uucore::error::{FromIo, UError, UResult, UUsageError};
|
||||
use uucore::format_usage;
|
||||
use uucore::{format_usage, help_about, help_usage};
|
||||
|
||||
use std::env;
|
||||
use std::error::Error;
|
||||
|
@ -28,8 +28,8 @@ use std::os::unix::prelude::PermissionsExt;
|
|||
use rand::Rng;
|
||||
use tempfile::Builder;
|
||||
|
||||
static ABOUT: &str = "Create a temporary file or directory.";
|
||||
const USAGE: &str = "{} [OPTION]... [TEMPLATE]";
|
||||
const ABOUT: &str = help_about!("mktemp.md");
|
||||
const USAGE: &str = help_usage!("mktemp.md");
|
||||
|
||||
static DEFAULT_TEMPLATE: &str = "tmp.XXXXXXXXXX";
|
||||
|
||||
|
|
|
@ -15,10 +15,10 @@ use uucore::{format_usage, help_about, help_usage};
|
|||
use uucore::display::println_verbatim;
|
||||
use uucore::error::{FromIo, UResult};
|
||||
|
||||
static ABOUT: &str = help_about!("pwd.md");
|
||||
const ABOUT: &str = help_about!("pwd.md");
|
||||
const USAGE: &str = help_usage!("pwd.md");
|
||||
static OPT_LOGICAL: &str = "logical";
|
||||
static OPT_PHYSICAL: &str = "physical";
|
||||
const OPT_LOGICAL: &str = "logical";
|
||||
const OPT_PHYSICAL: &str = "physical";
|
||||
|
||||
fn physical_path() -> io::Result<PathBuf> {
|
||||
// std::env::current_dir() is a thin wrapper around libc::getcwd().
|
||||
|
@ -84,36 +84,22 @@ fn logical_path() -> io::Result<PathBuf> {
|
|||
{
|
||||
use std::fs::metadata;
|
||||
use std::os::unix::fs::MetadataExt;
|
||||
let path_info = match metadata(path) {
|
||||
Ok(info) => info,
|
||||
Err(_) => return false,
|
||||
};
|
||||
let real_info = match metadata(".") {
|
||||
Ok(info) => info,
|
||||
Err(_) => return false,
|
||||
};
|
||||
if path_info.dev() != real_info.dev() || path_info.ino() != real_info.ino() {
|
||||
return false;
|
||||
match (metadata(path), metadata(".")) {
|
||||
(Ok(info1), Ok(info2)) => {
|
||||
info1.dev() == info2.dev() && info1.ino() == info2.ino()
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(unix))]
|
||||
{
|
||||
use std::fs::canonicalize;
|
||||
let canon_path = match canonicalize(path) {
|
||||
Ok(path) => path,
|
||||
Err(_) => return false,
|
||||
};
|
||||
let real_path = match canonicalize(".") {
|
||||
Ok(path) => path,
|
||||
Err(_) => return false,
|
||||
};
|
||||
if canon_path != real_path {
|
||||
return false;
|
||||
match (canonicalize(path), canonicalize(".")) {
|
||||
(Ok(path1), Ok(path2)) => path1 == path2,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
match env::var_os("PWD").map(PathBuf::from) {
|
||||
|
|
8
src/uu/relpath/relpath.md
Normal file
8
src/uu/relpath/relpath.md
Normal file
|
@ -0,0 +1,8 @@
|
|||
# relpath
|
||||
|
||||
```
|
||||
relpath [-d DIR] TO [FROM]
|
||||
```
|
||||
|
||||
Convert TO destination to the relative path from the FROM dir.
|
||||
If FROM path is omitted, current working dir will be used.
|
|
@ -12,12 +12,11 @@ use std::env;
|
|||
use std::path::{Path, PathBuf};
|
||||
use uucore::display::println_verbatim;
|
||||
use uucore::error::{FromIo, UResult};
|
||||
use uucore::format_usage;
|
||||
use uucore::fs::{canonicalize, MissingHandling, ResolveMode};
|
||||
use uucore::{format_usage, help_about, help_usage};
|
||||
|
||||
static ABOUT: &str = "Convert TO destination to the relative path from the FROM dir.
|
||||
If FROM path is omitted, current working dir will be used.";
|
||||
const USAGE: &str = "{} [-d DIR] TO [FROM]";
|
||||
const USAGE: &str = help_usage!("relpath.md");
|
||||
const ABOUT: &str = help_about!("relpath.md");
|
||||
|
||||
mod options {
|
||||
pub const DIR: &str = "DIR";
|
||||
|
|
|
@ -16,6 +16,7 @@ path = "src/sleep.rs"
|
|||
|
||||
[dependencies]
|
||||
clap = { workspace=true }
|
||||
fundu = { workspace=true }
|
||||
uucore = { workspace=true }
|
||||
|
||||
[[bin]]
|
||||
|
|
|
@ -14,6 +14,7 @@ use uucore::{
|
|||
};
|
||||
|
||||
use clap::{crate_version, Arg, ArgAction, Command};
|
||||
use fundu::{self, DurationParser, ParseError};
|
||||
|
||||
static ABOUT: &str = help_about!("sleep.md");
|
||||
const USAGE: &str = help_usage!("sleep.md");
|
||||
|
@ -61,14 +62,34 @@ pub fn uu_app() -> Command {
|
|||
|
||||
fn sleep(args: &[&str]) -> UResult<()> {
|
||||
let mut arg_error = false;
|
||||
|
||||
use fundu::TimeUnit::*;
|
||||
let parser = DurationParser::with_time_units(&[Second, Minute, Hour, Day]);
|
||||
|
||||
let sleep_dur = args
|
||||
.iter()
|
||||
.filter_map(|input| {
|
||||
uucore::parse_time::from_str(input.trim()).ok().or_else(|| {
|
||||
.filter_map(|input| match parser.parse(input.trim()) {
|
||||
Ok(duration) => Some(duration),
|
||||
Err(error) => {
|
||||
arg_error = true;
|
||||
show_error!("invalid time interval '{input}'");
|
||||
|
||||
let reason = match error {
|
||||
ParseError::Empty if input.is_empty() => "Input was empty".to_string(),
|
||||
ParseError::Empty => "Found only whitespace in input".to_string(),
|
||||
ParseError::Syntax(pos, description)
|
||||
| ParseError::TimeUnit(pos, description) => {
|
||||
format!("{description} at position {}", pos.saturating_add(1))
|
||||
}
|
||||
ParseError::NegativeExponentOverflow | ParseError::PositiveExponentOverflow => {
|
||||
"Exponent was out of bounds".to_string()
|
||||
}
|
||||
ParseError::NegativeNumber => "Number was negative".to_string(),
|
||||
error => error.to_string(),
|
||||
};
|
||||
show_error!("invalid time interval '{input}': {reason}");
|
||||
|
||||
None
|
||||
})
|
||||
}
|
||||
})
|
||||
.fold(Duration::ZERO, |acc, n| acc.saturating_add(n));
|
||||
|
||||
|
|
|
@ -196,6 +196,22 @@ fn report_if_verbose(signal: usize, cmd: &str, verbose: bool) {
|
|||
}
|
||||
}
|
||||
|
||||
fn send_signal(process: &mut Child, signal: usize, foreground: bool) {
|
||||
// NOTE: GNU timeout doesn't check for errors of signal.
|
||||
// The subprocess might have exited just after the timeout.
|
||||
// Sending a signal now would return "No such process", but we should still try to kill the children.
|
||||
_ = process.send_signal(signal);
|
||||
if !foreground {
|
||||
_ = process.send_signal_group(signal);
|
||||
let kill_signal = signal_by_name_or_value("KILL").unwrap();
|
||||
let continued_signal = signal_by_name_or_value("CONT").unwrap();
|
||||
if signal != kill_signal && signal != continued_signal {
|
||||
_ = process.send_signal(continued_signal);
|
||||
_ = process.send_signal_group(continued_signal);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Wait for a child process and send a kill signal if it does not terminate.
|
||||
///
|
||||
/// This function waits for the child `process` for the time period
|
||||
|
@ -217,10 +233,11 @@ fn report_if_verbose(signal: usize, cmd: &str, verbose: bool) {
|
|||
/// If there is a problem sending the `SIGKILL` signal or waiting for
|
||||
/// the process after that signal is sent.
|
||||
fn wait_or_kill_process(
|
||||
mut process: Child,
|
||||
process: &mut Child,
|
||||
cmd: &str,
|
||||
duration: Duration,
|
||||
preserve_status: bool,
|
||||
foreground: bool,
|
||||
verbose: bool,
|
||||
) -> std::io::Result<i32> {
|
||||
match process.wait_or_timeout(duration) {
|
||||
|
@ -234,7 +251,7 @@ fn wait_or_kill_process(
|
|||
Ok(None) => {
|
||||
let signal = signal_by_name_or_value("KILL").unwrap();
|
||||
report_if_verbose(signal, cmd, verbose);
|
||||
process.send_signal(signal)?;
|
||||
send_signal(process, signal, foreground);
|
||||
process.wait()?;
|
||||
Ok(ExitStatus::SignalSent(signal).into())
|
||||
}
|
||||
|
@ -300,7 +317,7 @@ fn timeout(
|
|||
|
||||
enable_pipe_errors()?;
|
||||
|
||||
let mut process = process::Command::new(&cmd[0])
|
||||
let process = &mut process::Command::new(&cmd[0])
|
||||
.args(&cmd[1..])
|
||||
.stdin(Stdio::inherit())
|
||||
.stdout(Stdio::inherit())
|
||||
|
@ -335,7 +352,7 @@ fn timeout(
|
|||
.into()),
|
||||
Ok(None) => {
|
||||
report_if_verbose(signal, &cmd[0], verbose);
|
||||
process.send_signal(signal)?;
|
||||
send_signal(process, signal, foreground);
|
||||
match kill_after {
|
||||
None => {
|
||||
if preserve_status {
|
||||
|
@ -350,6 +367,7 @@ fn timeout(
|
|||
&cmd[0],
|
||||
kill_after,
|
||||
preserve_status,
|
||||
foreground,
|
||||
verbose,
|
||||
) {
|
||||
Ok(status) => Err(status.into()),
|
||||
|
@ -363,11 +381,8 @@ fn timeout(
|
|||
}
|
||||
Err(_) => {
|
||||
// We're going to return ERR_EXIT_STATUS regardless of
|
||||
// whether `send_signal()` succeeds or fails, so just
|
||||
// ignore the return value.
|
||||
process
|
||||
.send_signal(signal)
|
||||
.map_err(|e| USimpleError::new(ExitStatus::TimeoutFailed.into(), format!("{e}")))?;
|
||||
// whether `send_signal()` succeeds or fails
|
||||
send_signal(process, signal, foreground);
|
||||
Err(ExitStatus::TimeoutFailed.into())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,17 +11,13 @@
|
|||
use chrono::{Local, TimeZone, Utc};
|
||||
use clap::{crate_version, Arg, ArgAction, Command};
|
||||
|
||||
use uucore::format_usage;
|
||||
// import crate time from utmpx
|
||||
pub use uucore::libc;
|
||||
use uucore::libc::time_t;
|
||||
use uucore::{format_usage, help_about, help_usage};
|
||||
|
||||
use uucore::error::{UResult, USimpleError};
|
||||
|
||||
static ABOUT: &str = "Display the current time, the length of time the system has been up,\n\
|
||||
the number of users on the system, and the average number of jobs\n\
|
||||
in the run queue over the last 1, 5 and 15 minutes.";
|
||||
const USAGE: &str = "{} [OPTION]...";
|
||||
const ABOUT: &str = help_about!("uptime.md");
|
||||
const USAGE: &str = help_usage!("uptime.md");
|
||||
pub mod options {
|
||||
pub static SINCE: &str = "since";
|
||||
}
|
||||
|
|
9
src/uu/uptime/uptime.md
Normal file
9
src/uu/uptime/uptime.md
Normal file
|
@ -0,0 +1,9 @@
|
|||
# uptime
|
||||
|
||||
```
|
||||
uptime [OPTION]...
|
||||
```
|
||||
|
||||
Display the current time, the length of time the system has been up,
|
||||
the number of users on the system, and the average number of jobs
|
||||
in the run queue over the last 1, 5 and 15 minutes.
|
|
@ -18,7 +18,7 @@ use std::ffi::CStr;
|
|||
use std::fmt::Write;
|
||||
use std::os::unix::fs::MetadataExt;
|
||||
use std::path::PathBuf;
|
||||
use uucore::format_usage;
|
||||
use uucore::{format_usage, help_about, help_usage};
|
||||
|
||||
mod options {
|
||||
pub const ALL: &str = "all";
|
||||
|
@ -38,8 +38,8 @@ mod options {
|
|||
pub const FILE: &str = "FILE"; // if length=1: FILE, if length=2: ARG1 ARG2
|
||||
}
|
||||
|
||||
static ABOUT: &str = "Print information about users who are currently logged in.";
|
||||
const USAGE: &str = "{} [OPTION]... [ FILE | ARG1 ARG2 ]";
|
||||
const ABOUT: &str = help_about!("who.md");
|
||||
const USAGE: &str = help_usage!("who.md");
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
static RUNLEVEL_HELP: &str = "print current runlevel";
|
||||
|
|
8
src/uu/who/who.md
Normal file
8
src/uu/who/who.md
Normal file
|
@ -0,0 +1,8 @@
|
|||
# who
|
||||
|
||||
```
|
||||
who [OPTION]... [ FILE | ARG1 ARG2 ]
|
||||
```
|
||||
|
||||
Print information about users who are currently logged in.
|
||||
|
|
@ -11,10 +11,12 @@ use clap::{crate_version, Command};
|
|||
|
||||
use uucore::display::println_verbatim;
|
||||
use uucore::error::{FromIo, UResult};
|
||||
use uucore::{format_usage, help_about, help_usage};
|
||||
|
||||
mod platform;
|
||||
|
||||
static ABOUT: &str = "Print the current username.";
|
||||
const ABOUT: &str = help_about!("whoami.md");
|
||||
const USAGE: &str = help_usage!("whoami.md");
|
||||
|
||||
#[uucore::main]
|
||||
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||
|
@ -28,5 +30,6 @@ pub fn uu_app() -> Command {
|
|||
Command::new(uucore::util_name())
|
||||
.version(crate_version!())
|
||||
.about(ABOUT)
|
||||
.override_usage(format_usage(USAGE))
|
||||
.infer_long_args(true)
|
||||
}
|
||||
|
|
7
src/uu/whoami/whoami.md
Normal file
7
src/uu/whoami/whoami.md
Normal file
|
@ -0,0 +1,7 @@
|
|||
# whoami
|
||||
|
||||
```
|
||||
whoami
|
||||
```
|
||||
|
||||
Print the current username.
|
|
@ -122,6 +122,15 @@ fn parse_change(mode: &str, fperm: u32, considering_dir: bool) -> (u32, usize) {
|
|||
'o' => srwx = ((fperm << 6) & 0o700) | ((fperm << 3) & 0o070) | (fperm & 0o007),
|
||||
_ => break,
|
||||
};
|
||||
if ch == 'u' || ch == 'g' || ch == 'o' {
|
||||
// symbolic modes only allows perms to be a single letter of 'ugo'
|
||||
// therefore this must either be the first char or it is unexpected
|
||||
if pos != 0 {
|
||||
break;
|
||||
}
|
||||
pos = 1;
|
||||
break;
|
||||
}
|
||||
pos += 1;
|
||||
}
|
||||
if pos == 0 {
|
||||
|
|
|
@ -48,6 +48,9 @@ pub trait ChildExt {
|
|||
/// send the signal to an unrelated process that recycled the PID.
|
||||
fn send_signal(&mut self, signal: usize) -> io::Result<()>;
|
||||
|
||||
/// Send a signal to a process group.
|
||||
fn send_signal_group(&mut self, signal: usize) -> io::Result<()>;
|
||||
|
||||
/// Wait for a process to finish or return after the specified duration.
|
||||
/// A `timeout` of zero disables the timeout.
|
||||
fn wait_or_timeout(&mut self, timeout: Duration) -> io::Result<Option<ExitStatus>>;
|
||||
|
@ -62,6 +65,18 @@ impl ChildExt for Child {
|
|||
}
|
||||
}
|
||||
|
||||
fn send_signal_group(&mut self, signal: usize) -> io::Result<()> {
|
||||
// Ignore the signal, so we don't go into a signal loop.
|
||||
if unsafe { libc::signal(signal as i32, libc::SIG_IGN) } != 0 {
|
||||
return Err(io::Error::last_os_error());
|
||||
}
|
||||
if unsafe { libc::kill(0, signal as i32) } != 0 {
|
||||
Err(io::Error::last_os_error())
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn wait_or_timeout(&mut self, timeout: Duration) -> io::Result<Option<ExitStatus>> {
|
||||
if timeout == Duration::from_micros(0) {
|
||||
return self.wait().map(Some);
|
||||
|
|
|
@ -4,9 +4,8 @@ use std::fs::{metadata, set_permissions, OpenOptions, Permissions};
|
|||
use std::os::unix::fs::{OpenOptionsExt, PermissionsExt};
|
||||
use std::sync::Mutex;
|
||||
|
||||
extern crate libc;
|
||||
use uucore::mode::strip_minus_from_mode;
|
||||
extern crate chmod;
|
||||
extern crate libc;
|
||||
use self::libc::umask;
|
||||
|
||||
static TEST_FILE: &str = "file";
|
||||
|
@ -503,35 +502,6 @@ fn test_chmod_symlink_non_existing_file_recursive() {
|
|||
.no_stderr();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_chmod_strip_minus_from_mode() {
|
||||
let tests = vec![
|
||||
// ( before, after )
|
||||
("chmod -v -xw -R FILE", "chmod -v xw -R FILE"),
|
||||
("chmod g=rwx FILE -c", "chmod g=rwx FILE -c"),
|
||||
(
|
||||
"chmod -c -R -w,o+w FILE --preserve-root",
|
||||
"chmod -c -R w,o+w FILE --preserve-root",
|
||||
),
|
||||
("chmod -c -R +w FILE ", "chmod -c -R +w FILE "),
|
||||
("chmod a=r,=xX FILE", "chmod a=r,=xX FILE"),
|
||||
(
|
||||
"chmod -v --reference REF_FILE -R FILE",
|
||||
"chmod -v --reference REF_FILE -R FILE",
|
||||
),
|
||||
("chmod -Rvc -w-x FILE", "chmod -Rvc w-x FILE"),
|
||||
("chmod 755 -v FILE", "chmod 755 -v FILE"),
|
||||
("chmod -v +0004 FILE -R", "chmod -v +0004 FILE -R"),
|
||||
("chmod -v -0007 FILE -R", "chmod -v 0007 FILE -R"),
|
||||
];
|
||||
|
||||
for test in tests {
|
||||
let mut args: Vec<String> = test.0.split(' ').map(|v| v.to_string()).collect();
|
||||
let _mode_had_minus_prefix = strip_minus_from_mode(&mut args);
|
||||
assert_eq!(test.1, args.join(" "));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_chmod_keep_setgid() {
|
||||
for (from, arg, to) in [
|
||||
|
@ -671,3 +641,68 @@ fn test_quiet_n_verbose_used_multiple_times() {
|
|||
.arg("file")
|
||||
.succeeds();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_gnu_invalid_mode() {
|
||||
let scene = TestScenario::new(util_name!());
|
||||
let at = &scene.fixtures;
|
||||
at.touch("file");
|
||||
scene.ucmd().arg("u+gr").arg("file").fails();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_gnu_options() {
|
||||
let scene = TestScenario::new(util_name!());
|
||||
let at = &scene.fixtures;
|
||||
at.touch("file");
|
||||
scene.ucmd().arg("-w").arg("file").succeeds();
|
||||
scene.ucmd().arg("file").arg("-w").succeeds();
|
||||
scene.ucmd().arg("-w").arg("--").arg("file").succeeds();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_gnu_repeating_options() {
|
||||
let scene = TestScenario::new(util_name!());
|
||||
let at = &scene.fixtures;
|
||||
at.touch("file");
|
||||
scene.ucmd().arg("-w").arg("-w").arg("file").succeeds();
|
||||
scene
|
||||
.ucmd()
|
||||
.arg("-w")
|
||||
.arg("-w")
|
||||
.arg("-w")
|
||||
.arg("file")
|
||||
.succeeds();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_gnu_special_filenames() {
|
||||
let scene = TestScenario::new(util_name!());
|
||||
let at = &scene.fixtures;
|
||||
let perms_before = Permissions::from_mode(0o100640);
|
||||
let perms_after = Permissions::from_mode(0o100440);
|
||||
|
||||
make_file(&at.plus_as_string("--"), perms_before.mode());
|
||||
scene.ucmd().arg("-w").arg("--").arg("--").succeeds();
|
||||
assert_eq!(at.metadata("--").permissions(), perms_after);
|
||||
set_permissions(at.plus("--"), perms_before.clone()).unwrap();
|
||||
scene.ucmd().arg("--").arg("-w").arg("--").succeeds();
|
||||
assert_eq!(at.metadata("--").permissions(), perms_after);
|
||||
at.remove("--");
|
||||
|
||||
make_file(&at.plus_as_string("-w"), perms_before.mode());
|
||||
scene.ucmd().arg("-w").arg("--").arg("-w").succeeds();
|
||||
assert_eq!(at.metadata("-w").permissions(), perms_after);
|
||||
set_permissions(at.plus("-w"), perms_before).unwrap();
|
||||
scene.ucmd().arg("--").arg("-w").arg("-w").succeeds();
|
||||
assert_eq!(at.metadata("-w").permissions(), perms_after);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_gnu_special_options() {
|
||||
let scene = TestScenario::new(util_name!());
|
||||
let at = &scene.fixtures;
|
||||
at.touch("file");
|
||||
scene.ucmd().arg("--").arg("--").arg("file").succeeds();
|
||||
scene.ucmd().arg("--").arg("--").fails();
|
||||
}
|
||||
|
|
|
@ -44,16 +44,91 @@ fn test_date_rfc_3339() {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn test_date_rfc_8601() {
|
||||
fn test_date_rfc_3339_invalid_arg() {
|
||||
for param in ["--iso-3339", "--rfc-3"] {
|
||||
new_ucmd!().arg(format!("{param}=foo")).fails();
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_date_rfc_8601_default() {
|
||||
let re = Regex::new(r"^\d{4}-\d{2}-\d{2}\n$").unwrap();
|
||||
for param in ["--iso-8601", "--i"] {
|
||||
new_ucmd!().arg(format!("{param}=ns")).succeeds();
|
||||
new_ucmd!().arg(param).succeeds().stdout_matches(&re);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_date_rfc_8601() {
|
||||
let re = Regex::new(r"^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2},\d{9}[+-]\d{2}:\d{2}\n$").unwrap();
|
||||
for param in ["--iso-8601", "--i"] {
|
||||
new_ucmd!()
|
||||
.arg(format!("{param}=ns"))
|
||||
.succeeds()
|
||||
.stdout_matches(&re);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_date_rfc_8601_invalid_arg() {
|
||||
for param in ["--iso-8601", "--i"] {
|
||||
new_ucmd!().arg(format!("{param}=@")).fails();
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_date_rfc_8601_second() {
|
||||
let re = Regex::new(r"^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}[+-]\d{2}:\d{2}\n$").unwrap();
|
||||
for param in ["--iso-8601", "--i"] {
|
||||
new_ucmd!().arg(format!("{param}=second")).succeeds();
|
||||
new_ucmd!()
|
||||
.arg(format!("{param}=second"))
|
||||
.succeeds()
|
||||
.stdout_matches(&re);
|
||||
new_ucmd!()
|
||||
.arg(format!("{param}=seconds"))
|
||||
.succeeds()
|
||||
.stdout_matches(&re);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_date_rfc_8601_minute() {
|
||||
let re = Regex::new(r"^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}[+-]\d{2}:\d{2}\n$").unwrap();
|
||||
for param in ["--iso-8601", "--i"] {
|
||||
new_ucmd!()
|
||||
.arg(format!("{param}=minute"))
|
||||
.succeeds()
|
||||
.stdout_matches(&re);
|
||||
new_ucmd!()
|
||||
.arg(format!("{param}=minutes"))
|
||||
.succeeds()
|
||||
.stdout_matches(&re);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_date_rfc_8601_hour() {
|
||||
let re = Regex::new(r"^\d{4}-\d{2}-\d{2}T\d{2}[+-]\d{2}:\d{2}\n$").unwrap();
|
||||
for param in ["--iso-8601", "--i"] {
|
||||
new_ucmd!()
|
||||
.arg(format!("{param}=hour"))
|
||||
.succeeds()
|
||||
.stdout_matches(&re);
|
||||
new_ucmd!()
|
||||
.arg(format!("{param}=hours"))
|
||||
.succeeds()
|
||||
.stdout_matches(&re);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_date_rfc_8601_date() {
|
||||
let re = Regex::new(r"^\d{4}-\d{2}-\d{2}\n$").unwrap();
|
||||
for param in ["--iso-8601", "--i"] {
|
||||
new_ucmd!()
|
||||
.arg(format!("{param}=date"))
|
||||
.succeeds()
|
||||
.stdout_matches(&re);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -198,6 +273,24 @@ fn test_date_set_valid_2() {
|
|||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_date_for_invalid_file() {
|
||||
let result = new_ucmd!().arg("--file").arg("invalid_file").fails();
|
||||
result.no_stdout();
|
||||
assert_eq!(
|
||||
result.stderr_str().trim(),
|
||||
"date: invalid_file: No such file or directory",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_date_for_file() {
|
||||
let (at, mut ucmd) = at_and_ucmd!();
|
||||
let file = "test_date_for_file";
|
||||
at.touch(file);
|
||||
ucmd.arg("--file").arg(file).succeeds();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(all(unix, not(target_os = "macos")))]
|
||||
/// TODO: expected to fail currently; change to succeeds() when required.
|
||||
|
@ -232,3 +325,13 @@ fn test_invalid_format_string() {
|
|||
result.no_stdout();
|
||||
assert!(result.stderr_str().starts_with("date: invalid format "));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_invalid_date_string() {
|
||||
new_ucmd!()
|
||||
.arg("-d")
|
||||
.arg("foo")
|
||||
.fails()
|
||||
.no_stdout()
|
||||
.stderr_contains("invalid date");
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ use regex::Regex;
|
|||
use std::fs::{File, OpenOptions};
|
||||
use std::io::{BufReader, Read, Write};
|
||||
use std::path::PathBuf;
|
||||
#[cfg(all(not(windows), not(target_os = "macos")))]
|
||||
#[cfg(all(unix, not(target_os = "macos"), not(target_os = "freebsd")))]
|
||||
use std::process::{Command, Stdio};
|
||||
#[cfg(not(windows))]
|
||||
use std::thread::sleep;
|
||||
|
@ -1520,3 +1520,17 @@ fn test_skip_input_fifo() {
|
|||
assert!(output.stdout.is_empty());
|
||||
assert_eq!(&output.stderr, b"1+0 records in\n1+0 records out\n");
|
||||
}
|
||||
|
||||
/// Test for reading part of stdin from each of two child processes.
|
||||
#[cfg(all(not(windows), feature = "printf"))]
|
||||
#[test]
|
||||
fn test_multiple_processes_reading_stdin() {
|
||||
// TODO Investigate if this is possible on Windows.
|
||||
let printf = format!("{TESTS_BINARY} printf 'abcdef\n'");
|
||||
let dd_skip = format!("{TESTS_BINARY} dd bs=1 skip=3 count=0");
|
||||
let dd = format!("{TESTS_BINARY} dd");
|
||||
UCommand::new()
|
||||
.arg(format!("{printf} | ( {dd_skip} && {dd} ) 2> /dev/null"))
|
||||
.succeeds()
|
||||
.stdout_only("def\n");
|
||||
}
|
||||
|
|
|
@ -82,10 +82,10 @@ fn _du_basics_subdir(s: &str) {
|
|||
))]
|
||||
fn _du_basics_subdir(s: &str) {
|
||||
// MS-WSL linux has altered expected output
|
||||
if !uucore::os::is_wsl_1() {
|
||||
assert_eq!(s, "8\tsubdir/deeper\n");
|
||||
} else {
|
||||
if uucore::os::is_wsl_1() {
|
||||
assert_eq!(s, "0\tsubdir/deeper\n");
|
||||
} else {
|
||||
assert_eq!(s, "8\tsubdir/deeper\n");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -164,10 +164,10 @@ fn _du_soft_link(s: &str) {
|
|||
))]
|
||||
fn _du_soft_link(s: &str) {
|
||||
// MS-WSL linux has altered expected output
|
||||
if !uucore::os::is_wsl_1() {
|
||||
assert_eq!(s, "16\tsubdir/links\n");
|
||||
} else {
|
||||
if uucore::os::is_wsl_1() {
|
||||
assert_eq!(s, "8\tsubdir/links\n");
|
||||
} else {
|
||||
assert_eq!(s, "16\tsubdir/links\n");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -212,10 +212,10 @@ fn _du_hard_link(s: &str) {
|
|||
))]
|
||||
fn _du_hard_link(s: &str) {
|
||||
// MS-WSL linux has altered expected output
|
||||
if !uucore::os::is_wsl_1() {
|
||||
assert_eq!(s, "16\tsubdir/links\n");
|
||||
} else {
|
||||
if uucore::os::is_wsl_1() {
|
||||
assert_eq!(s, "8\tsubdir/links\n");
|
||||
} else {
|
||||
assert_eq!(s, "16\tsubdir/links\n");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -255,10 +255,10 @@ fn _du_d_flag(s: &str) {
|
|||
))]
|
||||
fn _du_d_flag(s: &str) {
|
||||
// MS-WSL linux has altered expected output
|
||||
if !uucore::os::is_wsl_1() {
|
||||
assert_eq!(s, "28\t./subdir\n36\t.\n");
|
||||
} else {
|
||||
if uucore::os::is_wsl_1() {
|
||||
assert_eq!(s, "8\t./subdir\n8\t.\n");
|
||||
} else {
|
||||
assert_eq!(s, "28\t./subdir\n36\t.\n");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -303,10 +303,10 @@ fn _du_dereference(s: &str) {
|
|||
))]
|
||||
fn _du_dereference(s: &str) {
|
||||
// MS-WSL linux has altered expected output
|
||||
if !uucore::os::is_wsl_1() {
|
||||
assert_eq!(s, "8\tsubdir/links/deeper_dir\n24\tsubdir/links\n");
|
||||
} else {
|
||||
if uucore::os::is_wsl_1() {
|
||||
assert_eq!(s, "0\tsubdir/links/deeper_dir\n8\tsubdir/links\n");
|
||||
} else {
|
||||
assert_eq!(s, "8\tsubdir/links/deeper_dir\n24\tsubdir/links\n");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1280,7 +1280,7 @@ fn test_ls_long_formats() {
|
|||
// Zero or one "." for indicating a file with security context
|
||||
|
||||
// Regex for three names, so all of author, group and owner
|
||||
let re_three = Regex::new(r"[xrw-]{9}\.? \d ([-0-9_a-z]+ ){3}0").unwrap();
|
||||
let re_three = Regex::new(r"[xrw-]{9}\.? \d ([-0-9_a-z_A-Z]+ ){3}0").unwrap();
|
||||
|
||||
#[cfg(unix)]
|
||||
let re_three_num = Regex::new(r"[xrw-]{9}\.? \d (\d+ ){3}0").unwrap();
|
||||
|
@ -1289,13 +1289,13 @@ fn test_ls_long_formats() {
|
|||
// - group and owner
|
||||
// - author and owner
|
||||
// - author and group
|
||||
let re_two = Regex::new(r"[xrw-]{9}\.? \d ([-0-9_a-z]+ ){2}0").unwrap();
|
||||
let re_two = Regex::new(r"[xrw-]{9}\.? \d ([-0-9_a-z_A-Z]+ ){2}0").unwrap();
|
||||
|
||||
#[cfg(unix)]
|
||||
let re_two_num = Regex::new(r"[xrw-]{9}\.? \d (\d+ ){2}0").unwrap();
|
||||
|
||||
// Regex for one name: author, group or owner
|
||||
let re_one = Regex::new(r"[xrw-]{9}\.? \d [-0-9_a-z]+ 0").unwrap();
|
||||
let re_one = Regex::new(r"[xrw-]{9}\.? \d [-0-9_a-z_A-Z]+ 0").unwrap();
|
||||
|
||||
#[cfg(unix)]
|
||||
let re_one_num = Regex::new(r"[xrw-]{9}\.? \d \d+ 0").unwrap();
|
||||
|
|
|
@ -25,7 +25,6 @@ fn test_shred_remove() {
|
|||
assert!(at.file_exists(file_b));
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "freebsd"))]
|
||||
#[test]
|
||||
fn test_shred_force() {
|
||||
let scene = TestScenario::new(util_name!());
|
||||
|
|
|
@ -10,7 +10,7 @@ fn test_output_is_random_permutation() {
|
|||
let input_seq = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
|
||||
let input = input_seq
|
||||
.iter()
|
||||
.map(|x| x.to_string())
|
||||
.map(ToString::to_string)
|
||||
.collect::<Vec<String>>()
|
||||
.join("\n");
|
||||
|
||||
|
@ -52,7 +52,7 @@ fn test_echo() {
|
|||
.args(
|
||||
&input_seq
|
||||
.iter()
|
||||
.map(|x| x.to_string())
|
||||
.map(ToString::to_string)
|
||||
.collect::<Vec<String>>(),
|
||||
)
|
||||
.succeeds();
|
||||
|
@ -74,7 +74,7 @@ fn test_head_count() {
|
|||
let input_seq = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
|
||||
let input = input_seq
|
||||
.iter()
|
||||
.map(|x| x.to_string())
|
||||
.map(ToString::to_string)
|
||||
.collect::<Vec<String>>()
|
||||
.join("\n");
|
||||
|
||||
|
@ -105,7 +105,7 @@ fn test_repeat() {
|
|||
let input_seq = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
|
||||
let input = input_seq
|
||||
.iter()
|
||||
.map(|x| x.to_string())
|
||||
.map(ToString::to_string)
|
||||
.collect::<Vec<String>>()
|
||||
.join("\n");
|
||||
|
||||
|
|
|
@ -10,11 +10,11 @@ fn test_invalid_time_interval() {
|
|||
new_ucmd!()
|
||||
.arg("xyz")
|
||||
.fails()
|
||||
.usage_error("invalid time interval 'xyz'");
|
||||
.usage_error("invalid time interval 'xyz': Invalid character: 'x' at position 1");
|
||||
new_ucmd!()
|
||||
.args(&["--", "-1"])
|
||||
.fails()
|
||||
.usage_error("invalid time interval '-1'");
|
||||
.usage_error("invalid time interval '-1': Number was negative");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -204,14 +204,16 @@ fn test_sleep_when_input_has_only_whitespace_then_error(#[case] input: &str) {
|
|||
.arg(input)
|
||||
.timeout(Duration::from_secs(10))
|
||||
.fails()
|
||||
.usage_error(format!("invalid time interval '{input}'"));
|
||||
.usage_error(format!(
|
||||
"invalid time interval '{input}': Found only whitespace in input"
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sleep_when_multiple_input_some_with_error_then_shows_all_errors() {
|
||||
let expected = "invalid time interval 'abc'\n\
|
||||
sleep: invalid time interval '1years'\n\
|
||||
sleep: invalid time interval ' '";
|
||||
let expected = "invalid time interval 'abc': Invalid character: 'a' at position 1\n\
|
||||
sleep: invalid time interval '1years': Invalid time unit: 'years' at position 2\n\
|
||||
sleep: invalid time interval ' ': Found only whitespace in input";
|
||||
|
||||
// Even if one of the arguments is valid, but the rest isn't, we should still fail and exit early.
|
||||
// So, the timeout of 10 seconds ensures we haven't executed `thread::sleep` with the only valid
|
||||
|
@ -228,5 +230,5 @@ fn test_negative_interval() {
|
|||
new_ucmd!()
|
||||
.args(&["--", "-1"])
|
||||
.fails()
|
||||
.usage_error("invalid time interval '-1'");
|
||||
.usage_error("invalid time interval '-1': Number was negative");
|
||||
}
|
||||
|
|
|
@ -4459,7 +4459,7 @@ fn test_follow_when_files_are_pointing_to_same_relative_file_and_file_stays_same
|
|||
}
|
||||
|
||||
#[rstest]
|
||||
#[case::exponent_exceed_float_max("1.0e2048")]
|
||||
#[case::exponent_exceed_float_max("1.0e100000")]
|
||||
#[case::underscore_delimiter("1_000")]
|
||||
#[case::only_point(".")]
|
||||
#[case::space_in_primes("' '")]
|
||||
|
|
|
@ -138,3 +138,19 @@ fn test_kill_after_long() {
|
|||
.no_stdout()
|
||||
.no_stderr();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_kill_subprocess() {
|
||||
new_ucmd!()
|
||||
.args(&[
|
||||
// Make sure the CI can spawn the subprocess.
|
||||
"10",
|
||||
"sh",
|
||||
"-c",
|
||||
"sh -c \"trap 'echo xyz' TERM; sleep 30\"",
|
||||
])
|
||||
.fails()
|
||||
.code_is(124)
|
||||
.stdout_contains("xyz")
|
||||
.stderr_contains("Terminated");
|
||||
}
|
||||
|
|
|
@ -2822,7 +2822,7 @@ mod tests {
|
|||
#[should_panic]
|
||||
fn test_cmd_result_stdout_check_when_false_then_panics() {
|
||||
let result = TestScenario::new("echo").ucmd().arg("Hello world").run();
|
||||
result.stdout_check(|s| s.is_empty());
|
||||
result.stdout_check(<[u8]>::is_empty);
|
||||
}
|
||||
|
||||
#[cfg(feature = "echo")]
|
||||
|
@ -3065,9 +3065,10 @@ mod tests {
|
|||
// check `child.is_alive()` and `child.delay()` is working
|
||||
let mut trials = 10;
|
||||
while child.is_alive() {
|
||||
if trials <= 0 {
|
||||
panic!("Assertion failed: child process is still alive.")
|
||||
}
|
||||
assert!(
|
||||
trials > 0,
|
||||
"Assertion failed: child process is still alive."
|
||||
);
|
||||
|
||||
child.delay(500);
|
||||
trials -= 1;
|
||||
|
|
|
@ -18,7 +18,7 @@ for filepath in test_dir.glob("**/*.log"):
|
|||
current[key] = {}
|
||||
current = current[key]
|
||||
try:
|
||||
with open(path) as f:
|
||||
with open(path, errors="ignore") as f:
|
||||
content = f.read()
|
||||
result = re.search(
|
||||
r"(PASS|FAIL|SKIP|ERROR) [^ ]+ \(exit status: \d+\)$", content
|
||||
|
|
Loading…
Reference in a new issue