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:
commit
28810906a3
524 changed files with 9820 additions and 4775 deletions
|
@ -1,2 +1,2 @@
|
|||
msrv = "1.64.0"
|
||||
msrv = "1.70.0"
|
||||
cognitive-complexity-threshold = 10
|
||||
|
|
234
.github/workflows/CICD.yml
vendored
234
.github/workflows/CICD.yml
vendored
|
@ -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
|
||||
|
||||
|
|
26
.github/workflows/CheckScripts.yml
vendored
26
.github/workflows/CheckScripts.yml
vendored
|
@ -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 }}
|
||||
|
||||
|
|
4
.github/workflows/FixPR.yml
vendored
4
.github/workflows/FixPR.yml
vendored
|
@ -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
|
||||
|
|
12
.github/workflows/GnuTests.yml
vendored
12
.github/workflows/GnuTests.yml
vendored
|
@ -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:
|
||||
|
|
2
.github/workflows/android.yml
vendored
2
.github/workflows/android.yml
vendored
|
@ -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
180
.github/workflows/code-quality.yml
vendored
Normal 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
|
4
.github/workflows/freebsd.yml
vendored
4
.github/workflows/freebsd.yml
vendored
|
@ -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
64
.github/workflows/fuzzing.yml
vendored
Normal 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
|
|
@ -58,6 +58,7 @@ MinGW
|
|||
Minix
|
||||
NetBSD
|
||||
Novell
|
||||
Nushell
|
||||
OpenBSD
|
||||
POSIX
|
||||
PowerPC
|
||||
|
|
|
@ -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
|
||||
|
|
219
CONTRIBUTING.md
219
CONTRIBUTING.md
|
@ -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
746
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
275
Cargo.toml
275
Cargo.toml
|
@ -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
331
DEVELOPMENT.md
Normal 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.
|
|
@ -102,7 +102,6 @@ PROGS := \
|
|||
pwd \
|
||||
readlink \
|
||||
realpath \
|
||||
relpath \
|
||||
rm \
|
||||
rmdir \
|
||||
seq \
|
||||
|
|
10
README.md
10
README.md
|
@ -14,7 +14,7 @@
|
|||
[](https://deps.rs/repo/github/uutils/coreutils)
|
||||
|
||||
[](https://codecov.io/gh/uutils/coreutils)
|
||||

|
||||

|
||||
|
||||
</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).
|
||||
|
|
|
@ -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| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | |
|
6
build.rs
6
build.rs
|
@ -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(),
|
||||
)
|
||||
|
|
15
deny.toml
15
deny.toml
|
@ -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
1
docs/.gitignore
vendored
|
@ -1,4 +1,5 @@
|
|||
book
|
||||
src/utils
|
||||
src/SUMMARY.md
|
||||
src/platform_table.md
|
||||
tldr.zip
|
|
@ -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
|
||||
|
|
|
|
@ -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.
|
||||
|
|
|
@ -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
45
docs/src/platforms.md
Normal 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 }}
|
|
@ -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"
|
||||
|
|
107
fuzz/fuzz_targets/fuzz_common.rs
Normal file
107
fuzz/fuzz_targets/fuzz_common.rs
Normal 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),
|
||||
))
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
});
|
||||
|
|
120
fuzz/fuzz_targets/fuzz_expr.rs
Normal file
120
fuzz/fuzz_targets/fuzz_expr.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
});
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
|
|
234
fuzz/fuzz_targets/fuzz_test.rs
Normal file
234
fuzz/fuzz_targets/fuzz_test.rs
Normal 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..]);
|
||||
}
|
||||
}
|
||||
});
|
|
@ -2,8 +2,13 @@
|
|||
"project": {
|
||||
"name": "uutils coreutils"
|
||||
},
|
||||
"build": {
|
||||
"path_prefix": "coreutils"
|
||||
},
|
||||
"components": {
|
||||
"changelog": true
|
||||
"changelog": {
|
||||
"read_changelog_file": false
|
||||
}
|
||||
},
|
||||
"styles": {
|
||||
"theme": "light",
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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)"
|
||||
|
|
|
@ -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};
|
||||
|
||||
|
|
|
@ -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};
|
||||
|
||||
|
|
|
@ -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)"
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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()?;
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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)]
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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};
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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())?;
|
||||
|
|
|
@ -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 }
|
||||
|
||||
|
|
|
@ -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("./."));
|
||||
|
|
|
@ -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)?;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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")]
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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")),
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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$%",
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
)),
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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()))
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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.
|
||||
//!
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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};
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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]]
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
Loading…
Add table
Add a link
Reference in a new issue