1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2025-08-06 16:07:47 +00:00

Merge branch 'main' into printf-rewrite

This commit is contained in:
Terts Diepraam 2023-10-28 16:35:58 +02:00
commit 28810906a3
524 changed files with 9820 additions and 4775 deletions

View file

@ -1,2 +1,2 @@
msrv = "1.64.0" msrv = "1.70.0"
cognitive-complexity-threshold = 10 cognitive-complexity-threshold = 10

View file

@ -11,7 +11,7 @@ env:
PROJECT_NAME: coreutils PROJECT_NAME: coreutils
PROJECT_DESC: "Core universal (cross-platform) utilities" PROJECT_DESC: "Core universal (cross-platform) utilities"
PROJECT_AUTH: "uutils" PROJECT_AUTH: "uutils"
RUST_MIN_SRV: "1.64.0" RUST_MIN_SRV: "1.70.0"
# * style job configuration # * style job configuration
STYLE_FAIL_ON_FAULT: true ## (bool) fail the build if a style job contains a fault (error or warning); may be overridden on a per-job basis STYLE_FAIL_ON_FAULT: true ## (bool) fail the build if a style job contains a fault (error or warning); may be overridden on a per-job basis
@ -30,7 +30,7 @@ jobs:
name: Style/cargo-deny name: Style/cargo-deny
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- uses: EmbarkStudios/cargo-deny-action@v1 - uses: EmbarkStudios/cargo-deny-action@v1
style_deps: style_deps:
@ -47,7 +47,7 @@ jobs:
- { os: macos-latest , features: "feat_Tier1,feat_require_unix,feat_require_unix_utmpx" } - { os: macos-latest , features: "feat_Tier1,feat_require_unix,feat_require_unix_utmpx" }
- { os: windows-latest , features: feat_os_windows } - { os: windows-latest , features: feat_os_windows }
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@nightly - uses: dtolnay/rust-toolchain@nightly
## note: requires 'nightly' toolchain b/c `cargo-udeps` uses the `rustc` '-Z save-analysis' option ## note: requires 'nightly' toolchain b/c `cargo-udeps` uses the `rustc` '-Z save-analysis' option
## * ... ref: <https://github.com/est31/cargo-udeps/issues/73> ## * ... ref: <https://github.com/est31/cargo-udeps/issues/73>
@ -82,192 +82,6 @@ jobs:
grep --ignore-case "all deps seem to have been used" udeps.log || { printf "%s\n" "::${fault_type} ::${fault_prefix}: \`cargo udeps\`: style violation (unused dependency found)" ; fault=true ; } grep --ignore-case "all deps seem to have been used" udeps.log || { printf "%s\n" "::${fault_type} ::${fault_prefix}: \`cargo udeps\`: style violation (unused dependency found)" ; fault=true ; }
if [ -n "${{ steps.vars.outputs.FAIL_ON_FAULT }}" ] && [ -n "$fault" ]; then exit 1 ; fi if [ -n "${{ steps.vars.outputs.FAIL_ON_FAULT }}" ] && [ -n "$fault" ]; then exit 1 ; fi
style_format:
name: Style/format
runs-on: ${{ matrix.job.os }}
strategy:
fail-fast: false
matrix:
job:
- { os: ubuntu-latest , features: feat_os_unix }
steps:
- uses: actions/checkout@v3
- uses: dtolnay/rust-toolchain@master
with:
toolchain: stable
components: rustfmt
- uses: Swatinem/rust-cache@v2
- name: Initialize workflow variables
id: vars
shell: bash
run: |
## VARs setup
outputs() { step_id="${{ github.action }}"; for var in "$@" ; do echo steps.${step_id}.outputs.${var}="${!var}"; echo "${var}=${!var}" >> $GITHUB_OUTPUT; done; }
# failure mode
unset FAIL_ON_FAULT ; case '${{ env.STYLE_FAIL_ON_FAULT }}' in
''|0|f|false|n|no|off) FAULT_TYPE=warning ;;
*) FAIL_ON_FAULT=true ; FAULT_TYPE=error ;;
esac;
outputs FAIL_ON_FAULT FAULT_TYPE
# target-specific options
# * CARGO_FEATURES_OPTION
CARGO_FEATURES_OPTION='' ;
if [ -n "${{ matrix.job.features }}" ]; then CARGO_FEATURES_OPTION='--features "${{ matrix.job.features }}"' ; fi
outputs CARGO_FEATURES_OPTION
- name: "`cargo fmt` testing"
shell: bash
run: |
## `cargo fmt` testing
unset fault
fault_type="${{ steps.vars.outputs.FAULT_TYPE }}"
fault_prefix=$(echo "$fault_type" | tr '[:lower:]' '[:upper:]')
# * convert any errors/warnings to GHA UI annotations; ref: <https://help.github.com/en/actions/reference/workflow-commands-for-github-actions#setting-a-warning-message>
S=$(cargo fmt -- --check) && printf "%s\n" "$S" || { printf "%s\n" "$S" ; printf "%s\n" "$S" | sed -E -n -e "s/^Diff[[:space:]]+in[[:space:]]+${PWD//\//\\/}\/(.*)[[:space:]]+at[[:space:]]+[^0-9]+([0-9]+).*$/::${fault_type} file=\1,line=\2::${fault_prefix}: \`cargo fmt\`: style violation (file:'\1', line:\2; use \`cargo fmt -- \"\1\"\`)/p" ; fault=true ; }
if [ -n "${{ steps.vars.outputs.FAIL_ON_FAULT }}" ] && [ -n "$fault" ]; then exit 1 ; fi
fuzz:
name: Run the fuzzers
runs-on: ubuntu-latest
env:
RUN_FOR: 60
steps:
- uses: actions/checkout@v3
- uses: dtolnay/rust-toolchain@nightly
- name: Install `cargo-fuzz`
run: cargo install cargo-fuzz
- uses: Swatinem/rust-cache@v2
- name: Run fuzz_date for XX seconds
shell: bash
run: |
## Run it
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
shell: bash
run: |
## Run it
cd fuzz
cargo +nightly fuzz run fuzz_parse_glob -- -max_total_time=${{ env.RUN_FOR }} -detect_leaks=0
- name: Run fuzz_parse_size for XX seconds
shell: bash
run: |
## Run it
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
shell: bash
run: |
## Run it
cd fuzz
cargo +nightly fuzz run fuzz_parse_time -- -max_total_time=${{ env.RUN_FOR }} -detect_leaks=0
style_lint:
name: Style/lint
runs-on: ${{ matrix.job.os }}
env:
SCCACHE_GHA_ENABLED: "true"
RUSTC_WRAPPER: "sccache"
strategy:
fail-fast: false
matrix:
job:
- { os: ubuntu-latest , features: feat_os_unix }
- { os: macos-latest , features: feat_os_macos }
- { os: windows-latest , features: feat_os_windows }
steps:
- uses: actions/checkout@v3
- uses: dtolnay/rust-toolchain@master
with:
toolchain: stable
components: clippy
- uses: Swatinem/rust-cache@v2
- name: Run sccache-cache
uses: mozilla-actions/sccache-action@v0.0.3
- name: Initialize workflow variables
id: vars
shell: bash
run: |
## VARs setup
outputs() { step_id="${{ github.action }}"; for var in "$@" ; do echo steps.${step_id}.outputs.${var}="${!var}"; echo "${var}=${!var}" >> $GITHUB_OUTPUT; done; }
# failure mode
unset FAIL_ON_FAULT ; case '${{ env.STYLE_FAIL_ON_FAULT }}' in
''|0|f|false|n|no|off) FAULT_TYPE=warning ;;
*) FAIL_ON_FAULT=true ; FAULT_TYPE=error ;;
esac;
outputs FAIL_ON_FAULT FAULT_TYPE
# target-specific options
# * CARGO_FEATURES_OPTION
CARGO_FEATURES_OPTION='--all-features' ;
if [ -n "${{ matrix.job.features }}" ]; then CARGO_FEATURES_OPTION='--features ${{ matrix.job.features }}' ; fi
outputs CARGO_FEATURES_OPTION
# * determine sub-crate utility list
UTILITY_LIST="$(./util/show-utils.sh ${CARGO_FEATURES_OPTION})"
echo UTILITY_LIST=${UTILITY_LIST}
CARGO_UTILITY_LIST_OPTIONS="$(for u in ${UTILITY_LIST}; do echo -n "-puu_${u} "; done;)"
outputs CARGO_UTILITY_LIST_OPTIONS
- name: Install/setup prerequisites
shell: bash
run: |
## Install/setup prerequisites
case '${{ matrix.job.os }}' in
macos-latest) brew install coreutils ;; # needed for show-utils.sh
esac
- name: "`cargo clippy` lint testing"
shell: bash
run: |
## `cargo clippy` lint testing
unset fault
fault_type="${{ steps.vars.outputs.FAULT_TYPE }}"
fault_prefix=$(echo "$fault_type" | tr '[:lower:]' '[:upper:]')
# * convert any warnings to GHA UI annotations; ref: <https://help.github.com/en/actions/reference/workflow-commands-for-github-actions#setting-a-warning-message>
S=$(cargo clippy --all-targets ${{ matrix.job.cargo-options }} ${{ steps.vars.outputs.CARGO_UTILITY_LIST_OPTIONS }} -- -W clippy::default_trait_access -W clippy::manual_string_new -D warnings 2>&1) && printf "%s\n" "$S" || { printf "%s\n" "$S" ; printf "%s" "$S" | sed -E -n -e '/^error:/{' -e "N; s/^error:[[:space:]]+(.*)\\n[[:space:]]+-->[[:space:]]+(.*):([0-9]+):([0-9]+).*$/::${fault_type} file=\2,line=\3,col=\4::${fault_prefix}: \`cargo clippy\`: \1 (file:'\2', line:\3)/p;" -e '}' ; fault=true ; }
if [ -n "${{ steps.vars.outputs.FAIL_ON_FAULT }}" ] && [ -n "$fault" ]; then exit 1 ; fi
style_spellcheck:
name: Style/spelling
runs-on: ${{ matrix.job.os }}
strategy:
matrix:
job:
- { os: ubuntu-latest , features: feat_os_unix }
steps:
- uses: actions/checkout@v3
- name: Initialize workflow variables
id: vars
shell: bash
run: |
## VARs setup
outputs() { step_id="${{ github.action }}"; for var in "$@" ; do echo steps.${step_id}.outputs.${var}="${!var}"; echo "${var}=${!var}" >> $GITHUB_OUTPUT; done; }
# failure mode
unset FAIL_ON_FAULT ; case '${{ env.STYLE_FAIL_ON_FAULT }}' in
''|0|f|false|n|no|off) FAULT_TYPE=warning ;;
*) FAIL_ON_FAULT=true ; FAULT_TYPE=error ;;
esac;
outputs FAIL_ON_FAULT FAULT_TYPE
- name: Install/setup prerequisites
shell: bash
run: |
## Install/setup prerequisites
# * pin installed cspell to v4.2.8 (cspell v5+ is broken for NodeJS < v12)
## maint: [2021-11-10; rivy] `cspell` version may be advanced to v5 when used with NodeJS >= v12
sudo apt-get -y update ; sudo apt-get -y install npm ; sudo npm install cspell@4.2.8 -g ;
- name: Run `cspell`
shell: bash
run: |
## Run `cspell`
unset fault
fault_type="${{ steps.vars.outputs.FAULT_TYPE }}"
fault_prefix=$(echo "$fault_type" | tr '[:lower:]' '[:upper:]')
# * find cspell configuration ; note: avoid quotes around ${cfg_file} b/c `cspell` (v4) doesn't correctly dequote the config argument (or perhaps a subshell expansion issue?)
cfg_files=($(shopt -s nullglob ; echo {.vscode,.}/{,.}c[sS]pell{.json,.config{.js,.cjs,.json,.yaml,.yml},.yaml,.yml} ;))
cfg_file=${cfg_files[0]}
unset CSPELL_CFG_OPTION ; if [ -n "$cfg_file" ]; then CSPELL_CFG_OPTION="--config $cfg_file" ; fi
# * `cspell`
## maint: [2021-11-10; rivy] the `--no-progress` option for `cspell` is a `cspell` v5+ option
# S=$(cspell ${CSPELL_CFG_OPTION} --no-summary --no-progress "**/*") && printf "%s\n" "$S" || { printf "%s\n" "$S" ; printf "%s" "$S" | sed -E -n "s/${PWD//\//\\/}\/(.*):(.*):(.*) - (.*)/::${fault_type} file=\1,line=\2,col=\3::${fault_type^^}: \4 (file:'\1', line:\2)/p" ; fault=true ; true ; }
S=$(cspell ${CSPELL_CFG_OPTION} --no-summary "**/*") && printf "%s\n" "$S" || { printf "%s\n" "$S" ; printf "%s" "$S" | sed -E -n "s/${PWD//\//\\/}\/(.*):(.*):(.*) - (.*)/::${fault_type} file=\1,line=\2,col=\3::${fault_type^^}: \4 (file:'\1', line:\2)/p" ; fault=true ; true ; }
if [ -n "${{ steps.vars.outputs.FAIL_ON_FAULT }}" ] && [ -n "$fault" ]; then exit 1 ; fi
doc_warnings: doc_warnings:
name: Documentation/warnings name: Documentation/warnings
runs-on: ${{ matrix.job.os }} runs-on: ${{ matrix.job.os }}
@ -285,7 +99,7 @@ jobs:
# - { os: macos-latest , features: feat_os_macos } # - { os: macos-latest , features: feat_os_macos }
# - { os: windows-latest , features: feat_os_windows } # - { os: windows-latest , features: feat_os_windows }
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@master - uses: dtolnay/rust-toolchain@master
with: with:
toolchain: stable toolchain: stable
@ -319,9 +133,9 @@ jobs:
shell: bash shell: bash
run: | run: |
RUSTDOCFLAGS="-Dwarnings" cargo doc ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }} --no-deps --workspace --document-private-items RUSTDOCFLAGS="-Dwarnings" cargo doc ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }} --no-deps --workspace --document-private-items
- uses: DavidAnson/markdownlint-cli2-action@v11 - uses: DavidAnson/markdownlint-cli2-action@v13
with: with:
command: fix fix: "true"
globs: | globs: |
*.md *.md
docs/src/*.md docs/src/*.md
@ -338,7 +152,7 @@ jobs:
job: job:
- { os: ubuntu-latest , features: feat_os_unix } - { os: ubuntu-latest , features: feat_os_unix }
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@master - uses: dtolnay/rust-toolchain@master
with: with:
toolchain: ${{ env.RUST_MIN_SRV }} toolchain: ${{ env.RUST_MIN_SRV }}
@ -406,7 +220,7 @@ jobs:
job: job:
- { os: ubuntu-latest , features: feat_os_unix } - { os: ubuntu-latest , features: feat_os_unix }
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable - uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2 - uses: Swatinem/rust-cache@v2
- name: "`cargo update` testing" - name: "`cargo update` testing"
@ -429,7 +243,7 @@ jobs:
job: job:
- { os: ubuntu-latest , features: feat_os_unix } - { os: ubuntu-latest , features: feat_os_unix }
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable - uses: dtolnay/rust-toolchain@stable
- uses: taiki-e/install-action@nextest - uses: taiki-e/install-action@nextest
- uses: Swatinem/rust-cache@v2 - uses: Swatinem/rust-cache@v2
@ -483,7 +297,7 @@ jobs:
- { os: macos-latest , features: feat_os_macos } - { os: macos-latest , features: feat_os_macos }
- { os: windows-latest , features: feat_os_windows } - { os: windows-latest , features: feat_os_windows }
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable - uses: dtolnay/rust-toolchain@stable
- uses: taiki-e/install-action@nextest - uses: taiki-e/install-action@nextest
- uses: Swatinem/rust-cache@v2 - uses: Swatinem/rust-cache@v2
@ -510,7 +324,7 @@ jobs:
- { os: macos-latest , features: feat_os_macos } - { os: macos-latest , features: feat_os_macos }
- { os: windows-latest , features: feat_os_windows } - { os: windows-latest , features: feat_os_windows }
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@nightly - uses: dtolnay/rust-toolchain@nightly
- uses: taiki-e/install-action@nextest - uses: taiki-e/install-action@nextest
- uses: Swatinem/rust-cache@v2 - uses: Swatinem/rust-cache@v2
@ -534,7 +348,7 @@ jobs:
job: job:
- { os: ubuntu-latest , features: feat_os_unix } - { os: ubuntu-latest , features: feat_os_unix }
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable - uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2 - uses: Swatinem/rust-cache@v2
- name: Run sccache-cache - name: Run sccache-cache
@ -596,6 +410,12 @@ jobs:
check() { check() {
# Warn if the size increases by more than 5% # Warn if the size increases by more than 5%
threshold='1.05' threshold='1.05'
if [[ "$2" -eq 0 || "$3" -eq 0 ]]; then
echo "::warning file=$4::Invalid size for $1. Sizes cannot be 0."
return
fi
ratio=$(jq -n "$2 / $3") ratio=$(jq -n "$2 / $3")
echo "$1: size=$2, previous_size=$3, ratio=$ratio, threshold=$threshold" echo "$1: size=$2, previous_size=$3, ratio=$ratio, threshold=$threshold"
if [[ "$(jq -n "$ratio > $threshold")" == 'true' ]]; then if [[ "$(jq -n "$ratio > $threshold")" == 'true' ]]; then
@ -654,7 +474,7 @@ jobs:
- { os: windows-latest , target: x86_64-pc-windows-gnu , features: feat_os_windows } ## note: requires rust >= 1.43.0 to link correctly - { os: windows-latest , target: x86_64-pc-windows-gnu , features: feat_os_windows } ## note: requires rust >= 1.43.0 to link correctly
- { os: windows-latest , target: x86_64-pc-windows-msvc , features: feat_os_windows } - { os: windows-latest , target: x86_64-pc-windows-msvc , features: feat_os_windows }
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@master - uses: dtolnay/rust-toolchain@master
with: with:
toolchain: ${{ env.RUST_MIN_SRV }} toolchain: ${{ env.RUST_MIN_SRV }}
@ -913,7 +733,7 @@ jobs:
run: | run: |
## VARs setup ## VARs setup
echo "TEST_SUMMARY_FILE=busybox-result.json" >> $GITHUB_OUTPUT echo "TEST_SUMMARY_FILE=busybox-result.json" >> $GITHUB_OUTPUT
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- uses: Swatinem/rust-cache@v2 - uses: Swatinem/rust-cache@v2
- name: Run sccache-cache - name: Run sccache-cache
uses: mozilla-actions/sccache-action@v0.0.3 uses: mozilla-actions/sccache-action@v0.0.3
@ -993,7 +813,7 @@ jobs:
outputs() { step_id="${{ github.action }}"; for var in "$@" ; do echo steps.${step_id}.outputs.${var}="${!var}"; echo "${var}=${!var}" >> $GITHUB_OUTPUT; done; } outputs() { step_id="${{ github.action }}"; for var in "$@" ; do echo steps.${step_id}.outputs.${var}="${!var}"; echo "${var}=${!var}" >> $GITHUB_OUTPUT; done; }
TEST_SUMMARY_FILE="toybox-result.json" TEST_SUMMARY_FILE="toybox-result.json"
outputs TEST_SUMMARY_FILE outputs TEST_SUMMARY_FILE
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@master - uses: dtolnay/rust-toolchain@master
with: with:
toolchain: ${{ env.RUST_MIN_SRV }} toolchain: ${{ env.RUST_MIN_SRV }}
@ -1060,15 +880,6 @@ jobs:
name: toybox-result.json name: toybox-result.json
path: ${{ steps.vars.outputs.TEST_SUMMARY_FILE }} path: ${{ steps.vars.outputs.TEST_SUMMARY_FILE }}
toml_format:
runs-on: ubuntu-latest
steps:
- name: Clone repository
uses: actions/checkout@v3
- name: Check
run: npx --yes @taplo/cli fmt --check
coverage: coverage:
name: Code Coverage name: Code Coverage
runs-on: ${{ matrix.job.os }} runs-on: ${{ matrix.job.os }}
@ -1084,7 +895,7 @@ jobs:
- { os: macos-latest , features: macos, toolchain: nightly } - { os: macos-latest , features: macos, toolchain: nightly }
- { os: windows-latest , features: windows, toolchain: nightly-x86_64-pc-windows-gnu } - { os: windows-latest , features: windows, toolchain: nightly-x86_64-pc-windows-gnu }
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@master - uses: dtolnay/rust-toolchain@master
with: with:
toolchain: ${{ matrix.job.toolchain }} toolchain: ${{ matrix.job.toolchain }}
@ -1201,4 +1012,3 @@ jobs:
flags: ${{ steps.vars.outputs.CODECOV_FLAGS }} flags: ${{ steps.vars.outputs.CODECOV_FLAGS }}
name: codecov-umbrella name: codecov-umbrella
fail_ci_if_error: false fail_ci_if_error: false

View file

@ -29,7 +29,7 @@ jobs:
permissions: permissions:
contents: read contents: read
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- name: Run ShellCheck - name: Run ShellCheck
uses: ludeeus/action-shellcheck@master uses: ludeeus/action-shellcheck@master
env: env:
@ -41,34 +41,16 @@ jobs:
shell_fmt: shell_fmt:
name: ShellScript/Format name: ShellScript/Format
# no need to run in pr events
# shfmt will be performed on main branch when the PR is merged
if: github.event_name != 'pull_request'
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: [ shell_check ]
permissions: permissions:
contents: write contents: read
pull-requests: write
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- name: Setup shfmt - name: Setup shfmt
uses: mfinelli/setup-shfmt@v2 uses: mfinelli/setup-shfmt@v3
- name: Run shfmt - name: Run shfmt
shell: bash shell: bash
run: | run: |
# show differs first for every files that need to be formatted
# fmt options: bash syntax, 4 spaces indent, indent for switch-case # fmt options: bash syntax, 4 spaces indent, indent for switch-case
echo "## show the differences between formatted and original scripts..." echo "## show the differences between formatted and original scripts..."
find ${{ env.SCRIPT_DIR }} -name "*.sh" -print0 | xargs -0 shfmt -ln=bash -i 4 -ci -d || true find ${{ env.SCRIPT_DIR }} -name "*.sh" -print0 | xargs -0 shfmt -ln=bash -i 4 -ci -d || true
# perform a shell format
echo "## perform a shell format..."
# ignore the error code because `-d` will always return false when the file has difference
find ${{ env.SCRIPT_DIR }} -name "*.sh" -print0 | xargs -0 shfmt -ln=bash -i 4 -ci -w
- name: Commit any changes
uses: EndBug/add-and-commit@v9
with:
default_author: github_actions
message: "style: auto format by CI (shfmt)"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View file

@ -26,7 +26,7 @@ jobs:
job: job:
- { os: ubuntu-latest , features: feat_os_unix } - { os: ubuntu-latest , features: feat_os_unix }
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- name: Initialize job variables - name: Initialize job variables
id: vars id: vars
shell: bash shell: bash
@ -85,7 +85,7 @@ jobs:
job: job:
- { os: ubuntu-latest , features: feat_os_unix } - { os: ubuntu-latest , features: feat_os_unix }
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- name: Initialize job variables - name: Initialize job variables
id: vars id: vars
shell: bash shell: bash

View file

@ -42,7 +42,7 @@ jobs:
outputs path_GNU path_GNU_tests path_reference path_UUTILS outputs path_GNU path_GNU_tests path_reference path_UUTILS
# #
repo_default_branch="${{ github.event.repository.default_branch }}" repo_default_branch="${{ github.event.repository.default_branch }}"
repo_GNU_ref="v9.3" repo_GNU_ref="v9.4"
repo_reference_branch="${{ github.event.repository.default_branch }}" repo_reference_branch="${{ github.event.repository.default_branch }}"
outputs repo_default_branch repo_GNU_ref repo_reference_branch outputs repo_default_branch repo_GNU_ref repo_reference_branch
# #
@ -55,7 +55,7 @@ jobs:
TEST_FULL_SUMMARY_FILE='gnu-full-result.json' TEST_FULL_SUMMARY_FILE='gnu-full-result.json'
outputs SUITE_LOG_FILE ROOT_SUITE_LOG_FILE TEST_FILESET_PREFIX TEST_FILESET_SUFFIX TEST_LOGS_GLOB TEST_SUMMARY_FILE TEST_FULL_SUMMARY_FILE outputs SUITE_LOG_FILE ROOT_SUITE_LOG_FILE TEST_FILESET_PREFIX TEST_FILESET_SUFFIX TEST_LOGS_GLOB TEST_SUMMARY_FILE TEST_FULL_SUMMARY_FILE
- name: Checkout code (uutil) - name: Checkout code (uutil)
uses: actions/checkout@v3 uses: actions/checkout@v4
with: with:
path: '${{ steps.vars.outputs.path_UUTILS }}' path: '${{ steps.vars.outputs.path_UUTILS }}'
- uses: dtolnay/rust-toolchain@master - uses: dtolnay/rust-toolchain@master
@ -66,7 +66,7 @@ jobs:
with: with:
workspaces: "./${{ steps.vars.outputs.path_UUTILS }} -> target" workspaces: "./${{ steps.vars.outputs.path_UUTILS }} -> target"
- name: Checkout code (GNU coreutils) - name: Checkout code (GNU coreutils)
uses: actions/checkout@v3 uses: actions/checkout@v4
with: with:
repository: 'coreutils/coreutils' repository: 'coreutils/coreutils'
path: '${{ steps.vars.outputs.path_GNU }}' path: '${{ steps.vars.outputs.path_GNU }}'
@ -307,15 +307,15 @@ jobs:
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
steps: steps:
- name: Checkout code uutil - name: Checkout code uutil
uses: actions/checkout@v3 uses: actions/checkout@v4
with: with:
path: 'uutils' path: 'uutils'
- name: Checkout GNU coreutils - name: Checkout GNU coreutils
uses: actions/checkout@v3 uses: actions/checkout@v4
with: with:
repository: 'coreutils/coreutils' repository: 'coreutils/coreutils'
path: 'gnu' path: 'gnu'
ref: 'v9.3' ref: 'v9.4'
submodules: recursive submodules: recursive
- uses: dtolnay/rust-toolchain@master - uses: dtolnay/rust-toolchain@master
with: with:

View file

@ -26,7 +26,7 @@ jobs:
env: env:
TERMUX: v0.118.0 TERMUX: v0.118.0
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- name: Restore AVD cache - name: Restore AVD cache
uses: actions/cache/restore@v3 uses: actions/cache/restore@v3
id: avd-cache id: avd-cache

180
.github/workflows/code-quality.yml vendored Normal file
View file

@ -0,0 +1,180 @@
name: Code Quality
# spell-checker:ignore TERMUX reactivecircus Swatinem noaudio pkill swiftshader dtolnay juliangruber
on: [push, pull_request]
env:
# * style job configuration
STYLE_FAIL_ON_FAULT: true ## (bool) fail the build if a style job contains a fault (error or warning); may be overridden on a per-job basis
permissions:
contents: read # to fetch code (actions/checkout)
# End the current execution if there is a new changeset in the PR.
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
jobs:
style_format:
name: Style/format
runs-on: ${{ matrix.job.os }}
strategy:
fail-fast: false
matrix:
job:
- { os: ubuntu-latest , features: feat_os_unix }
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@master
with:
toolchain: stable
components: rustfmt
- uses: Swatinem/rust-cache@v2
- name: Initialize workflow variables
id: vars
shell: bash
run: |
## VARs setup
outputs() { step_id="${{ github.action }}"; for var in "$@" ; do echo steps.${step_id}.outputs.${var}="${!var}"; echo "${var}=${!var}" >> $GITHUB_OUTPUT; done; }
# failure mode
unset FAIL_ON_FAULT ; case '${{ env.STYLE_FAIL_ON_FAULT }}' in
''|0|f|false|n|no|off) FAULT_TYPE=warning ;;
*) FAIL_ON_FAULT=true ; FAULT_TYPE=error ;;
esac;
outputs FAIL_ON_FAULT FAULT_TYPE
# target-specific options
# * CARGO_FEATURES_OPTION
CARGO_FEATURES_OPTION='' ;
if [ -n "${{ matrix.job.features }}" ]; then CARGO_FEATURES_OPTION='--features "${{ matrix.job.features }}"' ; fi
outputs CARGO_FEATURES_OPTION
- name: "`cargo fmt` testing"
shell: bash
run: |
## `cargo fmt` testing
unset fault
fault_type="${{ steps.vars.outputs.FAULT_TYPE }}"
fault_prefix=$(echo "$fault_type" | tr '[:lower:]' '[:upper:]')
# * convert any errors/warnings to GHA UI annotations; ref: <https://help.github.com/en/actions/reference/workflow-commands-for-github-actions#setting-a-warning-message>
S=$(cargo fmt -- --check) && printf "%s\n" "$S" || { printf "%s\n" "$S" ; printf "%s\n" "$S" | sed -E -n -e "s/^Diff[[:space:]]+in[[:space:]]+${PWD//\//\\/}\/(.*)[[:space:]]+at[[:space:]]+[^0-9]+([0-9]+).*$/::${fault_type} file=\1,line=\2::${fault_prefix}: \`cargo fmt\`: style violation (file:'\1', line:\2; use \`cargo fmt -- \"\1\"\`)/p" ; fault=true ; }
if [ -n "${{ steps.vars.outputs.FAIL_ON_FAULT }}" ] && [ -n "$fault" ]; then exit 1 ; fi
style_lint:
name: Style/lint
runs-on: ${{ matrix.job.os }}
env:
SCCACHE_GHA_ENABLED: "true"
RUSTC_WRAPPER: "sccache"
strategy:
fail-fast: false
matrix:
job:
- { os: ubuntu-latest , features: feat_os_unix }
- { os: macos-latest , features: feat_os_macos }
- { os: windows-latest , features: feat_os_windows }
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@master
with:
toolchain: stable
components: clippy
- uses: Swatinem/rust-cache@v2
- name: Run sccache-cache
uses: mozilla-actions/sccache-action@v0.0.3
- name: Initialize workflow variables
id: vars
shell: bash
run: |
## VARs setup
outputs() { step_id="${{ github.action }}"; for var in "$@" ; do echo steps.${step_id}.outputs.${var}="${!var}"; echo "${var}=${!var}" >> $GITHUB_OUTPUT; done; }
# failure mode
unset FAIL_ON_FAULT ; case '${{ env.STYLE_FAIL_ON_FAULT }}' in
''|0|f|false|n|no|off) FAULT_TYPE=warning ;;
*) FAIL_ON_FAULT=true ; FAULT_TYPE=error ;;
esac;
outputs FAIL_ON_FAULT FAULT_TYPE
# target-specific options
# * CARGO_FEATURES_OPTION
CARGO_FEATURES_OPTION='--all-features' ;
if [ -n "${{ matrix.job.features }}" ]; then CARGO_FEATURES_OPTION='--features ${{ matrix.job.features }}' ; fi
outputs CARGO_FEATURES_OPTION
# * determine sub-crate utility list
UTILITY_LIST="$(./util/show-utils.sh ${CARGO_FEATURES_OPTION})"
echo UTILITY_LIST=${UTILITY_LIST}
CARGO_UTILITY_LIST_OPTIONS="$(for u in ${UTILITY_LIST}; do echo -n "-puu_${u} "; done;)"
outputs CARGO_UTILITY_LIST_OPTIONS
- name: Install/setup prerequisites
shell: bash
run: |
## Install/setup prerequisites
case '${{ matrix.job.os }}' in
macos-latest) brew install coreutils ;; # needed for show-utils.sh
esac
- name: "`cargo clippy` lint testing"
shell: bash
run: |
## `cargo clippy` lint testing
unset fault
CLIPPY_FLAGS="-W clippy::default_trait_access -W clippy::manual_string_new -W clippy::cognitive_complexity"
fault_type="${{ steps.vars.outputs.FAULT_TYPE }}"
fault_prefix=$(echo "$fault_type" | tr '[:lower:]' '[:upper:]')
# * convert any warnings to GHA UI annotations; ref: <https://help.github.com/en/actions/reference/workflow-commands-for-github-actions#setting-a-warning-message>
S=$(cargo clippy --all-targets ${{ matrix.job.cargo-options }} ${{ steps.vars.outputs.CARGO_UTILITY_LIST_OPTIONS }} -- ${CLIPPY_FLAGS} -D warnings 2>&1) && printf "%s\n" "$S" || { printf "%s\n" "$S" ; printf "%s" "$S" | sed -E -n -e '/^error:/{' -e "N; s/^error:[[:space:]]+(.*)\\n[[:space:]]+-->[[:space:]]+(.*):([0-9]+):([0-9]+).*$/::${fault_type} file=\2,line=\3,col=\4::${fault_prefix}: \`cargo clippy\`: \1 (file:'\2', line:\3)/p;" -e '}' ; fault=true ; }
if [ -n "${{ steps.vars.outputs.FAIL_ON_FAULT }}" ] && [ -n "$fault" ]; then exit 1 ; fi
style_spellcheck:
name: Style/spelling
runs-on: ${{ matrix.job.os }}
strategy:
matrix:
job:
- { os: ubuntu-latest , features: feat_os_unix }
steps:
- uses: actions/checkout@v4
- name: Initialize workflow variables
id: vars
shell: bash
run: |
## VARs setup
outputs() { step_id="${{ github.action }}"; for var in "$@" ; do echo steps.${step_id}.outputs.${var}="${!var}"; echo "${var}=${!var}" >> $GITHUB_OUTPUT; done; }
# failure mode
unset FAIL_ON_FAULT ; case '${{ env.STYLE_FAIL_ON_FAULT }}' in
''|0|f|false|n|no|off) FAULT_TYPE=warning ;;
*) FAIL_ON_FAULT=true ; FAULT_TYPE=error ;;
esac;
outputs FAIL_ON_FAULT FAULT_TYPE
- name: Install/setup prerequisites
shell: bash
run: |
## Install/setup prerequisites
# * pin installed cspell to v4.2.8 (cspell v5+ is broken for NodeJS < v12)
## maint: [2021-11-10; rivy] `cspell` version may be advanced to v5 when used with NodeJS >= v12
sudo apt-get -y update ; sudo apt-get -y install npm ; sudo npm install cspell@4.2.8 -g ;
- name: Run `cspell`
shell: bash
run: |
## Run `cspell`
unset fault
fault_type="${{ steps.vars.outputs.FAULT_TYPE }}"
fault_prefix=$(echo "$fault_type" | tr '[:lower:]' '[:upper:]')
# * find cspell configuration ; note: avoid quotes around ${cfg_file} b/c `cspell` (v4) doesn't correctly dequote the config argument (or perhaps a subshell expansion issue?)
cfg_files=($(shopt -s nullglob ; echo {.vscode,.}/{,.}c[sS]pell{.json,.config{.js,.cjs,.json,.yaml,.yml},.yaml,.yml} ;))
cfg_file=${cfg_files[0]}
unset CSPELL_CFG_OPTION ; if [ -n "$cfg_file" ]; then CSPELL_CFG_OPTION="--config $cfg_file" ; fi
# * `cspell`
## maint: [2021-11-10; rivy] the `--no-progress` option for `cspell` is a `cspell` v5+ option
# S=$(cspell ${CSPELL_CFG_OPTION} --no-summary --no-progress "**/*") && printf "%s\n" "$S" || { printf "%s\n" "$S" ; printf "%s" "$S" | sed -E -n "s/${PWD//\//\\/}\/(.*):(.*):(.*) - (.*)/::${fault_type} file=\1,line=\2,col=\3::${fault_type^^}: \4 (file:'\1', line:\2)/p" ; fault=true ; true ; }
S=$(cspell ${CSPELL_CFG_OPTION} --no-summary "**/*") && printf "%s\n" "$S" || { printf "%s\n" "$S" ; printf "%s" "$S" | sed -E -n "s/${PWD//\//\\/}\/(.*):(.*):(.*) - (.*)/::${fault_type} file=\1,line=\2,col=\3::${fault_type^^}: \4 (file:'\1', line:\2)/p" ; fault=true ; true ; }
if [ -n "${{ steps.vars.outputs.FAIL_ON_FAULT }}" ] && [ -n "$fault" ]; then exit 1 ; fi
toml_format:
name: Style/toml
runs-on: ubuntu-latest
steps:
- name: Clone repository
uses: actions/checkout@v4
- name: Check
run: npx --yes @taplo/cli fmt --check

View file

@ -30,7 +30,7 @@ jobs:
SCCACHE_GHA_ENABLED: "true" SCCACHE_GHA_ENABLED: "true"
RUSTC_WRAPPER: "sccache" RUSTC_WRAPPER: "sccache"
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- uses: Swatinem/rust-cache@v2 - uses: Swatinem/rust-cache@v2
- name: Run sccache-cache - name: Run sccache-cache
uses: mozilla-actions/sccache-action@v0.0.3 uses: mozilla-actions/sccache-action@v0.0.3
@ -120,7 +120,7 @@ jobs:
SCCACHE_GHA_ENABLED: "true" SCCACHE_GHA_ENABLED: "true"
RUSTC_WRAPPER: "sccache" RUSTC_WRAPPER: "sccache"
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- uses: Swatinem/rust-cache@v2 - uses: Swatinem/rust-cache@v2
- name: Run sccache-cache - name: Run sccache-cache
uses: mozilla-actions/sccache-action@v0.0.3 uses: mozilla-actions/sccache-action@v0.0.3

64
.github/workflows/fuzzing.yml vendored Normal file
View file

@ -0,0 +1,64 @@
name: Fuzzing
# spell-checker:ignore fuzzer
on: [push, pull_request]
permissions:
contents: read # to fetch code (actions/checkout)
# End the current execution if there is a new changeset in the PR.
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
jobs:
fuzz:
name: Run the fuzzers
runs-on: ubuntu-latest
env:
RUN_FOR: 60
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@nightly
- name: Install `cargo-fuzz`
run: cargo install cargo-fuzz
- uses: Swatinem/rust-cache@v2
- name: Restore Cached Corpus
uses: actions/cache/restore@v3
with:
key: corpus-cache
path: |
fuzz/corpus
- name: Run fuzz_date for XX seconds
continue-on-error: true
shell: bash
run: |
cargo +nightly fuzz run fuzz_date -- -max_total_time=${{ env.RUN_FOR }} -detect_leaks=0
- name: Run fuzz_test for XX seconds
shell: bash
run: |
cargo +nightly fuzz run fuzz_test -- -max_total_time=${{ env.RUN_FOR }} -detect_leaks=0
- name: Run fuzz_expr for XX seconds
continue-on-error: true
shell: bash
run: |
cargo +nightly fuzz run fuzz_expr -- -max_total_time=${{ env.RUN_FOR }} -detect_leaks=0
- name: Run fuzz_parse_glob for XX seconds
shell: bash
run: |
cargo +nightly fuzz run fuzz_parse_glob -- -max_total_time=${{ env.RUN_FOR }} -detect_leaks=0
- name: Run fuzz_parse_size for XX seconds
shell: bash
run: |
cargo +nightly fuzz run fuzz_parse_size -- -max_total_time=${{ env.RUN_FOR }} -detect_leaks=0
- name: Run fuzz_parse_time for XX seconds
shell: bash
run: |
cargo +nightly fuzz run fuzz_parse_time -- -max_total_time=${{ env.RUN_FOR }} -detect_leaks=0
- name: Save Corpus Cache
uses: actions/cache/save@v3
with:
key: corpus-cache
path: |
fuzz/corpus

View file

@ -58,6 +58,7 @@ MinGW
Minix Minix
NetBSD NetBSD
Novell Novell
Nushell
OpenBSD OpenBSD
POSIX POSIX
PowerPC PowerPC

View file

@ -83,6 +83,8 @@ codespell
commitlint commitlint
dprint dprint
dtrace dtrace
flamegraph
flamegraphs
gcov gcov
gmake gmake
grcov grcov
@ -90,6 +92,7 @@ grep
markdownlint markdownlint
rerast rerast
rollup rollup
samply
sed sed
selinuxenabled selinuxenabled
sestatus sestatus

View file

@ -38,199 +38,15 @@ CI. However, you can use `#[cfg(...)]` attributes to create platform dependent f
VirtualBox and Parallels) for development: VirtualBox and Parallels) for development:
<https://developer.microsoft.com/windows/downloads/virtual-machines/> <https://developer.microsoft.com/windows/downloads/virtual-machines/>
## Tools ## Setting up your development environment
We have an extensive CI that will check your code before it can be merged. This To setup your local development environment for this project please follow [DEVELOPMENT.md guide](DEVELOPMENT.md)
section explains how to run those checks locally to avoid waiting for the CI.
### pre-commit hooks It covers [installation of necessary tools and prerequisites](DEVELOPMENT.md#tools) as well as using those tools to [test your code changes locally](DEVELOPMENT.md#testing)
A configuration for `pre-commit` is provided in the repository. It allows ## Improving the GNU compatibility
automatically checking every git commit you make to ensure it compiles, and
passes `clippy` and `rustfmt` without warnings.
To use the provided hook: Please make sure you have installed [GNU utils and prerequisites](DEVELOPMENT.md#gnu-utils-and-prerequisites) and can execute commands described in [Comparing with GNU](DEVELOPMENT.md#comparing-with-gnu) section of [DEVELOPMENT.md](DEVELOPMENT.md)
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
```
Running the complete test suite might take a while. We use [nextest](https://nexte.st/index.html) in
the CI and you might want to try it out locally. It can speed up the execution time of the whole
test run significantly if the cpu has multiple cores.
```shell
cargo nextest run --features unix --no-fail-fast
```
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
```
To run tests with `nextest` just use the nextest target. Note you'll need to
[install](https://nexte.st/book/installation.html) `nextest` first. The `nextest` target accepts the
same arguments like the default `test` target, so it's possible to pass arguments to `nextest run`
via `CARGOFLAGS`:
```shell
make CARGOFLAGS='--no-fail-fast' UTILS='UTILITY_1 UTILITY_2' nextest
```
### 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
# Build uutils without release optimizations
UU_MAKE_PROFILE=debug 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 The Python script `./util/remaining-gnu-error.py` shows the list of failing
tests in the CI. tests in the CI.
@ -320,30 +136,7 @@ gitignore: add temporary files
## Code coverage ## Code coverage
<!-- spell-checker:ignore (flags) Ccodegen Coverflow Cpanic Zinstrument Zpanic --> To generate code coverage report locally please follow [Code coverage report](DEVELOPMENT.md#code-coverage-report) section of [DEVELOPMENT.md](DEVELOPMENT.md)
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.
## Other implementations ## Other implementations

746
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -5,7 +5,7 @@
[package] [package]
name = "coreutils" name = "coreutils"
version = "0.0.20" version = "0.0.22"
authors = ["uutils developers"] authors = ["uutils developers"]
license = "MIT" license = "MIT"
description = "coreutils ~ GNU coreutils (updated); implemented as universal (cross-platform) utils, written in Rust" description = "coreutils ~ GNU coreutils (updated); implemented as universal (cross-platform) utils, written in Rust"
@ -16,7 +16,7 @@ repository = "https://github.com/uutils/coreutils"
readme = "README.md" readme = "README.md"
keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"] keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"]
categories = ["command-line-utilities"] categories = ["command-line-utilities"]
rust-version = "1.64.0" rust-version = "1.70.0"
edition = "2021" edition = "2021"
build = "build.rs" build = "build.rs"
@ -100,7 +100,6 @@ feat_common_core = [
"pwd", "pwd",
"readlink", "readlink",
"realpath", "realpath",
"relpath",
"rm", "rm",
"rmdir", "rmdir",
"seq", "seq",
@ -152,6 +151,7 @@ feat_os_unix = [
"feat_require_crate_cpp", "feat_require_crate_cpp",
"feat_require_unix", "feat_require_unix",
"feat_require_unix_utmpx", "feat_require_unix_utmpx",
"feat_require_unix_hostid",
] ]
# "feat_os_windows" == set of utilities which can be built/run on modern/usual windows platforms # "feat_os_windows" == set of utilities which can be built/run on modern/usual windows platforms
feat_os_windows = [ feat_os_windows = [
@ -259,87 +259,86 @@ test = ["uu_test"]
[workspace.dependencies] [workspace.dependencies]
bigdecimal = "0.4" bigdecimal = "0.4"
binary-heap-plus = "0.5.0" binary-heap-plus = "0.5.0"
bstr = "1.6" bstr = "1.7"
bytecount = "0.6.3" bytecount = "0.6.7"
byteorder = "1.4.3" byteorder = "1.5.0"
chrono = { version = "^0.4.26", default-features = false, features = [ chrono = { version = "^0.4.31", default-features = false, features = [
"std", "std",
"alloc", "alloc",
"clock", "clock",
] } ] }
clap = { version = "4.3", features = ["wrap_help", "cargo"] } clap = { version = "4.4", features = ["wrap_help", "cargo"] }
clap_complete = "4.3" clap_complete = "4.4"
clap_mangen = "0.2" clap_mangen = "0.2"
compare = "0.1.0" compare = "0.1.0"
coz = { version = "0.1.3" } coz = { version = "0.1.3" }
crossterm = ">=0.26.1" crossterm = ">=0.27.0"
ctrlc = { version = "3.4", features = ["termination"] } ctrlc = { version = "3.4", features = ["termination"] }
exacl = "0.10.0" exacl = "0.11.0"
file_diff = "1.0.0" file_diff = "1.0.0"
filetime = "0.2" filetime = "0.2"
fnv = "1.0.7" fnv = "1.0.7"
fs_extra = "1.3.0" fs_extra = "1.3.0"
fts-sys = "0.2" fts-sys = "0.2"
fundu = "1.2.0" fundu = "2.0.0"
gcd = "2.3" gcd = "2.3"
glob = "0.3.1" glob = "0.3.1"
half = "2.2" half = "2.3"
indicatif = "0.17" indicatif = "0.17"
is-terminal = "0.4.7"
itertools = "0.11.0" itertools = "0.11.0"
libc = "0.2.147" libc = "0.2.149"
lscolors = { version = "0.15.0", default-features = false, features = [ lscolors = { version = "0.15.0", default-features = false, features = [
"nu-ansi-term", "nu-ansi-term",
] } ] }
memchr = "2" memchr = "2"
memmap2 = "0.7" memmap2 = "0.9"
nix = { version = "0.26", default-features = false } nix = { version = "0.27", default-features = false }
nom = "7.1.3" nom = "7.1.3"
notify = { version = "=6.0.1", features = ["macos_kqueue"] } notify = { version = "=6.0.1", features = ["macos_kqueue"] }
num-bigint = "0.4.3" num-bigint = "0.4.4"
num-traits = "0.2.16" num-traits = "0.2.17"
number_prefix = "0.4" number_prefix = "0.4"
once_cell = "1.18.0" once_cell = "1.18.0"
onig = { version = "~6.4", default-features = false } onig = { version = "~6.4", default-features = false }
parse_datetime = "0.4.0" parse_datetime = "0.5.0"
phf = "0.11.2" phf = "0.11.2"
phf_codegen = "0.11.2" phf_codegen = "0.11.2"
platform-info = "2.0.2" platform-info = "2.0.2"
quick-error = "2.0.1" quick-error = "2.0.1"
rand = { version = "0.8", features = ["small_rng"] } rand = { version = "0.8", features = ["small_rng"] }
rand_core = "0.6" rand_core = "0.6"
rayon = "1.7" rayon = "1.8"
redox_syscall = "0.3" redox_syscall = "0.4"
regex = "1.9.1" regex = "1.10.2"
rstest = "0.18.1" rstest = "0.18.2"
rust-ini = "0.19.0" rust-ini = "0.19.0"
same-file = "1.0.6" same-file = "1.0.6"
self_cell = "1.0.1" self_cell = "1.0.1"
selinux = "0.4" selinux = "0.4"
signal-hook = "0.3.17" signal-hook = "0.3.17"
smallvec = { version = "1.11", features = ["union"] } smallvec = { version = "1.11", features = ["union"] }
tempfile = "3.6.0" tempfile = "3.8.1"
term_grid = "0.1.5" uutils_term_grid = "0.3"
terminal_size = "0.2.6" terminal_size = "0.3.0"
textwrap = { version = "0.16.0", features = ["terminal_size"] } textwrap = { version = "0.16.0", features = ["terminal_size"] }
thiserror = "1.0" thiserror = "1.0"
time = { version = "0.3" } time = { version = "0.3" }
unicode-segmentation = "1.10.1" unicode-segmentation = "1.10.1"
unicode-width = "0.1.10" unicode-width = "0.1.11"
utf-8 = "0.7.6" utf-8 = "0.7.6"
walkdir = "2.3" walkdir = "2.4"
winapi-util = "0.1.5" winapi-util = "0.1.6"
windows-sys = { version = "0.48.0", default-features = false } windows-sys = { version = "0.48.0", default-features = false }
xattr = "1.0.1" xattr = "1.0.1"
zip = { version = "0.6.6", default_features = false, features = ["deflate"] } zip = { version = "0.6.6", default_features = false, features = ["deflate"] }
hex = "0.4.3" hex = "0.4.3"
md-5 = "0.10.5" md-5 = "0.10.6"
sha1 = "0.10.5" sha1 = "0.10.6"
sha2 = "0.10.7" sha2 = "0.10.8"
sha3 = "0.10.8" sha3 = "0.10.8"
blake2b_simd = "1.0.1" blake2b_simd = "1.0.2"
blake3 = "1.4.0" blake3 = "1.5.0"
sm3 = "0.4.2" sm3 = "0.4.2"
digest = "0.10.7" digest = "0.10.7"
@ -362,110 +361,109 @@ zip = { workspace = true, optional = true }
uuhelp_parser = { optional = true, version = ">=0.0.19", path = "src/uuhelp_parser" } uuhelp_parser = { optional = true, version = ">=0.0.19", path = "src/uuhelp_parser" }
# * uutils # * uutils
uu_test = { optional = true, version = "0.0.20", package = "uu_test", path = "src/uu/test" } uu_test = { optional = true, version = "0.0.22", package = "uu_test", path = "src/uu/test" }
# #
arch = { optional = true, version = "0.0.20", package = "uu_arch", path = "src/uu/arch" } arch = { optional = true, version = "0.0.22", package = "uu_arch", path = "src/uu/arch" }
base32 = { optional = true, version = "0.0.20", package = "uu_base32", path = "src/uu/base32" } base32 = { optional = true, version = "0.0.22", package = "uu_base32", path = "src/uu/base32" }
base64 = { optional = true, version = "0.0.20", package = "uu_base64", path = "src/uu/base64" } base64 = { optional = true, version = "0.0.22", package = "uu_base64", path = "src/uu/base64" }
basename = { optional = true, version = "0.0.20", package = "uu_basename", path = "src/uu/basename" } basename = { optional = true, version = "0.0.22", package = "uu_basename", path = "src/uu/basename" }
basenc = { optional = true, version = "0.0.20", package = "uu_basenc", path = "src/uu/basenc" } basenc = { optional = true, version = "0.0.22", package = "uu_basenc", path = "src/uu/basenc" }
cat = { optional = true, version = "0.0.20", package = "uu_cat", path = "src/uu/cat" } cat = { optional = true, version = "0.0.22", package = "uu_cat", path = "src/uu/cat" }
chcon = { optional = true, version = "0.0.20", package = "uu_chcon", path = "src/uu/chcon" } chcon = { optional = true, version = "0.0.22", package = "uu_chcon", path = "src/uu/chcon" }
chgrp = { optional = true, version = "0.0.20", package = "uu_chgrp", path = "src/uu/chgrp" } chgrp = { optional = true, version = "0.0.22", package = "uu_chgrp", path = "src/uu/chgrp" }
chmod = { optional = true, version = "0.0.20", package = "uu_chmod", path = "src/uu/chmod" } chmod = { optional = true, version = "0.0.22", package = "uu_chmod", path = "src/uu/chmod" }
chown = { optional = true, version = "0.0.20", package = "uu_chown", path = "src/uu/chown" } chown = { optional = true, version = "0.0.22", package = "uu_chown", path = "src/uu/chown" }
chroot = { optional = true, version = "0.0.20", package = "uu_chroot", path = "src/uu/chroot" } chroot = { optional = true, version = "0.0.22", package = "uu_chroot", path = "src/uu/chroot" }
cksum = { optional = true, version = "0.0.20", package = "uu_cksum", path = "src/uu/cksum" } cksum = { optional = true, version = "0.0.22", package = "uu_cksum", path = "src/uu/cksum" }
comm = { optional = true, version = "0.0.20", package = "uu_comm", path = "src/uu/comm" } comm = { optional = true, version = "0.0.22", package = "uu_comm", path = "src/uu/comm" }
cp = { optional = true, version = "0.0.20", package = "uu_cp", path = "src/uu/cp" } cp = { optional = true, version = "0.0.22", package = "uu_cp", path = "src/uu/cp" }
csplit = { optional = true, version = "0.0.20", package = "uu_csplit", path = "src/uu/csplit" } csplit = { optional = true, version = "0.0.22", package = "uu_csplit", path = "src/uu/csplit" }
cut = { optional = true, version = "0.0.20", package = "uu_cut", path = "src/uu/cut" } cut = { optional = true, version = "0.0.22", package = "uu_cut", path = "src/uu/cut" }
date = { optional = true, version = "0.0.20", package = "uu_date", path = "src/uu/date" } date = { optional = true, version = "0.0.22", package = "uu_date", path = "src/uu/date" }
dd = { optional = true, version = "0.0.20", package = "uu_dd", path = "src/uu/dd" } dd = { optional = true, version = "0.0.22", package = "uu_dd", path = "src/uu/dd" }
df = { optional = true, version = "0.0.20", package = "uu_df", path = "src/uu/df" } df = { optional = true, version = "0.0.22", package = "uu_df", path = "src/uu/df" }
dir = { optional = true, version = "0.0.20", package = "uu_dir", path = "src/uu/dir" } dir = { optional = true, version = "0.0.22", package = "uu_dir", path = "src/uu/dir" }
dircolors = { optional = true, version = "0.0.20", package = "uu_dircolors", path = "src/uu/dircolors" } dircolors = { optional = true, version = "0.0.22", package = "uu_dircolors", path = "src/uu/dircolors" }
dirname = { optional = true, version = "0.0.20", package = "uu_dirname", path = "src/uu/dirname" } dirname = { optional = true, version = "0.0.22", package = "uu_dirname", path = "src/uu/dirname" }
du = { optional = true, version = "0.0.20", package = "uu_du", path = "src/uu/du" } du = { optional = true, version = "0.0.22", package = "uu_du", path = "src/uu/du" }
echo = { optional = true, version = "0.0.20", package = "uu_echo", path = "src/uu/echo" } echo = { optional = true, version = "0.0.22", package = "uu_echo", path = "src/uu/echo" }
env = { optional = true, version = "0.0.20", package = "uu_env", path = "src/uu/env" } env = { optional = true, version = "0.0.22", package = "uu_env", path = "src/uu/env" }
expand = { optional = true, version = "0.0.20", package = "uu_expand", path = "src/uu/expand" } expand = { optional = true, version = "0.0.22", package = "uu_expand", path = "src/uu/expand" }
expr = { optional = true, version = "0.0.20", package = "uu_expr", path = "src/uu/expr" } expr = { optional = true, version = "0.0.22", package = "uu_expr", path = "src/uu/expr" }
factor = { optional = true, version = "0.0.20", package = "uu_factor", path = "src/uu/factor" } factor = { optional = true, version = "0.0.22", package = "uu_factor", path = "src/uu/factor" }
false = { optional = true, version = "0.0.20", package = "uu_false", path = "src/uu/false" } false = { optional = true, version = "0.0.22", package = "uu_false", path = "src/uu/false" }
fmt = { optional = true, version = "0.0.20", package = "uu_fmt", path = "src/uu/fmt" } fmt = { optional = true, version = "0.0.22", package = "uu_fmt", path = "src/uu/fmt" }
fold = { optional = true, version = "0.0.20", package = "uu_fold", path = "src/uu/fold" } fold = { optional = true, version = "0.0.22", package = "uu_fold", path = "src/uu/fold" }
groups = { optional = true, version = "0.0.20", package = "uu_groups", path = "src/uu/groups" } groups = { optional = true, version = "0.0.22", package = "uu_groups", path = "src/uu/groups" }
hashsum = { optional = true, version = "0.0.20", package = "uu_hashsum", path = "src/uu/hashsum" } hashsum = { optional = true, version = "0.0.22", package = "uu_hashsum", path = "src/uu/hashsum" }
head = { optional = true, version = "0.0.20", package = "uu_head", path = "src/uu/head" } head = { optional = true, version = "0.0.22", package = "uu_head", path = "src/uu/head" }
hostid = { optional = true, version = "0.0.20", package = "uu_hostid", path = "src/uu/hostid" } hostid = { optional = true, version = "0.0.22", package = "uu_hostid", path = "src/uu/hostid" }
hostname = { optional = true, version = "0.0.20", package = "uu_hostname", path = "src/uu/hostname" } hostname = { optional = true, version = "0.0.22", package = "uu_hostname", path = "src/uu/hostname" }
id = { optional = true, version = "0.0.20", package = "uu_id", path = "src/uu/id" } id = { optional = true, version = "0.0.22", package = "uu_id", path = "src/uu/id" }
install = { optional = true, version = "0.0.20", package = "uu_install", path = "src/uu/install" } install = { optional = true, version = "0.0.22", package = "uu_install", path = "src/uu/install" }
join = { optional = true, version = "0.0.20", package = "uu_join", path = "src/uu/join" } join = { optional = true, version = "0.0.22", package = "uu_join", path = "src/uu/join" }
kill = { optional = true, version = "0.0.20", package = "uu_kill", path = "src/uu/kill" } kill = { optional = true, version = "0.0.22", package = "uu_kill", path = "src/uu/kill" }
link = { optional = true, version = "0.0.20", package = "uu_link", path = "src/uu/link" } link = { optional = true, version = "0.0.22", package = "uu_link", path = "src/uu/link" }
ln = { optional = true, version = "0.0.20", package = "uu_ln", path = "src/uu/ln" } ln = { optional = true, version = "0.0.22", package = "uu_ln", path = "src/uu/ln" }
ls = { optional = true, version = "0.0.20", package = "uu_ls", path = "src/uu/ls" } ls = { optional = true, version = "0.0.22", package = "uu_ls", path = "src/uu/ls" }
logname = { optional = true, version = "0.0.20", package = "uu_logname", path = "src/uu/logname" } logname = { optional = true, version = "0.0.22", package = "uu_logname", path = "src/uu/logname" }
mkdir = { optional = true, version = "0.0.20", package = "uu_mkdir", path = "src/uu/mkdir" } mkdir = { optional = true, version = "0.0.22", package = "uu_mkdir", path = "src/uu/mkdir" }
mkfifo = { optional = true, version = "0.0.20", package = "uu_mkfifo", path = "src/uu/mkfifo" } mkfifo = { optional = true, version = "0.0.22", package = "uu_mkfifo", path = "src/uu/mkfifo" }
mknod = { optional = true, version = "0.0.20", package = "uu_mknod", path = "src/uu/mknod" } mknod = { optional = true, version = "0.0.22", package = "uu_mknod", path = "src/uu/mknod" }
mktemp = { optional = true, version = "0.0.20", package = "uu_mktemp", path = "src/uu/mktemp" } mktemp = { optional = true, version = "0.0.22", package = "uu_mktemp", path = "src/uu/mktemp" }
more = { optional = true, version = "0.0.20", package = "uu_more", path = "src/uu/more" } more = { optional = true, version = "0.0.22", package = "uu_more", path = "src/uu/more" }
mv = { optional = true, version = "0.0.20", package = "uu_mv", path = "src/uu/mv" } mv = { optional = true, version = "0.0.22", package = "uu_mv", path = "src/uu/mv" }
nice = { optional = true, version = "0.0.20", package = "uu_nice", path = "src/uu/nice" } nice = { optional = true, version = "0.0.22", package = "uu_nice", path = "src/uu/nice" }
nl = { optional = true, version = "0.0.20", package = "uu_nl", path = "src/uu/nl" } nl = { optional = true, version = "0.0.22", package = "uu_nl", path = "src/uu/nl" }
nohup = { optional = true, version = "0.0.20", package = "uu_nohup", path = "src/uu/nohup" } nohup = { optional = true, version = "0.0.22", package = "uu_nohup", path = "src/uu/nohup" }
nproc = { optional = true, version = "0.0.20", package = "uu_nproc", path = "src/uu/nproc" } nproc = { optional = true, version = "0.0.22", package = "uu_nproc", path = "src/uu/nproc" }
numfmt = { optional = true, version = "0.0.20", package = "uu_numfmt", path = "src/uu/numfmt" } numfmt = { optional = true, version = "0.0.22", package = "uu_numfmt", path = "src/uu/numfmt" }
od = { optional = true, version = "0.0.20", package = "uu_od", path = "src/uu/od" } od = { optional = true, version = "0.0.22", package = "uu_od", path = "src/uu/od" }
paste = { optional = true, version = "0.0.20", package = "uu_paste", path = "src/uu/paste" } paste = { optional = true, version = "0.0.22", package = "uu_paste", path = "src/uu/paste" }
pathchk = { optional = true, version = "0.0.20", package = "uu_pathchk", path = "src/uu/pathchk" } pathchk = { optional = true, version = "0.0.22", package = "uu_pathchk", path = "src/uu/pathchk" }
pinky = { optional = true, version = "0.0.20", package = "uu_pinky", path = "src/uu/pinky" } pinky = { optional = true, version = "0.0.22", package = "uu_pinky", path = "src/uu/pinky" }
pr = { optional = true, version = "0.0.20", package = "uu_pr", path = "src/uu/pr" } pr = { optional = true, version = "0.0.22", package = "uu_pr", path = "src/uu/pr" }
printenv = { optional = true, version = "0.0.20", package = "uu_printenv", path = "src/uu/printenv" } printenv = { optional = true, version = "0.0.22", package = "uu_printenv", path = "src/uu/printenv" }
printf = { optional = true, version = "0.0.20", package = "uu_printf", path = "src/uu/printf" } printf = { optional = true, version = "0.0.22", package = "uu_printf", path = "src/uu/printf" }
ptx = { optional = true, version = "0.0.20", package = "uu_ptx", path = "src/uu/ptx" } ptx = { optional = true, version = "0.0.22", package = "uu_ptx", path = "src/uu/ptx" }
pwd = { optional = true, version = "0.0.20", package = "uu_pwd", path = "src/uu/pwd" } pwd = { optional = true, version = "0.0.22", package = "uu_pwd", path = "src/uu/pwd" }
readlink = { optional = true, version = "0.0.20", package = "uu_readlink", path = "src/uu/readlink" } readlink = { optional = true, version = "0.0.22", package = "uu_readlink", path = "src/uu/readlink" }
realpath = { optional = true, version = "0.0.20", package = "uu_realpath", path = "src/uu/realpath" } realpath = { optional = true, version = "0.0.22", package = "uu_realpath", path = "src/uu/realpath" }
relpath = { optional = true, version = "0.0.20", package = "uu_relpath", path = "src/uu/relpath" } rm = { optional = true, version = "0.0.22", package = "uu_rm", path = "src/uu/rm" }
rm = { optional = true, version = "0.0.20", package = "uu_rm", path = "src/uu/rm" } rmdir = { optional = true, version = "0.0.22", package = "uu_rmdir", path = "src/uu/rmdir" }
rmdir = { optional = true, version = "0.0.20", package = "uu_rmdir", path = "src/uu/rmdir" } runcon = { optional = true, version = "0.0.22", package = "uu_runcon", path = "src/uu/runcon" }
runcon = { optional = true, version = "0.0.20", package = "uu_runcon", path = "src/uu/runcon" } seq = { optional = true, version = "0.0.22", package = "uu_seq", path = "src/uu/seq" }
seq = { optional = true, version = "0.0.20", package = "uu_seq", path = "src/uu/seq" } shred = { optional = true, version = "0.0.22", package = "uu_shred", path = "src/uu/shred" }
shred = { optional = true, version = "0.0.20", package = "uu_shred", path = "src/uu/shred" } shuf = { optional = true, version = "0.0.22", package = "uu_shuf", path = "src/uu/shuf" }
shuf = { optional = true, version = "0.0.20", package = "uu_shuf", path = "src/uu/shuf" } sleep = { optional = true, version = "0.0.22", package = "uu_sleep", path = "src/uu/sleep" }
sleep = { optional = true, version = "0.0.20", package = "uu_sleep", path = "src/uu/sleep" } sort = { optional = true, version = "0.0.22", package = "uu_sort", path = "src/uu/sort" }
sort = { optional = true, version = "0.0.20", package = "uu_sort", path = "src/uu/sort" } split = { optional = true, version = "0.0.22", package = "uu_split", path = "src/uu/split" }
split = { optional = true, version = "0.0.20", package = "uu_split", path = "src/uu/split" } stat = { optional = true, version = "0.0.22", package = "uu_stat", path = "src/uu/stat" }
stat = { optional = true, version = "0.0.20", package = "uu_stat", path = "src/uu/stat" } stdbuf = { optional = true, version = "0.0.22", package = "uu_stdbuf", path = "src/uu/stdbuf" }
stdbuf = { optional = true, version = "0.0.20", package = "uu_stdbuf", path = "src/uu/stdbuf" } stty = { optional = true, version = "0.0.22", package = "uu_stty", path = "src/uu/stty" }
stty = { optional = true, version = "0.0.20", package = "uu_stty", path = "src/uu/stty" } sum = { optional = true, version = "0.0.22", package = "uu_sum", path = "src/uu/sum" }
sum = { optional = true, version = "0.0.20", package = "uu_sum", path = "src/uu/sum" } sync = { optional = true, version = "0.0.22", package = "uu_sync", path = "src/uu/sync" }
sync = { optional = true, version = "0.0.20", package = "uu_sync", path = "src/uu/sync" } tac = { optional = true, version = "0.0.22", package = "uu_tac", path = "src/uu/tac" }
tac = { optional = true, version = "0.0.20", package = "uu_tac", path = "src/uu/tac" } tail = { optional = true, version = "0.0.22", package = "uu_tail", path = "src/uu/tail" }
tail = { optional = true, version = "0.0.20", package = "uu_tail", path = "src/uu/tail" } tee = { optional = true, version = "0.0.22", package = "uu_tee", path = "src/uu/tee" }
tee = { optional = true, version = "0.0.20", package = "uu_tee", path = "src/uu/tee" } timeout = { optional = true, version = "0.0.22", package = "uu_timeout", path = "src/uu/timeout" }
timeout = { optional = true, version = "0.0.20", package = "uu_timeout", path = "src/uu/timeout" } touch = { optional = true, version = "0.0.22", package = "uu_touch", path = "src/uu/touch" }
touch = { optional = true, version = "0.0.20", package = "uu_touch", path = "src/uu/touch" } tr = { optional = true, version = "0.0.22", package = "uu_tr", path = "src/uu/tr" }
tr = { optional = true, version = "0.0.20", package = "uu_tr", path = "src/uu/tr" } true = { optional = true, version = "0.0.22", package = "uu_true", path = "src/uu/true" }
true = { optional = true, version = "0.0.20", package = "uu_true", path = "src/uu/true" } truncate = { optional = true, version = "0.0.22", package = "uu_truncate", path = "src/uu/truncate" }
truncate = { optional = true, version = "0.0.20", package = "uu_truncate", path = "src/uu/truncate" } tsort = { optional = true, version = "0.0.22", package = "uu_tsort", path = "src/uu/tsort" }
tsort = { optional = true, version = "0.0.20", package = "uu_tsort", path = "src/uu/tsort" } tty = { optional = true, version = "0.0.22", package = "uu_tty", path = "src/uu/tty" }
tty = { optional = true, version = "0.0.20", package = "uu_tty", path = "src/uu/tty" } uname = { optional = true, version = "0.0.22", package = "uu_uname", path = "src/uu/uname" }
uname = { optional = true, version = "0.0.20", package = "uu_uname", path = "src/uu/uname" } unexpand = { optional = true, version = "0.0.22", package = "uu_unexpand", path = "src/uu/unexpand" }
unexpand = { optional = true, version = "0.0.20", package = "uu_unexpand", path = "src/uu/unexpand" } uniq = { optional = true, version = "0.0.22", package = "uu_uniq", path = "src/uu/uniq" }
uniq = { optional = true, version = "0.0.20", package = "uu_uniq", path = "src/uu/uniq" } unlink = { optional = true, version = "0.0.22", package = "uu_unlink", path = "src/uu/unlink" }
unlink = { optional = true, version = "0.0.20", package = "uu_unlink", path = "src/uu/unlink" } uptime = { optional = true, version = "0.0.22", package = "uu_uptime", path = "src/uu/uptime" }
uptime = { optional = true, version = "0.0.20", package = "uu_uptime", path = "src/uu/uptime" } users = { optional = true, version = "0.0.22", package = "uu_users", path = "src/uu/users" }
users = { optional = true, version = "0.0.20", package = "uu_users", path = "src/uu/users" } vdir = { optional = true, version = "0.0.22", package = "uu_vdir", path = "src/uu/vdir" }
vdir = { optional = true, version = "0.0.20", package = "uu_vdir", path = "src/uu/vdir" } wc = { optional = true, version = "0.0.22", package = "uu_wc", path = "src/uu/wc" }
wc = { optional = true, version = "0.0.20", package = "uu_wc", path = "src/uu/wc" } who = { optional = true, version = "0.0.22", package = "uu_who", path = "src/uu/who" }
who = { optional = true, version = "0.0.20", package = "uu_who", path = "src/uu/who" } whoami = { optional = true, version = "0.0.22", package = "uu_whoami", path = "src/uu/whoami" }
whoami = { optional = true, version = "0.0.20", package = "uu_whoami", path = "src/uu/whoami" } yes = { optional = true, version = "0.0.22", package = "uu_yes", path = "src/uu/yes" }
yes = { optional = true, version = "0.0.20", package = "uu_yes", path = "src/uu/yes" }
# this breaks clippy linting with: "tests/by-util/test_factor_benches.rs: No such file or directory (os error 2)" # this breaks clippy linting with: "tests/by-util/test_factor_benches.rs: No such file or directory (os error 2)"
# factor_benches = { optional = true, version = "0.0.0", package = "uu_factor_benches", path = "tests/benches/factor" } # factor_benches = { optional = true, version = "0.0.0", package = "uu_factor_benches", path = "tests/benches/factor" }
@ -490,7 +488,6 @@ time = { workspace = true, features = ["local-offset"] }
unindent = "0.2" unindent = "0.2"
uucore = { workspace = true, features = ["entries", "process", "signals"] } uucore = { workspace = true, features = ["entries", "process", "signals"] }
walkdir = { workspace = true } walkdir = { workspace = true }
is-terminal = { workspace = true }
hex-literal = "0.4.1" hex-literal = "0.4.1"
rstest = { workspace = true } rstest = { workspace = true }

331
DEVELOPMENT.md Normal file
View file

@ -0,0 +1,331 @@
<!-- spell-checker:ignore (flags) Ccodegen Coverflow Cpanic Zinstrument Zpanic reimplementing toybox RUNTEST CARGOFLAGS nextest prereq autopoint gettext texinfo automake findutils shellenv libexec gnubin toolchains -->
# Setting up your local development environment
For contributing rules and best practices please refer to [CONTRIBUTING.md](CONTRIBUTING.md)
## Before you start
For this guide we assume that you already have a GitHub account and have `git` and your favorite code editor or IDE installed and configured.
Before you start working on coreutils, please follow these steps:
1. Fork the [coreutils repository](https://github.com/uutils/coreutils) to your GitHub account.
***Tip:*** See [this GitHub guide](https://docs.github.com/en/get-started/quickstart/fork-a-repo) for more information on this step.
2. Clone that fork to your local development environment:
```shell
git clone https://github.com/YOUR-GITHUB-ACCOUNT/coreutils
cd coreutils
```
## Tools
You will need the tools mentioned in this section to build and test your code changes locally.
This section will explain how to install and configure these tools.
We also have an extensive CI that uses these tools and will check your code before it can be merged.
The next section [Testing](#testing) will explain how to run those checks locally to avoid waiting for the CI.
### Rust toolchain
[Install Rust](https://www.rust-lang.org/tools/install)
If you're using rustup to install and manage your Rust toolchains, `clippy` and `rustfmt` are usually already installed. If you are using one of the alternative methods, please make sure to install them manually. See following sub-sections for their usage: [clippy](#clippy) [rustfmt](#rustfmt).
***Tip*** You might also need to add 'llvm-tools' component if you are going to [generate code coverage reports locally](#code-coverage-report):
```shell
rustup component add llvm-tools-preview
```
### GNU utils and prerequisites
If you are developing on Linux, most likely you already have all/most GNU utilities and prerequisites installed.
To make sure, please check GNU coreutils [README-prereq](https://github.com/coreutils/coreutils/blob/master/README-prereq).
You will need these to [run uutils against the GNU test suite locally](#comparing-with-gnu).
For MacOS and Windows platform specific setup please check [MacOS GNU utils](#macos-gnu-utils) and [Windows GNU utils](#windows-gnu-utils) sections respectfully.
### 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.
**NOTE: On MacOS** the pre-commit hooks are currently broken. There are workarounds involving switching to unstable nightly Rust and components.
### 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:
```shell
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
This section explains how to run our CI checks locally.
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
# or
cargo test --all-features -p uucore
```
Running the complete test suite might take a while. We use [nextest](https://nexte.st/index.html) in
the CI and you might want to try it out locally. It can speed up the execution time of the whole
test run significantly if the cpu has multiple cores.
```shell
cargo nextest run --features unix --no-fail-fast
```
To debug:
```shell
rust-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
```
To run tests with `nextest` just use the nextest target. Note you'll need to
[install](https://nexte.st/book/installation.html) `nextest` first. The `nextest` target accepts the
same arguments like the default `test` target, so it's possible to pass arguments to `nextest run`
via `CARGOFLAGS`:
```shell
make CARGOFLAGS='--no-fail-fast' UTILS='UTILITY_1 UTILITY_2' nextest
```
### 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
# Build uutils without release optimizations
UU_MAKE_PROFILE=debug 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
```
***Tip:*** First time you run `bash util/build-gnu.sh` command, it will provide instructions on how to checkout GNU coreutils repository at the correct release tag. Please follow those instructions and when done, run `bash util/build-gnu.sh` command again.
Note that GNU test suite relies on individual utilities (not the multicall binary).
## Code coverage report
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.
## Tips for setting up on Mac
### C Compiler and linker
On MacOS you'll need to install C compiler & linker:
```shell
xcode-select --install
```
### MacOS GNU utils
On MacOS you will need to install [Homebrew](https://docs.brew.sh/Installation) and use it to install the following Homebrew formulas:
```shell
brew install \
coreutils \
autoconf \
gettext \
wget \
texinfo \
xz \
automake \
gnu-sed \
m4 \
bison \
pre-commit \
findutils
```
After installing these Homebrew formulas, please make sure to add the following lines to your `zsh` or `bash` rc file, i.e. `~/.profile` or `~/.zshrc` or `~/.bashrc` ...
(assuming Homebrew is installed at default location `/opt/homebrew`):
```shell
eval "$(/opt/homebrew/bin/brew shellenv)"
export PATH="/opt/homebrew/opt/coreutils/libexec/gnubin:$PATH"
export PATH="/opt/homebrew/opt/bison/bin:$PATH"
export PATH="/opt/homebrew/opt/findutils/libexec/gnubin:$PATH"
```
Last step is to link Homebrew coreutils version of `timeout` to `/usr/local/bin` (as admin user):
```shell
sudo ln -s /opt/homebrew/bin/timeout /usr/local/bin/timeout
```
Do not forget to either source updated rc file or restart you terminal session to update environment variables.
## Tips for setting up on Windows
### MSVC build tools
On Windows you'll need the MSVC build tools for Visual Studio 2013 or later.
If you are using `rustup-init.exe` to install Rust toolchain, it will guide you through the process of downloading and installing these prerequisites.
Otherwise please follow [this guide](https://learn.microsoft.com/en-us/windows/dev-environment/rust/setup).
### Windows GNU utils
If you have used [Git for Windows](https://gitforwindows.org) to install `git` on you Windows system you might already have some GNU core utilities installed as part of "GNU Bash" included in Git for Windows package, but it is not a complete package. [This article](https://gist.github.com/evanwill/0207876c3243bbb6863e65ec5dc3f058) provides instruction on how to add more to it.
Alternatively you can install [Cygwin](https://www.cygwin.com) and/or use [WSL2](https://learn.microsoft.com/en-us/windows/wsl/compare-versions#whats-new-in-wsl-2) to get access to all GNU core utilities on Windows.

View file

@ -102,7 +102,6 @@ PROGS := \
pwd \ pwd \
readlink \ readlink \
realpath \ realpath \
relpath \
rm \ rm \
rmdir \ rmdir \
seq \ seq \

View file

@ -14,7 +14,7 @@
[![dependency status](https://deps.rs/repo/github/uutils/coreutils/status.svg)](https://deps.rs/repo/github/uutils/coreutils) [![dependency status](https://deps.rs/repo/github/uutils/coreutils/status.svg)](https://deps.rs/repo/github/uutils/coreutils)
[![CodeCov](https://codecov.io/gh/uutils/coreutils/branch/master/graph/badge.svg)](https://codecov.io/gh/uutils/coreutils) [![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) ![MSRV](https://img.shields.io/badge/MSRV-1.70.0-brightgreen)
</div> </div>
@ -54,8 +54,8 @@ that scripts can be easily transferred between platforms.
uutils has both user and developer documentation available: uutils has both user and developer documentation available:
- [User Manual](https://uutils.github.io/user/) - [User Manual](https://uutils.github.io/coreutils/book/)
- [Developer Documentation](https://uutils.github.io/dev/coreutils/) - [Developer Documentation](https://uutils.github.io/dev/coreutils/) (currently offline, you can use docs.rs in the meantime)
Both can also be generated locally, the instructions for that can be found in 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. the [coreutils docs](https://github.com/uutils/uutils.github.io) repository.
@ -71,7 +71,7 @@ the [coreutils docs](https://github.com/uutils/uutils.github.io) repository.
### Rust Version ### Rust Version
uutils follows Rust's release channels and is tested against stable, beta and 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`. nightly. The current Minimum Supported Rust Version (MSRV) is `1.70.0`.
## Building ## Building
@ -303,7 +303,7 @@ make PREFIX=/my/path uninstall
Below is the evolution of how many GNU tests uutils passes. A more detailed 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 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). [in the user manual](https://uutils.github.io/coreutils/book/test_coverage.html).
See <https://github.com/uutils/coreutils/issues/3336> for the main meta bugs See <https://github.com/uutils/coreutils/issues/3336> for the main meta bugs
(many are missing). (many are missing).

View file

@ -1,28 +0,0 @@
# Targets that compile
**Note: this list isn't up to date.**
This is an auto-generated table showing which binaries compile for each target-triple. Note that this **does not** indicate that they are fully implemented, or that the tests pass.
|######OS######|###ARCH####|arch|base32|base64|basename|cat|chgrp|chmod|chown|chroot|cksum|comm|cp|csplit|cut|date|df|dircolors|dirname|du|echo|env|expand|expr|factor|false|fmt|fold|groups|hashsum|head|hostid|hostname|id|install|join|kill|link|ln|logname|ls|mkdir|mkfifo|mknod|mktemp|more|mv|nice|nl|nohup|nproc|numfmt|od|paste|pathchk|pinky|printenv|printf|ptx|pwd|readlink|realpath|relpath|rm|rmdir|seq|shred|shuf|sleep|sort|split|stat|stdbuf|sum|sync|tac|tail|tee|test|timeout|touch|tr|true|truncate|tsort|tty|uname|unexpand|uniq|unlink|uptime|users|wc|who|whoami|yes|chcon|pr|dir|vdir|dd|basenc|runcon|
|--------------|-----------|----|------|------|--------|---|-----|-----|-----|------|-----|----|--|------|---|----|--|---------|-------|--|----|---|------|----|------|-----|---|----|------|-------|----|------|--------|--|-------|----|----|----|--|-------|--|-----|------|-----|------|----|--|----|--|-----|-----|------|--|-----|-------|-----|--------|------|---|---|--------|--------|-------|--|-----|---|-----|----|-----|----|-----|----|------|---|----|---|----|---|----|-------|-----|--|----|--------|-----|---|-----|--------|----|------|------|-----|--|---|------|---|-----|--|---|----|--|------|------|
|linux-gnu|aarch64|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y| |y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|
|linux-gnu|i686|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|
|linux-gnu|powerpc64|y|y|y|y|y|y|y|y|y|y|y| |y|y|y|y|y|y|y|y|y|y| |y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|
|linux-gnu|riscv64gc| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | |
|linux-gnu|x86_64|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y| |y|y|y|y|y| |y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|
|windows-msvc|aarch64|y|y|y|y|y| | | | |y|y|y|y|y|y|y|y|y| |y|y|y| |y|y|y|y| |y|y|y|y| | |y| |y|y|y| |y| | |y|y|y| |y| |y|y|y|y| | |y|y|y|y|y|y|y|y|y|y|y|y|y|y|y| | |y|y|y|y|y|y| |y|y|y|y|y| |y|y|y| |y| |y| | |y|y|y|y|y|y|y|y|
|windows-gnu|i686|y|y|y|y|y| | | | |y|y|y|y|y|y|y|y|y| |y|y|y| |y|y|y|y| |y|y|y|y| | |y| |y|y|y|y|y| | |y|y|y| |y| |y|y|y|y| | |y|y|y|y|y|y|y|y|y|y|y|y|y|y|y| | |y|y|y|y|y|y| |y|y|y|y|y|y|y|y|y| |y| |y| |y|y|y|y|y|y|y|y|y|
|windows-msvc|i686|y|y|y|y|y| | | | |y|y|y|y|y|y|y|y|y| |y|y|y| |y|y|y|y| |y|y|y|y| | |y| |y|y|y|y|y| | |y|y|y| |y| |y|y|y|y| | |y|y|y|y|y|y|y|y|y|y|y|y|y|y|y| | |y|y|y|y|y|y| |y|y|y|y|y| |y|y|y| |y| |y| |y|y|y|y|y|y|y|y|y|
|windows-gnu|x86_64|y|y|y|y|y| | | | |y|y|y|y|y|y|y|y|y| |y|y|y| |y|y|y|y| |y|y|y|y| | |y| |y|y|y|y|y| | |y|y|y| |y| |y|y|y|y| | |y|y|y|y|y|y|y|y|y|y|y|y|y|y|y| | |y|y|y|y|y|y| |y|y|y|y|y|y|y|y|y| |y| |y| |y|y|y|y|y|y|y|y|y|
|windows-msvc|x86_64|y|y|y|y|y| | | | |y|y|y|y|y|y|y|y|y| |y|y|y| |y|y|y|y| |y|y|y|y| | |y| |y|y|y|y|y| | |y|y|y| |y| |y|y|y|y| | |y|y|y|y|y|y|y|y|y|y|y|y|y|y|y| | |y|y|y|y|y|y| |y|y|y|y|y| |y|y|y| |y| |y| |y|y|y|y|y|y|y|y|y|
|apple MacOS|x86_64|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y| |y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|
|freebsd|x86_64|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|
|netbsd|x86_64|y|y|y|y|y|y|y|y| |y|y|y|y|y|y| |y|y|y|y|y|y| |y|y|y|y|y|y|y|y|y| |y|y| |y|y|y|y|y|y|y|y|y|y|y|y| |y|y|y|y|y| |y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y| |y|y|y|y|y|y| |y|y|y| | |y| |y|y|y|y|y|y|y|y|y|
|android|aarch64|y|y|y|y|y|y|y|y| |y|y|y|y|y|y| |y|y|y|y|y|y| |y|y|y|y|y|y|y|y|y| |y|y| |y|y|y|y|y|y|y|y|y|y|y|y| |y|y|y|y|y| |y|y|y|y|y|y|y|y|y|y|y|y|y|y|y| |y|y| |y|y|y|y| |y|y|y|y|y|y| |y|y|y| | |y| |y|y|y|y|y|y|y|y|y|
|android|x86_64|y|y|y|y|y|y|y|y| |y|y|y|y|y|y| |y|y|y|y|y|y| |y|y|y|y|y|y|y|y|y| |y|y| |y|y|y|y|y|y|y|y|y|y|y|y| |y|y|y|y|y| |y|y|y|y|y|y|y|y|y|y|y|y|y|y|y| |y|y| |y|y|y|y| |y|y|y|y|y|y| |y|y|y| | |y| |y|y|y|y|y|y|y|y|y|
|solaris|x86_64| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | |
|wasi|wasm32| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | |
|redox|x86_64| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | |
|fuchsia|aarch64| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | |
|fuchsia|x86_64| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | |

View file

@ -1,3 +1,8 @@
// This file is part of the uutils coreutils package.
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
// spell-checker:ignore (vars) krate // spell-checker:ignore (vars) krate
use std::env; use std::env;
@ -40,6 +45,7 @@ pub fn main() {
mf.write_all( mf.write_all(
"type UtilityMap<T> = phf::OrderedMap<&'static str, (fn(T) -> i32, fn() -> Command)>;\n\ "type UtilityMap<T> = phf::OrderedMap<&'static str, (fn(T) -> i32, fn() -> Command)>;\n\
\n\ \n\
#[allow(clippy::too_many_lines)]
fn util_map<T: uucore::Args>() -> UtilityMap<T> {\n" fn util_map<T: uucore::Args>() -> UtilityMap<T> {\n"
.as_bytes(), .as_bytes(),
) )

View file

@ -59,9 +59,12 @@ highlight = "all"
# spell-checker: disable # spell-checker: disable
skip = [ skip = [
# procfs # procfs
{ name = "rustix", version = "0.36.14" }, { name = "rustix", version = "0.36.16" },
# rustix # rustix
{ name = "linux-raw-sys", version = "0.1.4" }, { name = "linux-raw-sys", version = "0.1.4" },
{ name = "linux-raw-sys", version = "0.3.8" },
# terminal_size
{ name = "rustix", version = "0.37.26" },
# various crates # various crates
{ name = "windows-sys", version = "0.45.0" }, { name = "windows-sys", version = "0.45.0" },
# windows-sys # windows-sys
@ -80,12 +83,14 @@ skip = [
{ name = "windows_x86_64_gnullvm", version = "0.42.2" }, { name = "windows_x86_64_gnullvm", version = "0.42.2" },
# windows-targets # windows-targets
{ name = "windows_x86_64_msvc", version = "0.42.2" }, { name = "windows_x86_64_msvc", version = "0.42.2" },
# tempfile
{ name = "redox_syscall", version = "0.3.5" },
# cpp_macros
{ name = "aho-corasick", version = "0.7.19" },
# various crates # various crates
{ name = "syn", version = "1.0.109" }, { name = "syn", version = "1.0.109" },
# various crates
{ name = "bitflags", version = "1.3.2" },
# various crates
{ name = "redox_syscall", version = "0.3.5" },
# clap_builder, textwrap
{ name = "terminal_size", version = "0.2.6" },
] ]
# spell-checker: enable # spell-checker: enable

1
docs/.gitignore vendored
View file

@ -1,4 +1,5 @@
book book
src/utils src/utils
src/SUMMARY.md src/SUMMARY.md
src/platform_table.md
tldr.zip tldr.zip

View file

@ -1,21 +1,21 @@
target,arch,base32,base64,basename,cat,chgrp,chmod,chown,chroot,cksum,comm,cp,csplit,cut,date,df,dircolors,dirname,du,echo,env,expand,expr,factor,false,fmt,fold,groups,hashsum,head,hostid,hostname,id,install,join,kill,link,ln,logname,ls,mkdir,mkfifo,mknod,mktemp,more,mv,nice,nl,nohup,nproc,numfmt,od,paste,pathchk,pinky,printenv,printf,ptx,pwd,readlink,realpath,relpath,rm,rmdir,seq,shred,shuf,sleep,sort,split,stat,stdbuf,sum,sync,tac,tail,tee,test,timeout,touch,tr,true,truncate,tsort,tty,uname,unexpand,uniq,unlink,uptime,users,wc,who,whoami,yes,chcon,pr,dir,vdir,dd,basenc,runcon target,arch,base32,base64,basename,cat,chgrp,chmod,chown,chroot,cksum,comm,cp,csplit,cut,date,df,dircolors,dirname,du,echo,env,expand,expr,factor,false,fmt,fold,groups,hashsum,head,hostid,hostname,id,install,join,kill,link,ln,logname,ls,mkdir,mkfifo,mknod,mktemp,more,mv,nice,nl,nohup,nproc,numfmt,od,paste,pathchk,pinky,printenv,printf,ptx,pwd,readlink,realpath,rm,rmdir,seq,shred,shuf,sleep,sort,split,stat,stdbuf,sum,sync,tac,tail,tee,test,timeout,touch,tr,true,truncate,tsort,tty,uname,unexpand,uniq,unlink,uptime,users,wc,who,whoami,yes,chcon,pr,dir,vdir,dd,basenc,runcon
aarch64-unknown-linux-gnu,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,101,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 aarch64-unknown-linux-gnu,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,101,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
i686-unknown-linux-gnu,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 i686-unknown-linux-gnu,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
powerpc64-unknown-linux-gnu,0,0,0,0,0,0,0,0,0,0,0,101,0,0,0,0,0,0,0,0,0,0,101,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 powerpc64-unknown-linux-gnu,0,0,0,0,0,0,0,0,0,0,0,101,0,0,0,0,0,0,0,0,0,0,101,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
riscv64gc-unknown-linux-gnu,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101 riscv64gc-unknown-linux-gnu,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101
x86_64-unknown-linux-gnu,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,101,0,0,0,0,0,101,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 x86_64-unknown-linux-gnu,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,101,0,0,0,0,0,101,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
aarch64-pc-windows-msvc,0,0,0,0,0,101,101,101,101,0,0,0,0,0,0,0,0,0,101,0,0,0,101,0,0,0,0,101,0,0,0,0,101,101,0,101,0,0,0,101,0,101,101,0,0,0,101,0,101,0,0,0,0,101,101,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,101,101,0,0,0,0,0,0,101,0,0,0,0,0,101,0,0,0,101,0,101,0,101,101,0,0,0,0,0,0,0,0 aarch64-pc-windows-msvc,0,0,0,0,0,101,101,101,101,0,0,0,0,0,0,0,0,0,101,0,0,0,101,0,0,0,0,101,0,0,0,0,101,101,0,101,0,0,0,101,0,101,101,0,0,0,101,0,101,0,0,0,0,101,101,0,0,0,0,0,0,0,0,0,0,0,0,0,0,101,101,0,0,0,0,0,0,101,0,0,0,0,0,101,0,0,0,101,0,101,0,101,101,0,0,0,0,0,0,0,0
i686-pc-windows-gnu,0,0,0,0,0,101,101,101,101,0,0,0,0,0,0,0,0,0,101,0,0,0,101,0,0,0,0,101,0,0,0,0,101,101,0,101,0,0,0,0,0,101,101,0,0,0,101,0,101,0,0,0,0,101,101,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,101,101,0,0,0,0,0,0,101,0,0,0,0,0,0,0,0,0,101,0,101,0,101,0,0,0,0,0,0,0,0,0 i686-pc-windows-gnu,0,0,0,0,0,101,101,101,101,0,0,0,0,0,0,0,0,0,101,0,0,0,101,0,0,0,0,101,0,0,0,0,101,101,0,101,0,0,0,0,0,101,101,0,0,0,101,0,101,0,0,0,0,101,101,0,0,0,0,0,0,0,0,0,0,0,0,0,0,101,101,0,0,0,0,0,0,101,0,0,0,0,0,0,0,0,0,101,0,101,0,101,0,0,0,0,0,0,0,0,0
i686-pc-windows-msvc,0,0,0,0,0,101,101,101,101,0,0,0,0,0,0,0,0,0,101,0,0,0,101,0,0,0,0,101,0,0,0,0,101,101,0,101,0,0,0,0,0,101,101,0,0,0,101,0,101,0,0,0,0,101,101,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,101,101,0,0,0,0,0,0,101,0,0,0,0,0,101,0,0,0,101,0,101,0,101,0,0,0,0,0,0,0,0,0 i686-pc-windows-msvc,0,0,0,0,0,101,101,101,101,0,0,0,0,0,0,0,0,0,101,0,0,0,101,0,0,0,0,101,0,0,0,0,101,101,0,101,0,0,0,0,0,101,101,0,0,0,101,0,101,0,0,0,0,101,101,0,0,0,0,0,0,0,0,0,0,0,0,0,0,101,101,0,0,0,0,0,0,101,0,0,0,0,0,101,0,0,0,101,0,101,0,101,0,0,0,0,0,0,0,0,0
x86_64-pc-windows-gnu,0,0,0,0,0,101,101,101,101,0,0,0,0,0,0,0,0,0,101,0,0,0,101,0,0,0,0,101,0,0,0,0,101,101,0,101,0,0,0,0,0,101,101,0,0,0,101,0,101,0,0,0,0,101,101,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,101,101,0,0,0,0,0,0,101,0,0,0,0,0,0,0,0,0,101,0,101,0,101,0,0,0,0,0,0,0,0,0 x86_64-pc-windows-gnu,0,0,0,0,0,101,101,101,101,0,0,0,0,0,0,0,0,0,101,0,0,0,101,0,0,0,0,101,0,0,0,0,101,101,0,101,0,0,0,0,0,101,101,0,0,0,101,0,101,0,0,0,0,101,101,0,0,0,0,0,0,0,0,0,0,0,0,0,0,101,101,0,0,0,0,0,0,101,0,0,0,0,0,0,0,0,0,101,0,101,0,101,0,0,0,0,0,0,0,0,0
x86_64-pc-windows-msvc,0,0,0,0,0,101,101,101,101,0,0,0,0,0,0,0,0,0,101,0,0,0,101,0,0,0,0,101,0,0,0,0,101,101,0,101,0,0,0,0,0,101,101,0,0,0,101,0,101,0,0,0,0,101,101,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,101,101,0,0,0,0,0,0,101,0,0,0,0,0,101,0,0,0,101,0,101,0,101,0,0,0,0,0,0,0,0,0 x86_64-pc-windows-msvc,0,0,0,0,0,101,101,101,101,0,0,0,0,0,0,0,0,0,101,0,0,0,101,0,0,0,0,101,0,0,0,0,101,101,0,101,0,0,0,0,0,101,101,0,0,0,101,0,101,0,0,0,0,101,101,0,0,0,0,0,0,0,0,0,0,0,0,0,0,101,101,0,0,0,0,0,0,101,0,0,0,0,0,101,0,0,0,101,0,101,0,101,0,0,0,0,0,0,0,0,0
x86_64-apple-darwin,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,101,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 x86_64-apple-darwin,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,101,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
x86_64-unknown-freebsd,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 x86_64-unknown-freebsd,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
x86_64-unknown-netbsd,0,0,0,0,0,0,0,0,101,0,0,0,0,0,0,101,0,0,0,0,0,0,101,0,0,0,0,0,0,0,0,0,101,0,0,101,0,0,0,0,0,0,0,0,0,0,0,0,101,0,0,0,0,0,101,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,101,0,0,0,0,0,0,101,0,0,0,101,101,0,101,0,0,0,0,0,0,0,0,0 x86_64-unknown-netbsd,0,0,0,0,0,0,0,0,101,0,0,0,0,0,0,101,0,0,0,0,0,0,101,0,0,0,0,0,0,0,0,0,101,0,0,101,0,0,0,0,0,0,0,0,0,0,0,0,101,0,0,0,0,0,101,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,101,0,0,0,0,0,0,101,0,0,0,101,101,0,101,0,0,0,0,0,0,0,0,0
aarch64-linux-android,0,0,0,0,0,0,0,0,101,0,0,0,0,0,0,101,0,0,0,0,0,0,101,0,0,0,0,0,0,0,0,0,101,0,0,101,0,0,0,0,0,0,0,0,0,0,0,0,101,0,0,0,0,0,101,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,101,0,0,101,0,0,0,0,101,0,0,0,0,0,0,101,0,0,0,101,101,0,101,0,0,0,0,0,0,0,0,0 aarch64-linux-android,0,0,0,0,0,0,0,0,101,0,0,0,0,0,0,101,0,0,0,0,0,0,101,0,0,0,0,0,0,0,0,0,101,0,0,101,0,0,0,0,0,0,0,0,0,0,0,0,101,0,0,0,0,0,101,0,0,0,0,0,0,0,0,0,0,0,0,0,0,101,0,0,101,0,0,0,0,101,0,0,0,0,0,0,101,0,0,0,101,101,0,101,0,0,0,0,0,0,0,0,0
x86_64-linux-android,0,0,0,0,0,0,0,0,101,0,0,0,0,0,0,101,0,0,0,0,0,0,101,0,0,0,0,0,0,0,0,0,101,0,0,101,0,0,0,0,0,0,0,0,0,0,0,0,101,0,0,0,0,0,101,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,101,0,0,101,0,0,0,0,101,0,0,0,0,0,0,101,0,0,0,101,101,0,101,0,0,0,0,0,0,0,0,0 x86_64-linux-android,0,0,0,0,0,0,0,0,101,0,0,0,0,0,0,101,0,0,0,0,0,0,101,0,0,0,0,0,0,0,0,0,101,0,0,101,0,0,0,0,0,0,0,0,0,0,0,0,101,0,0,0,0,0,101,0,0,0,0,0,0,0,0,0,0,0,0,0,0,101,0,0,101,0,0,0,0,101,0,0,0,0,0,0,101,0,0,0,101,101,0,101,0,0,0,0,0,0,0,0,0
x86_64-sun-solaris,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101 x86_64-sun-solaris,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101
wasm32-wasi,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101 wasm32-wasi,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101
x86_64-unknown-redox,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101 x86_64-unknown-redox,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101
aarch64-fuchsia,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101 aarch64-fuchsia,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101
x86_64-fuchsia,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101 x86_64-fuchsia,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101

1 target arch base32 base64 basename cat chgrp chmod chown chroot cksum comm cp csplit cut date df dircolors dirname du echo env expand expr factor false fmt fold groups hashsum head hostid hostname id install join kill link ln logname ls mkdir mkfifo mknod mktemp more mv nice nl nohup nproc numfmt od paste pathchk pinky printenv printf ptx pwd readlink realpath relpath rm rmdir seq shred shuf sleep sort split stat stdbuf sum sync tac tail tee test timeout touch tr true truncate tsort tty uname unexpand uniq unlink uptime users wc who whoami yes chcon pr dir vdir dd basenc runcon
2 aarch64-unknown-linux-gnu 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 101 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
3 i686-unknown-linux-gnu 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
4 powerpc64-unknown-linux-gnu 0 0 0 0 0 0 0 0 0 0 0 101 0 0 0 0 0 0 0 0 0 0 101 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
5 riscv64gc-unknown-linux-gnu 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101
6 x86_64-unknown-linux-gnu 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 101 0 0 0 0 0 101 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
7 aarch64-pc-windows-msvc 0 0 0 0 0 101 101 101 101 0 0 0 0 0 0 0 0 0 101 0 0 0 101 0 0 0 0 101 0 0 0 0 101 101 0 101 0 0 0 101 0 101 101 0 0 0 101 0 101 0 0 0 0 101 101 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 101 101 0 0 0 0 0 0 101 0 0 0 0 0 101 0 0 0 101 0 101 0 101 101 0 0 0 0 0 0 0 0
8 i686-pc-windows-gnu 0 0 0 0 0 101 101 101 101 0 0 0 0 0 0 0 0 0 101 0 0 0 101 0 0 0 0 101 0 0 0 0 101 101 0 101 0 0 0 0 0 101 101 0 0 0 101 0 101 0 0 0 0 101 101 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 101 101 0 0 0 0 0 0 101 0 0 0 0 0 0 0 0 0 101 0 101 0 101 0 0 0 0 0 0 0 0 0
9 i686-pc-windows-msvc 0 0 0 0 0 101 101 101 101 0 0 0 0 0 0 0 0 0 101 0 0 0 101 0 0 0 0 101 0 0 0 0 101 101 0 101 0 0 0 0 0 101 101 0 0 0 101 0 101 0 0 0 0 101 101 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 101 101 0 0 0 0 0 0 101 0 0 0 0 0 101 0 0 0 101 0 101 0 101 0 0 0 0 0 0 0 0 0
10 x86_64-pc-windows-gnu 0 0 0 0 0 101 101 101 101 0 0 0 0 0 0 0 0 0 101 0 0 0 101 0 0 0 0 101 0 0 0 0 101 101 0 101 0 0 0 0 0 101 101 0 0 0 101 0 101 0 0 0 0 101 101 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 101 101 0 0 0 0 0 0 101 0 0 0 0 0 0 0 0 0 101 0 101 0 101 0 0 0 0 0 0 0 0 0
11 x86_64-pc-windows-msvc 0 0 0 0 0 101 101 101 101 0 0 0 0 0 0 0 0 0 101 0 0 0 101 0 0 0 0 101 0 0 0 0 101 101 0 101 0 0 0 0 0 101 101 0 0 0 101 0 101 0 0 0 0 101 101 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 101 101 0 0 0 0 0 0 101 0 0 0 0 0 101 0 0 0 101 0 101 0 101 0 0 0 0 0 0 0 0 0
12 x86_64-apple-darwin 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 101 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
13 x86_64-unknown-freebsd 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
14 x86_64-unknown-netbsd 0 0 0 0 0 0 0 0 101 0 0 0 0 0 0 101 0 0 0 0 0 0 101 0 0 0 0 0 0 0 0 0 101 0 0 101 0 0 0 0 0 0 0 0 0 0 0 0 101 0 0 0 0 0 101 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 101 0 0 0 0 0 0 101 0 0 0 101 101 0 101 0 0 0 0 0 0 0 0 0
15 aarch64-linux-android 0 0 0 0 0 0 0 0 101 0 0 0 0 0 0 101 0 0 0 0 0 0 101 0 0 0 0 0 0 0 0 0 101 0 0 101 0 0 0 0 0 0 0 0 0 0 0 0 101 0 0 0 0 0 101 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 101 0 0 101 0 0 0 0 101 0 0 0 0 0 0 101 0 0 0 101 101 0 101 0 0 0 0 0 0 0 0 0
16 x86_64-linux-android 0 0 0 0 0 0 0 0 101 0 0 0 0 0 0 101 0 0 0 0 0 0 101 0 0 0 0 0 0 0 0 0 101 0 0 101 0 0 0 0 0 0 0 0 0 0 0 0 101 0 0 0 0 0 101 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 101 0 0 101 0 0 0 0 101 0 0 0 0 0 0 101 0 0 0 101 101 0 101 0 0 0 0 0 0 0 0 0
17 x86_64-sun-solaris 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101
18 wasm32-wasi 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101
19 x86_64-unknown-redox 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101
20 aarch64-fuchsia 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101
21 x86_64-fuchsia 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101

View file

@ -77,4 +77,5 @@ third way: `--long`.
## `du` ## `du`
`du` allows `birth` and `creation` as values for the `--time` argument to show the creation time. `du` allows `birth` and `creation` as values for the `--time` argument to show the creation time. It
also provides a `-v`/`--verbose` flag.

View file

@ -1,4 +1,4 @@
<!-- spell-checker:ignore pacman pamac nixpkgs openmandriva --> <!-- spell-checker:ignore pacman pamac nixpkgs openmandriva conda -->
# Installation # Installation
@ -139,6 +139,16 @@ pkg install rust-coreutils
scoop install uutils-coreutils scoop install uutils-coreutils
``` ```
## Alternative installers
### Conda
[Conda package](https://anaconda.org/conda-forge/uutils-coreutils)
```
conda install -c conda-forge uutils-coreutils
```
## Non-standard packages ## Non-standard packages
### `coreutils-hybrid` (AUR) ### `coreutils-hybrid` (AUR)

45
docs/src/platforms.md Normal file
View file

@ -0,0 +1,45 @@
# Platform support
<!-- markdownlint-disable MD033 -->
uutils aims to be as "universal" as possible, meaning that we try to support
many platforms. However, it is infeasible for us to guarantee that every
platform works. Just like Rust itself, we therefore have multiple tiers of
platform support, with different guarantees. We support two tiers of platforms:
- **Tier 1**: All applicable utils are compiled and tested in CI for these
platforms.
- **Tier 2**: These platforms are supported but not actively tested. We do accept
fixes for these platforms.
> **Note**: The tiers are dictated by our CI. We would happily accept a job
> in the CI for testing more platforms, bumping those platforms to tier 1.
## Platforms per tier
The platforms in tier 1 and the platforms that we test in CI are listed below.
| Operating system | Tested targets |
| ---------------- | -------------- |
| **Linux** | `x86_64-unknown-linux-gnu` <br> `x86_64-unknown-linux-musl` <br> `arm-unknown-linux-gnueabihf` <br> `i686-unknown-linux-gnu` <br> `aarch64-unknown-linux-gnu` |
| **macOS** | `x86_64-apple-darwin` |
| **Windows** | `i686-pc-windows-msvc` <br> `x86_64-pc-windows-gnu` <br> `x86_64-pc-windows-msvc` |
| **FreeBSD** | `x86_64-unknown-freebsd` |
| **Android** | `i686-linux-android` |
The platforms in tier 2 are more vague, but include:
- untested variations of the platforms above,
- Redox OS,
- and BSDs such as OpenBSD, NetBSD & DragonFlyBSD.
## Utility compatibility per platform
Not all utils work on every platform. For instance, `chgrp` is not supported on
Windows, because Windows does not have the concept of groups. Below is a full table
detailing which utilities are supported for the tier 1 platforms.
Note that for some utilities, not all functionality is supported on each
platform. This is documented per utility.
{{ #include platform_table.md }}

View file

@ -9,12 +9,14 @@ cargo-fuzz = true
[dependencies] [dependencies]
libfuzzer-sys = "0.4" libfuzzer-sys = "0.4"
libc = "0.2"
rand = { version = "0.8", features = ["small_rng"] }
[dependencies.uucore] uucore = { path = "../src/uucore/" }
path = "../src/uucore/" uu_date = { path = "../src/uu/date/" }
uu_test = { path = "../src/uu/test/" }
uu_expr = { path = "../src/uu/expr/" }
[dependencies.uu_date]
path = "../src/uu/date/"
# Prevent this from interfering with workspaces # Prevent this from interfering with workspaces
[workspace] [workspace]
@ -26,6 +28,18 @@ path = "fuzz_targets/fuzz_date.rs"
test = false test = false
doc = false doc = false
[[bin]]
name = "fuzz_expr"
path = "fuzz_targets/fuzz_expr.rs"
test = false
doc = false
[[bin]]
name = "fuzz_test"
path = "fuzz_targets/fuzz_test.rs"
test = false
doc = false
[[bin]] [[bin]]
name = "fuzz_parse_glob" name = "fuzz_parse_glob"
path = "fuzz_targets/fuzz_parse_glob.rs" path = "fuzz_targets/fuzz_parse_glob.rs"

View file

@ -0,0 +1,107 @@
// This file is part of the uutils coreutils package.
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
use libc::{dup, dup2, STDOUT_FILENO};
use std::ffi::OsString;
use std::io;
use std::process::Command;
use std::sync::atomic::Ordering;
use std::sync::{atomic::AtomicBool, Once};
static CHECK_GNU: Once = Once::new();
static IS_GNU: AtomicBool = AtomicBool::new(false);
pub fn is_gnu_cmd(cmd_path: &str) -> Result<(), std::io::Error> {
CHECK_GNU.call_once(|| {
let version_output = Command::new(cmd_path).arg("--version").output().unwrap();
println!("version_output {:#?}", version_output);
let version_str = String::from_utf8_lossy(&version_output.stdout).to_string();
if version_str.contains("GNU coreutils") {
IS_GNU.store(true, Ordering::Relaxed);
}
});
if IS_GNU.load(Ordering::Relaxed) {
Ok(())
} else {
panic!("Not the GNU implementation");
}
}
pub fn generate_and_run_uumain<F>(args: &[OsString], uumain_function: F) -> (String, i32)
where
F: FnOnce(std::vec::IntoIter<OsString>) -> i32,
{
let uumain_exit_status;
let original_stdout_fd = unsafe { dup(STDOUT_FILENO) };
println!("Running test {:?}", &args[1..]);
let mut pipe_fds = [-1; 2];
unsafe { libc::pipe(pipe_fds.as_mut_ptr()) };
{
unsafe { dup2(pipe_fds[1], STDOUT_FILENO) };
uumain_exit_status = uumain_function(args.to_owned().into_iter());
unsafe { dup2(original_stdout_fd, STDOUT_FILENO) };
unsafe { libc::close(original_stdout_fd) };
}
unsafe { libc::close(pipe_fds[1]) };
let mut captured_output = Vec::new();
let mut read_buffer = [0; 1024];
loop {
let bytes_read = unsafe {
libc::read(
pipe_fds[0],
read_buffer.as_mut_ptr() as *mut libc::c_void,
read_buffer.len(),
)
};
if bytes_read <= 0 {
break;
}
captured_output.extend_from_slice(&read_buffer[..bytes_read as usize]);
}
unsafe { libc::close(pipe_fds[0]) };
let my_output = String::from_utf8_lossy(&captured_output)
.to_string()
.trim()
.to_owned();
(my_output, uumain_exit_status)
}
pub fn run_gnu_cmd(
cmd_path: &str,
args: &[OsString],
check_gnu: bool,
) -> Result<(String, i32), io::Error> {
if check_gnu {
is_gnu_cmd(cmd_path)?; // Check if it's a GNU implementation
}
let mut command = Command::new(cmd_path);
for arg in args {
command.arg(arg);
}
let output = command.output()?;
let exit_code = output.status.code().unwrap_or(-1);
if output.status.success() || !check_gnu {
Ok((
String::from_utf8_lossy(&output.stdout).to_string(),
exit_code,
))
} else {
Err(io::Error::new(
io::ErrorKind::Other,
format!("GNU command execution failed with exit code {}", exit_code),
))
}
}

View file

@ -9,6 +9,6 @@ fuzz_target!(|data: &[u8]| {
let args = data let args = data
.split(|b| *b == delim) .split(|b| *b == delim)
.filter_map(|e| std::str::from_utf8(e).ok()) .filter_map(|e| std::str::from_utf8(e).ok())
.map(|e| OsString::from(e)); .map(OsString::from);
uumain(args); uumain(args);
}); });

View file

@ -0,0 +1,120 @@
// This file is part of the uutils coreutils package.
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
// spell-checker:ignore parens
#![no_main]
use libfuzzer_sys::fuzz_target;
use uu_expr::uumain;
use rand::seq::SliceRandom;
use rand::Rng;
use std::{env, ffi::OsString};
mod fuzz_common;
use crate::fuzz_common::{generate_and_run_uumain, run_gnu_cmd};
static CMD_PATH: &str = "expr";
fn generate_random_string(max_length: usize) -> String {
let mut rng = rand::thread_rng();
let valid_utf8: Vec<char> = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
.chars()
.collect();
let invalid_utf8 = [0xC3, 0x28]; // Invalid UTF-8 sequence
let mut result = String::new();
for _ in 0..rng.gen_range(1..=max_length) {
if rng.gen_bool(0.9) {
let ch = valid_utf8.choose(&mut rng).unwrap();
result.push(*ch);
} else {
let ch = invalid_utf8.choose(&mut rng).unwrap();
if let Some(c) = char::from_u32(*ch as u32) {
result.push(c);
}
}
}
result
}
fn generate_expr(max_depth: u32) -> String {
let mut rng = rand::thread_rng();
let ops = ["+", "-", "*", "/", "%", "<", ">", "=", "&", "|"];
let mut expr = String::new();
let mut depth = 0;
let mut last_was_operator = false;
while depth <= max_depth {
if last_was_operator || depth == 0 {
// Add a number
expr.push_str(&rng.gen_range(1..=100).to_string());
last_was_operator = false;
} else {
// 90% chance to add an operator followed by a number
if rng.gen_bool(0.9) {
let op = *ops.choose(&mut rng).unwrap();
expr.push_str(&format!(" {} ", op));
last_was_operator = true;
}
// 10% chance to add a random string (potentially invalid syntax)
else {
let random_str = generate_random_string(rng.gen_range(1..=10));
expr.push_str(&random_str);
last_was_operator = false;
}
}
depth += 1;
}
// Ensure the expression ends with a number if it ended with an operator
if last_was_operator {
expr.push_str(&rng.gen_range(1..=100).to_string());
}
expr
}
fuzz_target!(|_data: &[u8]| {
let mut rng = rand::thread_rng();
let expr = generate_expr(rng.gen_range(0..=20));
let mut args = vec![OsString::from("expr")];
args.extend(expr.split_whitespace().map(OsString::from));
let (rust_output, uumain_exit_code) = generate_and_run_uumain(&args, uumain);
// Use C locale to avoid false positives, like in https://github.com/uutils/coreutils/issues/5378,
// because uutils expr doesn't support localization yet
// TODO remove once uutils expr supports localization
env::set_var("LC_COLLATE", "C");
// Run GNU expr with the provided arguments and compare the output
match run_gnu_cmd(CMD_PATH, &args[1..], true) {
Ok((gnu_output, gnu_exit_code)) => {
let gnu_output = gnu_output.trim().to_owned();
if uumain_exit_code != gnu_exit_code {
println!("Expression: {}", expr);
println!("Rust code: {}", uumain_exit_code);
println!("GNU code: {}", gnu_exit_code);
panic!("Different error codes");
}
if rust_output == gnu_output {
println!(
"Outputs matched for expression: {} => Result: {}",
expr, rust_output
);
} else {
println!("Expression: {}", expr);
println!("Rust output: {}", rust_output);
println!("GNU output: {}", gnu_output);
panic!("Different output between Rust & GNU");
}
}
Err(_) => {
println!("GNU expr execution failed for expression: {}", expr);
}
}
});

View file

@ -5,6 +5,6 @@ use uucore::parse_glob;
fuzz_target!(|data: &[u8]| { fuzz_target!(|data: &[u8]| {
if let Ok(s) = std::str::from_utf8(data) { if let Ok(s) = std::str::from_utf8(data) {
_ = parse_glob::from_str(s) _ = parse_glob::from_str(s);
} }
}); });

View file

@ -0,0 +1,234 @@
// This file is part of the uutils coreutils package.
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
// spell-checker:ignore STRINGSTRING INTEGERINTEGER FILEFILE
#![no_main]
use libfuzzer_sys::fuzz_target;
use uu_test::uumain;
use rand::seq::SliceRandom;
use rand::Rng;
use std::ffi::OsString;
mod fuzz_common;
use crate::fuzz_common::{generate_and_run_uumain, run_gnu_cmd};
#[derive(PartialEq, Debug, Clone)]
enum ArgType {
STRING,
STRINGSTRING,
INTEGER,
INTEGERINTEGER,
FILE,
FILEFILE,
// Add any other types as needed
}
static CMD_PATH: &str = "test";
fn generate_random_string(max_length: usize) -> String {
let mut rng = rand::thread_rng();
let valid_utf8: Vec<char> = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
.chars()
.collect();
let invalid_utf8 = [0xC3, 0x28]; // Invalid UTF-8 sequence
let mut result = String::new();
for _ in 0..rng.gen_range(1..=max_length) {
if rng.gen_bool(0.9) {
let ch = valid_utf8.choose(&mut rng).unwrap();
result.push(*ch);
} else {
let ch = invalid_utf8.choose(&mut rng).unwrap();
if let Some(c) = char::from_u32(*ch as u32) {
result.push(c);
}
}
}
result
}
#[derive(Debug, Clone)]
struct TestArg {
arg: String,
arg_type: ArgType,
}
fn generate_random_path(rng: &mut dyn rand::RngCore) -> &'static str {
match rng.gen_range(0..=3) {
0 => "/dev/null",
1 => "/dev/random",
2 => "/tmp",
_ => "/dev/urandom",
}
}
fn generate_test_args() -> Vec<TestArg> {
vec![
TestArg {
arg: "-z".to_string(),
arg_type: ArgType::STRING,
},
TestArg {
arg: "-n".to_string(),
arg_type: ArgType::STRING,
},
TestArg {
arg: "=".to_string(),
arg_type: ArgType::STRINGSTRING,
},
TestArg {
arg: "!=".to_string(),
arg_type: ArgType::STRINGSTRING,
},
TestArg {
arg: "-eq".to_string(),
arg_type: ArgType::INTEGERINTEGER,
},
TestArg {
arg: "-ne".to_string(),
arg_type: ArgType::INTEGERINTEGER,
},
TestArg {
arg: "-gt".to_string(),
arg_type: ArgType::INTEGERINTEGER,
},
TestArg {
arg: "-ge".to_string(),
arg_type: ArgType::INTEGERINTEGER,
},
TestArg {
arg: "-lt".to_string(),
arg_type: ArgType::INTEGERINTEGER,
},
TestArg {
arg: "-le".to_string(),
arg_type: ArgType::INTEGERINTEGER,
},
TestArg {
arg: "-f".to_string(),
arg_type: ArgType::FILE,
},
TestArg {
arg: "-d".to_string(),
arg_type: ArgType::FILE,
},
TestArg {
arg: "-e".to_string(),
arg_type: ArgType::FILE,
},
TestArg {
arg: "-ef".to_string(),
arg_type: ArgType::FILEFILE,
},
TestArg {
arg: "-nt".to_string(),
arg_type: ArgType::FILEFILE,
},
]
}
fn generate_test_arg() -> String {
let mut rng = rand::thread_rng();
let test_args = generate_test_args();
let mut arg = String::new();
let choice = rng.gen_range(0..=5);
match choice {
0 => {
arg.push_str(&rng.gen_range(-100..=100).to_string());
}
1..=3 => {
let test_arg = test_args
.choose(&mut rng)
.expect("Failed to choose a random test argument");
if test_arg.arg_type == ArgType::INTEGER {
arg.push_str(&format!(
"{} {} {}",
&rng.gen_range(-100..=100).to_string(),
test_arg.arg,
&rng.gen_range(-100..=100).to_string()
));
} else if test_arg.arg_type == ArgType::STRINGSTRING {
let random_str = generate_random_string(rng.gen_range(1..=10));
let random_str2 = generate_random_string(rng.gen_range(1..=10));
arg.push_str(&format!(
"{} {} {}",
&random_str, test_arg.arg, &random_str2
));
} else if test_arg.arg_type == ArgType::STRING {
let random_str = generate_random_string(rng.gen_range(1..=10));
arg.push_str(&format!("{} {}", test_arg.arg, &random_str));
} else if test_arg.arg_type == ArgType::FILEFILE {
let path = generate_random_path(&mut rng);
let path2 = generate_random_path(&mut rng);
arg.push_str(&format!("{} {} {}", path, test_arg.arg, path2));
} else if test_arg.arg_type == ArgType::FILE {
let path = generate_random_path(&mut rng);
arg.push_str(&format!("{} {}", test_arg.arg, path));
}
}
4 => {
let random_str = generate_random_string(rng.gen_range(1..=10));
arg.push_str(&random_str);
}
_ => {
let path = generate_random_path(&mut rng);
let file_test_args: Vec<TestArg> = test_args
.iter()
.filter(|ta| ta.arg_type == ArgType::FILE)
.cloned()
.collect();
if let Some(test_arg) = file_test_args.choose(&mut rng) {
arg.push_str(&format!("{}{}", test_arg.arg, path));
}
}
}
arg
}
fuzz_target!(|_data: &[u8]| {
let mut rng = rand::thread_rng();
let max_args = rng.gen_range(1..=6);
let mut args = vec![OsString::from("test")];
for _ in 0..max_args {
args.push(OsString::from(generate_test_arg()));
}
let (rust_output, uumain_exit_status) = generate_and_run_uumain(&args, uumain);
// Run GNU test with the provided arguments and compare the output
match run_gnu_cmd(CMD_PATH, &args[1..], false) {
Ok((gnu_output, gnu_exit_status)) => {
let gnu_output = gnu_output.trim().to_owned();
println!("gnu_exit_status {}", gnu_exit_status);
println!("uumain_exit_status {}", uumain_exit_status);
if rust_output != gnu_output || uumain_exit_status != gnu_exit_status {
println!("Discrepancy detected!");
println!("Test: {:?}", &args[1..]);
println!("My output: {}", rust_output);
println!("GNU output: {}", gnu_output);
println!("My exit status: {}", uumain_exit_status);
println!("GNU exit status: {}", gnu_exit_status);
panic!();
} else {
println!(
"Outputs and exit statuses matched for expression {:?}",
&args[1..]
);
}
}
Err(_) => {
println!("GNU test execution failed for expression {:?}", &args[1..]);
}
}
});

View file

@ -2,8 +2,13 @@
"project": { "project": {
"name": "uutils coreutils" "name": "uutils coreutils"
}, },
"build": {
"path_prefix": "coreutils"
},
"components": { "components": {
"changelog": true "changelog": {
"read_changelog_file": false
}
}, },
"styles": { "styles": {
"theme": "light", "theme": "light",

View file

@ -1,7 +1,5 @@
// This file is part of the uutils coreutils package. // This file is part of the uutils coreutils package.
// //
// (c) Michael Gehring <mg@ebfe.org>
//
// For the full copyright and license information, please view the LICENSE // For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code. // file that was distributed with this source code.

View file

@ -42,6 +42,7 @@ fn main() -> io::Result<()> {
[Introduction](index.md)\n\ [Introduction](index.md)\n\
* [Installation](installation.md)\n\ * [Installation](installation.md)\n\
* [Build from source](build.md)\n\ * [Build from source](build.md)\n\
* [Platform support](platforms.md)\n\
* [Contributing](contributing.md)\n\ * [Contributing](contributing.md)\n\
* [GNU test coverage](test_coverage.md)\n\ * [GNU test coverage](test_coverage.md)\n\
* [Extensions](extensions.md)\n\ * [Extensions](extensions.md)\n\
@ -53,7 +54,7 @@ fn main() -> io::Result<()> {
println!("Gathering utils per platform"); println!("Gathering utils per platform");
let utils_per_platform = { let utils_per_platform = {
let mut map = HashMap::new(); let mut map = HashMap::new();
for platform in ["unix", "macos", "windows"] { for platform in ["unix", "macos", "windows", "unix_android"] {
let platform_utils: Vec<String> = String::from_utf8( let platform_utils: Vec<String> = String::from_utf8(
std::process::Command::new("./util/show-utils.sh") std::process::Command::new("./util/show-utils.sh")
.arg(format!("--features=feat_os_{}", platform)) .arg(format!("--features=feat_os_{}", platform))
@ -61,6 +62,7 @@ fn main() -> io::Result<()> {
.stdout, .stdout,
) )
.unwrap() .unwrap()
.trim()
.split(' ') .split(' ')
.map(ToString::to_string) .map(ToString::to_string)
.collect(); .collect();
@ -75,6 +77,7 @@ fn main() -> io::Result<()> {
.stdout, .stdout,
) )
.unwrap() .unwrap()
.trim()
.split(' ') .split(' ')
.map(ToString::to_string) .map(ToString::to_string)
.collect(); .collect();
@ -83,9 +86,47 @@ fn main() -> io::Result<()> {
map map
}; };
println!("Writing to utils");
let mut utils = utils.entries().collect::<Vec<_>>(); let mut utils = utils.entries().collect::<Vec<_>>();
utils.sort(); utils.sort();
println!("Writing util per platform table");
{
let mut platform_table_file = File::create("docs/src/platform_table.md").unwrap();
// sum, cksum, b2sum, etc. are all available on all platforms, but not in the data structure
// otherwise, we check the map for the util name.
let check_supported = |name: &str, platform: &str| {
if name.ends_with("sum") || utils_per_platform[platform].iter().any(|u| u == name) {
""
} else {
" "
}
};
writeln!(
platform_table_file,
"| util | Linux | macOS | Windows | FreeBSD | Android |\n\
| ---------------- | ----- | ----- | ------- | ------- | ------- |"
)?;
for (&name, _) in &utils {
if name == "[" {
continue;
}
// The alignment is not necessary, but makes the output a bit more
// pretty when viewed as plain markdown.
writeln!(
platform_table_file,
"| {:<16} | {:<5} | {:<5} | {:<7} | {:<7} | {:<7} |",
format!("**{name}**"),
check_supported(name, "linux"),
check_supported(name, "macos"),
check_supported(name, "windows"),
check_supported(name, "unix"),
check_supported(name, "unix_android"),
)?;
}
}
println!("Writing to utils");
for (&name, (_, command)) in utils { for (&name, (_, command)) in utils {
if name == "[" { if name == "[" {
continue; continue;

View file

@ -1,6 +1,6 @@
[package] [package]
name = "uu_arch" name = "uu_arch"
version = "0.0.20" version = "0.0.22"
authors = ["uutils developers"] authors = ["uutils developers"]
license = "MIT" license = "MIT"
description = "arch ~ (uutils) display machine architecture" description = "arch ~ (uutils) display machine architecture"

View file

@ -1,8 +1,5 @@
// This file is part of the uutils coreutils package. // This file is part of the uutils coreutils package.
// //
// (c) Smigle00 <smigle00@gmail.com>
// (c) Jian Zeng <anonymousknight96 AT gmail.com>
//
// For the full copyright and license information, please view the LICENSE // For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code. // file that was distributed with this source code.

View file

@ -1,6 +1,6 @@
[package] [package]
name = "uu_base32" name = "uu_base32"
version = "0.0.20" version = "0.0.22"
authors = ["uutils developers"] authors = ["uutils developers"]
license = "MIT" license = "MIT"
description = "base32 ~ (uutils) decode/encode input (base32-encoding)" description = "base32 ~ (uutils) decode/encode input (base32-encoding)"

View file

@ -1,9 +1,7 @@
// This file is part of the uutils coreutils package. // This file is part of the uutils coreutils package.
// //
// (c) Jian Zeng <anonymousknight96@gmail.com> // For the full copyright and license information, please view the LICENSE
// // file that was distributed with this source code.
// For the full copyright and license information, please view the LICENSE file
// that was distributed with this source code.
use std::io::{stdin, Read}; use std::io::{stdin, Read};

View file

@ -1,11 +1,7 @@
// This file is part of the uutils coreutils package. // This file is part of the uutils coreutils package.
// //
// (c) Jordy Dickinson <jordy.dickinson@gmail.com> // For the full copyright and license information, please view the LICENSE
// (c) Jian Zeng <anonymousknight96@gmail.com> // file that was distributed with this source code.
// (c) Alex Lyon <arcterus@mail.com>
//
// For the full copyright and license information, please view the LICENSE file
// that was distributed with this source code.
use std::io::{stdout, Read, Write}; use std::io::{stdout, Read, Write};

View file

@ -1,6 +1,6 @@
[package] [package]
name = "uu_base64" name = "uu_base64"
version = "0.0.20" version = "0.0.22"
authors = ["uutils developers"] authors = ["uutils developers"]
license = "MIT" license = "MIT"
description = "base64 ~ (uutils) decode/encode input (base64-encoding)" description = "base64 ~ (uutils) decode/encode input (base64-encoding)"

View file

@ -1,10 +1,7 @@
// This file is part of the uutils coreutils package. // This file is part of the uutils coreutils package.
// //
// (c) Jordy Dickinson <jordy.dickinson@gmail.com> // For the full copyright and license information, please view the LICENSE
// (c) Jian Zeng <anonymousknight96@gmail.com> // file that was distributed with this source code.
//
// For the full copyright and license information, please view the LICENSE file
// that was distributed with this source code.
use uu_base32::base_common; use uu_base32::base_common;
pub use uu_base32::uu_app; pub use uu_base32::uu_app;

View file

@ -1,6 +1,6 @@
[package] [package]
name = "uu_basename" name = "uu_basename"
version = "0.0.20" version = "0.0.22"
authors = ["uutils developers"] authors = ["uutils developers"]
license = "MIT" license = "MIT"
description = "basename ~ (uutils) display PATHNAME with leading directory components removed" description = "basename ~ (uutils) display PATHNAME with leading directory components removed"

View file

@ -1,7 +1,5 @@
// This file is part of the uutils coreutils package. // This file is part of the uutils coreutils package.
// //
// (c) Jimmy Lu <jimmy.lu.2011@gmail.com>
//
// For the full copyright and license information, please view the LICENSE // For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code. // file that was distributed with this source code.
@ -11,6 +9,7 @@ use clap::{crate_version, Arg, ArgAction, Command};
use std::path::{is_separator, PathBuf}; use std::path::{is_separator, PathBuf};
use uucore::display::Quotable; use uucore::display::Quotable;
use uucore::error::{UResult, UUsageError}; use uucore::error::{UResult, UUsageError};
use uucore::line_ending::LineEnding;
use uucore::{format_usage, help_about, help_usage}; use uucore::{format_usage, help_about, help_usage};
static ABOUT: &str = help_about!("basename.md"); static ABOUT: &str = help_about!("basename.md");
@ -54,9 +53,10 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
return Err(UUsageError::new(1, "missing operand".to_string())); return Err(UUsageError::new(1, "missing operand".to_string()));
} }
let line_ending = LineEnding::from_zero_flag(matches.get_flag(options::ZERO));
let opt_suffix = matches.get_one::<String>(options::SUFFIX).is_some(); let opt_suffix = matches.get_one::<String>(options::SUFFIX).is_some();
let opt_multiple = matches.get_flag(options::MULTIPLE); let opt_multiple = matches.get_flag(options::MULTIPLE);
let opt_zero = matches.get_flag(options::ZERO);
let multiple_paths = opt_suffix || opt_multiple; let multiple_paths = opt_suffix || opt_multiple;
let name_args_count = matches let name_args_count = matches
.get_many::<String>(options::NAME) .get_many::<String>(options::NAME)
@ -105,7 +105,6 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
.collect() .collect()
}; };
let line_ending = if opt_zero { "\0" } else { "\n" };
for path in paths { for path in paths {
print!("{}{}", basename(path, suffix), line_ending); print!("{}{}", basename(path, suffix), line_ending);
} }

View file

@ -1,6 +1,6 @@
[package] [package]
name = "uu_basenc" name = "uu_basenc"
version = "0.0.20" version = "0.0.22"
authors = ["uutils developers"] authors = ["uutils developers"]
license = "MIT" license = "MIT"
description = "basenc ~ (uutils) decode/encode input" description = "basenc ~ (uutils) decode/encode input"

View file

@ -1,10 +1,7 @@
// This file is part of the uutils coreutils package. // This file is part of the uutils coreutils package.
// //
// (c) Jordy Dickinson <jordy.dickinson@gmail.com> // For the full copyright and license information, please view the LICENSE
// (c) Jian Zeng <anonymousknight96@gmail.com> // file that was distributed with this source code.
//
// For the full copyright and license information, please view the LICENSE file
// that was distributed with this source code.
//spell-checker:ignore (args) lsbf msbf //spell-checker:ignore (args) lsbf msbf

View file

@ -1,6 +1,6 @@
[package] [package]
name = "uu_cat" name = "uu_cat"
version = "0.0.20" version = "0.0.22"
authors = ["uutils developers"] authors = ["uutils developers"]
license = "MIT" license = "MIT"
description = "cat ~ (uutils) concatenate and display input" description = "cat ~ (uutils) concatenate and display input"
@ -17,7 +17,6 @@ path = "src/cat.rs"
[dependencies] [dependencies]
clap = { workspace = true } clap = { workspace = true }
thiserror = { workspace = true } thiserror = { workspace = true }
is-terminal = { workspace = true }
uucore = { workspace = true, features = ["fs", "pipes"] } uucore = { workspace = true, features = ["fs", "pipes"] }
[target.'cfg(unix)'.dependencies] [target.'cfg(unix)'.dependencies]

View file

@ -1,10 +1,5 @@
// This file is part of the uutils coreutils package. // This file is part of the uutils coreutils package.
// //
// (c) Jordi Boggiano <j.boggiano@seld.be>
// (c) Evgeniy Klyuchikov <evgeniy.klyuchikov@gmail.com>
// (c) Joshua S. Miller <jsmiller@uchicago.edu>
// (c) Árni Dagur <arni@dagur.eu>
//
// For the full copyright and license information, please view the LICENSE // For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code. // file that was distributed with this source code.
@ -12,9 +7,8 @@
// last synced with: cat (GNU coreutils) 8.13 // last synced with: cat (GNU coreutils) 8.13
use clap::{crate_version, Arg, ArgAction, Command}; use clap::{crate_version, Arg, ArgAction, Command};
use is_terminal::IsTerminal;
use std::fs::{metadata, File}; use std::fs::{metadata, File};
use std::io::{self, Read, Write}; use std::io::{self, IsTerminal, Read, Write};
use thiserror::Error; use thiserror::Error;
use uucore::display::Quotable; use uucore::display::Quotable;
use uucore::error::UResult; use uucore::error::UResult;
@ -192,7 +186,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
NumberingMode::None NumberingMode::None
}; };
let show_nonprint = vec![ let show_nonprint = [
options::SHOW_ALL.to_owned(), options::SHOW_ALL.to_owned(),
options::SHOW_NONPRINTING_ENDS.to_owned(), options::SHOW_NONPRINTING_ENDS.to_owned(),
options::SHOW_NONPRINTING_TABS.to_owned(), options::SHOW_NONPRINTING_TABS.to_owned(),
@ -201,7 +195,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
.iter() .iter()
.any(|v| matches.get_flag(v)); .any(|v| matches.get_flag(v));
let show_ends = vec![ let show_ends = [
options::SHOW_ENDS.to_owned(), options::SHOW_ENDS.to_owned(),
options::SHOW_ALL.to_owned(), options::SHOW_ALL.to_owned(),
options::SHOW_NONPRINTING_ENDS.to_owned(), options::SHOW_NONPRINTING_ENDS.to_owned(),
@ -209,7 +203,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
.iter() .iter()
.any(|v| matches.get_flag(v)); .any(|v| matches.get_flag(v));
let show_tabs = vec![ let show_tabs = [
options::SHOW_ALL.to_owned(), options::SHOW_ALL.to_owned(),
options::SHOW_TABS.to_owned(), options::SHOW_TABS.to_owned(),
options::SHOW_NONPRINTING_TABS.to_owned(), options::SHOW_NONPRINTING_TABS.to_owned(),

View file

@ -1,3 +1,7 @@
// This file is part of the uutils coreutils package.
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
use super::{CatResult, FdReadable, InputHandle}; use super::{CatResult, FdReadable, InputHandle};
use nix::unistd; use nix::unistd;
@ -17,7 +21,7 @@ const BUF_SIZE: usize = 1024 * 16;
/// copying or not. False means we don't have to. /// copying or not. False means we don't have to.
#[inline] #[inline]
pub(super) fn write_fast_using_splice<R: FdReadable>( pub(super) fn write_fast_using_splice<R: FdReadable>(
handle: &mut InputHandle<R>, handle: &InputHandle<R>,
write_fd: &impl AsRawFd, write_fd: &impl AsRawFd,
) -> CatResult<bool> { ) -> CatResult<bool> {
let (pipe_rd, pipe_wr) = pipe()?; let (pipe_rd, pipe_wr) = pipe()?;

View file

@ -1,6 +1,6 @@
[package] [package]
name = "uu_chcon" name = "uu_chcon"
version = "0.0.20" version = "0.0.22"
authors = ["uutils developers"] authors = ["uutils developers"]
license = "MIT" license = "MIT"
description = "chcon ~ (uutils) change file security context" description = "chcon ~ (uutils) change file security context"

View file

@ -1,3 +1,7 @@
// This file is part of the uutils coreutils package.
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
// spell-checker:ignore (vars) RFILE // spell-checker:ignore (vars) RFILE
#![allow(clippy::upper_case_acronyms)] #![allow(clippy::upper_case_acronyms)]

View file

@ -1,3 +1,7 @@
// This file is part of the uutils coreutils package.
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
use std::ffi::OsString; use std::ffi::OsString;
use std::fmt::Write; use std::fmt::Write;
use std::io; use std::io;

View file

@ -1,3 +1,7 @@
// This file is part of the uutils coreutils package.
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
use std::ffi::{CStr, CString, OsStr}; use std::ffi::{CStr, CString, OsStr};
use std::marker::PhantomData; use std::marker::PhantomData;
use std::os::raw::{c_int, c_long, c_short}; use std::os::raw::{c_int, c_long, c_short};

View file

@ -1,6 +1,6 @@
[package] [package]
name = "uu_chgrp" name = "uu_chgrp"
version = "0.0.20" version = "0.0.22"
authors = ["uutils developers"] authors = ["uutils developers"]
license = "MIT" license = "MIT"
description = "chgrp ~ (uutils) change the group ownership of FILE" description = "chgrp ~ (uutils) change the group ownership of FILE"

View file

@ -1,7 +1,5 @@
// This file is part of the uutils coreutils package. // This file is part of the uutils coreutils package.
// //
// (c) Jian Zeng <anonymousknight96@gmail.com>
//
// For the full copyright and license information, please view the LICENSE // For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code. // file that was distributed with this source code.

View file

@ -1,6 +1,6 @@
[package] [package]
name = "uu_chmod" name = "uu_chmod"
version = "0.0.20" version = "0.0.22"
authors = ["uutils developers"] authors = ["uutils developers"]
license = "MIT" license = "MIT"
description = "chmod ~ (uutils) change mode of FILE" description = "chmod ~ (uutils) change mode of FILE"

View file

@ -1,7 +1,5 @@
// This file is part of the uutils coreutils package. // This file is part of the uutils coreutils package.
// //
// (c) Alex Lyon <arcterus@mail.com>
//
// For the full copyright and license information, please view the LICENSE // For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code. // file that was distributed with this source code.
@ -337,9 +335,7 @@ impl Chmoder {
let mut new_mode = fperm; let mut new_mode = fperm;
let mut naively_expected_new_mode = new_mode; let mut naively_expected_new_mode = new_mode;
for mode in cmode_unwrapped.split(',') { for mode in cmode_unwrapped.split(',') {
// cmode is guaranteed to be Some in this case let result = if mode.chars().any(|c| c.is_ascii_digit()) {
let arr: &[char] = &['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
let result = if mode.contains(arr) {
mode::parse_numeric(new_mode, mode, file.is_dir()).map(|v| (v, v)) mode::parse_numeric(new_mode, mode, file.is_dir()).map(|v| (v, v))
} else { } else {
mode::parse_symbolic(new_mode, mode, get_umask(), file.is_dir()).map(|m| { mode::parse_symbolic(new_mode, mode, get_umask(), file.is_dir()).map(|m| {
@ -354,20 +350,22 @@ impl Chmoder {
(m, naive_mode) (m, naive_mode)
}) })
}; };
match result { match result {
Ok((mode, naive_mode)) => { Ok((mode, naive_mode)) => {
new_mode = mode; new_mode = mode;
naively_expected_new_mode = naive_mode; naively_expected_new_mode = naive_mode;
} }
Err(f) => { Err(f) => {
if self.quiet { return if self.quiet {
return Err(ExitCode::new(1)); Err(ExitCode::new(1))
} else { } else {
return Err(USimpleError::new(1, f)); Err(USimpleError::new(1, f))
} };
} }
} }
} }
self.change_file(fperm, new_mode, file)?; self.change_file(fperm, new_mode, file)?;
// if a permission would have been removed if umask was 0, but it wasn't because umask was not 0, print an error and fail // if a permission would have been removed if umask was 0, but it wasn't because umask was not 0, print an error and fail
if (new_mode & !naively_expected_new_mode) != 0 { if (new_mode & !naively_expected_new_mode) != 0 {
@ -438,25 +436,25 @@ mod tests {
fn test_extract_negative_modes() { fn test_extract_negative_modes() {
// "chmod -w -r file" becomes "chmod -w,-r file". clap does not accept "-w,-r" as MODE. // "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. // Therefore, "w" is added as pseudo mode to pass clap.
let (c, a) = extract_negative_modes(vec!["-w", "-r", "file"].iter().map(OsString::from)); let (c, a) = extract_negative_modes(["-w", "-r", "file"].iter().map(OsString::from));
assert_eq!(c, Some("-w,-r".to_string())); assert_eq!(c, Some("-w,-r".to_string()));
assert_eq!(a, vec!["w", "file"]); assert_eq!(a, ["w", "file"]);
// "chmod -w file -r" becomes "chmod -w,-r file". clap does not accept "-w,-r" as MODE. // "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. // Therefore, "w" is added as pseudo mode to pass clap.
let (c, a) = extract_negative_modes(vec!["-w", "file", "-r"].iter().map(OsString::from)); let (c, a) = extract_negative_modes(["-w", "file", "-r"].iter().map(OsString::from));
assert_eq!(c, Some("-w,-r".to_string())); assert_eq!(c, Some("-w,-r".to_string()));
assert_eq!(a, vec!["w", "file"]); assert_eq!(a, ["w", "file"]);
// "chmod -w -- -r file" becomes "chmod -w -r file", where "-r" is interpreted as file. // "chmod -w -- -r file" becomes "chmod -w -r file", where "-r" is interpreted as file.
// Again, "w" is needed as pseudo mode. // Again, "w" is needed as pseudo mode.
let (c, a) = extract_negative_modes(vec!["-w", "--", "-r", "f"].iter().map(OsString::from)); let (c, a) = extract_negative_modes(["-w", "--", "-r", "f"].iter().map(OsString::from));
assert_eq!(c, Some("-w".to_string())); assert_eq!(c, Some("-w".to_string()));
assert_eq!(a, vec!["w", "--", "-r", "f"]); assert_eq!(a, ["w", "--", "-r", "f"]);
// "chmod -- -r file" becomes "chmod -r file". // "chmod -- -r file" becomes "chmod -r file".
let (c, a) = extract_negative_modes(vec!["--", "-r", "file"].iter().map(OsString::from)); let (c, a) = extract_negative_modes(["--", "-r", "file"].iter().map(OsString::from));
assert_eq!(c, None); assert_eq!(c, None);
assert_eq!(a, vec!["--", "-r", "file"]); assert_eq!(a, ["--", "-r", "file"]);
} }
} }

View file

@ -1,6 +1,6 @@
[package] [package]
name = "uu_chown" name = "uu_chown"
version = "0.0.20" version = "0.0.22"
authors = ["uutils developers"] authors = ["uutils developers"]
license = "MIT" license = "MIT"
description = "chown ~ (uutils) change the ownership of FILE" description = "chown ~ (uutils) change the ownership of FILE"

View file

@ -1,7 +1,5 @@
// This file is part of the uutils coreutils package. // This file is part of the uutils coreutils package.
// //
// (c) Jian Zeng <anonymousknight96@gmail.com>
//
// For the full copyright and license information, please view the LICENSE // For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code. // file that was distributed with this source code.
@ -197,6 +195,53 @@ pub fn uu_app() -> Command {
) )
} }
/// Parses the user string to extract the UID.
fn parse_uid(user: &str, spec: &str, sep: char) -> UResult<Option<u32>> {
if user.is_empty() {
return Ok(None);
}
match Passwd::locate(user) {
Ok(u) => Ok(Some(u.uid)), // We have been able to get the uid
Err(_) => {
// we have NOT been able to find the uid
// but we could be in the case where we have user.group
if spec.contains('.') && !spec.contains(':') && sep == ':' {
// but the input contains a '.' but not a ':'
// we might have something like username.groupname
// So, try to parse it this way
parse_spec(spec, '.').map(|(uid, _)| uid)
} else {
// It's possible that the `user` string contains a
// numeric user ID, in which case, we respect that.
match user.parse() {
Ok(uid) => Ok(Some(uid)),
Err(_) => Err(USimpleError::new(
1,
format!("invalid user: {}", spec.quote()),
)),
}
}
}
}
}
/// Parses the group string to extract the GID.
fn parse_gid(group: &str, spec: &str) -> UResult<Option<u32>> {
if group.is_empty() {
return Ok(None);
}
match Group::locate(group) {
Ok(g) => Ok(Some(g.gid)),
Err(_) => match group.parse() {
Ok(gid) => Ok(Some(gid)),
Err(_) => Err(USimpleError::new(
1,
format!("invalid group: {}", spec.quote()),
)),
},
}
}
/// Parse the owner/group specifier string into a user ID and a group ID. /// Parse the owner/group specifier string into a user ID and a group ID.
/// ///
/// The `spec` can be of the form: /// The `spec` can be of the form:
@ -215,52 +260,8 @@ fn parse_spec(spec: &str, sep: char) -> UResult<(Option<u32>, Option<u32>)> {
let user = args.next().unwrap_or(""); let user = args.next().unwrap_or("");
let group = args.next().unwrap_or(""); let group = args.next().unwrap_or("");
let uid = if user.is_empty() { let uid = parse_uid(user, spec, sep)?;
None let gid = parse_gid(group, spec)?;
} else {
Some(match Passwd::locate(user) {
Ok(u) => u.uid, // We have been able to get the uid
Err(_) =>
// we have NOT been able to find the uid
// but we could be in the case where we have user.group
{
if spec.contains('.') && !spec.contains(':') && sep == ':' {
// but the input contains a '.' but not a ':'
// we might have something like username.groupname
// So, try to parse it this way
return parse_spec(spec, '.');
} else {
// It's possible that the `user` string contains a
// numeric user ID, in which case, we respect that.
match user.parse() {
Ok(uid) => uid,
Err(_) => {
return Err(USimpleError::new(
1,
format!("invalid user: {}", spec.quote()),
))
}
}
}
}
})
};
let gid = if group.is_empty() {
None
} else {
Some(match Group::locate(group) {
Ok(g) => g.gid,
Err(_) => match group.parse() {
Ok(gid) => gid,
Err(_) => {
return Err(USimpleError::new(
1,
format!("invalid group: {}", spec.quote()),
));
}
},
})
};
if user.chars().next().map(char::is_numeric).unwrap_or(false) if user.chars().next().map(char::is_numeric).unwrap_or(false)
&& group.is_empty() && group.is_empty()

View file

@ -1,6 +1,6 @@
[package] [package]
name = "uu_chroot" name = "uu_chroot"
version = "0.0.20" version = "0.0.22"
authors = ["uutils developers"] authors = ["uutils developers"]
license = "MIT" license = "MIT"
description = "chroot ~ (uutils) run COMMAND under a new root directory" description = "chroot ~ (uutils) run COMMAND under a new root directory"

View file

@ -1,8 +1,5 @@
// This file is part of the uutils coreutils package. // This file is part of the uutils coreutils package.
// //
// (c) Vsevolod Velichko <torkvemada@sorokdva.net>
// (c) Jian Zeng <anonymousknight96 AT gmail.com>
//
// For the full copyright and license information, please view the LICENSE // For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code. // file that was distributed with this source code.

View file

@ -1,7 +1,7 @@
// * This file is part of the uutils coreutils package. // This file is part of the uutils coreutils package.
// * //
// * For the full copyright and license information, please view the LICENSE // For the full copyright and license information, please view the LICENSE
// * file that was distributed with this source code. // file that was distributed with this source code.
// spell-checker:ignore NEWROOT Userspec userspec // spell-checker:ignore NEWROOT Userspec userspec
//! Errors returned by chroot. //! Errors returned by chroot.
use std::fmt::Display; use std::fmt::Display;

View file

@ -1,6 +1,6 @@
[package] [package]
name = "uu_cksum" name = "uu_cksum"
version = "0.0.20" version = "0.0.22"
authors = ["uutils developers"] authors = ["uutils developers"]
license = "MIT" license = "MIT"
description = "cksum ~ (uutils) display CRC and size of input" description = "cksum ~ (uutils) display CRC and size of input"

View file

@ -1,8 +1,6 @@
// This file is part of the uutils coreutils package. // This file is part of the uutils coreutils package.
// //
// (c) Michael Gehring <mg@ebfe.org> // For the full copyright and license information, please view the LICENSE
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code. // file that was distributed with this source code.
// spell-checker:ignore (ToDO) fname, algo // spell-checker:ignore (ToDO) fname, algo
@ -209,8 +207,7 @@ fn digest_read<T: Read>(
Ok((digest.result_str(), output_size)) Ok((digest.result_str(), output_size))
} else { } else {
// Assume it's SHAKE. result_str() doesn't work with shake (as of 8/30/2016) // Assume it's SHAKE. result_str() doesn't work with shake (as of 8/30/2016)
let mut bytes = Vec::new(); let mut bytes = vec![0; (output_bits + 7) / 8];
bytes.resize((output_bits + 7) / 8, 0);
digest.hash_finalize(&mut bytes); digest.hash_finalize(&mut bytes);
Ok((encode(bytes), output_size)) Ok((encode(bytes), output_size))
} }

View file

@ -1,6 +1,6 @@
[package] [package]
name = "uu_comm" name = "uu_comm"
version = "0.0.20" version = "0.0.22"
authors = ["uutils developers"] authors = ["uutils developers"]
license = "MIT" license = "MIT"
description = "comm ~ (uutils) compare sorted inputs" description = "comm ~ (uutils) compare sorted inputs"

View file

@ -1,18 +1,16 @@
// This file is part of the uutils coreutils package. // This file is part of the uutils coreutils package.
// //
// (c) Michael Gehring <mg@ebfe.org>
//
// For the full copyright and license information, please view the LICENSE // For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code. // file that was distributed with this source code.
// spell-checker:ignore (ToDO) delim mkdelim // spell-checker:ignore (ToDO) delim mkdelim
use std::cmp::Ordering; use std::cmp::Ordering;
use std::fmt::Display;
use std::fs::File; use std::fs::File;
use std::io::{self, stdin, BufRead, BufReader, Stdin}; use std::io::{self, stdin, BufRead, BufReader, Stdin};
use std::path::Path; use std::path::Path;
use uucore::error::{FromIo, UResult}; use uucore::error::{FromIo, UResult};
use uucore::line_ending::LineEnding;
use uucore::{format_usage, help_about, help_usage}; use uucore::{format_usage, help_about, help_usage};
use clap::{crate_version, Arg, ArgAction, ArgMatches, Command}; use clap::{crate_version, Arg, ArgAction, ArgMatches, Command};
@ -32,46 +30,6 @@ mod options {
pub const ZERO_TERMINATED: &str = "zero-terminated"; pub const ZERO_TERMINATED: &str = "zero-terminated";
} }
fn column_width(col: &str, opts: &ArgMatches) -> usize {
if opts.get_flag(col) {
0
} else {
1
}
}
#[repr(u8)]
#[derive(Clone, Copy)]
enum LineEnding {
Newline = b'\n',
Nul = 0,
}
impl From<LineEnding> for u8 {
fn from(line_ending: LineEnding) -> Self {
line_ending as Self
}
}
impl From<bool> for LineEnding {
fn from(is_zero_terminated: bool) -> Self {
if is_zero_terminated {
Self::Nul
} else {
Self::Newline
}
}
}
impl Display for LineEnding {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Newline => writeln!(f),
Self::Nul => write!(f, "\0"),
}
}
}
enum Input { enum Input {
Stdin(Stdin), Stdin(Stdin),
FileIn(BufReader<File>), FileIn(BufReader<File>),
@ -109,8 +67,8 @@ fn comm(a: &mut LineReader, b: &mut LineReader, opts: &ArgMatches) {
delim => delim, delim => delim,
}; };
let width_col_1 = column_width(options::COLUMN_1, opts); let width_col_1 = usize::from(!opts.get_flag(options::COLUMN_1));
let width_col_2 = column_width(options::COLUMN_2, opts); let width_col_2 = usize::from(!opts.get_flag(options::COLUMN_2));
let delim_col_2 = delim.repeat(width_col_1); let delim_col_2 = delim.repeat(width_col_1);
let delim_col_3 = delim.repeat(width_col_1 + width_col_2); let delim_col_3 = delim.repeat(width_col_1 + width_col_2);
@ -168,7 +126,7 @@ fn comm(a: &mut LineReader, b: &mut LineReader, opts: &ArgMatches) {
} }
if opts.get_flag(options::TOTAL) { if opts.get_flag(options::TOTAL) {
let line_ending = LineEnding::from(opts.get_flag(options::ZERO_TERMINATED)); let line_ending = LineEnding::from_zero_flag(opts.get_flag(options::ZERO_TERMINATED));
print!("{total_col_1}{delim}{total_col_2}{delim}{total_col_3}{delim}total{line_ending}"); print!("{total_col_1}{delim}{total_col_2}{delim}{total_col_3}{delim}total{line_ending}");
} }
} }
@ -190,7 +148,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let args = args.collect_lossy(); let args = args.collect_lossy();
let matches = uu_app().try_get_matches_from(args)?; let matches = uu_app().try_get_matches_from(args)?;
let line_ending = LineEnding::from(matches.get_flag(options::ZERO_TERMINATED)); let line_ending = LineEnding::from_zero_flag(matches.get_flag(options::ZERO_TERMINATED));
let filename1 = matches.get_one::<String>(options::FILE_1).unwrap(); let filename1 = matches.get_one::<String>(options::FILE_1).unwrap();
let filename2 = matches.get_one::<String>(options::FILE_2).unwrap(); let filename2 = matches.get_one::<String>(options::FILE_2).unwrap();
let mut f1 = open_file(filename1, line_ending).map_err_context(|| filename1.to_string())?; let mut f1 = open_file(filename1, line_ending).map_err_context(|| filename1.to_string())?;

View file

@ -1,6 +1,6 @@
[package] [package]
name = "uu_cp" name = "uu_cp"
version = "0.0.20" version = "0.0.22"
authors = [ authors = [
"Jordy Dickinson <jordy.dickinson@gmail.com>", "Jordy Dickinson <jordy.dickinson@gmail.com>",
"Joshua S. Miller <jsmiller@uchicago.edu>", "Joshua S. Miller <jsmiller@uchicago.edu>",
@ -24,7 +24,14 @@ filetime = { workspace = true }
libc = { workspace = true } libc = { workspace = true }
quick-error = { workspace = true } quick-error = { workspace = true }
selinux = { workspace = true, optional = true } selinux = { workspace = true, optional = true }
uucore = { workspace = true, features = ["entries", "fs", "perms", "mode"] } uucore = { workspace = true, features = [
"backup-control",
"entries",
"fs",
"perms",
"mode",
"update-control",
] }
walkdir = { workspace = true } walkdir = { workspace = true }
indicatif = { workspace = true } indicatif = { workspace = true }

View file

@ -1,14 +1,14 @@
// * This file is part of the uutils coreutils package. // This file is part of the uutils coreutils package.
// * //
// * For the full copyright and license information, please view the LICENSE // For the full copyright and license information, please view the LICENSE
// * file that was distributed with this source code. // file that was distributed with this source code.
// spell-checker:ignore TODO canonicalizes direntry pathbuf symlinked // spell-checker:ignore TODO canonicalizes direntry pathbuf symlinked
//! Recursively copy the contents of a directory. //! Recursively copy the contents of a directory.
//! //!
//! See the [`copy_directory`] function for more information. //! See the [`copy_directory`] function for more information.
#[cfg(windows)] #[cfg(windows)]
use std::borrow::Cow; use std::borrow::Cow;
use std::collections::HashSet; use std::collections::{HashMap, HashSet};
use std::env; use std::env;
use std::fs; use std::fs;
use std::io; use std::io;
@ -24,8 +24,8 @@ use uucore::uio_error;
use walkdir::{DirEntry, WalkDir}; use walkdir::{DirEntry, WalkDir};
use crate::{ use crate::{
aligned_ancestors, context_for, copy_attributes, copy_file, copy_link, preserve_hardlinks, aligned_ancestors, context_for, copy_attributes, copy_file, copy_link, CopyResult, Error,
CopyResult, Error, Options, TargetSlice, Options,
}; };
/// Ensure a Windows path starts with a `\\?`. /// Ensure a Windows path starts with a `\\?`.
@ -33,8 +33,8 @@ use crate::{
fn adjust_canonicalization(p: &Path) -> Cow<Path> { fn adjust_canonicalization(p: &Path) -> Cow<Path> {
// In some cases, \\? can be missing on some Windows paths. Add it at the // In some cases, \\? can be missing on some Windows paths. Add it at the
// beginning unless the path is prefixed with a device namespace. // beginning unless the path is prefixed with a device namespace.
const VERBATIM_PREFIX: &str = r#"\\?"#; const VERBATIM_PREFIX: &str = r"\\?";
const DEVICE_NS_PREFIX: &str = r#"\\."#; const DEVICE_NS_PREFIX: &str = r"\\.";
let has_prefix = p let has_prefix = p
.components() .components()
@ -200,7 +200,7 @@ fn copy_direntry(
options: &Options, options: &Options,
symlinked_files: &mut HashSet<FileInformation>, symlinked_files: &mut HashSet<FileInformation>,
preserve_hard_links: bool, preserve_hard_links: bool,
hard_links: &mut Vec<(String, u64)>, copied_files: &mut HashMap<FileInformation, PathBuf>,
) -> CopyResult<()> { ) -> CopyResult<()> {
let Entry { let Entry {
source_absolute, source_absolute,
@ -240,30 +240,27 @@ fn copy_direntry(
// If the source is not a directory, then we need to copy the file. // If the source is not a directory, then we need to copy the file.
if !source_absolute.is_dir() { if !source_absolute.is_dir() {
if preserve_hard_links { if preserve_hard_links {
let dest = local_to_target.as_path().to_path_buf(); match copy_file(
let found_hard_link = preserve_hardlinks(hard_links, &source_absolute, &dest)?; progress_bar,
if !found_hard_link { &source_absolute,
match copy_file( local_to_target.as_path(),
progress_bar, options,
&source_absolute, symlinked_files,
local_to_target.as_path(), copied_files,
options, false,
symlinked_files, ) {
false, Ok(_) => Ok(()),
) { Err(err) => {
Ok(_) => Ok(()), if source_absolute.is_symlink() {
Err(err) => { // silent the error with a symlink
if source_absolute.is_symlink() { // In case we do --archive, we might copy the symlink
// silent the error with a symlink // before the file itself
// In case we do --archive, we might copy the symlink Ok(())
// before the file itself } else {
Ok(()) Err(err)
} else {
Err(err)
}
} }
}?; }
} }?;
} else { } else {
// At this point, `path` is just a plain old file. // At this point, `path` is just a plain old file.
// Terminate this function immediately if there is any // Terminate this function immediately if there is any
@ -277,6 +274,7 @@ fn copy_direntry(
local_to_target.as_path(), local_to_target.as_path(),
options, options,
symlinked_files, symlinked_files,
copied_files,
false, false,
) { ) {
Ok(_) => {} Ok(_) => {}
@ -307,9 +305,10 @@ fn copy_direntry(
pub(crate) fn copy_directory( pub(crate) fn copy_directory(
progress_bar: &Option<ProgressBar>, progress_bar: &Option<ProgressBar>,
root: &Path, root: &Path,
target: &TargetSlice, target: &Path,
options: &Options, options: &Options,
symlinked_files: &mut HashSet<FileInformation>, symlinked_files: &mut HashSet<FileInformation>,
copied_files: &mut HashMap<FileInformation, PathBuf>,
source_in_command_line: bool, source_in_command_line: bool,
) -> CopyResult<()> { ) -> CopyResult<()> {
if !options.recursive { if !options.recursive {
@ -324,6 +323,7 @@ pub(crate) fn copy_directory(
target, target,
options, options,
symlinked_files, symlinked_files,
copied_files,
source_in_command_line, source_in_command_line,
); );
} }
@ -372,7 +372,6 @@ pub(crate) fn copy_directory(
}; };
let target = tmp.as_path(); let target = tmp.as_path();
let mut hard_links: Vec<(String, u64)> = vec![];
let preserve_hard_links = options.preserve_hard_links(); let preserve_hard_links = options.preserve_hard_links();
// Collect some paths here that are invariant during the traversal // Collect some paths here that are invariant during the traversal
@ -397,7 +396,7 @@ pub(crate) fn copy_directory(
options, options,
symlinked_files, symlinked_files,
preserve_hard_links, preserve_hard_links,
&mut hard_links, copied_files,
)?; )?;
} }
// Print an error message, but continue traversing the directory. // Print an error message, but continue traversing the directory.
@ -448,6 +447,7 @@ mod tests {
use super::ends_with_slash_dot; use super::ends_with_slash_dot;
#[test] #[test]
#[allow(clippy::cognitive_complexity)]
fn test_ends_with_slash_dot() { fn test_ends_with_slash_dot() {
assert!(ends_with_slash_dot("/.")); assert!(ends_with_slash_dot("/."));
assert!(ends_with_slash_dot("./.")); assert!(ends_with_slash_dot("./."));

View file

@ -1,19 +1,15 @@
// This file is part of the uutils coreutils package.
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
// spell-checker:ignore (ToDO) copydir ficlone fiemap ftruncate linkgs lstat nlink nlinks pathbuf pwrite reflink strs xattrs symlinked deduplicated advcpmv nushell IRWXG IRWXO IRWXU IRWXUGO IRWXU IRWXG IRWXO IRWXUGO
#![allow(clippy::missing_safety_doc)] #![allow(clippy::missing_safety_doc)]
#![allow(clippy::extra_unused_lifetimes)] #![allow(clippy::extra_unused_lifetimes)]
// This file is part of the uutils coreutils package.
//
// (c) Jordy Dickinson <jordy.dickinson@gmail.com>
// (c) Joshua S. Miller <jsmiller@uchicago.edu>
//
// For the full copyright and license information, please view the LICENSE file
// that was distributed with this source code.
// spell-checker:ignore (ToDO) copydir ficlone fiemap ftruncate linkgs lstat nlink nlinks pathbuf pwrite reflink strs xattrs symlinked deduplicated advcpmv
use quick_error::quick_error; use quick_error::quick_error;
use std::borrow::Cow; use std::borrow::Cow;
use std::collections::HashSet; use std::cmp::Ordering;
use std::collections::{HashMap, HashSet};
use std::env; use std::env;
#[cfg(not(windows))] #[cfg(not(windows))]
use std::ffi::CString; use std::ffi::CString;
@ -34,14 +30,16 @@ use libc::mkfifo;
use quick_error::ResultExt; use quick_error::ResultExt;
use platform::copy_on_write; use platform::copy_on_write;
use uucore::backup_control::{self, BackupMode};
use uucore::display::Quotable; use uucore::display::Quotable;
use uucore::error::{set_exit_code, UClapError, UError, UResult, UUsageError}; use uucore::error::{set_exit_code, UClapError, UError, UResult, UUsageError};
use uucore::fs::{ use uucore::fs::{
canonicalize, is_symlink_loop, paths_refer_to_same_file, FileInformation, MissingHandling, canonicalize, is_symlink_loop, paths_refer_to_same_file, FileInformation, MissingHandling,
ResolveMode, ResolveMode,
}; };
use uucore::update_control::{self, UpdateMode}; use uucore::{backup_control, update_control};
// These are exposed for projects (e.g. nushell) that want to create an `Options` value, which
// requires these enum.
pub use uucore::{backup_control::BackupMode, update_control::UpdateMode};
use uucore::{ use uucore::{
crash, format_usage, help_about, help_section, help_usage, prompt_yes, show_error, crash, format_usage, help_about, help_section, help_usage, prompt_yes, show_error,
show_warning, util_name, show_warning, util_name,
@ -51,6 +49,7 @@ use crate::copydir::copy_directory;
mod copydir; mod copydir;
mod platform; mod platform;
quick_error! { quick_error! {
#[derive(Debug)] #[derive(Debug)]
pub enum Error { pub enum Error {
@ -108,12 +107,8 @@ impl UError for Error {
} }
pub type CopyResult<T> = Result<T, Error>; pub type CopyResult<T> = Result<T, Error>;
pub type Source = PathBuf;
pub type SourceSlice = Path;
pub type Target = PathBuf;
pub type TargetSlice = Path;
/// Specifies whether when overwrite files /// Specifies how to overwrite files.
#[derive(Clone, Copy, Eq, PartialEq)] #[derive(Clone, Copy, Eq, PartialEq)]
pub enum ClobberMode { pub enum ClobberMode {
Force, Force,
@ -121,7 +116,7 @@ pub enum ClobberMode {
Standard, Standard,
} }
/// Specifies whether when overwrite files /// Specifies whether files should be overwritten.
#[derive(Clone, Copy, Eq, PartialEq)] #[derive(Clone, Copy, Eq, PartialEq)]
pub enum OverwriteMode { pub enum OverwriteMode {
/// [Default] Always overwrite existing files /// [Default] Always overwrite existing files
@ -148,12 +143,13 @@ pub enum SparseMode {
Never, Never,
} }
/// Specifies the expected file type of copy target /// The expected file type of copy target
pub enum TargetType { pub enum TargetType {
Directory, Directory,
File, File,
} }
/// Copy action to perform
pub enum CopyMode { pub enum CopyMode {
Link, Link,
SymLink, SymLink,
@ -162,77 +158,130 @@ pub enum CopyMode {
AttrOnly, AttrOnly,
} }
/// Preservation settings for various attributes
///
/// It should be derived from options as follows:
///
/// - if there is a list of attributes to preserve (i.e. `--preserve=ATTR_LIST`) parse that list with [`Attributes::parse_iter`],
/// - if `-p` or `--preserve` is given without arguments, use [`Attributes::DEFAULT`],
/// - if `-a`/`--archive` is passed, use [`Attributes::ALL`],
/// - if `-d` is passed use [`Attributes::LINKS`],
/// - otherwise, use [`Attributes::NONE`].
///
/// For full compatibility with GNU, these options should also combine. We
/// currently only do a best effort imitation of that behavior, because it is
/// difficult to achieve in clap, especially with `--no-preserve`.
#[derive(Debug)] #[derive(Debug)]
pub struct Attributes { pub struct Attributes {
#[cfg(unix)] #[cfg(unix)]
ownership: Preserve, pub ownership: Preserve,
mode: Preserve, pub mode: Preserve,
timestamps: Preserve, pub timestamps: Preserve,
context: Preserve, pub context: Preserve,
links: Preserve, pub links: Preserve,
xattr: Preserve, pub xattr: Preserve,
} }
impl Attributes { #[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) fn max(&mut self, other: Self) {
#[cfg(unix)]
{
self.ownership = self.ownership.max(other.ownership);
}
self.mode = self.mode.max(other.mode);
self.timestamps = self.timestamps.max(other.timestamps);
self.context = self.context.max(other.context);
self.links = self.links.max(other.links);
self.xattr = self.xattr.max(other.xattr);
}
}
#[derive(Debug, PartialEq, Eq)]
pub enum Preserve { pub enum Preserve {
No, // explicit means whether the --no-preserve flag is used or not to distinguish out the default value.
// e.g. --no-preserve=mode means mode = No { explicit = true }
No { explicit: bool },
Yes { required: bool }, Yes { required: bool },
} }
impl Preserve { impl PartialOrd for Preserve {
/// Preservation level should only increase, with no preservation being the lowest option, fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
/// preserve but don't require - middle, and preserve and require - top. Some(self.cmp(other))
pub(crate) fn max(&self, other: Self) -> Self { }
}
impl Ord for Preserve {
fn cmp(&self, other: &Self) -> Ordering {
match (self, other) { match (self, other) {
(Self::Yes { required: true }, _) | (_, Self::Yes { required: true }) => { (Self::No { .. }, Self::No { .. }) => Ordering::Equal,
Self::Yes { required: true } (Self::Yes { .. }, Self::No { .. }) => Ordering::Greater,
} (Self::No { .. }, Self::Yes { .. }) => Ordering::Less,
(Self::Yes { required: false }, _) | (_, Self::Yes { required: false }) => { (
Self::Yes { required: false } Self::Yes { required: req_self },
} Self::Yes {
_ => Self::No, required: req_other,
},
) => req_self.cmp(req_other),
} }
} }
} }
/// Re-usable, extensible copy options /// Options for the `cp` command
///
/// All options are public so that the options can be programmatically
/// constructed by other crates, such as nushell. That means that this struct
/// is part of our public API. It should therefore not be changed without good
/// reason.
///
/// The fields are documented with the arguments that determine their value.
#[allow(dead_code)] #[allow(dead_code)]
pub struct Options { pub struct Options {
attributes_only: bool, /// `--attributes-only`
backup: BackupMode, pub attributes_only: bool,
copy_contents: bool, /// `--backup[=CONTROL]`, `-b`
cli_dereference: bool, pub backup: BackupMode,
copy_mode: CopyMode, /// `--copy-contents`
dereference: bool, pub copy_contents: bool,
no_target_dir: bool, /// `-H`
one_file_system: bool, pub cli_dereference: bool,
overwrite: OverwriteMode, /// Determines the type of copying that should be done
parents: bool, ///
sparse_mode: SparseMode, /// Set by the following arguments:
strip_trailing_slashes: bool, /// - `-l`, `--link`: [`CopyMode::Link`]
reflink_mode: ReflinkMode, /// - `-s`, `--symbolic-link`: [`CopyMode::SymLink`]
attributes: Attributes, /// - `-u`, `--update[=WHEN]`: [`CopyMode::Update`]
recursive: bool, /// - `--attributes-only`: [`CopyMode::AttrOnly`]
backup_suffix: String, /// - otherwise: [`CopyMode::Copy`]
target_dir: Option<PathBuf>, pub copy_mode: CopyMode,
update: UpdateMode, /// `-L`, `--dereference`
debug: bool, pub dereference: bool,
verbose: bool, /// `-T`, `--no-target-dir`
progress_bar: bool, pub no_target_dir: bool,
/// `-x`, `--one-file-system`
pub one_file_system: bool,
/// Specifies what to do with an existing destination
///
/// Set by the following arguments:
/// - `-i`, `--interactive`: [`OverwriteMode::Interactive`]
/// - `-n`, `--no-clobber`: [`OverwriteMode::NoClobber`]
/// - otherwise: [`OverwriteMode::Clobber`]
///
/// The `Interactive` and `Clobber` variants have a [`ClobberMode`] argument,
/// set by the following arguments:
/// - `-f`, `--force`: [`ClobberMode::Force`]
/// - `--remove-destination`: [`ClobberMode::RemoveDestination`]
/// - otherwise: [`ClobberMode::Standard`]
pub overwrite: OverwriteMode,
/// `--parents`
pub parents: bool,
/// `--sparse[=WHEN]`
pub sparse_mode: SparseMode,
/// `--strip-trailing-slashes`
pub strip_trailing_slashes: bool,
/// `--reflink[=WHEN]`
pub reflink_mode: ReflinkMode,
/// `--preserve=[=ATTRIBUTE_LIST]` and `--no-preserve=ATTRIBUTE_LIST`
pub attributes: Attributes,
/// `-R`, `-r`, `--recursive`
pub recursive: bool,
/// `-S`, `--suffix`
pub backup_suffix: String,
/// `-t`, `--target-directory`
pub target_dir: Option<PathBuf>,
/// `--update[=UPDATE]`
pub update: UpdateMode,
/// `--debug`
pub debug: bool,
/// `-v`, `--verbose`
pub verbose: bool,
/// `-g`, `--progress`
pub progress_bar: bool,
} }
/// Enum representing various debug states of the offload and reflink actions. /// Enum representing various debug states of the offload and reflink actions.
@ -741,67 +790,90 @@ impl CopyMode {
} }
impl Attributes { impl Attributes {
pub const ALL: Self = Self {
#[cfg(unix)]
ownership: Preserve::Yes { required: true },
mode: Preserve::Yes { required: true },
timestamps: Preserve::Yes { required: true },
context: {
#[cfg(feature = "feat_selinux")]
{
Preserve::Yes { required: false }
}
#[cfg(not(feature = "feat_selinux"))]
{
Preserve::No { explicit: false }
}
},
links: Preserve::Yes { required: true },
xattr: Preserve::Yes { required: false },
};
pub const NONE: Self = Self {
#[cfg(unix)]
ownership: Preserve::No { explicit: false },
mode: Preserve::No { explicit: false },
timestamps: Preserve::No { explicit: false },
context: Preserve::No { explicit: false },
links: Preserve::No { explicit: false },
xattr: Preserve::No { explicit: false },
};
// TODO: ownership is required if the user is root, for non-root users it's not required. // TODO: ownership is required if the user is root, for non-root users it's not required.
// See: https://github.com/coreutils/coreutils/blob/master/src/copy.c#L3181 pub const DEFAULT: Self = Self {
#[cfg(unix)]
ownership: Preserve::Yes { required: true },
mode: Preserve::Yes { required: true },
timestamps: Preserve::Yes { required: true },
..Self::NONE
};
fn all() -> Self { pub const LINKS: Self = Self {
links: Preserve::Yes { required: true },
..Self::NONE
};
pub fn union(self, other: &Self) -> Self {
Self { Self {
#[cfg(unix)] #[cfg(unix)]
ownership: Preserve::Yes { required: true }, ownership: self.ownership.max(other.ownership),
mode: Preserve::Yes { required: true }, context: self.context.max(other.context),
timestamps: Preserve::Yes { required: true }, timestamps: self.timestamps.max(other.timestamps),
context: { mode: self.mode.max(other.mode),
#[cfg(feature = "feat_selinux")] links: self.links.max(other.links),
{ xattr: self.xattr.max(other.xattr),
Preserve::Yes { required: false }
}
#[cfg(not(feature = "feat_selinux"))]
{
Preserve::No
}
},
links: Preserve::Yes { required: true },
xattr: Preserve::Yes { required: false },
} }
} }
fn default() -> Self { pub fn parse_iter<T>(values: impl Iterator<Item = T>) -> Result<Self, Error>
Self { where
#[cfg(unix)] T: AsRef<str>,
ownership: Preserve::Yes { required: true }, {
mode: Preserve::Yes { required: true }, let mut new = Self::NONE;
timestamps: Preserve::Yes { required: true }, for value in values {
context: Preserve::No, new = new.union(&Self::parse_single_string(value.as_ref())?);
links: Preserve::No,
xattr: Preserve::No,
}
}
fn none() -> Self {
Self {
#[cfg(unix)]
ownership: Preserve::No,
mode: Preserve::No,
timestamps: Preserve::No,
context: Preserve::No,
links: Preserve::No,
xattr: Preserve::No,
} }
Ok(new)
} }
/// Tries to match string containing a parameter to preserve with the corresponding entry in the /// Tries to match string containing a parameter to preserve with the corresponding entry in the
/// Attributes struct. /// Attributes struct.
fn try_set_from_string(&mut self, value: &str) -> Result<(), Error> { fn parse_single_string(value: &str) -> Result<Self, Error> {
let preserve_yes_required = Preserve::Yes { required: true }; let value = value.to_lowercase();
match &*value.to_lowercase() { if value == "all" {
"mode" => self.mode = preserve_yes_required, return Ok(Self::ALL);
}
let mut new = Self::NONE;
let attribute = match value.as_ref() {
"mode" => &mut new.mode,
#[cfg(unix)] #[cfg(unix)]
"ownership" => self.ownership = preserve_yes_required, "ownership" => &mut new.ownership,
"timestamps" => self.timestamps = preserve_yes_required, "timestamps" => &mut new.timestamps,
"context" => self.context = preserve_yes_required, "context" => &mut new.context,
"link" | "links" => self.links = preserve_yes_required, "link" | "links" => &mut new.links,
"xattr" => self.xattr = preserve_yes_required, "xattr" => &mut new.xattr,
_ => { _ => {
return Err(Error::InvalidArgument(format!( return Err(Error::InvalidArgument(format!(
"invalid attribute {}", "invalid attribute {}",
@ -809,7 +881,10 @@ impl Attributes {
))); )));
} }
}; };
Ok(())
*attribute = Preserve::Yes { required: true };
Ok(new)
} }
} }
@ -858,40 +933,35 @@ impl Options {
}; };
// Parse attributes to preserve // Parse attributes to preserve
let attributes: Attributes = if matches.contains_id(options::PRESERVE) { let mut attributes =
match matches.get_many::<String>(options::PRESERVE) { if let Some(attribute_strs) = matches.get_many::<String>(options::PRESERVE) {
None => Attributes::default(), if attribute_strs.len() == 0 {
Some(attribute_strs) => { Attributes::DEFAULT
let mut attributes: Attributes = Attributes::none(); } else {
let mut attributes_empty = true; Attributes::parse_iter(attribute_strs)?
for attribute_str in attribute_strs { }
attributes_empty = false; } else if matches.get_flag(options::ARCHIVE) {
if attribute_str == "all" { // --archive is used. Same as --preserve=all
attributes.max(Attributes::all()); Attributes::ALL
} else { } else if matches.get_flag(options::NO_DEREFERENCE_PRESERVE_LINKS) {
attributes.try_set_from_string(attribute_str)?; Attributes::LINKS
} } else if matches.get_flag(options::PRESERVE_DEFAULT_ATTRIBUTES) {
} Attributes::DEFAULT
// `--preserve` case, use the defaults } else {
if attributes_empty { Attributes::NONE
Attributes::default() };
} else {
attributes // handling no-preserve options and adjusting the attributes
} if let Some(attribute_strs) = matches.get_many::<String>(options::NO_PRESERVE) {
if attribute_strs.len() > 0 {
let no_preserve_attributes = Attributes::parse_iter(attribute_strs)?;
if matches!(no_preserve_attributes.links, Preserve::Yes { .. }) {
attributes.links = Preserve::No { explicit: true };
} else if matches!(no_preserve_attributes.mode, Preserve::Yes { .. }) {
attributes.mode = Preserve::No { explicit: true };
} }
} }
} else if matches.get_flag(options::ARCHIVE) { }
// --archive is used. Same as --preserve=all
Attributes::all()
} else if matches.get_flag(options::NO_DEREFERENCE_PRESERVE_LINKS) {
let mut attributes = Attributes::none();
attributes.links = Preserve::Yes { required: true };
attributes
} else if matches.get_flag(options::PRESERVE_DEFAULT_ATTRIBUTES) {
Attributes::default()
} else {
Attributes::none()
};
#[cfg(not(feature = "feat_selinux"))] #[cfg(not(feature = "feat_selinux"))]
if let Preserve::Yes { required } = attributes.context { if let Preserve::Yes { required } = attributes.context {
@ -984,11 +1054,22 @@ impl Options {
fn preserve_hard_links(&self) -> bool { fn preserve_hard_links(&self) -> bool {
match self.attributes.links { match self.attributes.links {
Preserve::No => false, Preserve::No { .. } => false,
Preserve::Yes { .. } => true, Preserve::Yes { .. } => true,
} }
} }
#[cfg(unix)]
fn preserve_mode(&self) -> (bool, bool) {
match self.attributes.mode {
Preserve::No { explicit } => match explicit {
true => (false, true),
false => (false, false),
},
Preserve::Yes { .. } => (true, false),
}
}
/// Whether to force overwriting the destination file. /// Whether to force overwriting the destination file.
fn force(&self) -> bool { fn force(&self) -> bool {
matches!(self.overwrite, OverwriteMode::Clobber(ClobberMode::Force)) matches!(self.overwrite, OverwriteMode::Clobber(ClobberMode::Force))
@ -1000,7 +1081,7 @@ impl TargetType {
/// ///
/// Treat target as a dir if we have multiple sources or the target /// Treat target as a dir if we have multiple sources or the target
/// exists and already is a directory /// exists and already is a directory
fn determine(sources: &[Source], target: &TargetSlice) -> Self { fn determine(sources: &[PathBuf], target: &Path) -> Self {
if sources.len() > 1 || target.is_dir() { if sources.len() > 1 || target.is_dir() {
Self::Directory Self::Directory
} else { } else {
@ -1010,10 +1091,16 @@ impl TargetType {
} }
/// Returns tuple of (Source paths, Target) /// Returns tuple of (Source paths, Target)
fn parse_path_args(mut paths: Vec<Source>, options: &Options) -> CopyResult<(Vec<Source>, Target)> { fn parse_path_args(
mut paths: Vec<PathBuf>,
options: &Options,
) -> CopyResult<(Vec<PathBuf>, PathBuf)> {
if paths.is_empty() { if paths.is_empty() {
// No files specified // No files specified
return Err("missing file operand".into()); return Err("missing file operand".into());
} else if paths.len() == 1 && options.target_dir.is_none() {
// Only one file specified
return Err(format!("missing destination file operand after {:?}", paths[0]).into());
} }
// Return an error if the user requested to copy more than one // Return an error if the user requested to copy more than one
@ -1044,66 +1131,6 @@ fn parse_path_args(mut paths: Vec<Source>, options: &Options) -> CopyResult<(Vec
Ok((paths, target)) Ok((paths, target))
} }
/// Get the inode information for a file.
fn get_inode(file_info: &FileInformation) -> u64 {
#[cfg(unix)]
let result = file_info.inode();
#[cfg(windows)]
let result = file_info.file_index();
result
}
#[cfg(target_os = "redox")]
fn preserve_hardlinks(
hard_links: &mut Vec<(String, u64)>,
source: &std::path::Path,
dest: &std::path::Path,
found_hard_link: &mut bool,
) -> CopyResult<()> {
// Redox does not currently support hard links
Ok(())
}
/// Hard link a pair of files if needed _and_ record if this pair is a new hard link.
#[cfg(not(target_os = "redox"))]
fn preserve_hardlinks(
hard_links: &mut Vec<(String, u64)>,
source: &std::path::Path,
dest: &std::path::Path,
) -> CopyResult<bool> {
let info = FileInformation::from_path(source, false)
.context(format!("cannot stat {}", source.quote()))?;
let inode = get_inode(&info);
let nlinks = info.number_of_links();
let mut found_hard_link = false;
for (link, link_inode) in hard_links.iter() {
if *link_inode == inode {
// Consider the following files:
//
// * `src/f` - a regular file
// * `src/link` - a hard link to `src/f`
// * `dest/src/f` - a different regular file
//
// In this scenario, if we do `cp -a src/ dest/`, it is
// possible that the order of traversal causes `src/link`
// to get copied first (to `dest/src/link`). In that case,
// in order to make sure `dest/src/link` is a hard link to
// `dest/src/f` and `dest/src/f` has the contents of
// `src/f`, we delete the existing file to allow the hard
// linking.
if file_or_link_exists(dest) && file_or_link_exists(Path::new(link)) {
std::fs::remove_file(dest)?;
}
std::fs::hard_link(link, dest).unwrap();
found_hard_link = true;
}
}
if !found_hard_link && nlinks > 1 {
hard_links.push((dest.to_str().unwrap().to_string(), inode));
}
Ok(found_hard_link)
}
/// When handling errors, we don't always want to show them to the user. This function handles that. /// When handling errors, we don't always want to show them to the user. This function handles that.
fn show_error_if_needed(error: &Error) { fn show_error_if_needed(error: &Error) {
match error { match error {
@ -1122,25 +1149,29 @@ fn show_error_if_needed(error: &Error) {
} }
} }
/// Copy all `sources` to `target`. Returns an /// Copy all `sources` to `target`.
/// `Err(Error::NotAllFilesCopied)` if at least one non-fatal error was
/// encountered.
/// ///
/// Behavior depends on path`options`, see [`Options`] for details. /// Returns an `Err(Error::NotAllFilesCopied)` if at least one non-fatal error
/// was encountered.
/// ///
/// [`Options`]: ./struct.Options.html /// Behavior is determined by the `options` parameter, see [`Options`] for details.
fn copy(sources: &[Source], target: &TargetSlice, options: &Options) -> CopyResult<()> { pub fn copy(sources: &[PathBuf], target: &Path, options: &Options) -> CopyResult<()> {
let target_type = TargetType::determine(sources, target); let target_type = TargetType::determine(sources, target);
verify_target_type(target, &target_type)?; verify_target_type(target, &target_type)?;
let preserve_hard_links = options.preserve_hard_links();
let mut hard_links: Vec<(String, u64)> = vec![];
let mut non_fatal_errors = false; let mut non_fatal_errors = false;
let mut seen_sources = HashSet::with_capacity(sources.len()); let mut seen_sources = HashSet::with_capacity(sources.len());
let mut symlinked_files = HashSet::new(); let mut symlinked_files = HashSet::new();
// to remember the copied files for further usage.
// the FileInformation implemented the Hash trait by using
// 1. inode number
// 2. device number
// the combination of a file's inode number and device number is unique throughout all the file systems.
//
// key is the source file's information and the value is the destination filepath.
let mut copied_files: HashMap<FileInformation, PathBuf> = HashMap::with_capacity(sources.len());
let progress_bar = if options.progress_bar { let progress_bar = if options.progress_bar {
let pb = ProgressBar::new(disk_usage(sources, options.recursive)?) let pb = ProgressBar::new(disk_usage(sources, options.recursive)?)
.with_style( .with_style(
@ -1156,33 +1187,29 @@ fn copy(sources: &[Source], target: &TargetSlice, options: &Options) -> CopyResu
None None
}; };
for source in sources.iter() { for source in sources {
if seen_sources.contains(source) { if seen_sources.contains(source) {
// FIXME: compare sources by the actual file they point to, not their path. (e.g. dir/file == dir/../dir/file in most cases) // FIXME: compare sources by the actual file they point to, not their path. (e.g. dir/file == dir/../dir/file in most cases)
show_warning!("source {} specified more than once", source.quote()); show_warning!("source {} specified more than once", source.quote());
} else { } else if let Err(error) = copy_source(
let found_hard_link = if preserve_hard_links && !source.is_dir() { &progress_bar,
let dest = construct_dest_path(source, target, &target_type, options)?; source,
preserve_hardlinks(&mut hard_links, source, &dest)? target,
} else { &target_type,
false options,
}; &mut symlinked_files,
if !found_hard_link { &mut copied_files,
if let Err(error) = copy_source( ) {
&progress_bar, show_error_if_needed(&error);
source, non_fatal_errors = true;
target,
&target_type,
options,
&mut symlinked_files,
) {
show_error_if_needed(&error);
non_fatal_errors = true;
}
}
seen_sources.insert(source);
} }
seen_sources.insert(source);
} }
if let Some(pb) = progress_bar {
pb.finish();
}
if non_fatal_errors { if non_fatal_errors {
Err(Error::NotAllFilesCopied) Err(Error::NotAllFilesCopied)
} else { } else {
@ -1192,7 +1219,7 @@ fn copy(sources: &[Source], target: &TargetSlice, options: &Options) -> CopyResu
fn construct_dest_path( fn construct_dest_path(
source_path: &Path, source_path: &Path,
target: &TargetSlice, target: &Path,
target_type: &TargetType, target_type: &TargetType,
options: &Options, options: &Options,
) -> CopyResult<PathBuf> { ) -> CopyResult<PathBuf> {
@ -1223,16 +1250,25 @@ fn construct_dest_path(
fn copy_source( fn copy_source(
progress_bar: &Option<ProgressBar>, progress_bar: &Option<ProgressBar>,
source: &SourceSlice, source: &Path,
target: &TargetSlice, target: &Path,
target_type: &TargetType, target_type: &TargetType,
options: &Options, options: &Options,
symlinked_files: &mut HashSet<FileInformation>, symlinked_files: &mut HashSet<FileInformation>,
copied_files: &mut HashMap<FileInformation, PathBuf>,
) -> CopyResult<()> { ) -> CopyResult<()> {
let source_path = Path::new(&source); let source_path = Path::new(&source);
if source_path.is_dir() { if source_path.is_dir() {
// Copy as directory // Copy as directory
copy_directory(progress_bar, source, target, options, symlinked_files, true) copy_directory(
progress_bar,
source,
target,
options,
symlinked_files,
copied_files,
true,
)
} else { } else {
// Copy as file // Copy as file
let dest = construct_dest_path(source_path, target, target_type, options)?; let dest = construct_dest_path(source_path, target, target_type, options)?;
@ -1242,6 +1278,7 @@ fn copy_source(
dest.as_path(), dest.as_path(),
options, options,
symlinked_files, symlinked_files,
copied_files,
true, true,
); );
if options.parents { if options.parents {
@ -1254,23 +1291,16 @@ fn copy_source(
} }
impl OverwriteMode { impl OverwriteMode {
fn verify(&self, path: &Path, verbose: bool) -> CopyResult<()> { fn verify(&self, path: &Path) -> CopyResult<()> {
match *self { match *self {
Self::NoClobber => { Self::NoClobber => {
if verbose { eprintln!("{}: not replacing {}", util_name(), path.quote());
println!("skipped {}", path.quote());
} else {
eprintln!("{}: not replacing {}", util_name(), path.quote());
}
Err(Error::NotAllFilesCopied) Err(Error::NotAllFilesCopied)
} }
Self::Interactive(_) => { Self::Interactive(_) => {
if prompt_yes!("overwrite {}?", path.quote()) { if prompt_yes!("overwrite {}?", path.quote()) {
Ok(()) Ok(())
} else { } else {
if verbose {
println!("skipped {}", path.quote());
}
Err(Error::Skipped) Err(Error::Skipped)
} }
} }
@ -1284,7 +1314,7 @@ impl OverwriteMode {
/// If it's required, then the error is thrown. /// If it's required, then the error is thrown.
fn handle_preserve<F: Fn() -> CopyResult<()>>(p: &Preserve, f: F) -> CopyResult<()> { fn handle_preserve<F: Fn() -> CopyResult<()>>(p: &Preserve, f: F) -> CopyResult<()> {
match p { match p {
Preserve::No => {} Preserve::No { .. } => {}
Preserve::Yes { required } => { Preserve::Yes { required } => {
let result = f(); let result = f();
if *required { if *required {
@ -1478,7 +1508,7 @@ fn handle_existing_dest(
return Err(format!("{} and {} are the same file", source.quote(), dest.quote()).into()); return Err(format!("{} and {} are the same file", source.quote(), dest.quote()).into());
} }
options.overwrite.verify(dest, options.verbose)?; options.overwrite.verify(dest)?;
let backup_path = backup_control::get_backup_path(options.backup, dest, &options.backup_suffix); let backup_path = backup_control::get_backup_path(options.backup, dest, &options.backup_suffix);
if let Some(backup_path) = backup_path { if let Some(backup_path) = backup_path {
@ -1503,6 +1533,24 @@ fn handle_existing_dest(
OverwriteMode::Clobber(ClobberMode::RemoveDestination) => { OverwriteMode::Clobber(ClobberMode::RemoveDestination) => {
fs::remove_file(dest)?; fs::remove_file(dest)?;
} }
OverwriteMode::Clobber(ClobberMode::Standard) => {
// Consider the following files:
//
// * `src/f` - a regular file
// * `src/link` - a hard link to `src/f`
// * `dest/src/f` - a different regular file
//
// In this scenario, if we do `cp -a src/ dest/`, it is
// possible that the order of traversal causes `src/link`
// to get copied first (to `dest/src/link`). In that case,
// in order to make sure `dest/src/link` is a hard link to
// `dest/src/f` and `dest/src/f` has the contents of
// `src/f`, we delete the existing file to allow the hard
// linking.
if options.preserve_hard_links() {
fs::remove_file(dest)?;
}
}
_ => (), _ => (),
}; };
@ -1576,6 +1624,7 @@ fn copy_file(
dest: &Path, dest: &Path,
options: &Options, options: &Options,
symlinked_files: &mut HashSet<FileInformation>, symlinked_files: &mut HashSet<FileInformation>,
copied_files: &mut HashMap<FileInformation, PathBuf>,
source_in_command_line: bool, source_in_command_line: bool,
) -> CopyResult<()> { ) -> CopyResult<()> {
if (options.update == UpdateMode::ReplaceIfOlder || options.update == UpdateMode::ReplaceNone) if (options.update == UpdateMode::ReplaceIfOlder || options.update == UpdateMode::ReplaceNone)
@ -1613,12 +1662,33 @@ fn copy_file(
dest.display() dest.display()
))); )));
} }
if paths_refer_to_same_file(source, dest, true)
&& matches!(
options.overwrite,
OverwriteMode::Clobber(ClobberMode::RemoveDestination)
)
{
fs::remove_file(dest)?;
}
} }
if file_or_link_exists(dest) { if file_or_link_exists(dest) {
handle_existing_dest(source, dest, options, source_in_command_line)?; handle_existing_dest(source, dest, options, source_in_command_line)?;
} }
if options.preserve_hard_links() {
// if we encounter a matching device/inode pair in the source tree
// we can arrange to create a hard link between the corresponding names
// in the destination tree.
if let Some(new_source) = copied_files.get(
&FileInformation::from_path(source, options.dereference(source_in_command_line))
.context(format!("cannot stat {}", source.quote()))?,
) {
std::fs::hard_link(new_source, dest)?;
return Ok(());
};
}
if options.verbose { if options.verbose {
if let Some(pb) = progress_bar { if let Some(pb) = progress_bar {
// Suspend (hide) the progress bar so the println won't overlap with the progress bar. // Suspend (hide) the progress bar so the println won't overlap with the progress bar.
@ -1681,15 +1751,10 @@ fn copy_file(
let mut permissions = source_metadata.permissions(); let mut permissions = source_metadata.permissions();
#[cfg(unix)] #[cfg(unix)]
{ {
use uucore::mode::get_umask; let mut mode = handle_no_preserve_mode(options, permissions.mode());
let mut mode = permissions.mode();
// remove sticky bit, suid and gid bit
const SPECIAL_PERMS_MASK: u32 = 0o7000;
mode &= !SPECIAL_PERMS_MASK;
// apply umask // apply umask
use uucore::mode::get_umask;
mode &= !get_umask(); mode &= !get_umask();
permissions.set_mode(mode); permissions.set_mode(mode);
@ -1806,6 +1871,11 @@ fn copy_file(
copy_attributes(source, dest, &options.attributes)?; copy_attributes(source, dest, &options.attributes)?;
copied_files.insert(
FileInformation::from_path(source, options.dereference(source_in_command_line))?,
dest.to_path_buf(),
);
if let Some(progress_bar) = progress_bar { if let Some(progress_bar) = progress_bar {
progress_bar.inc(fs::metadata(source)?.len()); progress_bar.inc(fs::metadata(source)?.len());
} }
@ -1813,6 +1883,49 @@ fn copy_file(
Ok(()) Ok(())
} }
#[cfg(unix)]
fn handle_no_preserve_mode(options: &Options, org_mode: u32) -> u32 {
let (is_preserve_mode, is_explicit_no_preserve_mode) = options.preserve_mode();
if !is_preserve_mode {
use libc::{
S_IRGRP, S_IROTH, S_IRUSR, S_IRWXG, S_IRWXO, S_IRWXU, S_IWGRP, S_IWOTH, S_IWUSR,
};
#[cfg(not(any(
target_os = "android",
target_os = "macos",
target_os = "macos-12",
target_os = "freebsd",
)))]
{
const MODE_RW_UGO: u32 = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH;
const S_IRWXUGO: u32 = S_IRWXU | S_IRWXG | S_IRWXO;
match is_explicit_no_preserve_mode {
true => return MODE_RW_UGO,
false => return org_mode & S_IRWXUGO,
};
}
#[cfg(any(
target_os = "android",
target_os = "macos",
target_os = "macos-12",
target_os = "freebsd",
))]
{
const MODE_RW_UGO: u32 =
(S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH) as u32;
const S_IRWXUGO: u32 = (S_IRWXU | S_IRWXG | S_IRWXO) as u32;
match is_explicit_no_preserve_mode {
true => return MODE_RW_UGO,
false => return org_mode & S_IRWXUGO,
};
}
}
org_mode
}
/// Copy the file from `source` to `dest` either using the normal `fs::copy` or a /// Copy the file from `source` to `dest` either using the normal `fs::copy` or a
/// copy-on-write scheme if --reflink is specified and the filesystem supports it. /// copy-on-write scheme if --reflink is specified and the filesystem supports it.
fn copy_helper( fn copy_helper(
@ -1836,7 +1949,7 @@ fn copy_helper(
File::create(dest).context(dest.display().to_string())?; File::create(dest).context(dest.display().to_string())?;
} else if source_is_fifo && options.recursive && !options.copy_contents { } else if source_is_fifo && options.recursive && !options.copy_contents {
#[cfg(unix)] #[cfg(unix)]
copy_fifo(dest, options.overwrite, options.verbose)?; copy_fifo(dest, options.overwrite)?;
} else if source_is_symlink { } else if source_is_symlink {
copy_link(source, dest, symlinked_files)?; copy_link(source, dest, symlinked_files)?;
} else { } else {
@ -1861,9 +1974,9 @@ fn copy_helper(
// "Copies" a FIFO by creating a new one. This workaround is because Rust's // "Copies" a FIFO by creating a new one. This workaround is because Rust's
// built-in fs::copy does not handle FIFOs (see rust-lang/rust/issues/79390). // built-in fs::copy does not handle FIFOs (see rust-lang/rust/issues/79390).
#[cfg(unix)] #[cfg(unix)]
fn copy_fifo(dest: &Path, overwrite: OverwriteMode, verbose: bool) -> CopyResult<()> { fn copy_fifo(dest: &Path, overwrite: OverwriteMode) -> CopyResult<()> {
if dest.exists() { if dest.exists() {
overwrite.verify(dest, verbose)?; overwrite.verify(dest)?;
fs::remove_file(dest)?; fs::remove_file(dest)?;
} }

View file

@ -1,7 +1,7 @@
// * This file is part of the uutils coreutils package. // This file is part of the uutils coreutils package.
// * //
// * For the full copyright and license information, please view the LICENSE // For the full copyright and license information, please view the LICENSE
// * file that was distributed with this source code. // file that was distributed with this source code.
// spell-checker:ignore ficlone reflink ftruncate pwrite fiemap // spell-checker:ignore ficlone reflink ftruncate pwrite fiemap
use std::fs::{File, OpenOptions}; use std::fs::{File, OpenOptions};
use std::io::Read; use std::io::Read;

View file

@ -1,7 +1,7 @@
// * This file is part of the uutils coreutils package. // This file is part of the uutils coreutils package.
// * //
// * For the full copyright and license information, please view the LICENSE // For the full copyright and license information, please view the LICENSE
// * file that was distributed with this source code. // file that was distributed with this source code.
// spell-checker:ignore reflink // spell-checker:ignore reflink
use std::ffi::CString; use std::ffi::CString;
use std::fs::{self, File}; use std::fs::{self, File};
@ -63,8 +63,15 @@ pub(crate) fn copy_on_write(
{ {
// clonefile(2) fails if the destination exists. Remove it and try again. Do not // clonefile(2) fails if the destination exists. Remove it and try again. Do not
// bother to check if removal worked because we're going to try to clone again. // bother to check if removal worked because we're going to try to clone again.
let _ = fs::remove_file(dest); // first lets make sure the dest file is not read only
error = pfn(src.as_ptr(), dst.as_ptr(), 0); if fs::metadata(dest).map_or(false, |md| !md.permissions().readonly()) {
// remove and copy again
// TODO: rewrite this to better match linux behavior
// linux first opens the source file and destination file then uses the file
// descriptors to do the clone.
let _ = fs::remove_file(dest);
error = pfn(src.as_ptr(), dst.as_ptr(), 0);
}
} }
} }
} }

View file

@ -1,7 +1,7 @@
// * This file is part of the uutils coreutils package. // This file is part of the uutils coreutils package.
// * //
// * For the full copyright and license information, please view the LICENSE // For the full copyright and license information, please view the LICENSE
// * file that was distributed with this source code. // file that was distributed with this source code.
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
mod macos; mod macos;
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]

View file

@ -1,7 +1,7 @@
// * This file is part of the uutils coreutils package. // This file is part of the uutils coreutils package.
// * //
// * For the full copyright and license information, please view the LICENSE // For the full copyright and license information, please view the LICENSE
// * file that was distributed with this source code. // file that was distributed with this source code.
// spell-checker:ignore reflink // spell-checker:ignore reflink
use std::fs; use std::fs;
use std::path::Path; use std::path::Path;

View file

@ -1,6 +1,6 @@
[package] [package]
name = "uu_csplit" name = "uu_csplit"
version = "0.0.20" version = "0.0.22"
authors = ["uutils developers"] authors = ["uutils developers"]
license = "MIT" license = "MIT"
description = "csplit ~ (uutils) Output pieces of FILE separated by PATTERN(s) to files 'xx00', 'xx01', ..., and output byte counts of each piece to standard output" description = "csplit ~ (uutils) Output pieces of FILE separated by PATTERN(s) to files 'xx00', 'xx01', ..., and output byte counts of each piece to standard output"

View file

@ -1,3 +1,7 @@
// This file is part of the uutils coreutils package.
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
#![crate_name = "uu_csplit"] #![crate_name = "uu_csplit"]
// spell-checker:ignore rustdoc // spell-checker:ignore rustdoc
#![allow(rustdoc::private_intra_doc_links)] #![allow(rustdoc::private_intra_doc_links)]
@ -660,6 +664,7 @@ mod tests {
use super::*; use super::*;
#[test] #[test]
#[allow(clippy::cognitive_complexity)]
fn input_splitter() { fn input_splitter() {
let input = vec![ let input = vec![
Ok(String::from("aaa")), Ok(String::from("aaa")),
@ -732,6 +737,7 @@ mod tests {
} }
#[test] #[test]
#[allow(clippy::cognitive_complexity)]
fn input_splitter_interrupt_rewind() { fn input_splitter_interrupt_rewind() {
let input = vec![ let input = vec![
Ok(String::from("aaa")), Ok(String::from("aaa")),

View file

@ -1,3 +1,7 @@
// This file is part of the uutils coreutils package.
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
use std::io; use std::io;
use thiserror::Error; use thiserror::Error;

View file

@ -1,3 +1,7 @@
// This file is part of the uutils coreutils package.
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
// spell-checker:ignore (regex) SKIPTO UPTO ; (vars) ntimes // spell-checker:ignore (regex) SKIPTO UPTO ; (vars) ntimes
use crate::csplit_error::CsplitError; use crate::csplit_error::CsplitError;
@ -207,6 +211,7 @@ mod tests {
} }
#[test] #[test]
#[allow(clippy::cognitive_complexity)]
fn up_to_match_pattern() { fn up_to_match_pattern() {
let input: Vec<String> = vec![ let input: Vec<String> = vec![
"/test1.*end$/", "/test1.*end$/",
@ -260,6 +265,7 @@ mod tests {
} }
#[test] #[test]
#[allow(clippy::cognitive_complexity)]
fn skip_to_match_pattern() { fn skip_to_match_pattern() {
let input: Vec<String> = vec![ let input: Vec<String> = vec![
"%test1.*end$%", "%test1.*end$%",

View file

@ -1,3 +1,7 @@
// This file is part of the uutils coreutils package.
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
// spell-checker:ignore (regex) diuox // spell-checker:ignore (regex) diuox
use regex::Regex; use regex::Regex;

View file

@ -1,6 +1,6 @@
[package] [package]
name = "uu_cut" name = "uu_cut"
version = "0.0.20" version = "0.0.22"
authors = ["uutils developers"] authors = ["uutils developers"]
license = "MIT" license = "MIT"
description = "cut ~ (uutils) display byte/field columns of input lines" description = "cut ~ (uutils) display byte/field columns of input lines"
@ -16,10 +16,9 @@ path = "src/cut.rs"
[dependencies] [dependencies]
clap = { workspace = true } clap = { workspace = true }
uucore = { workspace = true } uucore = { workspace = true, features = ["ranges"] }
memchr = { workspace = true } memchr = { workspace = true }
bstr = { workspace = true } bstr = { workspace = true }
is-terminal = { workspace = true }
[[bin]] [[bin]]
name = "cut" name = "cut"

View file

@ -1,7 +1,5 @@
// This file is part of the uutils coreutils package. // This file is part of the uutils coreutils package.
// //
// (c) Rolf Morel <rolfmorel@gmail.com>
//
// For the full copyright and license information, please view the LICENSE // For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code. // file that was distributed with this source code.
@ -9,12 +7,12 @@
use bstr::io::BufReadExt; use bstr::io::BufReadExt;
use clap::{crate_version, Arg, ArgAction, Command}; use clap::{crate_version, Arg, ArgAction, Command};
use is_terminal::IsTerminal;
use std::fs::File; use std::fs::File;
use std::io::{stdin, stdout, BufReader, BufWriter, Read, Write}; use std::io::{stdin, stdout, BufReader, BufWriter, IsTerminal, Read, Write};
use std::path::Path; use std::path::Path;
use uucore::display::Quotable; use uucore::display::Quotable;
use uucore::error::{FromIo, UResult, USimpleError}; use uucore::error::{FromIo, UResult, USimpleError};
use uucore::line_ending::LineEnding;
use self::searcher::Searcher; use self::searcher::Searcher;
use matcher::{ExactMatcher, Matcher, WhitespaceMatcher}; use matcher::{ExactMatcher, Matcher, WhitespaceMatcher};
@ -30,7 +28,7 @@ const AFTER_HELP: &str = help_section!("after help", "cut.md");
struct Options { struct Options {
out_delim: Option<String>, out_delim: Option<String>,
zero_terminated: bool, line_ending: LineEnding,
} }
enum Delimiter { enum Delimiter {
@ -42,7 +40,7 @@ struct FieldOptions {
delimiter: Delimiter, delimiter: Delimiter,
out_delimiter: Option<String>, out_delimiter: Option<String>,
only_delimited: bool, only_delimited: bool,
zero_terminated: bool, line_ending: LineEnding,
} }
enum Mode { enum Mode {
@ -68,7 +66,7 @@ fn list_to_ranges(list: &str, complement: bool) -> Result<Vec<Range>, String> {
} }
fn cut_bytes<R: Read>(reader: R, ranges: &[Range], opts: &Options) -> UResult<()> { fn cut_bytes<R: Read>(reader: R, ranges: &[Range], opts: &Options) -> UResult<()> {
let newline_char = if opts.zero_terminated { b'\0' } else { b'\n' }; let newline_char = opts.line_ending.into();
let mut buf_in = BufReader::new(reader); let mut buf_in = BufReader::new(reader);
let mut out = stdout_writer(); let mut out = stdout_writer();
let delim = opts let delim = opts
@ -259,7 +257,7 @@ fn cut_fields_implicit_out_delim<R: Read, M: Matcher>(
} }
fn cut_fields<R: Read>(reader: R, ranges: &[Range], opts: &FieldOptions) -> UResult<()> { fn cut_fields<R: Read>(reader: R, ranges: &[Range], opts: &FieldOptions) -> UResult<()> {
let newline_char = if opts.zero_terminated { b'\0' } else { b'\n' }; let newline_char = opts.line_ending.into();
match opts.delimiter { match opts.delimiter {
Delimiter::String(ref delim) => { Delimiter::String(ref delim) => {
let matcher = ExactMatcher::new(delim.as_bytes()); let matcher = ExactMatcher::new(delim.as_bytes());
@ -376,7 +374,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
.unwrap_or_default() .unwrap_or_default()
.to_owned(), .to_owned(),
), ),
zero_terminated: matches.get_flag(options::ZERO_TERMINATED), line_ending: LineEnding::from_zero_flag(matches.get_flag(options::ZERO_TERMINATED)),
}, },
) )
}), }),
@ -391,7 +389,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
.unwrap_or_default() .unwrap_or_default()
.to_owned(), .to_owned(),
), ),
zero_terminated: matches.get_flag(options::ZERO_TERMINATED), line_ending: LineEnding::from_zero_flag(matches.get_flag(options::ZERO_TERMINATED)),
}, },
) )
}), }),
@ -411,6 +409,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let only_delimited = matches.get_flag(options::ONLY_DELIMITED); let only_delimited = matches.get_flag(options::ONLY_DELIMITED);
let whitespace_delimited = matches.get_flag(options::WHITESPACE_DELIMITED); let whitespace_delimited = matches.get_flag(options::WHITESPACE_DELIMITED);
let zero_terminated = matches.get_flag(options::ZERO_TERMINATED); let zero_terminated = matches.get_flag(options::ZERO_TERMINATED);
let line_ending = LineEnding::from_zero_flag(zero_terminated);
match matches.get_one::<String>(options::DELIMITER).map(|s| s.as_str()) { match matches.get_one::<String>(options::DELIMITER).map(|s| s.as_str()) {
Some(_) if whitespace_delimited => { Some(_) if whitespace_delimited => {
@ -441,7 +440,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
delimiter: Delimiter::String(delim), delimiter: Delimiter::String(delim),
out_delimiter: out_delim, out_delimiter: out_delim,
only_delimited, only_delimited,
zero_terminated, line_ending,
}, },
)) ))
} }
@ -455,7 +454,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
}, },
out_delimiter: out_delim, out_delimiter: out_delim,
only_delimited, only_delimited,
zero_terminated, line_ending,
}, },
)), )),
} }

View file

@ -1,7 +1,5 @@
// This file is part of the uutils coreutils package. // This file is part of the uutils coreutils package.
// //
// (c) Rolf Morel <rolfmorel@gmail.com>
//
// For the full copyright and license information, please view the LICENSE // For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code. // file that was distributed with this source code.

View file

@ -1,7 +1,7 @@
# spell-checker:ignore datetime # spell-checker:ignore datetime
[package] [package]
name = "uu_date" name = "uu_date"
version = "0.0.20" version = "0.0.22"
authors = ["uutils developers"] authors = ["uutils developers"]
license = "MIT" license = "MIT"
description = "date ~ (uutils) display or set the current time" description = "date ~ (uutils) display or set the current time"

View file

@ -1,8 +1,5 @@
// This file is part of the uutils coreutils package. // This file is part of the uutils coreutils package.
// //
// (c) Anthony Deschamps <anthony.j.deschamps@gmail.com>
// (c) Sylvestre Ledru <sylvestre@debian.org>
//
// For the full copyright and license information, please view the LICENSE // For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code. // file that was distributed with this source code.
@ -169,7 +166,9 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
}; };
let date_source = if let Some(date) = matches.get_one::<String>(OPT_DATE) { let date_source = if let Some(date) = matches.get_one::<String>(OPT_DATE) {
if let Ok(duration) = parse_datetime::from_str(date.as_str()) { let ref_time = Local::now();
if let Ok(new_time) = parse_datetime::parse_datetime_at_date(ref_time, date.as_str()) {
let duration = new_time.signed_duration_since(ref_time);
DateSource::Human(duration) DateSource::Human(duration)
} else { } else {
DateSource::Custom(date.into()) DateSource::Custom(date.into())
@ -227,8 +226,20 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
DateSource::Human(relative_time) => { DateSource::Human(relative_time) => {
// Get the current DateTime<FixedOffset> for things like "1 year ago" // Get the current DateTime<FixedOffset> for things like "1 year ago"
let current_time = DateTime::<FixedOffset>::from(Local::now()); let current_time = DateTime::<FixedOffset>::from(Local::now());
let iter = std::iter::once(Ok(current_time + relative_time)); // double check the result is overflow or not of the current_time + relative_time
Box::new(iter) // it may cause a panic of chrono::datetime::DateTime add
match current_time.checked_add_signed(relative_time) {
Some(date) => {
let iter = std::iter::once(Ok(date));
Box::new(iter)
}
None => {
return Err(USimpleError::new(
1,
format!("invalid date {}", relative_time),
));
}
}
} }
DateSource::File(ref path) => { DateSource::File(ref path) => {
if path.is_dir() { if path.is_dir() {

View file

@ -1,6 +1,6 @@
[package] [package]
name = "uu_dd" name = "uu_dd"
version = "0.0.20" version = "0.0.22"
authors = ["uutils developers"] authors = ["uutils developers"]
license = "MIT" license = "MIT"
description = "dd ~ (uutils) copy and convert files" description = "dd ~ (uutils) copy and convert files"

View file

@ -1,7 +1,7 @@
// * This file is part of the uutils coreutils package. // This file is part of the uutils coreutils package.
// * //
// * For the full copyright and license information, please view the LICENSE // For the full copyright and license information, please view the LICENSE
// * file that was distributed with this source code. // file that was distributed with this source code.
// spell-checker:ignore datastructures rstat rposition cflags ctable // spell-checker:ignore datastructures rstat rposition cflags ctable

View file

@ -1,7 +1,5 @@
// This file is part of the uutils coreutils package. // This file is part of the uutils coreutils package.
// //
// (c) Tyler Steele <tyler.steele@protonmail.com>
//
// For the full copyright and license information, please view the LICENSE // For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code. // file that was distributed with this source code.

View file

@ -1,7 +1,5 @@
// This file is part of the uutils coreutils package. // This file is part of the uutils coreutils package.
// //
// (c) Tyler Steele <tyler.steele@protonmail.com>
//
// For the full copyright and license information, please view the LICENSE // For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code. // file that was distributed with this source code.
// spell-checker:ignore ctable, outfile, iseek, oseek // spell-checker:ignore ctable, outfile, iseek, oseek

View file

@ -1,11 +1,9 @@
// This file is part of the uutils coreutils package. // This file is part of the uutils coreutils package.
// //
// (c) Tyler Steele <tyler.steele@protonmail.com>
//
// For the full copyright and license information, please view the LICENSE // For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code. // file that was distributed with this source code.
// spell-checker:ignore fname, tname, fpath, specfile, testfile, unspec, ifile, ofile, outfile, fullblock, urand, fileio, atoe, atoibm, behaviour, bmax, bremain, cflags, creat, ctable, ctty, datastructures, doesnt, etoa, fileout, fname, gnudd, iconvflags, iseek, nocache, noctty, noerror, nofollow, nolinks, nonblock, oconvflags, oseek, outfile, parseargs, rlen, rmax, rremain, rsofar, rstat, sigusr, wlen, wstat seekable oconv canonicalized fadvise Fadvise FADV DONTNEED ESPIPE // spell-checker:ignore fname, ftype, tname, fpath, specfile, testfile, unspec, ifile, ofile, outfile, fullblock, urand, fileio, atoe, atoibm, behaviour, bmax, bremain, cflags, creat, ctable, ctty, datastructures, doesnt, etoa, fileout, fname, gnudd, iconvflags, iseek, nocache, noctty, noerror, nofollow, nolinks, nonblock, oconvflags, oseek, outfile, parseargs, rlen, rmax, rremain, rsofar, rstat, sigusr, wlen, wstat seekable oconv canonicalized fadvise Fadvise FADV DONTNEED ESPIPE
mod datastructures; mod datastructures;
use datastructures::*; use datastructures::*;
@ -51,6 +49,8 @@ use nix::{
fcntl::{posix_fadvise, PosixFadviseAdvice}, fcntl::{posix_fadvise, PosixFadviseAdvice},
}; };
use uucore::display::Quotable; use uucore::display::Quotable;
#[cfg(unix)]
use uucore::error::set_exit_code;
use uucore::error::{FromIo, UResult}; use uucore::error::{FromIo, UResult};
use uucore::{format_usage, help_about, help_section, help_usage, show_error}; use uucore::{format_usage, help_about, help_section, help_usage, show_error};
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
@ -202,14 +202,25 @@ impl Source {
Err(e) => Err(e), Err(e) => Err(e),
}, },
#[cfg(unix)] #[cfg(unix)]
Self::StdinFile(f) => match io::copy(&mut f.take(n), &mut io::sink()) { Self::StdinFile(f) => {
Ok(m) if m < n => { if let Ok(Some(len)) = try_get_len_of_block_device(f) {
show_error!("'standard input': cannot skip to specified offset"); if len < n {
Ok(m) // GNU compatibility:
// this case prints the stats but sets the exit code to 1
show_error!("'standard input': cannot skip: Invalid argument");
set_exit_code(1);
return Ok(len);
}
} }
Ok(m) => Ok(m), match io::copy(&mut f.take(n), &mut io::sink()) {
Err(e) => Err(e), 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)), Self::File(f) => f.seek(io::SeekFrom::Start(n)),
#[cfg(unix)] #[cfg(unix)]
Self::Fifo(f) => io::copy(&mut f.take(n), &mut io::sink()), Self::Fifo(f) => io::copy(&mut f.take(n), &mut io::sink()),
@ -529,7 +540,19 @@ impl Dest {
fn seek(&mut self, n: u64) -> io::Result<u64> { fn seek(&mut self, n: u64) -> io::Result<u64> {
match self { match self {
Self::Stdout(stdout) => io::copy(&mut io::repeat(0).take(n), stdout), Self::Stdout(stdout) => io::copy(&mut io::repeat(0).take(n), stdout),
Self::File(f, _) => f.seek(io::SeekFrom::Start(n)), Self::File(f, _) => {
#[cfg(unix)]
if let Ok(Some(len)) = try_get_len_of_block_device(f) {
if len < n {
// GNU compatibility:
// this case prints the stats but sets the exit code to 1
show_error!("'standard output': cannot seek: Invalid argument");
set_exit_code(1);
return Ok(len);
}
}
f.seek(io::SeekFrom::Start(n))
}
#[cfg(unix)] #[cfg(unix)]
Self::Fifo(f) => { Self::Fifo(f) => {
// Seeking in a named pipe means *reading* from the pipe. // Seeking in a named pipe means *reading* from the pipe.
@ -1135,6 +1158,20 @@ fn is_stdout_redirected_to_seekable_file() -> bool {
} }
} }
/// Try to get the len if it is a block device
#[cfg(unix)]
fn try_get_len_of_block_device(file: &mut File) -> io::Result<Option<u64>> {
let ftype = file.metadata()?.file_type();
if !ftype.is_block_device() {
return Ok(None);
}
// FIXME: this can be replaced by file.stream_len() when stable.
let len = file.seek(SeekFrom::End(0))?;
file.rewind()?;
Ok(Some(len))
}
/// Decide whether the named file is a named pipe, also known as a FIFO. /// Decide whether the named file is a named pipe, also known as a FIFO.
#[cfg(unix)] #[cfg(unix)]
fn is_fifo(filename: &str) -> bool { fn is_fifo(filename: &str) -> bool {

View file

@ -1,7 +1,7 @@
// * This file is part of the uutils coreutils package. // This file is part of the uutils coreutils package.
// * //
// * For the full copyright and license information, please view the LICENSE // For the full copyright and license information, please view the LICENSE
// * file that was distributed with this source code. // file that was distributed with this source code.
/// Functions for formatting a number as a magnitude and a unit suffix. /// Functions for formatting a number as a magnitude and a unit suffix.
/// The first ten powers of 1024. /// The first ten powers of 1024.
@ -115,6 +115,7 @@ mod tests {
} }
#[test] #[test]
#[allow(clippy::cognitive_complexity)]
fn test_to_magnitude_and_suffix_not_powers_of_1024() { fn test_to_magnitude_and_suffix_not_powers_of_1024() {
assert_eq!(to_magnitude_and_suffix(1, SuffixType::Si), "1.0 B"); assert_eq!(to_magnitude_and_suffix(1, SuffixType::Si), "1.0 B");
assert_eq!(to_magnitude_and_suffix(999, SuffixType::Si), "999 B"); assert_eq!(to_magnitude_and_suffix(999, SuffixType::Si), "999 B");

View file

@ -1,7 +1,5 @@
// This file is part of the uutils coreutils package. // This file is part of the uutils coreutils package.
// //
// (c) Tyler Steele <tyler.steele@protonmail.com>
//
// For the full copyright and license information, please view the LICENSE // For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code. // file that was distributed with this source code.
// spell-checker:ignore ctty, ctable, iseek, oseek, iconvflags, oconvflags parseargs outfile oconv // spell-checker:ignore ctty, ctable, iseek, oseek, iconvflags, oconvflags parseargs outfile oconv
@ -247,29 +245,29 @@ impl Parser {
None => return Err(ParseError::UnrecognizedOperand(operand.to_string())), None => return Err(ParseError::UnrecognizedOperand(operand.to_string())),
Some((k, v)) => match k { Some((k, v)) => match k {
"bs" => { "bs" => {
let bs = self.parse_bytes(k, v)?; let bs = Self::parse_bytes(k, v)?;
self.ibs = bs; self.ibs = bs;
self.obs = bs; self.obs = bs;
} }
"cbs" => self.cbs = Some(self.parse_bytes(k, v)?), "cbs" => self.cbs = Some(Self::parse_bytes(k, v)?),
"conv" => self.parse_conv_flags(v)?, "conv" => self.parse_conv_flags(v)?,
"count" => self.count = Some(self.parse_n(v)?), "count" => self.count = Some(Self::parse_n(v)?),
"ibs" => self.ibs = self.parse_bytes(k, v)?, "ibs" => self.ibs = Self::parse_bytes(k, v)?,
"if" => self.infile = Some(v.to_string()), "if" => self.infile = Some(v.to_string()),
"iflag" => self.parse_input_flags(v)?, "iflag" => self.parse_input_flags(v)?,
"obs" => self.obs = self.parse_bytes(k, v)?, "obs" => self.obs = Self::parse_bytes(k, v)?,
"of" => self.outfile = Some(v.to_string()), "of" => self.outfile = Some(v.to_string()),
"oflag" => self.parse_output_flags(v)?, "oflag" => self.parse_output_flags(v)?,
"seek" | "oseek" => self.seek = self.parse_n(v)?, "seek" | "oseek" => self.seek = Self::parse_n(v)?,
"skip" | "iseek" => self.skip = self.parse_n(v)?, "skip" | "iseek" => self.skip = Self::parse_n(v)?,
"status" => self.status = Some(self.parse_status_level(v)?), "status" => self.status = Some(Self::parse_status_level(v)?),
_ => return Err(ParseError::UnrecognizedOperand(operand.to_string())), _ => return Err(ParseError::UnrecognizedOperand(operand.to_string())),
}, },
} }
Ok(()) Ok(())
} }
fn parse_n(&self, val: &str) -> Result<Num, ParseError> { fn parse_n(val: &str) -> Result<Num, ParseError> {
let n = parse_bytes_with_opt_multiplier(val)?; let n = parse_bytes_with_opt_multiplier(val)?;
Ok(if val.ends_with('B') { Ok(if val.ends_with('B') {
Num::Bytes(n) Num::Bytes(n)
@ -278,13 +276,13 @@ impl Parser {
}) })
} }
fn parse_bytes(&self, arg: &str, val: &str) -> Result<usize, ParseError> { fn parse_bytes(arg: &str, val: &str) -> Result<usize, ParseError> {
parse_bytes_with_opt_multiplier(val)? parse_bytes_with_opt_multiplier(val)?
.try_into() .try_into()
.map_err(|_| ParseError::BsOutOfRange(arg.to_string())) .map_err(|_| ParseError::BsOutOfRange(arg.to_string()))
} }
fn parse_status_level(&self, val: &str) -> Result<StatusLevel, ParseError> { fn parse_status_level(val: &str) -> Result<StatusLevel, ParseError> {
match val { match val {
"none" => Ok(StatusLevel::None), "none" => Ok(StatusLevel::None),
"noxfer" => Ok(StatusLevel::Noxfer), "noxfer" => Ok(StatusLevel::Noxfer),
@ -506,7 +504,7 @@ fn parse_bytes_no_x(full: &str, s: &str) -> Result<u64, ParseError> {
..Default::default() ..Default::default()
}; };
let (num, multiplier) = match (s.find('c'), s.rfind('w'), s.rfind('b')) { let (num, multiplier) = match (s.find('c'), s.rfind('w'), s.rfind('b')) {
(None, None, None) => match parser.parse(s) { (None, None, None) => match parser.parse_u64(s) {
Ok(n) => (n, 1), Ok(n) => (n, 1),
Err(ParseSizeError::InvalidSuffix(_) | ParseSizeError::ParseFailure(_)) => { Err(ParseSizeError::InvalidSuffix(_) | ParseSizeError::ParseFailure(_)) => {
return Err(ParseError::InvalidNumber(full.to_string())) return Err(ParseError::InvalidNumber(full.to_string()))

View file

@ -1,3 +1,7 @@
// This file is part of the uutils coreutils package.
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
// spell-checker:ignore fname, tname, fpath, specfile, testfile, unspec, ifile, ofile, outfile, fullblock, urand, fileio, atoe, atoibm, behaviour, bmax, bremain, btotal, cflags, creat, ctable, ctty, datastructures, doesnt, etoa, fileout, fname, gnudd, iconvflags, iseek, nocache, noctty, noerror, nofollow, nolinks, nonblock, oconvflags, oseek, outfile, parseargs, rlen, rmax, rposition, rremain, rsofar, rstat, sigusr, sigval, wlen, wstat, oconv // spell-checker:ignore fname, tname, fpath, specfile, testfile, unspec, ifile, ofile, outfile, fullblock, urand, fileio, atoe, atoibm, behaviour, bmax, bremain, btotal, cflags, creat, ctable, ctty, datastructures, doesnt, etoa, fileout, fname, gnudd, iconvflags, iseek, nocache, noctty, noerror, nofollow, nolinks, nonblock, oconvflags, oseek, outfile, parseargs, rlen, rmax, rposition, rremain, rsofar, rstat, sigusr, sigval, wlen, wstat, oconv
use super::*; use super::*;
@ -99,6 +103,7 @@ fn test_status_level_none() {
} }
#[test] #[test]
#[allow(clippy::cognitive_complexity)]
fn test_all_top_level_args_no_leading_dashes() { fn test_all_top_level_args_no_leading_dashes() {
let args = &[ let args = &[
"if=foo.file", "if=foo.file",

View file

@ -1,7 +1,7 @@
// * This file is part of the uutils coreutils package. // This file is part of the uutils coreutils package.
// * //
// * For the full copyright and license information, please view the LICENSE // For the full copyright and license information, please view the LICENSE
// * file that was distributed with this source code. // file that was distributed with this source code.
// spell-checker:ignore btotal sigval // spell-checker:ignore btotal sigval
//! Read and write progress tracking for dd. //! Read and write progress tracking for dd.
//! //!

View file

@ -1,6 +1,6 @@
[package] [package]
name = "uu_df" name = "uu_df"
version = "0.0.20" version = "0.0.22"
authors = ["uutils developers"] authors = ["uutils developers"]
license = "MIT" license = "MIT"
description = "df ~ (uutils) display file system information" description = "df ~ (uutils) display file system information"

View file

@ -1,7 +1,7 @@
// * This file is part of the uutils coreutils package. // This file is part of the uutils coreutils package.
// * //
// * For the full copyright and license information, please view the LICENSE // For the full copyright and license information, please view the LICENSE
// * file that was distributed with this source code. // file that was distributed with this source code.
//! Types for representing and displaying block sizes. //! Types for representing and displaying block sizes.
use crate::{OPT_BLOCKSIZE, OPT_PORTABILITY}; use crate::{OPT_BLOCKSIZE, OPT_PORTABILITY};
use clap::ArgMatches; use clap::ArgMatches;
@ -9,7 +9,7 @@ use std::{env, fmt};
use uucore::{ use uucore::{
display::Quotable, display::Quotable,
parse_size::{parse_size, ParseSizeError}, parse_size::{parse_size_u64, ParseSizeError},
}; };
/// The first ten powers of 1024. /// The first ten powers of 1024.
@ -165,7 +165,7 @@ impl Default for BlockSize {
pub(crate) fn read_block_size(matches: &ArgMatches) -> Result<BlockSize, ParseSizeError> { pub(crate) fn read_block_size(matches: &ArgMatches) -> Result<BlockSize, ParseSizeError> {
if matches.contains_id(OPT_BLOCKSIZE) { if matches.contains_id(OPT_BLOCKSIZE) {
let s = matches.get_one::<String>(OPT_BLOCKSIZE).unwrap(); let s = matches.get_one::<String>(OPT_BLOCKSIZE).unwrap();
let bytes = parse_size(s)?; let bytes = parse_size_u64(s)?;
if bytes > 0 { if bytes > 0 {
Ok(BlockSize::Bytes(bytes)) Ok(BlockSize::Bytes(bytes))
@ -184,7 +184,7 @@ pub(crate) fn read_block_size(matches: &ArgMatches) -> Result<BlockSize, ParseSi
fn block_size_from_env() -> Option<u64> { fn block_size_from_env() -> Option<u64> {
for env_var in ["DF_BLOCK_SIZE", "BLOCK_SIZE", "BLOCKSIZE"] { for env_var in ["DF_BLOCK_SIZE", "BLOCK_SIZE", "BLOCKSIZE"] {
if let Ok(env_size) = env::var(env_var) { if let Ok(env_size) = env::var(env_var) {
if let Ok(size) = parse_size(&env_size) { if let Ok(size) = parse_size_u64(&env_size) {
return Some(size); return Some(size);
} else { } else {
return None; return None;
@ -239,6 +239,7 @@ mod tests {
} }
#[test] #[test]
#[allow(clippy::cognitive_complexity)]
fn test_to_magnitude_and_suffix_not_powers_of_1024() { fn test_to_magnitude_and_suffix_not_powers_of_1024() {
assert_eq!(to_magnitude_and_suffix(1, SuffixType::Si), "1B"); assert_eq!(to_magnitude_and_suffix(1, SuffixType::Si), "1B");
assert_eq!(to_magnitude_and_suffix(999, SuffixType::Si), "999B"); assert_eq!(to_magnitude_and_suffix(999, SuffixType::Si), "999B");

View file

@ -1,7 +1,7 @@
// * This file is part of the uutils coreutils package. // This file is part of the uutils coreutils package.
// * //
// * For the full copyright and license information, please view the LICENSE // For the full copyright and license information, please view the LICENSE
// * file that was distributed with this source code. // file that was distributed with this source code.
// spell-checker:ignore itotal iused iavail ipcent pcent squashfs // spell-checker:ignore itotal iused iavail ipcent pcent squashfs
use crate::{OPT_INODES, OPT_OUTPUT, OPT_PRINT_TYPE}; use crate::{OPT_INODES, OPT_OUTPUT, OPT_PRINT_TYPE};
use clap::{parser::ValueSource, ArgMatches}; use clap::{parser::ValueSource, ArgMatches};

View file

@ -1,10 +1,7 @@
// This file is part of the uutils coreutils package. // This file is part of the uutils coreutils package.
// //
// (c) Fangxu Hu <framlog@gmail.com> // For the full copyright and license information, please view the LICENSE
// (c) Sylvestre Ledru <sylvestre@debian.org> // file that was distributed with this source code.
//
// For the full copyright and license information, please view the LICENSE file
// that was distributed with this source code.
// spell-checker:ignore itotal iused iavail ipcent pcent tmpfs squashfs lofs // spell-checker:ignore itotal iused iavail ipcent pcent tmpfs squashfs lofs
mod blocks; mod blocks;
mod columns; mod columns;

View file

@ -1,7 +1,7 @@
// * This file is part of the uutils coreutils package. // This file is part of the uutils coreutils package.
// * //
// * For the full copyright and license information, please view the LICENSE // For the full copyright and license information, please view the LICENSE
// * file that was distributed with this source code. // file that was distributed with this source code.
//! Provides a summary representation of a filesystem. //! Provides a summary representation of a filesystem.
//! //!
//! A [`Filesystem`] struct represents a device containing a //! A [`Filesystem`] struct represents a device containing a

View file

@ -1,8 +1,8 @@
// * This file is part of the uutils coreutils package. // This file is part of the uutils coreutils package.
// * //
// * For the full copyright and license information, please view the LICENSE // For the full copyright and license information, please view the LICENSE
// * file that was distributed with this source code. // file that was distributed with this source code.
// spell-checker:ignore tmpfs Pcent Itotal Iused Iavail Ipcent // spell-checker:ignore tmpfs Pcent Itotal Iused Iavail Ipcent nosuid nodev
//! The filesystem usage data table. //! The filesystem usage data table.
//! //!
//! A table ([`Table`]) comprises a header row ([`Header`]) and a //! A table ([`Table`]) comprises a header row ([`Header`]) and a
@ -152,8 +152,10 @@ impl From<Filesystem> for Row {
ffree, ffree,
.. ..
} = fs.usage; } = fs.usage;
let bused = blocks - bfree;
let fused = files - ffree; // On Windows WSL, files can be less than ffree. Protect such cases via saturating_sub.
let bused = blocks.saturating_sub(bfree);
let fused = files.saturating_sub(ffree);
Self { Self {
file: fs.file, file: fs.file,
fs_device: dev_name, fs_device: dev_name,
@ -815,4 +817,35 @@ mod tests {
assert_eq!(get_formatted_values(1000, 1000, 0), vec!("1", "1", "0")); assert_eq!(get_formatted_values(1000, 1000, 0), vec!("1", "1", "0"));
assert_eq!(get_formatted_values(1001, 1000, 1), vec!("2", "1", "1")); assert_eq!(get_formatted_values(1001, 1000, 1), vec!("2", "1", "1"));
} }
#[test]
fn test_row_converter_with_invalid_numbers() {
// copy from wsl linux
let d = crate::Filesystem {
file: None,
mount_info: crate::MountInfo {
dev_id: "28".to_string(),
dev_name: "none".to_string(),
fs_type: "9p".to_string(),
mount_dir: "/usr/lib/wsl/drivers".to_string(),
mount_option: "ro,nosuid,nodev,noatime".to_string(),
mount_root: "/".to_string(),
remote: false,
dummy: false,
},
usage: crate::table::FsUsage {
blocksize: 4096,
blocks: 244029695,
bfree: 125085030,
bavail: 125085030,
bavail_top_bit_set: false,
files: 999,
ffree: 1000000,
},
};
let row = Row::from(d);
assert_eq!(row.inodes_used, 0);
}
} }

View file

@ -1,6 +1,6 @@
[package] [package]
name = "uu_dir" name = "uu_dir"
version = "0.0.20" version = "0.0.22"
authors = ["uutils developers"] authors = ["uutils developers"]
license = "MIT" license = "MIT"
description = "shortcut to ls -C -b" description = "shortcut to ls -C -b"
@ -16,7 +16,7 @@ path = "src/dir.rs"
[dependencies] [dependencies]
clap = { workspace = true, features = ["env"] } clap = { workspace = true, features = ["env"] }
uucore = { workspace = true, features = ["entries", "fs"] } uucore = { workspace = true, features = ["entries", "fs", "quoting-style"] }
uu_ls = { workspace = true } uu_ls = { workspace = true }
[[bin]] [[bin]]

View file

@ -1,9 +1,7 @@
// * This file is part of the uutils coreutils package. // This file is part of the uutils coreutils package.
// * //
// * (c) gmnsii <gmnsii@protonmail.com> // For the full copyright and license information, please view the LICENSE
// * // file that was distributed with this source code.
// * For the full copyright and license information, please view the LICENSE file
// * that was distributed with this source code.
use clap::Command; use clap::Command;
use std::ffi::OsString; use std::ffi::OsString;

View file

@ -1,6 +1,6 @@
[package] [package]
name = "uu_dircolors" name = "uu_dircolors"
version = "0.0.20" version = "0.0.22"
authors = ["uutils developers"] authors = ["uutils developers"]
license = "MIT" license = "MIT"
description = "dircolors ~ (uutils) display commands to set LS_COLORS" description = "dircolors ~ (uutils) display commands to set LS_COLORS"

Some files were not shown because too many files have changed in this diff Show more