1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2025-08-05 23:47:46 +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

View file

@ -11,7 +11,7 @@ env:
PROJECT_NAME: coreutils
PROJECT_DESC: "Core universal (cross-platform) utilities"
PROJECT_AUTH: "uutils"
RUST_MIN_SRV: "1.64.0"
RUST_MIN_SRV: "1.70.0"
# * 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
@ -30,7 +30,7 @@ jobs:
name: Style/cargo-deny
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: EmbarkStudios/cargo-deny-action@v1
style_deps:
@ -47,7 +47,7 @@ jobs:
- { os: macos-latest , features: "feat_Tier1,feat_require_unix,feat_require_unix_utmpx" }
- { os: windows-latest , features: feat_os_windows }
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@nightly
## note: requires 'nightly' toolchain b/c `cargo-udeps` uses the `rustc` '-Z save-analysis' option
## * ... 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 ; }
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:
name: Documentation/warnings
runs-on: ${{ matrix.job.os }}
@ -285,7 +99,7 @@ jobs:
# - { os: macos-latest , features: feat_os_macos }
# - { os: windows-latest , features: feat_os_windows }
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@master
with:
toolchain: stable
@ -319,9 +133,9 @@ jobs:
shell: bash
run: |
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:
command: fix
fix: "true"
globs: |
*.md
docs/src/*.md
@ -338,7 +152,7 @@ jobs:
job:
- { os: ubuntu-latest , features: feat_os_unix }
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@master
with:
toolchain: ${{ env.RUST_MIN_SRV }}
@ -406,7 +220,7 @@ jobs:
job:
- { os: ubuntu-latest , features: feat_os_unix }
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- name: "`cargo update` testing"
@ -429,7 +243,7 @@ jobs:
job:
- { os: ubuntu-latest , features: feat_os_unix }
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: taiki-e/install-action@nextest
- uses: Swatinem/rust-cache@v2
@ -483,7 +297,7 @@ jobs:
- { os: macos-latest , features: feat_os_macos }
- { os: windows-latest , features: feat_os_windows }
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: taiki-e/install-action@nextest
- uses: Swatinem/rust-cache@v2
@ -510,7 +324,7 @@ jobs:
- { os: macos-latest , features: feat_os_macos }
- { os: windows-latest , features: feat_os_windows }
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@nightly
- uses: taiki-e/install-action@nextest
- uses: Swatinem/rust-cache@v2
@ -534,7 +348,7 @@ jobs:
job:
- { os: ubuntu-latest , features: feat_os_unix }
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- name: Run sccache-cache
@ -596,6 +410,12 @@ jobs:
check() {
# Warn if the size increases by more than 5%
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")
echo "$1: size=$2, previous_size=$3, ratio=$ratio, threshold=$threshold"
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-msvc , features: feat_os_windows }
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@master
with:
toolchain: ${{ env.RUST_MIN_SRV }}
@ -913,7 +733,7 @@ jobs:
run: |
## VARs setup
echo "TEST_SUMMARY_FILE=busybox-result.json" >> $GITHUB_OUTPUT
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: Swatinem/rust-cache@v2
- name: Run sccache-cache
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; }
TEST_SUMMARY_FILE="toybox-result.json"
outputs TEST_SUMMARY_FILE
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@master
with:
toolchain: ${{ env.RUST_MIN_SRV }}
@ -1060,15 +880,6 @@ jobs:
name: toybox-result.json
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:
name: Code Coverage
runs-on: ${{ matrix.job.os }}
@ -1084,7 +895,7 @@ jobs:
- { os: macos-latest , features: macos, toolchain: nightly }
- { os: windows-latest , features: windows, toolchain: nightly-x86_64-pc-windows-gnu }
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@master
with:
toolchain: ${{ matrix.job.toolchain }}
@ -1201,4 +1012,3 @@ jobs:
flags: ${{ steps.vars.outputs.CODECOV_FLAGS }}
name: codecov-umbrella
fail_ci_if_error: false

View file

@ -29,7 +29,7 @@ jobs:
permissions:
contents: read
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Run ShellCheck
uses: ludeeus/action-shellcheck@master
env:
@ -41,34 +41,16 @@ jobs:
shell_fmt:
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
needs: [ shell_check ]
permissions:
contents: write
pull-requests: write
contents: read
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Setup shfmt
uses: mfinelli/setup-shfmt@v2
uses: mfinelli/setup-shfmt@v3
- name: Run shfmt
shell: bash
run: |
# show differs first for every files that need to be formatted
# fmt options: bash syntax, 4 spaces indent, indent for switch-case
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
# 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:
- { os: ubuntu-latest , features: feat_os_unix }
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Initialize job variables
id: vars
shell: bash
@ -85,7 +85,7 @@ jobs:
job:
- { os: ubuntu-latest , features: feat_os_unix }
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Initialize job variables
id: vars
shell: bash

View file

@ -42,7 +42,7 @@ jobs:
outputs path_GNU path_GNU_tests path_reference path_UUTILS
#
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 }}"
outputs repo_default_branch repo_GNU_ref repo_reference_branch
#
@ -55,7 +55,7 @@ jobs:
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
- name: Checkout code (uutil)
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
path: '${{ steps.vars.outputs.path_UUTILS }}'
- uses: dtolnay/rust-toolchain@master
@ -66,7 +66,7 @@ jobs:
with:
workspaces: "./${{ steps.vars.outputs.path_UUTILS }} -> target"
- name: Checkout code (GNU coreutils)
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
repository: 'coreutils/coreutils'
path: '${{ steps.vars.outputs.path_GNU }}'
@ -307,15 +307,15 @@ jobs:
runs-on: ubuntu-20.04
steps:
- name: Checkout code uutil
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
path: 'uutils'
- name: Checkout GNU coreutils
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
repository: 'coreutils/coreutils'
path: 'gnu'
ref: 'v9.3'
ref: 'v9.4'
submodules: recursive
- uses: dtolnay/rust-toolchain@master
with:

View file

@ -26,7 +26,7 @@ jobs:
env:
TERMUX: v0.118.0
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Restore AVD cache
uses: actions/cache/restore@v3
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"
RUSTC_WRAPPER: "sccache"
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: Swatinem/rust-cache@v2
- name: Run sccache-cache
uses: mozilla-actions/sccache-action@v0.0.3
@ -120,7 +120,7 @@ jobs:
SCCACHE_GHA_ENABLED: "true"
RUSTC_WRAPPER: "sccache"
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: Swatinem/rust-cache@v2
- name: Run sccache-cache
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
NetBSD
Novell
Nushell
OpenBSD
POSIX
PowerPC

View file

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

View file

@ -38,199 +38,15 @@ CI. However, you can use `#[cfg(...)]` attributes to create platform dependent f
VirtualBox and Parallels) for development:
<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
section explains how to run those checks locally to avoid waiting for the CI.
To setup your local development environment for this project please follow [DEVELOPMENT.md guide](DEVELOPMENT.md)
### 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
automatically checking every git commit you make to ensure it compiles, and
passes `clippy` and `rustfmt` without warnings.
## Improving the GNU compatibility
To use the provided hook:
1. [Install `pre-commit`](https://pre-commit.com/#install)
1. Run `pre-commit install` while in the repository directory
Your git commits will then automatically be checked. If a check fails, an error
message will explain why, and your commit will be canceled. You can then make
the suggested changes, and run `git commit ...` again.
### clippy
```shell
cargo clippy --all-targets --all-features
```
The `msrv` key in the clippy configuration file `clippy.toml` is used to disable
lints pertaining to newer features by specifying the minimum supported Rust
version (MSRV).
### rustfmt
```shell
cargo fmt --all
```
### cargo-deny
This project uses [cargo-deny](https://github.com/EmbarkStudios/cargo-deny/) to
detect duplicate dependencies, checks licenses, etc. To run it locally, first
install it and then run with:
```
cargo deny --all-features check all
```
### Markdown linter
We use [markdownlint](https://github.com/DavidAnson/markdownlint) to lint the
Markdown files in the repository.
### Spell checker
We use `cspell` as spell checker for all files in the project. If you are using
VS Code, you can install the
[code spell checker](https://marketplace.visualstudio.com/items?itemName=streetsidesoftware.code-spell-checker)
extension to enable spell checking within your editor. Otherwise, you can
install [cspell](https://cspell.org/) separately.
If you want to make the spell checker ignore a word, you can add
```rust
// spell-checker:ignore word_to_ignore
```
at the top of the file.
## Testing
Testing can be done using either Cargo or `make`.
### Testing with Cargo
Just like with building, we follow the standard procedure for testing using
Cargo:
```shell
cargo test
```
By default, `cargo test` only runs the common programs. To run also platform
specific tests, run:
```shell
cargo test --features unix
```
If you would prefer to test a select few utilities:
```shell
cargo test --features "chmod mv tail" --no-default-features
```
If you also want to test the core utilities:
```shell
cargo test -p uucore -p coreutils
```
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
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)
The Python script `./util/remaining-gnu-error.py` shows the list of failing
tests in the CI.
@ -320,30 +136,7 @@ gitignore: add temporary files
## Code coverage
<!-- spell-checker:ignore (flags) Ccodegen Coverflow Cpanic Zinstrument Zpanic -->
Code coverage report can be generated using [grcov](https://github.com/mozilla/grcov).
### Using Nightly Rust
To generate [gcov-based](https://github.com/mozilla/grcov#example-how-to-generate-gcda-files-for-a-rust-project) coverage report
```shell
export CARGO_INCREMENTAL=0
export RUSTFLAGS="-Zprofile -Ccodegen-units=1 -Copt-level=0 -Clink-dead-code -Coverflow-checks=off -Zpanic_abort_tests -Cpanic=abort"
export RUSTDOCFLAGS="-Cpanic=abort"
cargo build <options...> # e.g., --features feat_os_unix
cargo test <options...> # e.g., --features feat_os_unix test_pathchk
grcov . -s . --binary-path ./target/debug/ -t html --branch --ignore-not-existing --ignore build.rs --excl-br-line "^\s*((debug_)?assert(_eq|_ne)?\#\[derive\()" -o ./target/debug/coverage/
# open target/debug/coverage/index.html in browser
```
if changes are not reflected in the report then run `cargo clean` and run the above commands.
### Using Stable Rust
If you are using stable version of Rust that doesn't enable code coverage instrumentation by default
then add `-Z-Zinstrument-coverage` flag to `RUSTFLAGS` env variable specified above.
To generate code coverage report locally please follow [Code coverage report](DEVELOPMENT.md#code-coverage-report) section of [DEVELOPMENT.md](DEVELOPMENT.md)
## Other implementations

746
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -5,7 +5,7 @@
[package]
name = "coreutils"
version = "0.0.20"
version = "0.0.22"
authors = ["uutils developers"]
license = "MIT"
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"
keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"]
categories = ["command-line-utilities"]
rust-version = "1.64.0"
rust-version = "1.70.0"
edition = "2021"
build = "build.rs"
@ -100,7 +100,6 @@ feat_common_core = [
"pwd",
"readlink",
"realpath",
"relpath",
"rm",
"rmdir",
"seq",
@ -152,6 +151,7 @@ feat_os_unix = [
"feat_require_crate_cpp",
"feat_require_unix",
"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 = [
@ -259,87 +259,86 @@ test = ["uu_test"]
[workspace.dependencies]
bigdecimal = "0.4"
binary-heap-plus = "0.5.0"
bstr = "1.6"
bytecount = "0.6.3"
byteorder = "1.4.3"
chrono = { version = "^0.4.26", default-features = false, features = [
bstr = "1.7"
bytecount = "0.6.7"
byteorder = "1.5.0"
chrono = { version = "^0.4.31", default-features = false, features = [
"std",
"alloc",
"clock",
] }
clap = { version = "4.3", features = ["wrap_help", "cargo"] }
clap_complete = "4.3"
clap = { version = "4.4", features = ["wrap_help", "cargo"] }
clap_complete = "4.4"
clap_mangen = "0.2"
compare = "0.1.0"
coz = { version = "0.1.3" }
crossterm = ">=0.26.1"
crossterm = ">=0.27.0"
ctrlc = { version = "3.4", features = ["termination"] }
exacl = "0.10.0"
exacl = "0.11.0"
file_diff = "1.0.0"
filetime = "0.2"
fnv = "1.0.7"
fs_extra = "1.3.0"
fts-sys = "0.2"
fundu = "1.2.0"
fundu = "2.0.0"
gcd = "2.3"
glob = "0.3.1"
half = "2.2"
half = "2.3"
indicatif = "0.17"
is-terminal = "0.4.7"
itertools = "0.11.0"
libc = "0.2.147"
libc = "0.2.149"
lscolors = { version = "0.15.0", default-features = false, features = [
"nu-ansi-term",
] }
memchr = "2"
memmap2 = "0.7"
nix = { version = "0.26", default-features = false }
memmap2 = "0.9"
nix = { version = "0.27", default-features = false }
nom = "7.1.3"
notify = { version = "=6.0.1", features = ["macos_kqueue"] }
num-bigint = "0.4.3"
num-traits = "0.2.16"
num-bigint = "0.4.4"
num-traits = "0.2.17"
number_prefix = "0.4"
once_cell = "1.18.0"
onig = { version = "~6.4", default-features = false }
parse_datetime = "0.4.0"
parse_datetime = "0.5.0"
phf = "0.11.2"
phf_codegen = "0.11.2"
platform-info = "2.0.2"
quick-error = "2.0.1"
rand = { version = "0.8", features = ["small_rng"] }
rand_core = "0.6"
rayon = "1.7"
redox_syscall = "0.3"
regex = "1.9.1"
rstest = "0.18.1"
rayon = "1.8"
redox_syscall = "0.4"
regex = "1.10.2"
rstest = "0.18.2"
rust-ini = "0.19.0"
same-file = "1.0.6"
self_cell = "1.0.1"
selinux = "0.4"
signal-hook = "0.3.17"
smallvec = { version = "1.11", features = ["union"] }
tempfile = "3.6.0"
term_grid = "0.1.5"
terminal_size = "0.2.6"
tempfile = "3.8.1"
uutils_term_grid = "0.3"
terminal_size = "0.3.0"
textwrap = { version = "0.16.0", features = ["terminal_size"] }
thiserror = "1.0"
time = { version = "0.3" }
unicode-segmentation = "1.10.1"
unicode-width = "0.1.10"
unicode-width = "0.1.11"
utf-8 = "0.7.6"
walkdir = "2.3"
winapi-util = "0.1.5"
walkdir = "2.4"
winapi-util = "0.1.6"
windows-sys = { version = "0.48.0", default-features = false }
xattr = "1.0.1"
zip = { version = "0.6.6", default_features = false, features = ["deflate"] }
hex = "0.4.3"
md-5 = "0.10.5"
sha1 = "0.10.5"
sha2 = "0.10.7"
md-5 = "0.10.6"
sha1 = "0.10.6"
sha2 = "0.10.8"
sha3 = "0.10.8"
blake2b_simd = "1.0.1"
blake3 = "1.4.0"
blake2b_simd = "1.0.2"
blake3 = "1.5.0"
sm3 = "0.4.2"
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" }
# * 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" }
base32 = { optional = true, version = "0.0.20", package = "uu_base32", path = "src/uu/base32" }
base64 = { optional = true, version = "0.0.20", package = "uu_base64", path = "src/uu/base64" }
basename = { optional = true, version = "0.0.20", package = "uu_basename", path = "src/uu/basename" }
basenc = { optional = true, version = "0.0.20", package = "uu_basenc", path = "src/uu/basenc" }
cat = { optional = true, version = "0.0.20", package = "uu_cat", path = "src/uu/cat" }
chcon = { optional = true, version = "0.0.20", package = "uu_chcon", path = "src/uu/chcon" }
chgrp = { optional = true, version = "0.0.20", package = "uu_chgrp", path = "src/uu/chgrp" }
chmod = { optional = true, version = "0.0.20", package = "uu_chmod", path = "src/uu/chmod" }
chown = { optional = true, version = "0.0.20", package = "uu_chown", path = "src/uu/chown" }
chroot = { optional = true, version = "0.0.20", package = "uu_chroot", path = "src/uu/chroot" }
cksum = { optional = true, version = "0.0.20", package = "uu_cksum", path = "src/uu/cksum" }
comm = { optional = true, version = "0.0.20", package = "uu_comm", path = "src/uu/comm" }
cp = { optional = true, version = "0.0.20", package = "uu_cp", path = "src/uu/cp" }
csplit = { optional = true, version = "0.0.20", package = "uu_csplit", path = "src/uu/csplit" }
cut = { optional = true, version = "0.0.20", package = "uu_cut", path = "src/uu/cut" }
date = { optional = true, version = "0.0.20", package = "uu_date", path = "src/uu/date" }
dd = { optional = true, version = "0.0.20", package = "uu_dd", path = "src/uu/dd" }
df = { optional = true, version = "0.0.20", package = "uu_df", path = "src/uu/df" }
dir = { optional = true, version = "0.0.20", package = "uu_dir", path = "src/uu/dir" }
dircolors = { optional = true, version = "0.0.20", package = "uu_dircolors", path = "src/uu/dircolors" }
dirname = { optional = true, version = "0.0.20", package = "uu_dirname", path = "src/uu/dirname" }
du = { optional = true, version = "0.0.20", package = "uu_du", path = "src/uu/du" }
echo = { optional = true, version = "0.0.20", package = "uu_echo", path = "src/uu/echo" }
env = { optional = true, version = "0.0.20", package = "uu_env", path = "src/uu/env" }
expand = { optional = true, version = "0.0.20", package = "uu_expand", path = "src/uu/expand" }
expr = { optional = true, version = "0.0.20", package = "uu_expr", path = "src/uu/expr" }
factor = { optional = true, version = "0.0.20", package = "uu_factor", path = "src/uu/factor" }
false = { optional = true, version = "0.0.20", package = "uu_false", path = "src/uu/false" }
fmt = { optional = true, version = "0.0.20", package = "uu_fmt", path = "src/uu/fmt" }
fold = { optional = true, version = "0.0.20", package = "uu_fold", path = "src/uu/fold" }
groups = { optional = true, version = "0.0.20", package = "uu_groups", path = "src/uu/groups" }
hashsum = { optional = true, version = "0.0.20", package = "uu_hashsum", path = "src/uu/hashsum" }
head = { optional = true, version = "0.0.20", package = "uu_head", path = "src/uu/head" }
hostid = { optional = true, version = "0.0.20", package = "uu_hostid", path = "src/uu/hostid" }
hostname = { optional = true, version = "0.0.20", package = "uu_hostname", path = "src/uu/hostname" }
id = { optional = true, version = "0.0.20", package = "uu_id", path = "src/uu/id" }
install = { optional = true, version = "0.0.20", package = "uu_install", path = "src/uu/install" }
join = { optional = true, version = "0.0.20", package = "uu_join", path = "src/uu/join" }
kill = { optional = true, version = "0.0.20", package = "uu_kill", path = "src/uu/kill" }
link = { optional = true, version = "0.0.20", package = "uu_link", path = "src/uu/link" }
ln = { optional = true, version = "0.0.20", package = "uu_ln", path = "src/uu/ln" }
ls = { optional = true, version = "0.0.20", package = "uu_ls", path = "src/uu/ls" }
logname = { optional = true, version = "0.0.20", package = "uu_logname", path = "src/uu/logname" }
mkdir = { optional = true, version = "0.0.20", package = "uu_mkdir", path = "src/uu/mkdir" }
mkfifo = { optional = true, version = "0.0.20", package = "uu_mkfifo", path = "src/uu/mkfifo" }
mknod = { optional = true, version = "0.0.20", package = "uu_mknod", path = "src/uu/mknod" }
mktemp = { optional = true, version = "0.0.20", package = "uu_mktemp", path = "src/uu/mktemp" }
more = { optional = true, version = "0.0.20", package = "uu_more", path = "src/uu/more" }
mv = { optional = true, version = "0.0.20", package = "uu_mv", path = "src/uu/mv" }
nice = { optional = true, version = "0.0.20", package = "uu_nice", path = "src/uu/nice" }
nl = { optional = true, version = "0.0.20", package = "uu_nl", path = "src/uu/nl" }
nohup = { optional = true, version = "0.0.20", package = "uu_nohup", path = "src/uu/nohup" }
nproc = { optional = true, version = "0.0.20", package = "uu_nproc", path = "src/uu/nproc" }
numfmt = { optional = true, version = "0.0.20", package = "uu_numfmt", path = "src/uu/numfmt" }
od = { optional = true, version = "0.0.20", package = "uu_od", path = "src/uu/od" }
paste = { optional = true, version = "0.0.20", package = "uu_paste", path = "src/uu/paste" }
pathchk = { optional = true, version = "0.0.20", package = "uu_pathchk", path = "src/uu/pathchk" }
pinky = { optional = true, version = "0.0.20", package = "uu_pinky", path = "src/uu/pinky" }
pr = { optional = true, version = "0.0.20", package = "uu_pr", path = "src/uu/pr" }
printenv = { optional = true, version = "0.0.20", package = "uu_printenv", path = "src/uu/printenv" }
printf = { optional = true, version = "0.0.20", package = "uu_printf", path = "src/uu/printf" }
ptx = { optional = true, version = "0.0.20", package = "uu_ptx", path = "src/uu/ptx" }
pwd = { optional = true, version = "0.0.20", package = "uu_pwd", path = "src/uu/pwd" }
readlink = { optional = true, version = "0.0.20", package = "uu_readlink", path = "src/uu/readlink" }
realpath = { optional = true, version = "0.0.20", 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.20", package = "uu_rm", path = "src/uu/rm" }
rmdir = { optional = true, version = "0.0.20", package = "uu_rmdir", path = "src/uu/rmdir" }
runcon = { optional = true, version = "0.0.20", package = "uu_runcon", path = "src/uu/runcon" }
seq = { optional = true, version = "0.0.20", package = "uu_seq", path = "src/uu/seq" }
shred = { optional = true, version = "0.0.20", package = "uu_shred", path = "src/uu/shred" }
shuf = { optional = true, version = "0.0.20", package = "uu_shuf", path = "src/uu/shuf" }
sleep = { optional = true, version = "0.0.20", package = "uu_sleep", path = "src/uu/sleep" }
sort = { optional = true, version = "0.0.20", package = "uu_sort", path = "src/uu/sort" }
split = { optional = true, version = "0.0.20", package = "uu_split", path = "src/uu/split" }
stat = { optional = true, version = "0.0.20", package = "uu_stat", path = "src/uu/stat" }
stdbuf = { optional = true, version = "0.0.20", package = "uu_stdbuf", path = "src/uu/stdbuf" }
stty = { optional = true, version = "0.0.20", package = "uu_stty", path = "src/uu/stty" }
sum = { optional = true, version = "0.0.20", package = "uu_sum", path = "src/uu/sum" }
sync = { optional = true, version = "0.0.20", package = "uu_sync", path = "src/uu/sync" }
tac = { optional = true, version = "0.0.20", package = "uu_tac", path = "src/uu/tac" }
tail = { optional = true, version = "0.0.20", package = "uu_tail", path = "src/uu/tail" }
tee = { optional = true, version = "0.0.20", package = "uu_tee", path = "src/uu/tee" }
timeout = { optional = true, version = "0.0.20", package = "uu_timeout", path = "src/uu/timeout" }
touch = { optional = true, version = "0.0.20", package = "uu_touch", path = "src/uu/touch" }
tr = { optional = true, version = "0.0.20", package = "uu_tr", path = "src/uu/tr" }
true = { optional = true, version = "0.0.20", package = "uu_true", path = "src/uu/true" }
truncate = { optional = true, version = "0.0.20", package = "uu_truncate", path = "src/uu/truncate" }
tsort = { optional = true, version = "0.0.20", package = "uu_tsort", path = "src/uu/tsort" }
tty = { optional = true, version = "0.0.20", package = "uu_tty", path = "src/uu/tty" }
uname = { optional = true, version = "0.0.20", package = "uu_uname", path = "src/uu/uname" }
unexpand = { optional = true, version = "0.0.20", package = "uu_unexpand", path = "src/uu/unexpand" }
uniq = { optional = true, version = "0.0.20", package = "uu_uniq", path = "src/uu/uniq" }
unlink = { optional = true, version = "0.0.20", package = "uu_unlink", path = "src/uu/unlink" }
uptime = { optional = true, version = "0.0.20", package = "uu_uptime", path = "src/uu/uptime" }
users = { optional = true, version = "0.0.20", package = "uu_users", path = "src/uu/users" }
vdir = { optional = true, version = "0.0.20", package = "uu_vdir", path = "src/uu/vdir" }
wc = { optional = true, version = "0.0.20", package = "uu_wc", path = "src/uu/wc" }
who = { optional = true, version = "0.0.20", package = "uu_who", path = "src/uu/who" }
whoami = { optional = true, version = "0.0.20", package = "uu_whoami", path = "src/uu/whoami" }
yes = { optional = true, version = "0.0.20", package = "uu_yes", path = "src/uu/yes" }
arch = { optional = true, version = "0.0.22", package = "uu_arch", path = "src/uu/arch" }
base32 = { optional = true, version = "0.0.22", package = "uu_base32", path = "src/uu/base32" }
base64 = { optional = true, version = "0.0.22", package = "uu_base64", path = "src/uu/base64" }
basename = { optional = true, version = "0.0.22", package = "uu_basename", path = "src/uu/basename" }
basenc = { optional = true, version = "0.0.22", package = "uu_basenc", path = "src/uu/basenc" }
cat = { optional = true, version = "0.0.22", package = "uu_cat", path = "src/uu/cat" }
chcon = { optional = true, version = "0.0.22", package = "uu_chcon", path = "src/uu/chcon" }
chgrp = { optional = true, version = "0.0.22", package = "uu_chgrp", path = "src/uu/chgrp" }
chmod = { optional = true, version = "0.0.22", package = "uu_chmod", path = "src/uu/chmod" }
chown = { optional = true, version = "0.0.22", package = "uu_chown", path = "src/uu/chown" }
chroot = { optional = true, version = "0.0.22", package = "uu_chroot", path = "src/uu/chroot" }
cksum = { optional = true, version = "0.0.22", package = "uu_cksum", path = "src/uu/cksum" }
comm = { optional = true, version = "0.0.22", package = "uu_comm", path = "src/uu/comm" }
cp = { optional = true, version = "0.0.22", package = "uu_cp", path = "src/uu/cp" }
csplit = { optional = true, version = "0.0.22", package = "uu_csplit", path = "src/uu/csplit" }
cut = { optional = true, version = "0.0.22", package = "uu_cut", path = "src/uu/cut" }
date = { optional = true, version = "0.0.22", package = "uu_date", path = "src/uu/date" }
dd = { optional = true, version = "0.0.22", package = "uu_dd", path = "src/uu/dd" }
df = { optional = true, version = "0.0.22", package = "uu_df", path = "src/uu/df" }
dir = { optional = true, version = "0.0.22", package = "uu_dir", path = "src/uu/dir" }
dircolors = { optional = true, version = "0.0.22", package = "uu_dircolors", path = "src/uu/dircolors" }
dirname = { optional = true, version = "0.0.22", package = "uu_dirname", path = "src/uu/dirname" }
du = { optional = true, version = "0.0.22", package = "uu_du", path = "src/uu/du" }
echo = { optional = true, version = "0.0.22", package = "uu_echo", path = "src/uu/echo" }
env = { optional = true, version = "0.0.22", package = "uu_env", path = "src/uu/env" }
expand = { optional = true, version = "0.0.22", package = "uu_expand", path = "src/uu/expand" }
expr = { optional = true, version = "0.0.22", package = "uu_expr", path = "src/uu/expr" }
factor = { optional = true, version = "0.0.22", package = "uu_factor", path = "src/uu/factor" }
false = { optional = true, version = "0.0.22", package = "uu_false", path = "src/uu/false" }
fmt = { optional = true, version = "0.0.22", package = "uu_fmt", path = "src/uu/fmt" }
fold = { optional = true, version = "0.0.22", package = "uu_fold", path = "src/uu/fold" }
groups = { optional = true, version = "0.0.22", package = "uu_groups", path = "src/uu/groups" }
hashsum = { optional = true, version = "0.0.22", package = "uu_hashsum", path = "src/uu/hashsum" }
head = { optional = true, version = "0.0.22", package = "uu_head", path = "src/uu/head" }
hostid = { optional = true, version = "0.0.22", package = "uu_hostid", path = "src/uu/hostid" }
hostname = { optional = true, version = "0.0.22", package = "uu_hostname", path = "src/uu/hostname" }
id = { optional = true, version = "0.0.22", package = "uu_id", path = "src/uu/id" }
install = { optional = true, version = "0.0.22", package = "uu_install", path = "src/uu/install" }
join = { optional = true, version = "0.0.22", package = "uu_join", path = "src/uu/join" }
kill = { optional = true, version = "0.0.22", package = "uu_kill", path = "src/uu/kill" }
link = { optional = true, version = "0.0.22", package = "uu_link", path = "src/uu/link" }
ln = { optional = true, version = "0.0.22", package = "uu_ln", path = "src/uu/ln" }
ls = { optional = true, version = "0.0.22", package = "uu_ls", path = "src/uu/ls" }
logname = { optional = true, version = "0.0.22", package = "uu_logname", path = "src/uu/logname" }
mkdir = { optional = true, version = "0.0.22", package = "uu_mkdir", path = "src/uu/mkdir" }
mkfifo = { optional = true, version = "0.0.22", package = "uu_mkfifo", path = "src/uu/mkfifo" }
mknod = { optional = true, version = "0.0.22", package = "uu_mknod", path = "src/uu/mknod" }
mktemp = { optional = true, version = "0.0.22", package = "uu_mktemp", path = "src/uu/mktemp" }
more = { optional = true, version = "0.0.22", package = "uu_more", path = "src/uu/more" }
mv = { optional = true, version = "0.0.22", package = "uu_mv", path = "src/uu/mv" }
nice = { optional = true, version = "0.0.22", package = "uu_nice", path = "src/uu/nice" }
nl = { optional = true, version = "0.0.22", package = "uu_nl", path = "src/uu/nl" }
nohup = { optional = true, version = "0.0.22", package = "uu_nohup", path = "src/uu/nohup" }
nproc = { optional = true, version = "0.0.22", package = "uu_nproc", path = "src/uu/nproc" }
numfmt = { optional = true, version = "0.0.22", package = "uu_numfmt", path = "src/uu/numfmt" }
od = { optional = true, version = "0.0.22", package = "uu_od", path = "src/uu/od" }
paste = { optional = true, version = "0.0.22", package = "uu_paste", path = "src/uu/paste" }
pathchk = { optional = true, version = "0.0.22", package = "uu_pathchk", path = "src/uu/pathchk" }
pinky = { optional = true, version = "0.0.22", package = "uu_pinky", path = "src/uu/pinky" }
pr = { optional = true, version = "0.0.22", package = "uu_pr", path = "src/uu/pr" }
printenv = { optional = true, version = "0.0.22", package = "uu_printenv", path = "src/uu/printenv" }
printf = { optional = true, version = "0.0.22", package = "uu_printf", path = "src/uu/printf" }
ptx = { optional = true, version = "0.0.22", package = "uu_ptx", path = "src/uu/ptx" }
pwd = { optional = true, version = "0.0.22", package = "uu_pwd", path = "src/uu/pwd" }
readlink = { optional = true, version = "0.0.22", package = "uu_readlink", path = "src/uu/readlink" }
realpath = { optional = true, version = "0.0.22", package = "uu_realpath", path = "src/uu/realpath" }
rm = { optional = true, version = "0.0.22", package = "uu_rm", path = "src/uu/rm" }
rmdir = { optional = true, version = "0.0.22", package = "uu_rmdir", path = "src/uu/rmdir" }
runcon = { optional = true, version = "0.0.22", package = "uu_runcon", path = "src/uu/runcon" }
seq = { optional = true, version = "0.0.22", package = "uu_seq", path = "src/uu/seq" }
shred = { optional = true, version = "0.0.22", package = "uu_shred", path = "src/uu/shred" }
shuf = { optional = true, version = "0.0.22", package = "uu_shuf", path = "src/uu/shuf" }
sleep = { optional = true, version = "0.0.22", package = "uu_sleep", path = "src/uu/sleep" }
sort = { optional = true, version = "0.0.22", package = "uu_sort", path = "src/uu/sort" }
split = { optional = true, version = "0.0.22", package = "uu_split", path = "src/uu/split" }
stat = { optional = true, version = "0.0.22", package = "uu_stat", path = "src/uu/stat" }
stdbuf = { optional = true, version = "0.0.22", package = "uu_stdbuf", path = "src/uu/stdbuf" }
stty = { optional = true, version = "0.0.22", package = "uu_stty", path = "src/uu/stty" }
sum = { optional = true, version = "0.0.22", package = "uu_sum", path = "src/uu/sum" }
sync = { optional = true, version = "0.0.22", package = "uu_sync", path = "src/uu/sync" }
tac = { optional = true, version = "0.0.22", package = "uu_tac", path = "src/uu/tac" }
tail = { optional = true, version = "0.0.22", package = "uu_tail", path = "src/uu/tail" }
tee = { optional = true, version = "0.0.22", package = "uu_tee", path = "src/uu/tee" }
timeout = { optional = true, version = "0.0.22", package = "uu_timeout", path = "src/uu/timeout" }
touch = { optional = true, version = "0.0.22", package = "uu_touch", path = "src/uu/touch" }
tr = { optional = true, version = "0.0.22", package = "uu_tr", path = "src/uu/tr" }
true = { optional = true, version = "0.0.22", package = "uu_true", path = "src/uu/true" }
truncate = { optional = true, version = "0.0.22", package = "uu_truncate", path = "src/uu/truncate" }
tsort = { optional = true, version = "0.0.22", package = "uu_tsort", path = "src/uu/tsort" }
tty = { optional = true, version = "0.0.22", package = "uu_tty", path = "src/uu/tty" }
uname = { optional = true, version = "0.0.22", package = "uu_uname", path = "src/uu/uname" }
unexpand = { optional = true, version = "0.0.22", package = "uu_unexpand", path = "src/uu/unexpand" }
uniq = { optional = true, version = "0.0.22", package = "uu_uniq", path = "src/uu/uniq" }
unlink = { optional = true, version = "0.0.22", package = "uu_unlink", path = "src/uu/unlink" }
uptime = { optional = true, version = "0.0.22", package = "uu_uptime", path = "src/uu/uptime" }
users = { optional = true, version = "0.0.22", package = "uu_users", path = "src/uu/users" }
vdir = { optional = true, version = "0.0.22", package = "uu_vdir", path = "src/uu/vdir" }
wc = { optional = true, version = "0.0.22", package = "uu_wc", path = "src/uu/wc" }
who = { optional = true, version = "0.0.22", package = "uu_who", path = "src/uu/who" }
whoami = { optional = true, version = "0.0.22", package = "uu_whoami", path = "src/uu/whoami" }
yes = { optional = true, version = "0.0.22", 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)"
# 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"
uucore = { workspace = true, features = ["entries", "process", "signals"] }
walkdir = { workspace = true }
is-terminal = { workspace = true }
hex-literal = "0.4.1"
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 \
readlink \
realpath \
relpath \
rm \
rmdir \
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)
[![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>
@ -54,8 +54,8 @@ that scripts can be easily transferred between platforms.
uutils has both user and developer documentation available:
- [User Manual](https://uutils.github.io/user/)
- [Developer Documentation](https://uutils.github.io/dev/coreutils/)
- [User Manual](https://uutils.github.io/coreutils/book/)
- [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
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
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
@ -303,7 +303,7 @@ make PREFIX=/my/path uninstall
Below is the evolution of how many GNU tests uutils passes. A more detailed
breakdown of the GNU test results of the main branch can be found
[in the user manual](https://uutils.github.io/user/test_coverage.html).
[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
(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
use std::env;
@ -40,6 +45,7 @@ pub fn main() {
mf.write_all(
"type UtilityMap<T> = phf::OrderedMap<&'static str, (fn(T) -> i32, fn() -> Command)>;\n\
\n\
#[allow(clippy::too_many_lines)]
fn util_map<T: uucore::Args>() -> UtilityMap<T> {\n"
.as_bytes(),
)

View file

@ -59,9 +59,12 @@ highlight = "all"
# spell-checker: disable
skip = [
# procfs
{ name = "rustix", version = "0.36.14" },
{ name = "rustix", version = "0.36.16" },
# rustix
{ 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
{ name = "windows-sys", version = "0.45.0" },
# windows-sys
@ -80,12 +83,14 @@ skip = [
{ name = "windows_x86_64_gnullvm", version = "0.42.2" },
# windows-targets
{ 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
{ 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

1
docs/.gitignore vendored
View file

@ -1,4 +1,5 @@
book
src/utils
src/SUMMARY.md
src/platform_table.md
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
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
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
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
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
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
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
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-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-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-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-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-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-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
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
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-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
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
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
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
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
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
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
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,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,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,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,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,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,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
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,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,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
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
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

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` 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
@ -139,6 +139,16 @@ pkg install rust-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
### `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]
libfuzzer-sys = "0.4"
libc = "0.2"
rand = { version = "0.8", features = ["small_rng"] }
[dependencies.uucore]
path = "../src/uucore/"
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
[workspace]
@ -26,6 +28,18 @@ path = "fuzz_targets/fuzz_date.rs"
test = 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]]
name = "fuzz_parse_glob"
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
.split(|b| *b == delim)
.filter_map(|e| std::str::from_utf8(e).ok())
.map(|e| OsString::from(e));
.map(OsString::from);
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]| {
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": {
"name": "uutils coreutils"
},
"build": {
"path_prefix": "coreutils"
},
"components": {
"changelog": true
"changelog": {
"read_changelog_file": false
}
},
"styles": {
"theme": "light",

View file

@ -1,7 +1,5 @@
// 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
// file that was distributed with this source code.

View file

@ -42,6 +42,7 @@ fn main() -> io::Result<()> {
[Introduction](index.md)\n\
* [Installation](installation.md)\n\
* [Build from source](build.md)\n\
* [Platform support](platforms.md)\n\
* [Contributing](contributing.md)\n\
* [GNU test coverage](test_coverage.md)\n\
* [Extensions](extensions.md)\n\
@ -53,7 +54,7 @@ fn main() -> io::Result<()> {
println!("Gathering utils per platform");
let utils_per_platform = {
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(
std::process::Command::new("./util/show-utils.sh")
.arg(format!("--features=feat_os_{}", platform))
@ -61,6 +62,7 @@ fn main() -> io::Result<()> {
.stdout,
)
.unwrap()
.trim()
.split(' ')
.map(ToString::to_string)
.collect();
@ -75,6 +77,7 @@ fn main() -> io::Result<()> {
.stdout,
)
.unwrap()
.trim()
.split(' ')
.map(ToString::to_string)
.collect();
@ -83,9 +86,47 @@ fn main() -> io::Result<()> {
map
};
println!("Writing to utils");
let mut utils = utils.entries().collect::<Vec<_>>();
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 {
if name == "[" {
continue;

View file

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

View file

@ -1,8 +1,5 @@
// 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
// file that was distributed with this source code.

View file

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

View file

@ -1,9 +1,7 @@
// 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};

View file

@ -1,11 +1,7 @@
// This file is part of the uutils coreutils package.
//
// (c) Jordy Dickinson <jordy.dickinson@gmail.com>
// (c) Jian Zeng <anonymousknight96@gmail.com>
// (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.
// 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};

View file

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

View file

@ -1,10 +1,7 @@
// This file is part of the uutils coreutils package.
//
// (c) Jordy Dickinson <jordy.dickinson@gmail.com>
// (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 uu_base32::base_common;
pub use uu_base32::uu_app;

View file

@ -1,6 +1,6 @@
[package]
name = "uu_basename"
version = "0.0.20"
version = "0.0.22"
authors = ["uutils developers"]
license = "MIT"
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.
//
// (c) Jimmy Lu <jimmy.lu.2011@gmail.com>
//
// For the full copyright and license information, please view the LICENSE
// 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 uucore::display::Quotable;
use uucore::error::{UResult, UUsageError};
use uucore::line_ending::LineEnding;
use uucore::{format_usage, help_about, help_usage};
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()));
}
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_multiple = matches.get_flag(options::MULTIPLE);
let opt_zero = matches.get_flag(options::ZERO);
let multiple_paths = opt_suffix || opt_multiple;
let name_args_count = matches
.get_many::<String>(options::NAME)
@ -105,7 +105,6 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
.collect()
};
let line_ending = if opt_zero { "\0" } else { "\n" };
for path in paths {
print!("{}{}", basename(path, suffix), line_ending);
}

View file

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

View file

@ -1,10 +1,7 @@
// This file is part of the uutils coreutils package.
//
// (c) Jordy Dickinson <jordy.dickinson@gmail.com>
// (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.
//spell-checker:ignore (args) lsbf msbf

View file

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

View file

@ -1,10 +1,5 @@
// 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
// file that was distributed with this source code.
@ -12,9 +7,8 @@
// last synced with: cat (GNU coreutils) 8.13
use clap::{crate_version, Arg, ArgAction, Command};
use is_terminal::IsTerminal;
use std::fs::{metadata, File};
use std::io::{self, Read, Write};
use std::io::{self, IsTerminal, Read, Write};
use thiserror::Error;
use uucore::display::Quotable;
use uucore::error::UResult;
@ -192,7 +186,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
NumberingMode::None
};
let show_nonprint = vec![
let show_nonprint = [
options::SHOW_ALL.to_owned(),
options::SHOW_NONPRINTING_ENDS.to_owned(),
options::SHOW_NONPRINTING_TABS.to_owned(),
@ -201,7 +195,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
.iter()
.any(|v| matches.get_flag(v));
let show_ends = vec![
let show_ends = [
options::SHOW_ENDS.to_owned(),
options::SHOW_ALL.to_owned(),
options::SHOW_NONPRINTING_ENDS.to_owned(),
@ -209,7 +203,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
.iter()
.any(|v| matches.get_flag(v));
let show_tabs = vec![
let show_tabs = [
options::SHOW_ALL.to_owned(),
options::SHOW_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 nix::unistd;
@ -17,7 +21,7 @@ const BUF_SIZE: usize = 1024 * 16;
/// copying or not. False means we don't have to.
#[inline]
pub(super) fn write_fast_using_splice<R: FdReadable>(
handle: &mut InputHandle<R>,
handle: &InputHandle<R>,
write_fd: &impl AsRawFd,
) -> CatResult<bool> {
let (pipe_rd, pipe_wr) = pipe()?;

View file

@ -1,6 +1,6 @@
[package]
name = "uu_chcon"
version = "0.0.20"
version = "0.0.22"
authors = ["uutils developers"]
license = "MIT"
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
#![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::fmt::Write;
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::marker::PhantomData;
use std::os::raw::{c_int, c_long, c_short};

View file

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

View file

@ -1,7 +1,5 @@
// 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.

View file

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

View file

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

View file

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

View file

@ -1,7 +1,5 @@
// 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.
@ -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.
///
/// 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 group = args.next().unwrap_or("");
let uid = if user.is_empty() {
None
} 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()),
));
}
},
})
};
let uid = parse_uid(user, spec, sep)?;
let gid = parse_gid(group, spec)?;
if user.chars().next().map(char::is_numeric).unwrap_or(false)
&& group.is_empty()

View file

@ -1,6 +1,6 @@
[package]
name = "uu_chroot"
version = "0.0.20"
version = "0.0.22"
authors = ["uutils developers"]
license = "MIT"
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.
//
// (c) Vsevolod Velichko <torkvemada@sorokdva.net>
// (c) Jian Zeng <anonymousknight96 AT gmail.com>
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.

View file

@ -1,7 +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.
// 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 NEWROOT Userspec userspec
//! Errors returned by chroot.
use std::fmt::Display;

View file

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

View file

@ -1,8 +1,6 @@
// 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.
// spell-checker:ignore (ToDO) fname, algo
@ -209,8 +207,7 @@ fn digest_read<T: Read>(
Ok((digest.result_str(), output_size))
} else {
// Assume it's SHAKE. result_str() doesn't work with shake (as of 8/30/2016)
let mut bytes = Vec::new();
bytes.resize((output_bits + 7) / 8, 0);
let mut bytes = vec![0; (output_bits + 7) / 8];
digest.hash_finalize(&mut bytes);
Ok((encode(bytes), output_size))
}

View file

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

View file

@ -1,18 +1,16 @@
// 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
// file that was distributed with this source code.
// spell-checker:ignore (ToDO) delim mkdelim
use std::cmp::Ordering;
use std::fmt::Display;
use std::fs::File;
use std::io::{self, stdin, BufRead, BufReader, Stdin};
use std::path::Path;
use uucore::error::{FromIo, UResult};
use uucore::line_ending::LineEnding;
use uucore::{format_usage, help_about, help_usage};
use clap::{crate_version, Arg, ArgAction, ArgMatches, Command};
@ -32,46 +30,6 @@ mod options {
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 {
Stdin(Stdin),
FileIn(BufReader<File>),
@ -109,8 +67,8 @@ fn comm(a: &mut LineReader, b: &mut LineReader, opts: &ArgMatches) {
delim => delim,
};
let width_col_1 = column_width(options::COLUMN_1, opts);
let width_col_2 = column_width(options::COLUMN_2, opts);
let width_col_1 = usize::from(!opts.get_flag(options::COLUMN_1));
let width_col_2 = usize::from(!opts.get_flag(options::COLUMN_2));
let delim_col_2 = delim.repeat(width_col_1);
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) {
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}");
}
}
@ -190,7 +148,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let args = args.collect_lossy();
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 filename2 = matches.get_one::<String>(options::FILE_2).unwrap();
let mut f1 = open_file(filename1, line_ending).map_err_context(|| filename1.to_string())?;

View file

@ -1,6 +1,6 @@
[package]
name = "uu_cp"
version = "0.0.20"
version = "0.0.22"
authors = [
"Jordy Dickinson <jordy.dickinson@gmail.com>",
"Joshua S. Miller <jsmiller@uchicago.edu>",
@ -24,7 +24,14 @@ filetime = { workspace = true }
libc = { workspace = true }
quick-error = { workspace = 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 }
indicatif = { workspace = true }

View file

@ -1,14 +1,14 @@
// * 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.
// 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 canonicalizes direntry pathbuf symlinked
//! Recursively copy the contents of a directory.
//!
//! See the [`copy_directory`] function for more information.
#[cfg(windows)]
use std::borrow::Cow;
use std::collections::HashSet;
use std::collections::{HashMap, HashSet};
use std::env;
use std::fs;
use std::io;
@ -24,8 +24,8 @@ use uucore::uio_error;
use walkdir::{DirEntry, WalkDir};
use crate::{
aligned_ancestors, context_for, copy_attributes, copy_file, copy_link, preserve_hardlinks,
CopyResult, Error, Options, TargetSlice,
aligned_ancestors, context_for, copy_attributes, copy_file, copy_link, CopyResult, Error,
Options,
};
/// Ensure a Windows path starts with a `\\?`.
@ -33,8 +33,8 @@ use crate::{
fn adjust_canonicalization(p: &Path) -> Cow<Path> {
// In some cases, \\? can be missing on some Windows paths. Add it at the
// beginning unless the path is prefixed with a device namespace.
const VERBATIM_PREFIX: &str = r#"\\?"#;
const DEVICE_NS_PREFIX: &str = r#"\\."#;
const VERBATIM_PREFIX: &str = r"\\?";
const DEVICE_NS_PREFIX: &str = r"\\.";
let has_prefix = p
.components()
@ -200,7 +200,7 @@ fn copy_direntry(
options: &Options,
symlinked_files: &mut HashSet<FileInformation>,
preserve_hard_links: bool,
hard_links: &mut Vec<(String, u64)>,
copied_files: &mut HashMap<FileInformation, PathBuf>,
) -> CopyResult<()> {
let Entry {
source_absolute,
@ -240,30 +240,27 @@ fn copy_direntry(
// If the source is not a directory, then we need to copy the file.
if !source_absolute.is_dir() {
if preserve_hard_links {
let dest = local_to_target.as_path().to_path_buf();
let found_hard_link = preserve_hardlinks(hard_links, &source_absolute, &dest)?;
if !found_hard_link {
match copy_file(
progress_bar,
&source_absolute,
local_to_target.as_path(),
options,
symlinked_files,
false,
) {
Ok(_) => Ok(()),
Err(err) => {
if source_absolute.is_symlink() {
// silent the error with a symlink
// In case we do --archive, we might copy the symlink
// before the file itself
Ok(())
} else {
Err(err)
}
match copy_file(
progress_bar,
&source_absolute,
local_to_target.as_path(),
options,
symlinked_files,
copied_files,
false,
) {
Ok(_) => Ok(()),
Err(err) => {
if source_absolute.is_symlink() {
// silent the error with a symlink
// In case we do --archive, we might copy the symlink
// before the file itself
Ok(())
} else {
Err(err)
}
}?;
}
}
}?;
} else {
// At this point, `path` is just a plain old file.
// Terminate this function immediately if there is any
@ -277,6 +274,7 @@ fn copy_direntry(
local_to_target.as_path(),
options,
symlinked_files,
copied_files,
false,
) {
Ok(_) => {}
@ -307,9 +305,10 @@ fn copy_direntry(
pub(crate) fn copy_directory(
progress_bar: &Option<ProgressBar>,
root: &Path,
target: &TargetSlice,
target: &Path,
options: &Options,
symlinked_files: &mut HashSet<FileInformation>,
copied_files: &mut HashMap<FileInformation, PathBuf>,
source_in_command_line: bool,
) -> CopyResult<()> {
if !options.recursive {
@ -324,6 +323,7 @@ pub(crate) fn copy_directory(
target,
options,
symlinked_files,
copied_files,
source_in_command_line,
);
}
@ -372,7 +372,6 @@ pub(crate) fn copy_directory(
};
let target = tmp.as_path();
let mut hard_links: Vec<(String, u64)> = vec![];
let preserve_hard_links = options.preserve_hard_links();
// Collect some paths here that are invariant during the traversal
@ -397,7 +396,7 @@ pub(crate) fn copy_directory(
options,
symlinked_files,
preserve_hard_links,
&mut hard_links,
copied_files,
)?;
}
// Print an error message, but continue traversing the directory.
@ -448,6 +447,7 @@ mod tests {
use super::ends_with_slash_dot;
#[test]
#[allow(clippy::cognitive_complexity)]
fn test_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::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 std::borrow::Cow;
use std::collections::HashSet;
use std::cmp::Ordering;
use std::collections::{HashMap, HashSet};
use std::env;
#[cfg(not(windows))]
use std::ffi::CString;
@ -34,14 +30,16 @@ use libc::mkfifo;
use quick_error::ResultExt;
use platform::copy_on_write;
use uucore::backup_control::{self, BackupMode};
use uucore::display::Quotable;
use uucore::error::{set_exit_code, UClapError, UError, UResult, UUsageError};
use uucore::fs::{
canonicalize, is_symlink_loop, paths_refer_to_same_file, FileInformation, MissingHandling,
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::{
crash, format_usage, help_about, help_section, help_usage, prompt_yes, show_error,
show_warning, util_name,
@ -51,6 +49,7 @@ use crate::copydir::copy_directory;
mod copydir;
mod platform;
quick_error! {
#[derive(Debug)]
pub enum Error {
@ -108,12 +107,8 @@ impl UError for 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)]
pub enum ClobberMode {
Force,
@ -121,7 +116,7 @@ pub enum ClobberMode {
Standard,
}
/// Specifies whether when overwrite files
/// Specifies whether files should be overwritten.
#[derive(Clone, Copy, Eq, PartialEq)]
pub enum OverwriteMode {
/// [Default] Always overwrite existing files
@ -148,12 +143,13 @@ pub enum SparseMode {
Never,
}
/// Specifies the expected file type of copy target
/// The expected file type of copy target
pub enum TargetType {
Directory,
File,
}
/// Copy action to perform
pub enum CopyMode {
Link,
SymLink,
@ -162,77 +158,130 @@ pub enum CopyMode {
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)]
pub struct Attributes {
#[cfg(unix)]
ownership: Preserve,
mode: Preserve,
timestamps: Preserve,
context: Preserve,
links: Preserve,
xattr: Preserve,
pub ownership: Preserve,
pub mode: Preserve,
pub timestamps: Preserve,
pub context: Preserve,
pub links: Preserve,
pub xattr: Preserve,
}
impl Attributes {
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)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
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 },
}
impl Preserve {
/// Preservation level should only increase, with no preservation being the lowest option,
/// preserve but don't require - middle, and preserve and require - top.
pub(crate) fn max(&self, other: Self) -> Self {
impl PartialOrd for Preserve {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Preserve {
fn cmp(&self, other: &Self) -> Ordering {
match (self, other) {
(Self::Yes { required: true }, _) | (_, Self::Yes { required: true }) => {
Self::Yes { required: true }
}
(Self::Yes { required: false }, _) | (_, Self::Yes { required: false }) => {
Self::Yes { required: false }
}
_ => Self::No,
(Self::No { .. }, Self::No { .. }) => Ordering::Equal,
(Self::Yes { .. }, Self::No { .. }) => Ordering::Greater,
(Self::No { .. }, Self::Yes { .. }) => Ordering::Less,
(
Self::Yes { required: req_self },
Self::Yes {
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)]
pub struct Options {
attributes_only: bool,
backup: BackupMode,
copy_contents: bool,
cli_dereference: bool,
copy_mode: CopyMode,
dereference: bool,
no_target_dir: bool,
one_file_system: bool,
overwrite: OverwriteMode,
parents: bool,
sparse_mode: SparseMode,
strip_trailing_slashes: bool,
reflink_mode: ReflinkMode,
attributes: Attributes,
recursive: bool,
backup_suffix: String,
target_dir: Option<PathBuf>,
update: UpdateMode,
debug: bool,
verbose: bool,
progress_bar: bool,
/// `--attributes-only`
pub attributes_only: bool,
/// `--backup[=CONTROL]`, `-b`
pub backup: BackupMode,
/// `--copy-contents`
pub copy_contents: bool,
/// `-H`
pub cli_dereference: bool,
/// Determines the type of copying that should be done
///
/// Set by the following arguments:
/// - `-l`, `--link`: [`CopyMode::Link`]
/// - `-s`, `--symbolic-link`: [`CopyMode::SymLink`]
/// - `-u`, `--update[=WHEN]`: [`CopyMode::Update`]
/// - `--attributes-only`: [`CopyMode::AttrOnly`]
/// - otherwise: [`CopyMode::Copy`]
pub copy_mode: CopyMode,
/// `-L`, `--dereference`
pub dereference: bool,
/// `-T`, `--no-target-dir`
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.
@ -741,67 +790,90 @@ impl CopyMode {
}
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.
// 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 {
#[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
}
},
links: Preserve::Yes { required: true },
xattr: Preserve::Yes { required: false },
ownership: self.ownership.max(other.ownership),
context: self.context.max(other.context),
timestamps: self.timestamps.max(other.timestamps),
mode: self.mode.max(other.mode),
links: self.links.max(other.links),
xattr: self.xattr.max(other.xattr),
}
}
fn default() -> Self {
Self {
#[cfg(unix)]
ownership: Preserve::Yes { required: true },
mode: Preserve::Yes { required: true },
timestamps: Preserve::Yes { required: true },
context: Preserve::No,
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,
pub fn parse_iter<T>(values: impl Iterator<Item = T>) -> Result<Self, Error>
where
T: AsRef<str>,
{
let mut new = Self::NONE;
for value in values {
new = new.union(&Self::parse_single_string(value.as_ref())?);
}
Ok(new)
}
/// Tries to match string containing a parameter to preserve with the corresponding entry in the
/// Attributes struct.
fn try_set_from_string(&mut self, value: &str) -> Result<(), Error> {
let preserve_yes_required = Preserve::Yes { required: true };
fn parse_single_string(value: &str) -> Result<Self, Error> {
let value = value.to_lowercase();
match &*value.to_lowercase() {
"mode" => self.mode = preserve_yes_required,
if value == "all" {
return Ok(Self::ALL);
}
let mut new = Self::NONE;
let attribute = match value.as_ref() {
"mode" => &mut new.mode,
#[cfg(unix)]
"ownership" => self.ownership = preserve_yes_required,
"timestamps" => self.timestamps = preserve_yes_required,
"context" => self.context = preserve_yes_required,
"link" | "links" => self.links = preserve_yes_required,
"xattr" => self.xattr = preserve_yes_required,
"ownership" => &mut new.ownership,
"timestamps" => &mut new.timestamps,
"context" => &mut new.context,
"link" | "links" => &mut new.links,
"xattr" => &mut new.xattr,
_ => {
return Err(Error::InvalidArgument(format!(
"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
let attributes: Attributes = if matches.contains_id(options::PRESERVE) {
match matches.get_many::<String>(options::PRESERVE) {
None => Attributes::default(),
Some(attribute_strs) => {
let mut attributes: Attributes = Attributes::none();
let mut attributes_empty = true;
for attribute_str in attribute_strs {
attributes_empty = false;
if attribute_str == "all" {
attributes.max(Attributes::all());
} else {
attributes.try_set_from_string(attribute_str)?;
}
}
// `--preserve` case, use the defaults
if attributes_empty {
Attributes::default()
} else {
attributes
}
let mut attributes =
if let Some(attribute_strs) = matches.get_many::<String>(options::PRESERVE) {
if attribute_strs.len() == 0 {
Attributes::DEFAULT
} else {
Attributes::parse_iter(attribute_strs)?
}
} 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) {
Attributes::LINKS
} else if matches.get_flag(options::PRESERVE_DEFAULT_ATTRIBUTES) {
Attributes::DEFAULT
} else {
Attributes::NONE
};
// 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"))]
if let Preserve::Yes { required } = attributes.context {
@ -984,11 +1054,22 @@ impl Options {
fn preserve_hard_links(&self) -> bool {
match self.attributes.links {
Preserve::No => false,
Preserve::No { .. } => false,
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.
fn force(&self) -> bool {
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
/// 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() {
Self::Directory
} else {
@ -1010,10 +1091,16 @@ impl TargetType {
}
/// 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() {
// No files specified
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
@ -1044,66 +1131,6 @@ fn parse_path_args(mut paths: Vec<Source>, options: &Options) -> CopyResult<(Vec
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.
fn show_error_if_needed(error: &Error) {
match error {
@ -1122,25 +1149,29 @@ fn show_error_if_needed(error: &Error) {
}
}
/// Copy all `sources` to `target`. Returns an
/// `Err(Error::NotAllFilesCopied)` if at least one non-fatal error was
/// encountered.
/// Copy all `sources` to `target`.
///
/// 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
fn copy(sources: &[Source], target: &TargetSlice, options: &Options) -> CopyResult<()> {
/// Behavior is determined by the `options` parameter, see [`Options`] for details.
pub fn copy(sources: &[PathBuf], target: &Path, options: &Options) -> CopyResult<()> {
let target_type = TargetType::determine(sources, target);
verify_target_type(target, &target_type)?;
let preserve_hard_links = options.preserve_hard_links();
let mut hard_links: Vec<(String, u64)> = vec![];
let mut non_fatal_errors = false;
let mut seen_sources = HashSet::with_capacity(sources.len());
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 pb = ProgressBar::new(disk_usage(sources, options.recursive)?)
.with_style(
@ -1156,33 +1187,29 @@ fn copy(sources: &[Source], target: &TargetSlice, options: &Options) -> CopyResu
None
};
for source in sources.iter() {
for source in sources {
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)
show_warning!("source {} specified more than once", source.quote());
} else {
let found_hard_link = if preserve_hard_links && !source.is_dir() {
let dest = construct_dest_path(source, target, &target_type, options)?;
preserve_hardlinks(&mut hard_links, source, &dest)?
} else {
false
};
if !found_hard_link {
if let Err(error) = copy_source(
&progress_bar,
source,
target,
&target_type,
options,
&mut symlinked_files,
) {
show_error_if_needed(&error);
non_fatal_errors = true;
}
}
seen_sources.insert(source);
} else if let Err(error) = copy_source(
&progress_bar,
source,
target,
&target_type,
options,
&mut symlinked_files,
&mut copied_files,
) {
show_error_if_needed(&error);
non_fatal_errors = true;
}
seen_sources.insert(source);
}
if let Some(pb) = progress_bar {
pb.finish();
}
if non_fatal_errors {
Err(Error::NotAllFilesCopied)
} else {
@ -1192,7 +1219,7 @@ fn copy(sources: &[Source], target: &TargetSlice, options: &Options) -> CopyResu
fn construct_dest_path(
source_path: &Path,
target: &TargetSlice,
target: &Path,
target_type: &TargetType,
options: &Options,
) -> CopyResult<PathBuf> {
@ -1223,16 +1250,25 @@ fn construct_dest_path(
fn copy_source(
progress_bar: &Option<ProgressBar>,
source: &SourceSlice,
target: &TargetSlice,
source: &Path,
target: &Path,
target_type: &TargetType,
options: &Options,
symlinked_files: &mut HashSet<FileInformation>,
copied_files: &mut HashMap<FileInformation, PathBuf>,
) -> CopyResult<()> {
let source_path = Path::new(&source);
if source_path.is_dir() {
// 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 {
// Copy as file
let dest = construct_dest_path(source_path, target, target_type, options)?;
@ -1242,6 +1278,7 @@ fn copy_source(
dest.as_path(),
options,
symlinked_files,
copied_files,
true,
);
if options.parents {
@ -1254,23 +1291,16 @@ fn copy_source(
}
impl OverwriteMode {
fn verify(&self, path: &Path, verbose: bool) -> CopyResult<()> {
fn verify(&self, path: &Path) -> CopyResult<()> {
match *self {
Self::NoClobber => {
if verbose {
println!("skipped {}", path.quote());
} else {
eprintln!("{}: not replacing {}", util_name(), path.quote());
}
eprintln!("{}: not replacing {}", util_name(), path.quote());
Err(Error::NotAllFilesCopied)
}
Self::Interactive(_) => {
if prompt_yes!("overwrite {}?", path.quote()) {
Ok(())
} else {
if verbose {
println!("skipped {}", path.quote());
}
Err(Error::Skipped)
}
}
@ -1284,7 +1314,7 @@ impl OverwriteMode {
/// If it's required, then the error is thrown.
fn handle_preserve<F: Fn() -> CopyResult<()>>(p: &Preserve, f: F) -> CopyResult<()> {
match p {
Preserve::No => {}
Preserve::No { .. } => {}
Preserve::Yes { required } => {
let result = f();
if *required {
@ -1478,7 +1508,7 @@ fn handle_existing_dest(
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);
if let Some(backup_path) = backup_path {
@ -1503,6 +1533,24 @@ fn handle_existing_dest(
OverwriteMode::Clobber(ClobberMode::RemoveDestination) => {
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,
options: &Options,
symlinked_files: &mut HashSet<FileInformation>,
copied_files: &mut HashMap<FileInformation, PathBuf>,
source_in_command_line: bool,
) -> CopyResult<()> {
if (options.update == UpdateMode::ReplaceIfOlder || options.update == UpdateMode::ReplaceNone)
@ -1613,12 +1662,33 @@ fn copy_file(
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) {
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 let Some(pb) = 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();
#[cfg(unix)]
{
use uucore::mode::get_umask;
let mut mode = permissions.mode();
// remove sticky bit, suid and gid bit
const SPECIAL_PERMS_MASK: u32 = 0o7000;
mode &= !SPECIAL_PERMS_MASK;
let mut mode = handle_no_preserve_mode(options, permissions.mode());
// apply umask
use uucore::mode::get_umask;
mode &= !get_umask();
permissions.set_mode(mode);
@ -1806,6 +1871,11 @@ fn copy_file(
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 {
progress_bar.inc(fs::metadata(source)?.len());
}
@ -1813,6 +1883,49 @@ fn copy_file(
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-on-write scheme if --reflink is specified and the filesystem supports it.
fn copy_helper(
@ -1836,7 +1949,7 @@ fn copy_helper(
File::create(dest).context(dest.display().to_string())?;
} else if source_is_fifo && options.recursive && !options.copy_contents {
#[cfg(unix)]
copy_fifo(dest, options.overwrite, options.verbose)?;
copy_fifo(dest, options.overwrite)?;
} else if source_is_symlink {
copy_link(source, dest, symlinked_files)?;
} else {
@ -1861,9 +1974,9 @@ fn copy_helper(
// "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).
#[cfg(unix)]
fn copy_fifo(dest: &Path, overwrite: OverwriteMode, verbose: bool) -> CopyResult<()> {
fn copy_fifo(dest: &Path, overwrite: OverwriteMode) -> CopyResult<()> {
if dest.exists() {
overwrite.verify(dest, verbose)?;
overwrite.verify(dest)?;
fs::remove_file(dest)?;
}

View file

@ -1,7 +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.
// 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 ficlone reflink ftruncate pwrite fiemap
use std::fs::{File, OpenOptions};
use std::io::Read;

View file

@ -1,7 +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.
// 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 reflink
use std::ffi::CString;
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
// bother to check if removal worked because we're going to try to clone again.
let _ = fs::remove_file(dest);
error = pfn(src.as_ptr(), dst.as_ptr(), 0);
// first lets make sure the dest file is not read only
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.
// *
// * For the full copyright and license information, please view the LICENSE
// * file that was distributed with this source code.
// 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.
#[cfg(target_os = "macos")]
mod macos;
#[cfg(target_os = "macos")]

View file

@ -1,7 +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.
// 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 reflink
use std::fs;
use std::path::Path;

View file

@ -1,6 +1,6 @@
[package]
name = "uu_csplit"
version = "0.0.20"
version = "0.0.22"
authors = ["uutils developers"]
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"

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"]
// spell-checker:ignore rustdoc
#![allow(rustdoc::private_intra_doc_links)]
@ -660,6 +664,7 @@ mod tests {
use super::*;
#[test]
#[allow(clippy::cognitive_complexity)]
fn input_splitter() {
let input = vec![
Ok(String::from("aaa")),
@ -732,6 +737,7 @@ mod tests {
}
#[test]
#[allow(clippy::cognitive_complexity)]
fn input_splitter_interrupt_rewind() {
let input = vec![
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 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
use crate::csplit_error::CsplitError;
@ -207,6 +211,7 @@ mod tests {
}
#[test]
#[allow(clippy::cognitive_complexity)]
fn up_to_match_pattern() {
let input: Vec<String> = vec![
"/test1.*end$/",
@ -260,6 +265,7 @@ mod tests {
}
#[test]
#[allow(clippy::cognitive_complexity)]
fn skip_to_match_pattern() {
let input: Vec<String> = vec![
"%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
use regex::Regex;

View file

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

View file

@ -1,7 +1,5 @@
// 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
// file that was distributed with this source code.
@ -9,12 +7,12 @@
use bstr::io::BufReadExt;
use clap::{crate_version, Arg, ArgAction, Command};
use is_terminal::IsTerminal;
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 uucore::display::Quotable;
use uucore::error::{FromIo, UResult, USimpleError};
use uucore::line_ending::LineEnding;
use self::searcher::Searcher;
use matcher::{ExactMatcher, Matcher, WhitespaceMatcher};
@ -30,7 +28,7 @@ const AFTER_HELP: &str = help_section!("after help", "cut.md");
struct Options {
out_delim: Option<String>,
zero_terminated: bool,
line_ending: LineEnding,
}
enum Delimiter {
@ -42,7 +40,7 @@ struct FieldOptions {
delimiter: Delimiter,
out_delimiter: Option<String>,
only_delimited: bool,
zero_terminated: bool,
line_ending: LineEnding,
}
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<()> {
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 out = stdout_writer();
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<()> {
let newline_char = if opts.zero_terminated { b'\0' } else { b'\n' };
let newline_char = opts.line_ending.into();
match opts.delimiter {
Delimiter::String(ref delim) => {
let matcher = ExactMatcher::new(delim.as_bytes());
@ -376,7 +374,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
.unwrap_or_default()
.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()
.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 whitespace_delimited = matches.get_flag(options::WHITESPACE_DELIMITED);
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()) {
Some(_) if whitespace_delimited => {
@ -441,7 +440,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
delimiter: Delimiter::String(delim),
out_delimiter: out_delim,
only_delimited,
zero_terminated,
line_ending,
},
))
}
@ -455,7 +454,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
},
out_delimiter: out_delim,
only_delimited,
zero_terminated,
line_ending,
},
)),
}

View file

@ -1,7 +1,5 @@
// 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
// file that was distributed with this source code.

View file

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

View file

@ -1,8 +1,5 @@
// 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
// 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) {
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)
} else {
DateSource::Custom(date.into())
@ -227,8 +226,20 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
DateSource::Human(relative_time) => {
// Get the current DateTime<FixedOffset> for things like "1 year ago"
let current_time = DateTime::<FixedOffset>::from(Local::now());
let iter = std::iter::once(Ok(current_time + relative_time));
Box::new(iter)
// double check the result is overflow or not of the current_time + relative_time
// 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) => {
if path.is_dir() {

View file

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

View file

@ -1,7 +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.
// 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 datastructures rstat rposition cflags ctable

View file

@ -1,7 +1,5 @@
// 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
// file that was distributed with this source code.

View file

@ -1,7 +1,5 @@
// 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
// file that was distributed with this source code.
// spell-checker:ignore ctable, outfile, iseek, oseek

View file

@ -1,11 +1,9 @@
// 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
// 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;
use datastructures::*;
@ -51,6 +49,8 @@ use nix::{
fcntl::{posix_fadvise, PosixFadviseAdvice},
};
use uucore::display::Quotable;
#[cfg(unix)]
use uucore::error::set_exit_code;
use uucore::error::{FromIo, UResult};
use uucore::{format_usage, help_about, help_section, help_usage, show_error};
#[cfg(target_os = "linux")]
@ -202,14 +202,25 @@ impl Source {
Err(e) => Err(e),
},
#[cfg(unix)]
Self::StdinFile(f) => match io::copy(&mut f.take(n), &mut io::sink()) {
Ok(m) if m < n => {
show_error!("'standard input': cannot skip to specified offset");
Ok(m)
Self::StdinFile(f) => {
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 input': cannot skip: Invalid argument");
set_exit_code(1);
return Ok(len);
}
}
Ok(m) => Ok(m),
Err(e) => Err(e),
},
match io::copy(&mut f.take(n), &mut io::sink()) {
Ok(m) if m < n => {
show_error!("'standard input': cannot skip to specified offset");
Ok(m)
}
Ok(m) => Ok(m),
Err(e) => Err(e),
}
}
Self::File(f) => f.seek(io::SeekFrom::Start(n)),
#[cfg(unix)]
Self::Fifo(f) => io::copy(&mut f.take(n), &mut io::sink()),
@ -529,7 +540,19 @@ impl Dest {
fn seek(&mut self, n: u64) -> io::Result<u64> {
match self {
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)]
Self::Fifo(f) => {
// 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.
#[cfg(unix)]
fn is_fifo(filename: &str) -> bool {

View file

@ -1,7 +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.
// 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.
/// Functions for formatting a number as a magnitude and a unit suffix.
/// The first ten powers of 1024.
@ -115,6 +115,7 @@ mod tests {
}
#[test]
#[allow(clippy::cognitive_complexity)]
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(999, SuffixType::Si), "999 B");

View file

@ -1,7 +1,5 @@
// 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
// file that was distributed with this source code.
// 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())),
Some((k, v)) => match k {
"bs" => {
let bs = self.parse_bytes(k, v)?;
let bs = Self::parse_bytes(k, v)?;
self.ibs = 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)?,
"count" => self.count = Some(self.parse_n(v)?),
"ibs" => self.ibs = self.parse_bytes(k, v)?,
"count" => self.count = Some(Self::parse_n(v)?),
"ibs" => self.ibs = Self::parse_bytes(k, v)?,
"if" => self.infile = Some(v.to_string()),
"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()),
"oflag" => self.parse_output_flags(v)?,
"seek" | "oseek" => self.seek = self.parse_n(v)?,
"skip" | "iseek" => self.skip = self.parse_n(v)?,
"status" => self.status = Some(self.parse_status_level(v)?),
"seek" | "oseek" => self.seek = Self::parse_n(v)?,
"skip" | "iseek" => self.skip = Self::parse_n(v)?,
"status" => self.status = Some(Self::parse_status_level(v)?),
_ => return Err(ParseError::UnrecognizedOperand(operand.to_string())),
},
}
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)?;
Ok(if val.ends_with('B') {
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)?
.try_into()
.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 {
"none" => Ok(StatusLevel::None),
"noxfer" => Ok(StatusLevel::Noxfer),
@ -506,7 +504,7 @@ fn parse_bytes_no_x(full: &str, s: &str) -> Result<u64, ParseError> {
..Default::default()
};
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),
Err(ParseSizeError::InvalidSuffix(_) | ParseSizeError::ParseFailure(_)) => {
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
use super::*;
@ -99,6 +103,7 @@ fn test_status_level_none() {
}
#[test]
#[allow(clippy::cognitive_complexity)]
fn test_all_top_level_args_no_leading_dashes() {
let args = &[
"if=foo.file",

View file

@ -1,7 +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.
// 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 btotal sigval
//! Read and write progress tracking for dd.
//!

View file

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

View file

@ -1,7 +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.
// 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.
//! Types for representing and displaying block sizes.
use crate::{OPT_BLOCKSIZE, OPT_PORTABILITY};
use clap::ArgMatches;
@ -9,7 +9,7 @@ use std::{env, fmt};
use uucore::{
display::Quotable,
parse_size::{parse_size, ParseSizeError},
parse_size::{parse_size_u64, ParseSizeError},
};
/// 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> {
if matches.contains_id(OPT_BLOCKSIZE) {
let s = matches.get_one::<String>(OPT_BLOCKSIZE).unwrap();
let bytes = parse_size(s)?;
let bytes = parse_size_u64(s)?;
if bytes > 0 {
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> {
for env_var in ["DF_BLOCK_SIZE", "BLOCK_SIZE", "BLOCKSIZE"] {
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);
} else {
return None;
@ -239,6 +239,7 @@ mod tests {
}
#[test]
#[allow(clippy::cognitive_complexity)]
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(999, SuffixType::Si), "999B");

View file

@ -1,7 +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.
// 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 itotal iused iavail ipcent pcent squashfs
use crate::{OPT_INODES, OPT_OUTPUT, OPT_PRINT_TYPE};
use clap::{parser::ValueSource, ArgMatches};

View file

@ -1,10 +1,7 @@
// This file is part of the uutils coreutils package.
//
// (c) Fangxu Hu <framlog@gmail.com>
// (c) Sylvestre Ledru <sylvestre@debian.org>
//
// 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.
// spell-checker:ignore itotal iused iavail ipcent pcent tmpfs squashfs lofs
mod blocks;
mod columns;

View file

@ -1,7 +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.
// 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.
//! Provides a summary representation of a filesystem.
//!
//! A [`Filesystem`] struct represents a device containing a

View file

@ -1,8 +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 tmpfs Pcent Itotal Iused Iavail Ipcent
// 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 tmpfs Pcent Itotal Iused Iavail Ipcent nosuid nodev
//! The filesystem usage data table.
//!
//! A table ([`Table`]) comprises a header row ([`Header`]) and a
@ -152,8 +152,10 @@ impl From<Filesystem> for Row {
ffree,
..
} = 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 {
file: fs.file,
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(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]
name = "uu_dir"
version = "0.0.20"
version = "0.0.22"
authors = ["uutils developers"]
license = "MIT"
description = "shortcut to ls -C -b"
@ -16,7 +16,7 @@ path = "src/dir.rs"
[dependencies]
clap = { workspace = true, features = ["env"] }
uucore = { workspace = true, features = ["entries", "fs"] }
uucore = { workspace = true, features = ["entries", "fs", "quoting-style"] }
uu_ls = { workspace = true }
[[bin]]

View file

@ -1,9 +1,7 @@
// * 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.
// 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 clap::Command;
use std::ffi::OsString;

View file

@ -1,6 +1,6 @@
[package]
name = "uu_dircolors"
version = "0.0.20"
version = "0.0.22"
authors = ["uutils developers"]
license = "MIT"
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