diff --git a/.github/workflows/CICD.yml b/.github/workflows/CICD.yml index 66ee23168b8..ad12ede6833 100644 --- a/.github/workflows/CICD.yml +++ b/.github/workflows/CICD.yml @@ -15,7 +15,11 @@ 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 -on: [push, pull_request] +on: + pull_request: + push: + branches: + - main permissions: contents: read # to fetch code (actions/checkout) @@ -133,7 +137,7 @@ 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@v13 + - uses: DavidAnson/markdownlint-cli2-action@v15 with: fix: "true" globs: | @@ -204,7 +208,7 @@ jobs: echo "## dependency list" ## * using the 'stable' toolchain is necessary to avoid "unexpected '--filter-platform'" errors RUSTUP_TOOLCHAIN=stable cargo fetch --locked --quiet - RUSTUP_TOOLCHAIN=stable cargo tree --all --locked --no-dev-dependencies --no-indent ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }} | grep -vE "$PWD" | sort --unique + RUSTUP_TOOLCHAIN=stable cargo tree --no-dedupe --locked -e=no-dev --prefix=none ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }} | grep -vE "$PWD" | sort --unique - name: Test run: cargo nextest run --hide-progress-bar --profile ci ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }} -p uucore -p coreutils env: @@ -391,14 +395,14 @@ jobs: --arg multisize "$SIZE_MULTI" \ '{($date): { sha: $sha, size: $size, multisize: $multisize, }}' > size-result.json - name: Download the previous individual size result - uses: dawidd6/action-download-artifact@v2 + uses: dawidd6/action-download-artifact@v3 with: workflow: CICD.yml name: individual-size-result repo: uutils/coreutils path: dl - name: Download the previous size result - uses: dawidd6/action-download-artifact@v2 + uses: dawidd6/action-download-artifact@v3 with: workflow: CICD.yml name: size-result @@ -435,12 +439,12 @@ jobs: previous_multisize=$(cat dl/size-result.json | jq -r '.[] | .multisize') check 'multicall binary' "$multisize" "$previous_multisize" 'size-result.json' - name: Upload the individual size result - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: individual-size-result path: individual-size-result.json - name: Upload the size result - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: size-result path: size-result.json @@ -464,11 +468,13 @@ jobs: # - { os , target , cargo-options , features , use-cross , toolchain, skip-tests } - { os: ubuntu-latest , target: arm-unknown-linux-gnueabihf, features: feat_os_unix_gnueabihf, use-cross: use-cross, } - { os: ubuntu-latest , target: aarch64-unknown-linux-gnu , features: feat_os_unix_gnueabihf , use-cross: use-cross } + - { os: ubuntu-latest , target: aarch64-unknown-linux-musl , features: feat_os_unix_musl , use-cross: use-cross } # - { os: ubuntu-latest , target: x86_64-unknown-linux-gnu , features: feat_selinux , use-cross: use-cross } - { os: ubuntu-latest , target: i686-unknown-linux-gnu , features: feat_os_unix , use-cross: use-cross } - { os: ubuntu-latest , target: i686-unknown-linux-musl , features: feat_os_unix_musl , use-cross: use-cross } - { os: ubuntu-latest , target: x86_64-unknown-linux-gnu , features: feat_os_unix , use-cross: use-cross } - { os: ubuntu-latest , target: x86_64-unknown-linux-musl , features: feat_os_unix_musl , use-cross: use-cross } + - { os: ubuntu-latest , target: x86_64-unknown-redox , features: feat_os_unix_redox , use-cross: redoxer , skip-tests: true } - { os: macos-latest , target: aarch64-apple-darwin , features: feat_os_macos , use-cross: use-cross, skip-tests: true} # Hopefully github provides free M1 runners soon... - { os: macos-latest , target: x86_64-apple-darwin , features: feat_os_macos } - { os: windows-latest , target: i686-pc-windows-msvc , features: feat_os_windows } @@ -521,7 +527,13 @@ jobs: i686-*) TARGET_ARCH=i686 ;; x86_64-*) TARGET_ARCH=x86_64 ;; esac; - unset TARGET_OS ; case '${{ matrix.job.target }}' in *-linux-*) TARGET_OS=linux ;; *-apple-*) TARGET_OS=macos ;; *-windows-*) TARGET_OS=windows ;; esac; + unset TARGET_OS + case '${{ matrix.job.target }}' in + *-linux-*) TARGET_OS=linux ;; + *-apple-*) TARGET_OS=macos ;; + *-windows-*) TARGET_OS=windows ;; + *-redox*) TARGET_OS=redox ;; + esac outputs TARGET_ARCH TARGET_OS # package name PKG_suffix=".tar.gz" ; case '${{ matrix.job.target }}' in *-pc-windows-*) PKG_suffix=".zip" ;; esac; @@ -556,8 +568,19 @@ jobs: if [ -n "${{ matrix.job.features }}" ]; then CARGO_FEATURES_OPTION='--features=${{ matrix.job.features }}' ; fi outputs CARGO_FEATURES_OPTION # * CARGO_CMD - CARGO_CMD='cross' ; case '${{ matrix.job.use-cross }}' in ''|0|f|false|n|no) CARGO_CMD='cargo' ;; esac; + CARGO_CMD='cross' + CARGO_CMD_OPTIONS='+${{ env.RUST_MIN_SRV }}' + case '${{ matrix.job.use-cross }}' in + ''|0|f|false|n|no) + CARGO_CMD='cargo' + ;; + redoxer) + CARGO_CMD='redoxer' + CARGO_CMD_OPTIONS='' + ;; + esac outputs CARGO_CMD + outputs CARGO_CMD_OPTIONS # ** pass needed environment into `cross` container (iff `cross` not already configured via "Cross.toml") if [ "${CARGO_CMD}" = 'cross' ] && [ ! -e "Cross.toml" ] ; then printf "[build.env]\npassthrough = [\"CI\", \"RUST_BACKTRACE\", \"CARGO_TERM_COLOR\"]\n" > Cross.toml @@ -568,7 +591,7 @@ jobs: # * executable for `strip`? STRIP="strip" case ${{ matrix.job.target }} in - aarch64-*-linux-gnu) STRIP="aarch64-linux-gnu-strip" ;; + aarch64-*-linux-*) STRIP="aarch64-linux-gnu-strip" ;; arm-*-linux-gnueabihf) STRIP="arm-linux-gnueabihf-strip" ;; *-pc-windows-msvc) STRIP="" ;; esac; @@ -589,8 +612,23 @@ jobs: run: | ## Install/setup prerequisites case '${{ matrix.job.target }}' in - arm-unknown-linux-gnueabihf) sudo apt-get -y update ; sudo apt-get -y install gcc-arm-linux-gnueabihf ;; - aarch64-unknown-linux-gnu) sudo apt-get -y update ; sudo apt-get -y install gcc-aarch64-linux-gnu ;; + arm-unknown-linux-gnueabihf) + sudo apt-get -y update + sudo apt-get -y install gcc-arm-linux-gnueabihf + ;; + aarch64-unknown-linux-*) + sudo apt-get -y update + sudo apt-get -y install gcc-aarch64-linux-gnu + ;; + *-redox*) + sudo apt-get -y update + sudo apt-get -y install fuse3 libfuse-dev + ;; + # Update binutils if MinGW due to https://github.com/rust-lang/rust/issues/112368 + x86_64-pc-windows-gnu) + C:/msys64/usr/bin/pacman.exe -Syu --needed mingw-w64-x86_64-gcc --noconfirm + echo "C:\msys64\mingw64\bin" >> $GITHUB_PATH + ;; esac case '${{ matrix.job.os }}' in macos-latest) brew install coreutils ;; # needed for testing @@ -610,6 +648,10 @@ jobs: echo "foo" > /home/runner/.plan ;; esac + - uses: taiki-e/install-action@v2 + if: steps.vars.outputs.CARGO_CMD == 'redoxer' + with: + tool: redoxer@0.2.37 - name: Initialize toolchain-dependent workflow variables id: dep_vars shell: bash @@ -643,19 +685,19 @@ jobs: # dependencies echo "## dependency list" cargo fetch --locked --quiet - cargo tree --locked --target=${{ matrix.job.target }} ${{ matrix.job.cargo-options }} ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }} --all --no-dev-dependencies --no-indent | grep -vE "$PWD" | sort --unique + cargo tree --locked --target=${{ matrix.job.target }} ${{ matrix.job.cargo-options }} ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }} --no-dedupe -e=no-dev --prefix=none | grep -vE "$PWD" | sort --unique - name: Build shell: bash run: | ## Build - ${{ steps.vars.outputs.CARGO_CMD }} +${{ env.RUST_MIN_SRV }} build --release \ + ${{ steps.vars.outputs.CARGO_CMD }} ${{ steps.vars.outputs.CARGO_CMD_OPTIONS }} build --release \ --target=${{ matrix.job.target }} ${{ matrix.job.cargo-options }} ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }} - name: Test if: matrix.job.skip-tests != true shell: bash run: | ## Test - ${{ steps.vars.outputs.CARGO_CMD }} +${{ env.RUST_MIN_SRV }} test --target=${{ matrix.job.target }} \ + ${{ steps.vars.outputs.CARGO_CMD }} ${{ steps.vars.outputs.CARGO_CMD_OPTIONS }} test --target=${{ matrix.job.target }} \ ${{ steps.vars.outputs.CARGO_TEST_OPTIONS}} ${{ matrix.job.cargo-options }} ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }} env: RUST_BACKTRACE: "1" @@ -664,12 +706,12 @@ jobs: shell: bash run: | ## Test individual utilities - ${{ steps.vars.outputs.CARGO_CMD }} +${{ env.RUST_MIN_SRV }} test --target=${{ matrix.job.target }} \ + ${{ steps.vars.outputs.CARGO_CMD }} ${{ steps.vars.outputs.CARGO_CMD_OPTIONS }} test --target=${{ matrix.job.target }} \ ${{ steps.vars.outputs.CARGO_TEST_OPTIONS}} ${{ matrix.job.cargo-options }} ${{ steps.dep_vars.outputs.CARGO_UTILITY_LIST_OPTIONS }} env: RUST_BACKTRACE: "1" - name: Archive executable artifacts - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: ${{ env.PROJECT_NAME }}-${{ matrix.job.target }} path: target/${{ matrix.job.target }}/release/${{ env.PROJECT_NAME }}${{ steps.vars.outputs.EXE_suffix }} @@ -780,17 +822,17 @@ jobs: HASH=$(sha1sum '${{ steps.vars.outputs.TEST_SUMMARY_FILE }}' | cut --delim=" " -f 1) echo "HASH=${HASH}" >> $GITHUB_OUTPUT - name: Reserve SHA1/ID of 'test-summary' - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: "${{ steps.summary.outputs.HASH }}" path: "${{ steps.vars.outputs.TEST_SUMMARY_FILE }}" - name: Reserve test results summary - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: - name: test-summary + name: busybox-test-summary path: "${{ steps.vars.outputs.TEST_SUMMARY_FILE }}" - name: Upload json results - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: busybox-result.json path: ${{ steps.vars.outputs.TEST_SUMMARY_FILE }} @@ -868,17 +910,17 @@ jobs: HASH=$(sha1sum '${{ steps.vars.outputs.TEST_SUMMARY_FILE }}' | cut --delim=" " -f 1) echo "HASH=${HASH}" >> $GITHUB_OUTPUT - name: Reserve SHA1/ID of 'test-summary' - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: "${{ steps.summary.outputs.HASH }}" path: "${{ steps.vars.outputs.TEST_SUMMARY_FILE }}" - name: Reserve test results summary - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: - name: test-summary + name: toybox-test-summary path: "${{ steps.vars.outputs.TEST_SUMMARY_FILE }}" - name: Upload json results - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: toybox-result.json path: ${{ steps.vars.outputs.TEST_SUMMARY_FILE }} @@ -956,6 +998,10 @@ jobs: echo "foo" > /home/runner/.plan ;; esac + case '${{ matrix.job.os }}' in + # Update binutils if MinGW due to https://github.com/rust-lang/rust/issues/112368 + windows-latest) C:/msys64/usr/bin/pacman.exe -Syu --needed mingw-w64-x86_64-gcc --noconfirm ; echo "C:\msys64\mingw64\bin" >> $GITHUB_PATH ;; + esac - name: Initialize toolchain-dependent workflow variables id: dep_vars shell: bash @@ -966,16 +1012,8 @@ jobs: UTILITY_LIST="$(./util/show-utils.sh ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }})" CARGO_UTILITY_LIST_OPTIONS="$(for u in ${UTILITY_LIST}; do echo -n "-puu_${u} "; done;)" outputs CARGO_UTILITY_LIST_OPTIONS - - name: Test uucore - run: cargo nextest run --profile ci --hide-progress-bar -p uucore - env: - RUSTC_WRAPPER: "" - RUSTFLAGS: "-Zprofile -Ccodegen-units=1 -Copt-level=0 -Clink-dead-code -Coverflow-checks=off -Zpanic_abort_tests -Cpanic=abort" - RUSTDOCFLAGS: "-Cpanic=abort" - RUST_BACKTRACE: "1" - # RUSTUP_TOOLCHAIN: ${{ steps.vars.outputs.TOOLCHAIN }} - name: Test - run: cargo nextest run --profile ci --hide-progress-bar ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }} + run: cargo nextest run --profile ci --hide-progress-bar ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }} -p uucore -p coreutils env: RUSTC_WRAPPER: "" RUSTFLAGS: "-Zprofile -Ccodegen-units=1 -Copt-level=0 -Clink-dead-code -Coverflow-checks=off -Zpanic_abort_tests -Cpanic=abort" diff --git a/.github/workflows/FixPR.yml b/.github/workflows/FixPR.yml index 7f5e5234d8b..e837b354687 100644 --- a/.github/workflows/FixPR.yml +++ b/.github/workflows/FixPR.yml @@ -63,7 +63,7 @@ jobs: echo "## dependency list" cargo fetch --locked --quiet ## * using the 'stable' toolchain is necessary to avoid "unexpected '--filter-platform'" errors - RUSTUP_TOOLCHAIN=stable cargo tree --locked --all --no-dev-dependencies --no-indent --features ${{ matrix.job.features }} | grep -vE "$PWD" | sort --unique + RUSTUP_TOOLCHAIN=stable cargo tree --locked --no-dedupe -e=no-dev --prefix=none --features ${{ matrix.job.features }} | grep -vE "$PWD" | sort --unique - name: Commit any changes (to '${{ env.BRANCH_TARGET }}') uses: EndBug/add-and-commit@v9 with: diff --git a/.github/workflows/GnuTests.yml b/.github/workflows/GnuTests.yml index 61f30eba4c1..89fa5a7da50 100644 --- a/.github/workflows/GnuTests.yml +++ b/.github/workflows/GnuTests.yml @@ -9,7 +9,11 @@ name: GnuTests # * note: to run a single test => `REPO/util/run-gnu-test.sh PATH/TO/TEST/SCRIPT` -on: [push, pull_request] +on: + pull_request: + push: + branches: + - main permissions: contents: read @@ -26,7 +30,7 @@ jobs: contents: read # for actions/checkout to fetch code pull-requests: read # for dawidd6/action-download-artifact to query commit hash name: Run GNU tests - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 steps: - name: Initialize workflow variables id: vars @@ -73,7 +77,7 @@ jobs: ref: ${{ steps.vars.outputs.repo_GNU_ref }} submodules: recursive - name: Retrieve reference artifacts - uses: dawidd6/action-download-artifact@v2 + uses: dawidd6/action-download-artifact@v3 # ref: continue-on-error: true ## don't break the build for missing reference artifacts (may be expired or just not generated yet) with: @@ -112,7 +116,7 @@ jobs: run: | ## Build binaries cd '${{ steps.vars.outputs.path_UUTILS }}' - bash util/build-gnu.sh + bash util/build-gnu.sh --release-build - name: Run GNU tests shell: bash run: | @@ -175,22 +179,22 @@ jobs: # Compress logs before upload (fails otherwise) gzip ${{ steps.vars.outputs.TEST_LOGS_GLOB }} - name: Reserve SHA1/ID of 'test-summary' - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: "${{ steps.summary.outputs.HASH }}" path: "${{ steps.vars.outputs.TEST_SUMMARY_FILE }}" - name: Reserve test results summary - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: test-summary path: "${{ steps.vars.outputs.TEST_SUMMARY_FILE }}" - name: Reserve test logs - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: test-logs path: "${{ steps.vars.outputs.TEST_LOGS_GLOB }}" - name: Upload full json results - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: gnu-full-result.json path: ${{ steps.vars.outputs.TEST_FULL_SUMMARY_FILE }} @@ -200,6 +204,7 @@ jobs: ## Compare test failures VS reference have_new_failures="" REF_LOG_FILE='${{ steps.vars.outputs.path_reference }}/test-logs/test-suite.log' + ROOT_REF_LOG_FILE='${{ steps.vars.outputs.path_reference }}/test-logs/test-suite-root.log' REF_SUMMARY_FILE='${{ steps.vars.outputs.path_reference }}/test-summary/gnu-result.json' REPO_DEFAULT_BRANCH='${{ steps.vars.outputs.repo_default_branch }}' path_UUTILS='${{ steps.vars.outputs.path_UUTILS }}' @@ -219,72 +224,96 @@ jobs: rm -f ${COMMENT_LOG} touch ${COMMENT_LOG} - if test -f "${REF_LOG_FILE}"; then - echo "Reference SHA1/ID: $(sha1sum -- "${REF_SUMMARY_FILE}")" - REF_ERROR=$(sed -n "s/^ERROR: \([[:print:]]\+\).*/\1/p" "${REF_LOG_FILE}" | sort) - NEW_ERROR=$(sed -n "s/^ERROR: \([[:print:]]\+\).*/\1/p" '${{ steps.vars.outputs.path_GNU_tests }}/test-suite.log' | sort) - REF_FAILING=$(sed -n "s/^FAIL: \([[:print:]]\+\).*/\1/p" "${REF_LOG_FILE}" | sort) - NEW_FAILING=$(sed -n "s/^FAIL: \([[:print:]]\+\).*/\1/p" '${{ steps.vars.outputs.path_GNU_tests }}/test-suite.log' | sort) - for LINE in ${REF_FAILING} - do - if ! grep -Fxq ${LINE}<<<"${NEW_FAILING}"; then - if ! grep ${LINE} ${IGNORE_INTERMITTENT} + compare_tests() { + local new_log_file=$1 + local ref_log_file=$2 + local test_type=$3 # "standard" or "root" + + if test -f "${ref_log_file}"; then + echo "Reference ${test_type} test log SHA1/ID: $(sha1sum -- "${ref_log_file}") - ${test_type}" + REF_ERROR=$(sed -n "s/^ERROR: \([[:print:]]\+\).*/\1/p" "${ref_log_file}"| sort) + CURRENT_RUN_ERROR=$(sed -n "s/^ERROR: \([[:print:]]\+\).*/\1/p" "${new_log_file}" | sort) + REF_FAILING=$(sed -n "s/^FAIL: \([[:print:]]\+\).*/\1/p" "${ref_log_file}"| sort) + CURRENT_RUN_FAILING=$(sed -n "s/^FAIL: \([[:print:]]\+\).*/\1/p" "${new_log_file}" | sort) + echo "Detailled information:" + echo "REF_ERROR = ${REF_ERROR}" + echo "CURRENT_RUN_ERROR = ${CURRENT_RUN_ERROR}" + echo "REF_FAILING = ${REF_FAILING}" + echo "CURRENT_RUN_FAILING = ${CURRENT_RUN_FAILING}" + + # Compare failing and error tests + for LINE in ${CURRENT_RUN_FAILING} + do + if ! grep -Fxq ${LINE}<<<"${REF_FAILING}" then - MSG="Congrats! The gnu test ${LINE} is no longer failing!" - echo "::warning ::$MSG" - echo $MSG >> ${COMMENT_LOG} - else - MSG="Skipping an intermittent issue ${LINE}" - echo "::warning ::$MSG" - echo $MSG >> ${COMMENT_LOG} - echo "" + if ! grep ${LINE} ${IGNORE_INTERMITTENT} + then + MSG="GNU test failed: ${LINE}. ${LINE} is passing on '${REPO_DEFAULT_BRANCH}'. Maybe you have to rebase?" + echo "::error ::$MSG" + echo $MSG >> ${COMMENT_LOG} + have_new_failures="true" + else + MSG="Skip an intermittent issue ${LINE} (fails in this run but passes in the 'main' branch)" + echo "::warning ::$MSG" + echo $MSG >> ${COMMENT_LOG} + echo "" + fi fi - fi - done - for LINE in ${NEW_FAILING} - do - if ! grep -Fxq ${LINE}<<<"${REF_FAILING}" - then - if ! grep ${LINE} ${IGNORE_INTERMITTENT} + done + + for LINE in ${REF_FAILING} + do + if ! grep -Fxq ${LINE}<<<"${CURRENT_RUN_FAILING}" + then + if ! grep ${LINE} ${IGNORE_INTERMITTENT} + then + MSG="Congrats! The gnu test ${LINE} is no longer failing!" + echo "::warning ::$MSG" + echo $MSG >> ${COMMENT_LOG} + else + MSG="Skipping an intermittent issue ${LINE} (passes in this run but fails in the 'main' branch)" + echo "::warning ::$MSG" + echo $MSG >> ${COMMENT_LOG} + echo "" + fi + fi + done + + for LINE in ${CURRENT_RUN_ERROR} + do + if ! grep -Fxq ${LINE}<<<"${REF_ERROR}" then - MSG="GNU test failed: ${LINE}. ${LINE} is passing on '${{ steps.vars.outputs.repo_default_branch }}'. Maybe you have to rebase?" + MSG="GNU test error: ${LINE}. ${LINE} is passing on '${REPO_DEFAULT_BRANCH}'. Maybe you have to rebase?" echo "::error ::$MSG" echo $MSG >> ${COMMENT_LOG} have_new_failures="true" - else - MSG="Skip an intermittent issue ${LINE}" + fi + done + + for LINE in ${REF_ERROR} + do + if ! grep -Fxq ${LINE}<<<"${CURRENT_RUN_ERROR}" + then + MSG="Congrats! The gnu test ${LINE} is no longer ERROR!" echo "::warning ::$MSG" echo $MSG >> ${COMMENT_LOG} - echo "" fi - fi - done - for LINE in ${REF_ERROR} - do - if ! grep -Fxq ${LINE}<<<"${NEW_ERROR}"; then - MSG="Congrats! The gnu test ${LINE} is no longer ERROR!" - echo "::warning ::$MSG" - echo $MSG >> ${COMMENT_LOG} - fi - done - for LINE in ${NEW_ERROR} - do - if ! grep -Fxq ${LINE}<<<"${REF_ERROR}" - then - MSG="GNU test error: ${LINE}. ${LINE} is passing on '${{ steps.vars.outputs.repo_default_branch }}'. Maybe you have to rebase?" - echo "::error ::$MSG" - echo $MSG >> ${COMMENT_LOG} - have_new_failures="true" - fi - done + done + else + echo "::warning ::Skipping ${test_type} test failure comparison; no prior reference test logs are available." + fi + } + + # Compare standard tests + compare_tests '${{ steps.vars.outputs.path_GNU_tests }}/test-suite.log' "${REF_LOG_FILE}" "standard" + + # Compare root tests + compare_tests '${{ steps.vars.outputs.path_GNU_tests }}/test-suite-root.log' "${ROOT_REF_LOG_FILE}" "root" - else - echo "::warning ::Skipping test failure comparison; no prior reference test logs are available." - fi if test -n "${have_new_failures}" ; then exit -1 ; fi - name: Upload comparison log (for GnuComment workflow) if: success() || failure() # run regardless of prior step success/failure - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: comment path: ${{ steps.vars.outputs.path_reference }}/comment/ @@ -304,7 +333,7 @@ jobs: gnu_coverage: name: Run GNU tests with coverage - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 steps: - name: Checkout code uutil uses: actions/checkout@v4 @@ -350,7 +379,7 @@ jobs: run: | ## Build binaries cd uutils - UU_MAKE_PROFILE=debug bash util/build-gnu.sh + bash util/build-gnu.sh - name: Run GNU tests run: bash uutils/util/run-gnu-test.sh - name: Generate coverage data (via `grcov`) diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index 5834aceffe8..86b5a89e83a 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -2,7 +2,12 @@ name: Android # spell-checker:ignore TERMUX reactivecircus Swatinem noaudio pkill swiftshader dtolnay juliangruber -on: [push, pull_request] +on: + pull_request: + push: + branches: + - main + permissions: contents: read # to fetch code (actions/checkout) @@ -28,7 +33,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Restore AVD cache - uses: actions/cache/restore@v3 + uses: actions/cache/restore@v4 id: avd-cache with: path: | @@ -47,12 +52,12 @@ jobs: ram-size: 2048M disk-size: 7GB force-avd-creation: true - emulator-options: -no-snapshot-load -noaudio -no-boot-anim -camera-back none + emulator-options: -no-window -no-snapshot-load -noaudio -no-boot-anim -camera-back none script: | util/android-commands.sh init "${{ matrix.arch }}" "${{ matrix.api-level }}" "${{ env.TERMUX }}" - name: Save AVD cache if: steps.avd-cache.outputs.cache-hit != 'true' - uses: actions/cache/save@v3 + uses: actions/cache/save@v4 with: path: | ~/.android/avd/* @@ -68,7 +73,7 @@ jobs: trim: true - name: Restore rust cache id: rust-cache - uses: actions/cache/restore@v3 + uses: actions/cache/restore@v4 with: path: ~/__rust_cache__ # The version vX at the end of the key is just a development version to avoid conflicts in @@ -83,7 +88,7 @@ jobs: ram-size: 2048M disk-size: 7GB force-avd-creation: false - emulator-options: -no-snapshot-save -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none -snapshot ${{ matrix.api-level }}-${{ matrix.arch }}+termux-${{ env.TERMUX }} + emulator-options: -no-window -no-snapshot-save -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none -snapshot ${{ matrix.api-level }}-${{ matrix.arch }}+termux-${{ env.TERMUX }} # This is not a usual script. Every line is executed in a separate shell with `sh -c`. If # one of the lines returns with error the whole script is failed (like running a script with # set -e) and in consequences the other lines (shells) are not executed. @@ -94,7 +99,7 @@ jobs: if [[ "${{ steps.rust-cache.outputs.cache-hit }}" != 'true' ]]; then util/android-commands.sh sync_image; fi; exit 0 - name: Save rust cache if: steps.rust-cache.outputs.cache-hit != 'true' - uses: actions/cache/save@v3 + uses: actions/cache/save@v4 with: path: ~/__rust_cache__ key: ${{ matrix.arch }}_${{ matrix.target}}_${{ steps.read_rustc_hash.outputs.content }}_${{ hashFiles('**/Cargo.toml', '**/Cargo.lock') }}_v3 diff --git a/.github/workflows/code-quality.yml b/.github/workflows/code-quality.yml index 98691f34bc8..289830f8171 100644 --- a/.github/workflows/code-quality.yml +++ b/.github/workflows/code-quality.yml @@ -2,7 +2,11 @@ name: Code Quality # spell-checker:ignore TERMUX reactivecircus Swatinem noaudio pkill swiftshader dtolnay juliangruber -on: [push, pull_request] +on: + pull_request: + push: + branches: + - main env: # * style job configuration diff --git a/.github/workflows/freebsd.yml b/.github/workflows/freebsd.yml index 5af3da320a7..25655f0917f 100644 --- a/.github/workflows/freebsd.yml +++ b/.github/workflows/freebsd.yml @@ -6,7 +6,11 @@ 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 -on: [push, pull_request] +on: + pull_request: + push: + branches: + - main permissions: contents: read # to fetch code (actions/checkout) @@ -25,7 +29,7 @@ jobs: fail-fast: false matrix: job: - - { os: macos-12 , features: unix } ## GHA MacOS-11.0 VM won't have VirtualBox; refs: , + - { os: ubuntu-22.04 , features: unix } env: SCCACHE_GHA_ENABLED: "true" RUSTC_WRAPPER: "sccache" @@ -35,11 +39,13 @@ jobs: - name: Run sccache-cache uses: mozilla-actions/sccache-action@v0.0.3 - name: Prepare, build and test - uses: vmactions/freebsd-vm@v0.3.1 + uses: vmactions/freebsd-vm@v1.0.5 with: usesh: true - # We need jq to run show-utils.sh and bash to use inline shell string replacement - prepare: pkg install -y curl sudo jq bash + sync: rsync + copyback: false + # We need jq and GNU coreutils to run show-utils.sh and bash to use inline shell string replacement + prepare: pkg install -y curl sudo jq coreutils bash run: | ## Prepare, build, and test # implementation modelled after ref: @@ -48,11 +54,11 @@ jobs: # TEST_USER=tester REPO_NAME=${GITHUB_WORKSPACE##*/} - WORKSPACE_PARENT="/Users/runner/work/${REPO_NAME}" + WORKSPACE_PARENT="/home/runner/work/${REPO_NAME}" WORKSPACE="${WORKSPACE_PARENT}/${REPO_NAME}" # pw adduser -n ${TEST_USER} -d /root/ -g wheel -c "Coreutils user to build" -w random - chown -R ${TEST_USER}:wheel /root/ "/Users/runner/work/${REPO_NAME}"/ + chown -R ${TEST_USER}:wheel /root/ "${WORKSPACE_PARENT}"/ whoami # # Further work needs to be done in a sudo as we are changing users @@ -114,7 +120,7 @@ jobs: fail-fast: false matrix: job: - - { os: macos-12 , features: unix } ## GHA MacOS-11.0 VM won't have VirtualBox; refs: , + - { os: ubuntu-22.04 , features: unix } env: mem: 4096 SCCACHE_GHA_ENABLED: "true" @@ -125,10 +131,11 @@ jobs: - name: Run sccache-cache uses: mozilla-actions/sccache-action@v0.0.3 - name: Prepare, build and test - uses: vmactions/freebsd-vm@v0.3.1 + uses: vmactions/freebsd-vm@v1.0.5 with: usesh: true - # sync: sshfs + sync: rsync + copyback: false prepare: pkg install -y curl gmake sudo run: | ## Prepare, build, and test @@ -141,12 +148,12 @@ jobs: # TEST_USER=tester REPO_NAME=${GITHUB_WORKSPACE##*/} - WORKSPACE_PARENT="/Users/runner/work/${REPO_NAME}" + WORKSPACE_PARENT="/home/runner/work/${REPO_NAME}" WORKSPACE="${WORKSPACE_PARENT}/${REPO_NAME}" # pw adduser -n ${TEST_USER} -d /root/ -g wheel -c "Coreutils user to build" -w random # chown -R ${TEST_USER}:wheel /root/ "${WORKSPACE_PARENT}"/ - chown -R ${TEST_USER}:wheel /root/ "/Users/runner/work/${REPO_NAME}"/ + chown -R ${TEST_USER}:wheel /root/ "${WORKSPACE_PARENT}"/ whoami # # Further work needs to be done in a sudo as we are changing users diff --git a/.github/workflows/fuzzing.yml b/.github/workflows/fuzzing.yml index e7a9cb1e329..c60c01ff4be 100644 --- a/.github/workflows/fuzzing.yml +++ b/.github/workflows/fuzzing.yml @@ -2,7 +2,11 @@ name: Fuzzing # spell-checker:ignore fuzzer -on: [push, pull_request] +on: + pull_request: + push: + branches: + - main permissions: contents: read # to fetch code (actions/checkout) @@ -32,21 +36,26 @@ jobs: needs: fuzz-build name: Run the fuzzers runs-on: ubuntu-latest + timeout-minutes: 5 env: RUN_FOR: 60 strategy: matrix: test-target: - [ - fuzz_date, - fuzz_test, - fuzz_expr, - fuzz_parse_glob, - fuzz_parse_size, - fuzz_parse_time, - # adding more fuzz tests here. - # e.g. fuzz_test_a, - ] + - { name: fuzz_test, should_pass: true } + # https://github.com/uutils/coreutils/issues/5311 + - { name: fuzz_date, should_pass: false } + - { name: fuzz_expr, should_pass: true } + - { name: fuzz_printf, should_pass: false } + - { name: fuzz_echo, should_pass: true } + - { name: fuzz_seq, should_pass: false } + - { name: fuzz_sort, should_pass: false } + - { name: fuzz_wc, should_pass: false } + - { name: fuzz_cut, should_pass: false } + - { name: fuzz_split, should_pass: false } + - { name: fuzz_parse_glob, should_pass: true } + - { name: fuzz_parse_size, should_pass: true } + - { name: fuzz_parse_time, should_pass: true } steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@nightly @@ -57,18 +66,19 @@ jobs: shared-key: "cargo-fuzz-cache-key" cache-directories: "fuzz/target" - name: Restore Cached Corpus - uses: actions/cache/restore@v3 + uses: actions/cache/restore@v4 with: - key: corpus-cache-${{ matrix.test-target }} + key: corpus-cache-${{ matrix.test-target.name }} path: | - fuzz/corpus/${{ matrix.test-target }} - - name: Run ${{ matrix.test-target }} for XX seconds + fuzz/corpus/${{ matrix.test-target.name }} + - name: Run ${{ matrix.test-target.name }} for XX seconds shell: bash + continue-on-error: ${{ !matrix.test-target.name.should_pass }} run: | - cargo +nightly fuzz run ${{ matrix.test-target }} -- -max_total_time=${{ env.RUN_FOR }} -detect_leaks=0 + cargo +nightly fuzz run ${{ matrix.test-target.name }} -- -max_total_time=${{ env.RUN_FOR }} -detect_leaks=0 - name: Save Corpus Cache - uses: actions/cache/save@v3 + uses: actions/cache/save@v4 with: - key: corpus-cache-${{ matrix.test-target }} + key: corpus-cache-${{ matrix.test-target.name }} path: | - fuzz/corpus/${{ matrix.test-target }} + fuzz/corpus/${{ matrix.test-target.name }} diff --git a/.github/workflows/ignore-intermittent.txt b/.github/workflows/ignore-intermittent.txt index 759bd96eb8a..e163202a2ca 100644 --- a/.github/workflows/ignore-intermittent.txt +++ b/.github/workflows/ignore-intermittent.txt @@ -1,3 +1,3 @@ -tests/tail-2/inotify-dir-recreate +tests/tail/inotify-dir-recreate tests/misc/timeout tests/rm/rm1 diff --git a/.vscode/cspell.dictionaries/acronyms+names.wordlist.txt b/.vscode/cspell.dictionaries/acronyms+names.wordlist.txt index c004ea2f822..4a59ed094bd 100644 --- a/.vscode/cspell.dictionaries/acronyms+names.wordlist.txt +++ b/.vscode/cspell.dictionaries/acronyms+names.wordlist.txt @@ -37,6 +37,7 @@ aarch flac impls lzma +loongarch # * names BusyBox diff --git a/.vscode/cspell.dictionaries/jargon.wordlist.txt b/.vscode/cspell.dictionaries/jargon.wordlist.txt index dca883dc804..20e26990f3b 100644 --- a/.vscode/cspell.dictionaries/jargon.wordlist.txt +++ b/.vscode/cspell.dictionaries/jargon.wordlist.txt @@ -42,6 +42,7 @@ fileio filesystem filesystems flamegraph +fsxattr fullblock getfacl gibi @@ -133,6 +134,7 @@ urand whitespace wordlist wordlists +xattrs # * abbreviations consts diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 255ed2c53e3..b10d3d11472 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -14,7 +14,8 @@ check out these documents: Now follows a very important warning: -> [!WARNING] uutils is original code and cannot contain any code from GNU or +> [!WARNING] +> uutils is original code and cannot contain any code from GNU or > other implementations. This means that **we cannot accept any changes based on > the GNU source code**. To make sure that cannot happen, **you cannot link to > the GNU source code** either. diff --git a/Cargo.lock b/Cargo.lock index d83adfee600..e8c0ce0ca28 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -188,9 +188,9 @@ dependencies = [ [[package]] name = "bstr" -version = "1.8.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "542f33a8835a0884b006a0c3df3dadd99c0c3f296ed26c2fdc8028e01ad6230c" +checksum = "c48f0051a4b4c5e0b6d365cd04af53aeaa209e3cc15ec2cdb69e73cc87fbd0dc" dependencies = [ "memchr", "regex-automata", @@ -238,14 +238,14 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.31" +version = "0.4.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" +checksum = "41daef31d7a747c5c847246f36de49ced6f7403b4cdabc807a97b5cc184cda7a" dependencies = [ "android-tzdata", "iana-time-zone", "num-traits", - "windows-targets 0.48.0", + "windows-targets 0.52.0", ] [[package]] @@ -320,15 +320,15 @@ checksum = "120133d4db2ec47efe2e26502ee984747630c67f51974fca0b6c1340cf2368d3" [[package]] name = "console" -version = "0.15.7" +version = "0.15.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c926e00cc70edefdc64d3a5ff31cc65bb97a3460097762bd23afb4d8145fccf8" +checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb" dependencies = [ "encode_unicode", "lazy_static", "libc", "unicode-width", - "windows-sys 0.45.0", + "windows-sys 0.52.0", ] [[package]] @@ -374,7 +374,7 @@ checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" [[package]] name = "coreutils" -version = "0.0.23" +version = "0.0.24" dependencies = [ "chrono", "clap", @@ -507,6 +507,7 @@ dependencies = [ "uucore", "uuhelp_parser", "walkdir", + "xattr", "zip", ] @@ -590,9 +591,9 @@ dependencies = [ [[package]] name = "crossbeam-channel" -version = "0.5.8" +version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200" +checksum = "82a9b73a36529d9c47029b9fb3a6f0ea3cc916a261195352ba19e770fc1748b2" dependencies = [ "cfg-if", "crossbeam-utils", @@ -600,9 +601,9 @@ dependencies = [ [[package]] name = "crossbeam-deque" -version = "0.8.2" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "715e8152b692bba2d374b53d4875445368fdf21a94751410af607a5ac677d1fc" +checksum = "fca89a0e215bab21874660c67903c5f143333cab1da83d041c7ded6053774751" dependencies = [ "cfg-if", "crossbeam-epoch", @@ -611,22 +612,20 @@ dependencies = [ [[package]] name = "crossbeam-epoch" -version = "0.9.14" +version = "0.9.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46bd5f3f85273295a9d14aedfb86f6aadbff6d8f5295c4a9edb08e819dcf5695" +checksum = "0e3681d554572a651dda4186cd47240627c3d0114d45a95f6ad27f2f22e7548d" dependencies = [ "autocfg", "cfg-if", "crossbeam-utils", - "memoffset", - "scopeguard", ] [[package]] name = "crossbeam-utils" -version = "0.8.15" +version = "0.8.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c063cd8cc95f5c377ed0d4b49a4b21f632396ff690e8470c29b3359b346984b" +checksum = "c3a430a770ebd84726f584a90ee7f020d28db52c6d02138900f22341f866d39c" dependencies = [ "cfg-if", ] @@ -690,15 +689,15 @@ checksum = "ef8ae57c4978a2acd8b869ce6b9ca1dfe817bff704c220209fdef2c0b75a01b9" [[package]] name = "data-encoding" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308" +checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5" [[package]] name = "data-encoding-macro" -version = "0.1.13" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c904b33cc60130e1aeea4956ab803d08a3f4a0ca82d64ed757afac3891f2bb99" +checksum = "20c01c06f5f429efdf2bae21eb67c28b3df3cf85b7dd2d8ef09c0838dac5d33e" dependencies = [ "data-encoding", "data-encoding-macro-internal", @@ -706,9 +705,9 @@ dependencies = [ [[package]] name = "data-encoding-macro-internal" -version = "0.1.11" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fdf3fce3ce863539ec1d7fd1b6dcc3c645663376b43ed376bbf887733e4f772" +checksum = "0047d07f2c89b17dd631c80450d69841a6b5d7fb17278cbc43d7e4cfcf2576f3" dependencies = [ "data-encoding", "syn 1.0.109", @@ -781,12 +780,12 @@ dependencies = [ [[package]] name = "errno" -version = "0.3.5" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3e13f66a2f95e32a39eaa81f6b95d42878ca0e1db0c7543723dfe12557e860" +checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" dependencies = [ "libc", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -815,14 +814,14 @@ checksum = "31a7a908b8f32538a2143e59a6e4e2508988832d5d4d6f7c156b3cbc762643a5" [[package]] name = "filetime" -version = "0.2.22" +version = "0.2.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4029edd3e734da6fe05b6cd7bd2960760a616bd2ddd0d59a0124746d6272af0" +checksum = "1ee447700ac8aa0b2f2bd7bc4462ad686ba06baa6727ac149a2d6277f0d240fd" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.3.5", - "windows-sys 0.48.0", + "redox_syscall", + "windows-sys 0.52.0", ] [[package]] @@ -1122,9 +1121,9 @@ dependencies = [ [[package]] name = "itertools" -version = "0.11.0" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +checksum = "25db6b064527c5d482d0423354fcd07a89a2dfe07b67892e62411946db7f07b0" dependencies = [ "either", ] @@ -1187,9 +1186,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" -version = "0.2.150" +version = "0.2.152" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" +checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7" [[package]] name = "libloading" @@ -1215,9 +1214,9 @@ checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" [[package]] name = "linux-raw-sys" -version = "0.4.10" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da2479e8c062e40bf0066ffa0bc823de0a9368974af99c9f6df941d2c231e03f" +checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" [[package]] name = "lock_api" @@ -1240,9 +1239,9 @@ dependencies = [ [[package]] name = "lscolors" -version = "0.15.0" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf7015a04103ad78abb77e4b79ed151e767922d1cfde5f62640471c629a2320d" +checksum = "ab0b209ec3976527806024406fe765474b9a1750a0ed4b8f0372364741f50e7b" dependencies = [ "nu-ansi-term", ] @@ -1265,9 +1264,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.6.2" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5486aed0026218e61b8a01d5fbd5a0a134649abb71a0e53b7bc088529dced86e" +checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" [[package]] name = "memmap2" @@ -1278,15 +1277,6 @@ dependencies = [ "libc", ] -[[package]] -name = "memoffset" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d61c719bcfbcf5d62b3a09efa6088de8c54bc0bfcd3ea7ae39fcc186108b8de1" -dependencies = [ - "autocfg", -] - [[package]] name = "minimal-lexical" version = "0.2.1" @@ -1304,14 +1294,14 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.6" +version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9" +checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09" dependencies = [ "libc", "log", "wasi", - "windows-sys 0.45.0", + "windows-sys 0.48.0", ] [[package]] @@ -1409,9 +1399,9 @@ checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" [[package]] name = "once_cell" -version = "1.18.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "onig" @@ -1466,13 +1456,13 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.8" +version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" +checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.3.5", + "redox_syscall", "smallvec", "windows-targets 0.48.0", ] @@ -1600,7 +1590,7 @@ dependencies = [ "hex", "lazy_static", "procfs-core", - "rustix 0.38.21", + "rustix 0.38.30", ] [[package]] @@ -1700,18 +1690,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" -dependencies = [ - "bitflags 1.3.2", -] - -[[package]] -name = "redox_syscall" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ded0bce2d41cc3c57aefa284708ced249a64acb01745dbbe72bd78610bfd644c" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" dependencies = [ "bitflags 1.3.2", ] @@ -1724,9 +1705,9 @@ checksum = "f1bfbf25d7eb88ddcbb1ec3d755d0634da8f7657b2cb8b74089121409ab8228f" [[package]] name = "regex" -version = "1.10.2" +version = "1.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" +checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" dependencies = [ "aho-corasick", "memchr", @@ -1736,9 +1717,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.3" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" +checksum = "3b7fa1134405e2ec9353fd416b17f8dacd46c473d7d3fd1cf202706a14eb792a" dependencies = [ "aho-corasick", "memchr", @@ -1842,15 +1823,15 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.21" +version = "0.38.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b426b0506e5d50a7d8dafcf2e81471400deb602392c7dd110815afb4eaf02a3" +checksum = "322394588aaf33c24007e8bb3238ee3e4c5c09c084ab32bc73890b99ff326bca" dependencies = [ "bitflags 2.4.0", "errno", "libc", - "linux-raw-sys 0.4.10", - "windows-sys 0.48.0", + "linux-raw-sys 0.4.12", + "windows-sys 0.52.0", ] [[package]] @@ -1870,9 +1851,9 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] name = "self_cell" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e388332cd64eb80cd595a00941baf513caffae8dce9cfd0467fc9c66397dade6" +checksum = "58bf37232d3bb9a2c4e641ca2a11d83b5062066f88df7fed36c28772046d65ba" [[package]] name = "selinux" @@ -2006,9 +1987,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.11.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9" +checksum = "3b187f0231d56fe41bfb12034819dd2bf336422a5866de41bc3fec4b2e3883e8" [[package]] name = "smawk" @@ -2056,15 +2037,15 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.8.1" +version = "3.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5" +checksum = "01ce4141aa927a6d1bd34a041795abd0db1cccba5d5f24b009f694bdf3a1f3fa" dependencies = [ "cfg-if", "fastrand", - "redox_syscall 0.4.0", - "rustix 0.38.21", - "windows-sys 0.48.0", + "redox_syscall", + "rustix 0.38.30", + "windows-sys 0.52.0", ] [[package]] @@ -2083,7 +2064,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "21bebf2b7c9e0a515f6e0f8c51dc0f8e4696391e6f1ff30379559f8365fb0df7" dependencies = [ - "rustix 0.38.21", + "rustix 0.38.30", "windows-sys 0.48.0", ] @@ -2207,7 +2188,7 @@ checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" [[package]] name = "uu_arch" -version = "0.0.23" +version = "0.0.24" dependencies = [ "clap", "platform-info", @@ -2216,7 +2197,7 @@ dependencies = [ [[package]] name = "uu_base32" -version = "0.0.23" +version = "0.0.24" dependencies = [ "clap", "uucore", @@ -2224,7 +2205,7 @@ dependencies = [ [[package]] name = "uu_base64" -version = "0.0.23" +version = "0.0.24" dependencies = [ "uu_base32", "uucore", @@ -2232,7 +2213,7 @@ dependencies = [ [[package]] name = "uu_basename" -version = "0.0.23" +version = "0.0.24" dependencies = [ "clap", "uucore", @@ -2240,7 +2221,7 @@ dependencies = [ [[package]] name = "uu_basenc" -version = "0.0.23" +version = "0.0.24" dependencies = [ "clap", "uu_base32", @@ -2249,7 +2230,7 @@ dependencies = [ [[package]] name = "uu_cat" -version = "0.0.23" +version = "0.0.24" dependencies = [ "clap", "nix", @@ -2259,7 +2240,7 @@ dependencies = [ [[package]] name = "uu_chcon" -version = "0.0.23" +version = "0.0.24" dependencies = [ "clap", "fts-sys", @@ -2271,7 +2252,7 @@ dependencies = [ [[package]] name = "uu_chgrp" -version = "0.0.23" +version = "0.0.24" dependencies = [ "clap", "uucore", @@ -2279,7 +2260,7 @@ dependencies = [ [[package]] name = "uu_chmod" -version = "0.0.23" +version = "0.0.24" dependencies = [ "clap", "libc", @@ -2288,7 +2269,7 @@ dependencies = [ [[package]] name = "uu_chown" -version = "0.0.23" +version = "0.0.24" dependencies = [ "clap", "uucore", @@ -2296,7 +2277,7 @@ dependencies = [ [[package]] name = "uu_chroot" -version = "0.0.23" +version = "0.0.24" dependencies = [ "clap", "uucore", @@ -2304,7 +2285,7 @@ dependencies = [ [[package]] name = "uu_cksum" -version = "0.0.23" +version = "0.0.24" dependencies = [ "clap", "hex", @@ -2313,7 +2294,7 @@ dependencies = [ [[package]] name = "uu_comm" -version = "0.0.23" +version = "0.0.24" dependencies = [ "clap", "uucore", @@ -2321,7 +2302,7 @@ dependencies = [ [[package]] name = "uu_cp" -version = "0.0.23" +version = "0.0.24" dependencies = [ "clap", "exacl", @@ -2337,7 +2318,7 @@ dependencies = [ [[package]] name = "uu_csplit" -version = "0.0.23" +version = "0.0.24" dependencies = [ "clap", "regex", @@ -2347,7 +2328,7 @@ dependencies = [ [[package]] name = "uu_cut" -version = "0.0.23" +version = "0.0.24" dependencies = [ "bstr", "clap", @@ -2357,7 +2338,7 @@ dependencies = [ [[package]] name = "uu_date" -version = "0.0.23" +version = "0.0.24" dependencies = [ "chrono", "clap", @@ -2369,7 +2350,7 @@ dependencies = [ [[package]] name = "uu_dd" -version = "0.0.23" +version = "0.0.24" dependencies = [ "clap", "gcd", @@ -2381,7 +2362,7 @@ dependencies = [ [[package]] name = "uu_df" -version = "0.0.23" +version = "0.0.24" dependencies = [ "clap", "tempfile", @@ -2391,7 +2372,7 @@ dependencies = [ [[package]] name = "uu_dir" -version = "0.0.23" +version = "0.0.24" dependencies = [ "clap", "uu_ls", @@ -2400,7 +2381,7 @@ dependencies = [ [[package]] name = "uu_dircolors" -version = "0.0.23" +version = "0.0.24" dependencies = [ "clap", "uucore", @@ -2408,7 +2389,7 @@ dependencies = [ [[package]] name = "uu_dirname" -version = "0.0.23" +version = "0.0.24" dependencies = [ "clap", "uucore", @@ -2416,7 +2397,7 @@ dependencies = [ [[package]] name = "uu_du" -version = "0.0.23" +version = "0.0.24" dependencies = [ "chrono", "clap", @@ -2427,7 +2408,7 @@ dependencies = [ [[package]] name = "uu_echo" -version = "0.0.23" +version = "0.0.24" dependencies = [ "clap", "uucore", @@ -2435,7 +2416,7 @@ dependencies = [ [[package]] name = "uu_env" -version = "0.0.23" +version = "0.0.24" dependencies = [ "clap", "nix", @@ -2445,7 +2426,7 @@ dependencies = [ [[package]] name = "uu_expand" -version = "0.0.23" +version = "0.0.24" dependencies = [ "clap", "unicode-width", @@ -2454,7 +2435,7 @@ dependencies = [ [[package]] name = "uu_expr" -version = "0.0.23" +version = "0.0.24" dependencies = [ "clap", "num-bigint", @@ -2465,7 +2446,7 @@ dependencies = [ [[package]] name = "uu_factor" -version = "0.0.23" +version = "0.0.24" dependencies = [ "clap", "coz", @@ -2478,7 +2459,7 @@ dependencies = [ [[package]] name = "uu_false" -version = "0.0.23" +version = "0.0.24" dependencies = [ "clap", "uucore", @@ -2486,7 +2467,7 @@ dependencies = [ [[package]] name = "uu_fmt" -version = "0.0.23" +version = "0.0.24" dependencies = [ "clap", "unicode-width", @@ -2495,7 +2476,7 @@ dependencies = [ [[package]] name = "uu_fold" -version = "0.0.23" +version = "0.0.24" dependencies = [ "clap", "uucore", @@ -2503,7 +2484,7 @@ dependencies = [ [[package]] name = "uu_groups" -version = "0.0.23" +version = "0.0.24" dependencies = [ "clap", "uucore", @@ -2511,7 +2492,7 @@ dependencies = [ [[package]] name = "uu_hashsum" -version = "0.0.23" +version = "0.0.24" dependencies = [ "clap", "hex", @@ -2522,7 +2503,7 @@ dependencies = [ [[package]] name = "uu_head" -version = "0.0.23" +version = "0.0.24" dependencies = [ "clap", "memchr", @@ -2531,7 +2512,7 @@ dependencies = [ [[package]] name = "uu_hostid" -version = "0.0.23" +version = "0.0.24" dependencies = [ "clap", "libc", @@ -2540,7 +2521,7 @@ dependencies = [ [[package]] name = "uu_hostname" -version = "0.0.23" +version = "0.0.24" dependencies = [ "clap", "hostname", @@ -2550,7 +2531,7 @@ dependencies = [ [[package]] name = "uu_id" -version = "0.0.23" +version = "0.0.24" dependencies = [ "clap", "selinux", @@ -2559,7 +2540,7 @@ dependencies = [ [[package]] name = "uu_install" -version = "0.0.23" +version = "0.0.24" dependencies = [ "clap", "file_diff", @@ -2570,7 +2551,7 @@ dependencies = [ [[package]] name = "uu_join" -version = "0.0.23" +version = "0.0.24" dependencies = [ "clap", "memchr", @@ -2579,7 +2560,7 @@ dependencies = [ [[package]] name = "uu_kill" -version = "0.0.23" +version = "0.0.24" dependencies = [ "clap", "nix", @@ -2588,7 +2569,7 @@ dependencies = [ [[package]] name = "uu_link" -version = "0.0.23" +version = "0.0.24" dependencies = [ "clap", "uucore", @@ -2596,7 +2577,7 @@ dependencies = [ [[package]] name = "uu_ln" -version = "0.0.23" +version = "0.0.24" dependencies = [ "clap", "uucore", @@ -2604,7 +2585,7 @@ dependencies = [ [[package]] name = "uu_logname" -version = "0.0.23" +version = "0.0.24" dependencies = [ "clap", "libc", @@ -2613,11 +2594,12 @@ dependencies = [ [[package]] name = "uu_ls" -version = "0.0.23" +version = "0.0.24" dependencies = [ "chrono", "clap", "glob", + "hostname", "lscolors", "number_prefix", "once_cell", @@ -2630,7 +2612,7 @@ dependencies = [ [[package]] name = "uu_mkdir" -version = "0.0.23" +version = "0.0.24" dependencies = [ "clap", "uucore", @@ -2638,7 +2620,7 @@ dependencies = [ [[package]] name = "uu_mkfifo" -version = "0.0.23" +version = "0.0.24" dependencies = [ "clap", "libc", @@ -2647,7 +2629,7 @@ dependencies = [ [[package]] name = "uu_mknod" -version = "0.0.23" +version = "0.0.24" dependencies = [ "clap", "libc", @@ -2656,7 +2638,7 @@ dependencies = [ [[package]] name = "uu_mktemp" -version = "0.0.23" +version = "0.0.24" dependencies = [ "clap", "rand", @@ -2666,7 +2648,7 @@ dependencies = [ [[package]] name = "uu_more" -version = "0.0.23" +version = "0.0.24" dependencies = [ "clap", "crossterm", @@ -2678,7 +2660,7 @@ dependencies = [ [[package]] name = "uu_mv" -version = "0.0.23" +version = "0.0.24" dependencies = [ "clap", "fs_extra", @@ -2688,7 +2670,7 @@ dependencies = [ [[package]] name = "uu_nice" -version = "0.0.23" +version = "0.0.24" dependencies = [ "clap", "libc", @@ -2698,7 +2680,7 @@ dependencies = [ [[package]] name = "uu_nl" -version = "0.0.23" +version = "0.0.24" dependencies = [ "clap", "regex", @@ -2707,7 +2689,7 @@ dependencies = [ [[package]] name = "uu_nohup" -version = "0.0.23" +version = "0.0.24" dependencies = [ "clap", "libc", @@ -2716,7 +2698,7 @@ dependencies = [ [[package]] name = "uu_nproc" -version = "0.0.23" +version = "0.0.24" dependencies = [ "clap", "libc", @@ -2725,7 +2707,7 @@ dependencies = [ [[package]] name = "uu_numfmt" -version = "0.0.23" +version = "0.0.24" dependencies = [ "clap", "uucore", @@ -2733,7 +2715,7 @@ dependencies = [ [[package]] name = "uu_od" -version = "0.0.23" +version = "0.0.24" dependencies = [ "byteorder", "clap", @@ -2743,7 +2725,7 @@ dependencies = [ [[package]] name = "uu_paste" -version = "0.0.23" +version = "0.0.24" dependencies = [ "clap", "uucore", @@ -2751,7 +2733,7 @@ dependencies = [ [[package]] name = "uu_pathchk" -version = "0.0.23" +version = "0.0.24" dependencies = [ "clap", "libc", @@ -2760,7 +2742,7 @@ dependencies = [ [[package]] name = "uu_pinky" -version = "0.0.23" +version = "0.0.24" dependencies = [ "clap", "uucore", @@ -2768,7 +2750,7 @@ dependencies = [ [[package]] name = "uu_pr" -version = "0.0.23" +version = "0.0.24" dependencies = [ "chrono", "clap", @@ -2780,7 +2762,7 @@ dependencies = [ [[package]] name = "uu_printenv" -version = "0.0.23" +version = "0.0.24" dependencies = [ "clap", "uucore", @@ -2788,7 +2770,7 @@ dependencies = [ [[package]] name = "uu_printf" -version = "0.0.23" +version = "0.0.24" dependencies = [ "clap", "uucore", @@ -2796,7 +2778,7 @@ dependencies = [ [[package]] name = "uu_ptx" -version = "0.0.23" +version = "0.0.24" dependencies = [ "clap", "regex", @@ -2805,7 +2787,7 @@ dependencies = [ [[package]] name = "uu_pwd" -version = "0.0.23" +version = "0.0.24" dependencies = [ "clap", "uucore", @@ -2813,7 +2795,7 @@ dependencies = [ [[package]] name = "uu_readlink" -version = "0.0.23" +version = "0.0.24" dependencies = [ "clap", "uucore", @@ -2821,7 +2803,7 @@ dependencies = [ [[package]] name = "uu_realpath" -version = "0.0.23" +version = "0.0.24" dependencies = [ "clap", "uucore", @@ -2829,7 +2811,7 @@ dependencies = [ [[package]] name = "uu_rm" -version = "0.0.23" +version = "0.0.24" dependencies = [ "clap", "libc", @@ -2840,7 +2822,7 @@ dependencies = [ [[package]] name = "uu_rmdir" -version = "0.0.23" +version = "0.0.24" dependencies = [ "clap", "libc", @@ -2849,7 +2831,7 @@ dependencies = [ [[package]] name = "uu_runcon" -version = "0.0.23" +version = "0.0.24" dependencies = [ "clap", "libc", @@ -2860,7 +2842,7 @@ dependencies = [ [[package]] name = "uu_seq" -version = "0.0.23" +version = "0.0.24" dependencies = [ "bigdecimal", "clap", @@ -2871,7 +2853,7 @@ dependencies = [ [[package]] name = "uu_shred" -version = "0.0.23" +version = "0.0.24" dependencies = [ "clap", "libc", @@ -2881,7 +2863,7 @@ dependencies = [ [[package]] name = "uu_shuf" -version = "0.0.23" +version = "0.0.24" dependencies = [ "clap", "memchr", @@ -2892,7 +2874,7 @@ dependencies = [ [[package]] name = "uu_sleep" -version = "0.0.23" +version = "0.0.24" dependencies = [ "clap", "fundu", @@ -2901,7 +2883,7 @@ dependencies = [ [[package]] name = "uu_sort" -version = "0.0.23" +version = "0.0.24" dependencies = [ "binary-heap-plus", "clap", @@ -2920,7 +2902,7 @@ dependencies = [ [[package]] name = "uu_split" -version = "0.0.23" +version = "0.0.24" dependencies = [ "clap", "memchr", @@ -2929,7 +2911,7 @@ dependencies = [ [[package]] name = "uu_stat" -version = "0.0.23" +version = "0.0.24" dependencies = [ "clap", "uucore", @@ -2937,7 +2919,7 @@ dependencies = [ [[package]] name = "uu_stdbuf" -version = "0.0.23" +version = "0.0.24" dependencies = [ "clap", "tempfile", @@ -2947,17 +2929,16 @@ dependencies = [ [[package]] name = "uu_stdbuf_libstdbuf" -version = "0.0.23" +version = "0.0.24" dependencies = [ "cpp", "cpp_build", "libc", - "uucore", ] [[package]] name = "uu_stty" -version = "0.0.23" +version = "0.0.24" dependencies = [ "clap", "nix", @@ -2966,7 +2947,7 @@ dependencies = [ [[package]] name = "uu_sum" -version = "0.0.23" +version = "0.0.24" dependencies = [ "clap", "uucore", @@ -2974,7 +2955,7 @@ dependencies = [ [[package]] name = "uu_sync" -version = "0.0.23" +version = "0.0.24" dependencies = [ "clap", "libc", @@ -2985,7 +2966,7 @@ dependencies = [ [[package]] name = "uu_tac" -version = "0.0.23" +version = "0.0.24" dependencies = [ "clap", "memchr", @@ -2996,7 +2977,7 @@ dependencies = [ [[package]] name = "uu_tail" -version = "0.0.23" +version = "0.0.24" dependencies = [ "clap", "fundu", @@ -3012,7 +2993,7 @@ dependencies = [ [[package]] name = "uu_tee" -version = "0.0.23" +version = "0.0.24" dependencies = [ "clap", "libc", @@ -3021,17 +3002,17 @@ dependencies = [ [[package]] name = "uu_test" -version = "0.0.23" +version = "0.0.24" dependencies = [ "clap", "libc", - "redox_syscall 0.4.0", + "redox_syscall", "uucore", ] [[package]] name = "uu_timeout" -version = "0.0.23" +version = "0.0.24" dependencies = [ "clap", "libc", @@ -3041,7 +3022,7 @@ dependencies = [ [[package]] name = "uu_touch" -version = "0.0.23" +version = "0.0.24" dependencies = [ "chrono", "clap", @@ -3053,7 +3034,7 @@ dependencies = [ [[package]] name = "uu_tr" -version = "0.0.23" +version = "0.0.24" dependencies = [ "clap", "nom", @@ -3062,7 +3043,7 @@ dependencies = [ [[package]] name = "uu_true" -version = "0.0.23" +version = "0.0.24" dependencies = [ "clap", "uucore", @@ -3070,7 +3051,7 @@ dependencies = [ [[package]] name = "uu_truncate" -version = "0.0.23" +version = "0.0.24" dependencies = [ "clap", "uucore", @@ -3078,7 +3059,7 @@ dependencies = [ [[package]] name = "uu_tsort" -version = "0.0.23" +version = "0.0.24" dependencies = [ "clap", "uucore", @@ -3086,7 +3067,7 @@ dependencies = [ [[package]] name = "uu_tty" -version = "0.0.23" +version = "0.0.24" dependencies = [ "clap", "nix", @@ -3095,7 +3076,7 @@ dependencies = [ [[package]] name = "uu_uname" -version = "0.0.23" +version = "0.0.24" dependencies = [ "clap", "platform-info", @@ -3104,7 +3085,7 @@ dependencies = [ [[package]] name = "uu_unexpand" -version = "0.0.23" +version = "0.0.24" dependencies = [ "clap", "unicode-width", @@ -3113,7 +3094,7 @@ dependencies = [ [[package]] name = "uu_uniq" -version = "0.0.23" +version = "0.0.24" dependencies = [ "clap", "uucore", @@ -3121,7 +3102,7 @@ dependencies = [ [[package]] name = "uu_unlink" -version = "0.0.23" +version = "0.0.24" dependencies = [ "clap", "uucore", @@ -3129,7 +3110,7 @@ dependencies = [ [[package]] name = "uu_uptime" -version = "0.0.23" +version = "0.0.24" dependencies = [ "chrono", "clap", @@ -3138,7 +3119,7 @@ dependencies = [ [[package]] name = "uu_users" -version = "0.0.23" +version = "0.0.24" dependencies = [ "clap", "uucore", @@ -3146,7 +3127,7 @@ dependencies = [ [[package]] name = "uu_vdir" -version = "0.0.23" +version = "0.0.24" dependencies = [ "clap", "uu_ls", @@ -3155,7 +3136,7 @@ dependencies = [ [[package]] name = "uu_wc" -version = "0.0.23" +version = "0.0.24" dependencies = [ "bytecount", "clap", @@ -3168,7 +3149,7 @@ dependencies = [ [[package]] name = "uu_who" -version = "0.0.23" +version = "0.0.24" dependencies = [ "clap", "uucore", @@ -3176,7 +3157,7 @@ dependencies = [ [[package]] name = "uu_whoami" -version = "0.0.23" +version = "0.0.24" dependencies = [ "clap", "libc", @@ -3186,7 +3167,7 @@ dependencies = [ [[package]] name = "uu_yes" -version = "0.0.23" +version = "0.0.24" dependencies = [ "clap", "itertools", @@ -3196,7 +3177,7 @@ dependencies = [ [[package]] name = "uucore" -version = "0.0.23" +version = "0.0.24" dependencies = [ "blake2b_simd", "blake3", @@ -3227,12 +3208,13 @@ dependencies = [ "wild", "winapi-util", "windows-sys 0.48.0", + "xattr", "z85", ] [[package]] name = "uucore_procs" -version = "0.0.23" +version = "0.0.24" dependencies = [ "proc-macro2", "quote", @@ -3241,7 +3223,7 @@ dependencies = [ [[package]] name = "uuhelp_parser" -version = "0.0.23" +version = "0.0.24" [[package]] name = "uuid" @@ -3403,6 +3385,15 @@ dependencies = [ "windows-targets 0.48.0", ] +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.0", +] + [[package]] name = "windows-targets" version = "0.42.2" @@ -3433,6 +3424,21 @@ dependencies = [ "windows_x86_64_msvc 0.48.0", ] +[[package]] +name = "windows-targets" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +dependencies = [ + "windows_aarch64_gnullvm 0.52.0", + "windows_aarch64_msvc 0.52.0", + "windows_i686_gnu 0.52.0", + "windows_i686_msvc 0.52.0", + "windows_x86_64_gnu 0.52.0", + "windows_x86_64_gnullvm 0.52.0", + "windows_x86_64_msvc 0.52.0", +] + [[package]] name = "windows_aarch64_gnullvm" version = "0.42.2" @@ -3445,6 +3451,12 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" + [[package]] name = "windows_aarch64_msvc" version = "0.42.2" @@ -3457,6 +3469,12 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" + [[package]] name = "windows_i686_gnu" version = "0.42.2" @@ -3469,6 +3487,12 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" +[[package]] +name = "windows_i686_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" + [[package]] name = "windows_i686_msvc" version = "0.42.2" @@ -3481,6 +3505,12 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" +[[package]] +name = "windows_i686_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" + [[package]] name = "windows_x86_64_gnu" version = "0.42.2" @@ -3493,6 +3523,12 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" + [[package]] name = "windows_x86_64_gnullvm" version = "0.42.2" @@ -3505,6 +3541,12 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" + [[package]] name = "windows_x86_64_msvc" version = "0.42.2" @@ -3517,13 +3559,21 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" + [[package]] name = "xattr" -version = "1.0.1" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4686009f71ff3e5c4dbcf1a282d0a44db3f021ba69350cd42086b3e5f1c6985" +checksum = "8da84f1a25939b27f6820d92aed108f83ff920fdf11a7b19366c27c4cda81d4f" dependencies = [ "libc", + "linux-raw-sys 0.4.12", + "rustix 0.38.30", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index f313e2b03e4..757cacf3492 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,7 @@ [package] name = "coreutils" -version = "0.0.23" +version = "0.0.24" authors = ["uutils developers"] license = "MIT" description = "coreutils ~ GNU coreutils (updated); implemented as universal (cross-platform) utils, written in Rust" @@ -240,6 +240,7 @@ feat_os_unix_redox = [ "feat_common_core", # "chmod", + "stat", "uname", ] # "feat_os_windows_legacy" == slightly restricted set of utilities which can be built/run on early windows platforms (eg, "WinXP") @@ -259,10 +260,10 @@ test = ["uu_test"] [workspace.dependencies] bigdecimal = "0.4" binary-heap-plus = "0.5.0" -bstr = "1.8" +bstr = "1.9" bytecount = "0.6.7" byteorder = "1.5.0" -chrono = { version = "^0.4.31", default-features = false, features = [ +chrono = { version = "^0.4.32", default-features = false, features = [ "std", "alloc", "clock", @@ -284,11 +285,12 @@ fundu = "2.0.0" gcd = "2.3" glob = "0.3.1" half = "2.3" +hostname = "0.3" indicatif = "0.17" -itertools = "0.11.0" -libc = "0.2.150" -lscolors = { version = "0.15.0", default-features = false, features = [ - "nu-ansi-term", +itertools = "0.12.0" +libc = "0.2.152" +lscolors = { version = "0.16.0", default-features = false, features = [ + "gnu_legacy", ] } memchr = "2" memmap2 = "0.9" @@ -298,7 +300,7 @@ notify = { version = "=6.0.1", features = ["macos_kqueue"] } num-bigint = "0.4.4" num-traits = "0.2.17" number_prefix = "0.4" -once_cell = "1.18.0" +once_cell = "1.19.0" onig = { version = "~6.4", default-features = false } parse_datetime = "0.5.0" phf = "0.11.2" @@ -309,15 +311,15 @@ rand = { version = "0.8", features = ["small_rng"] } rand_core = "0.6" rayon = "1.8" redox_syscall = "0.4" -regex = "1.10.2" +regex = "1.10.3" rstest = "0.18.2" rust-ini = "0.19.0" same-file = "1.0.6" -self_cell = "1.0.2" +self_cell = "1.0.3" selinux = "0.4" signal-hook = "0.3.17" -smallvec = { version = "1.11", features = ["union"] } -tempfile = "3.8.1" +smallvec = { version = "1.13", features = ["union"] } +tempfile = "3.9.0" uutils_term_grid = "0.3" terminal_size = "0.3.0" textwrap = { version = "0.16.0", features = ["terminal_size"] } @@ -329,8 +331,8 @@ utf-8 = "0.7.6" 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"] } +xattr = "1.3.1" +zip = { version = "0.6.6", default-features = false, features = ["deflate"] } hex = "0.4.3" md-5 = "0.10.6" @@ -361,109 +363,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.23", package = "uu_test", path = "src/uu/test" } +uu_test = { optional = true, version = "0.0.24", package = "uu_test", path = "src/uu/test" } # -arch = { optional = true, version = "0.0.23", package = "uu_arch", path = "src/uu/arch" } -base32 = { optional = true, version = "0.0.23", package = "uu_base32", path = "src/uu/base32" } -base64 = { optional = true, version = "0.0.23", package = "uu_base64", path = "src/uu/base64" } -basename = { optional = true, version = "0.0.23", package = "uu_basename", path = "src/uu/basename" } -basenc = { optional = true, version = "0.0.23", package = "uu_basenc", path = "src/uu/basenc" } -cat = { optional = true, version = "0.0.23", package = "uu_cat", path = "src/uu/cat" } -chcon = { optional = true, version = "0.0.23", package = "uu_chcon", path = "src/uu/chcon" } -chgrp = { optional = true, version = "0.0.23", package = "uu_chgrp", path = "src/uu/chgrp" } -chmod = { optional = true, version = "0.0.23", package = "uu_chmod", path = "src/uu/chmod" } -chown = { optional = true, version = "0.0.23", package = "uu_chown", path = "src/uu/chown" } -chroot = { optional = true, version = "0.0.23", package = "uu_chroot", path = "src/uu/chroot" } -cksum = { optional = true, version = "0.0.23", package = "uu_cksum", path = "src/uu/cksum" } -comm = { optional = true, version = "0.0.23", package = "uu_comm", path = "src/uu/comm" } -cp = { optional = true, version = "0.0.23", package = "uu_cp", path = "src/uu/cp" } -csplit = { optional = true, version = "0.0.23", package = "uu_csplit", path = "src/uu/csplit" } -cut = { optional = true, version = "0.0.23", package = "uu_cut", path = "src/uu/cut" } -date = { optional = true, version = "0.0.23", package = "uu_date", path = "src/uu/date" } -dd = { optional = true, version = "0.0.23", package = "uu_dd", path = "src/uu/dd" } -df = { optional = true, version = "0.0.23", package = "uu_df", path = "src/uu/df" } -dir = { optional = true, version = "0.0.23", package = "uu_dir", path = "src/uu/dir" } -dircolors = { optional = true, version = "0.0.23", package = "uu_dircolors", path = "src/uu/dircolors" } -dirname = { optional = true, version = "0.0.23", package = "uu_dirname", path = "src/uu/dirname" } -du = { optional = true, version = "0.0.23", package = "uu_du", path = "src/uu/du" } -echo = { optional = true, version = "0.0.23", package = "uu_echo", path = "src/uu/echo" } -env = { optional = true, version = "0.0.23", package = "uu_env", path = "src/uu/env" } -expand = { optional = true, version = "0.0.23", package = "uu_expand", path = "src/uu/expand" } -expr = { optional = true, version = "0.0.23", package = "uu_expr", path = "src/uu/expr" } -factor = { optional = true, version = "0.0.23", package = "uu_factor", path = "src/uu/factor" } -false = { optional = true, version = "0.0.23", package = "uu_false", path = "src/uu/false" } -fmt = { optional = true, version = "0.0.23", package = "uu_fmt", path = "src/uu/fmt" } -fold = { optional = true, version = "0.0.23", package = "uu_fold", path = "src/uu/fold" } -groups = { optional = true, version = "0.0.23", package = "uu_groups", path = "src/uu/groups" } -hashsum = { optional = true, version = "0.0.23", package = "uu_hashsum", path = "src/uu/hashsum" } -head = { optional = true, version = "0.0.23", package = "uu_head", path = "src/uu/head" } -hostid = { optional = true, version = "0.0.23", package = "uu_hostid", path = "src/uu/hostid" } -hostname = { optional = true, version = "0.0.23", package = "uu_hostname", path = "src/uu/hostname" } -id = { optional = true, version = "0.0.23", package = "uu_id", path = "src/uu/id" } -install = { optional = true, version = "0.0.23", package = "uu_install", path = "src/uu/install" } -join = { optional = true, version = "0.0.23", package = "uu_join", path = "src/uu/join" } -kill = { optional = true, version = "0.0.23", package = "uu_kill", path = "src/uu/kill" } -link = { optional = true, version = "0.0.23", package = "uu_link", path = "src/uu/link" } -ln = { optional = true, version = "0.0.23", package = "uu_ln", path = "src/uu/ln" } -ls = { optional = true, version = "0.0.23", package = "uu_ls", path = "src/uu/ls" } -logname = { optional = true, version = "0.0.23", package = "uu_logname", path = "src/uu/logname" } -mkdir = { optional = true, version = "0.0.23", package = "uu_mkdir", path = "src/uu/mkdir" } -mkfifo = { optional = true, version = "0.0.23", package = "uu_mkfifo", path = "src/uu/mkfifo" } -mknod = { optional = true, version = "0.0.23", package = "uu_mknod", path = "src/uu/mknod" } -mktemp = { optional = true, version = "0.0.23", package = "uu_mktemp", path = "src/uu/mktemp" } -more = { optional = true, version = "0.0.23", package = "uu_more", path = "src/uu/more" } -mv = { optional = true, version = "0.0.23", package = "uu_mv", path = "src/uu/mv" } -nice = { optional = true, version = "0.0.23", package = "uu_nice", path = "src/uu/nice" } -nl = { optional = true, version = "0.0.23", package = "uu_nl", path = "src/uu/nl" } -nohup = { optional = true, version = "0.0.23", package = "uu_nohup", path = "src/uu/nohup" } -nproc = { optional = true, version = "0.0.23", package = "uu_nproc", path = "src/uu/nproc" } -numfmt = { optional = true, version = "0.0.23", package = "uu_numfmt", path = "src/uu/numfmt" } -od = { optional = true, version = "0.0.23", package = "uu_od", path = "src/uu/od" } -paste = { optional = true, version = "0.0.23", package = "uu_paste", path = "src/uu/paste" } -pathchk = { optional = true, version = "0.0.23", package = "uu_pathchk", path = "src/uu/pathchk" } -pinky = { optional = true, version = "0.0.23", package = "uu_pinky", path = "src/uu/pinky" } -pr = { optional = true, version = "0.0.23", package = "uu_pr", path = "src/uu/pr" } -printenv = { optional = true, version = "0.0.23", package = "uu_printenv", path = "src/uu/printenv" } -printf = { optional = true, version = "0.0.23", package = "uu_printf", path = "src/uu/printf" } -ptx = { optional = true, version = "0.0.23", package = "uu_ptx", path = "src/uu/ptx" } -pwd = { optional = true, version = "0.0.23", package = "uu_pwd", path = "src/uu/pwd" } -readlink = { optional = true, version = "0.0.23", package = "uu_readlink", path = "src/uu/readlink" } -realpath = { optional = true, version = "0.0.23", package = "uu_realpath", path = "src/uu/realpath" } -rm = { optional = true, version = "0.0.23", package = "uu_rm", path = "src/uu/rm" } -rmdir = { optional = true, version = "0.0.23", package = "uu_rmdir", path = "src/uu/rmdir" } -runcon = { optional = true, version = "0.0.23", package = "uu_runcon", path = "src/uu/runcon" } -seq = { optional = true, version = "0.0.23", package = "uu_seq", path = "src/uu/seq" } -shred = { optional = true, version = "0.0.23", package = "uu_shred", path = "src/uu/shred" } -shuf = { optional = true, version = "0.0.23", package = "uu_shuf", path = "src/uu/shuf" } -sleep = { optional = true, version = "0.0.23", package = "uu_sleep", path = "src/uu/sleep" } -sort = { optional = true, version = "0.0.23", package = "uu_sort", path = "src/uu/sort" } -split = { optional = true, version = "0.0.23", package = "uu_split", path = "src/uu/split" } -stat = { optional = true, version = "0.0.23", package = "uu_stat", path = "src/uu/stat" } -stdbuf = { optional = true, version = "0.0.23", package = "uu_stdbuf", path = "src/uu/stdbuf" } -stty = { optional = true, version = "0.0.23", package = "uu_stty", path = "src/uu/stty" } -sum = { optional = true, version = "0.0.23", package = "uu_sum", path = "src/uu/sum" } -sync = { optional = true, version = "0.0.23", package = "uu_sync", path = "src/uu/sync" } -tac = { optional = true, version = "0.0.23", package = "uu_tac", path = "src/uu/tac" } -tail = { optional = true, version = "0.0.23", package = "uu_tail", path = "src/uu/tail" } -tee = { optional = true, version = "0.0.23", package = "uu_tee", path = "src/uu/tee" } -timeout = { optional = true, version = "0.0.23", package = "uu_timeout", path = "src/uu/timeout" } -touch = { optional = true, version = "0.0.23", package = "uu_touch", path = "src/uu/touch" } -tr = { optional = true, version = "0.0.23", package = "uu_tr", path = "src/uu/tr" } -true = { optional = true, version = "0.0.23", package = "uu_true", path = "src/uu/true" } -truncate = { optional = true, version = "0.0.23", package = "uu_truncate", path = "src/uu/truncate" } -tsort = { optional = true, version = "0.0.23", package = "uu_tsort", path = "src/uu/tsort" } -tty = { optional = true, version = "0.0.23", package = "uu_tty", path = "src/uu/tty" } -uname = { optional = true, version = "0.0.23", package = "uu_uname", path = "src/uu/uname" } -unexpand = { optional = true, version = "0.0.23", package = "uu_unexpand", path = "src/uu/unexpand" } -uniq = { optional = true, version = "0.0.23", package = "uu_uniq", path = "src/uu/uniq" } -unlink = { optional = true, version = "0.0.23", package = "uu_unlink", path = "src/uu/unlink" } -uptime = { optional = true, version = "0.0.23", package = "uu_uptime", path = "src/uu/uptime" } -users = { optional = true, version = "0.0.23", package = "uu_users", path = "src/uu/users" } -vdir = { optional = true, version = "0.0.23", package = "uu_vdir", path = "src/uu/vdir" } -wc = { optional = true, version = "0.0.23", package = "uu_wc", path = "src/uu/wc" } -who = { optional = true, version = "0.0.23", package = "uu_who", path = "src/uu/who" } -whoami = { optional = true, version = "0.0.23", package = "uu_whoami", path = "src/uu/whoami" } -yes = { optional = true, version = "0.0.23", package = "uu_yes", path = "src/uu/yes" } +arch = { optional = true, version = "0.0.24", package = "uu_arch", path = "src/uu/arch" } +base32 = { optional = true, version = "0.0.24", package = "uu_base32", path = "src/uu/base32" } +base64 = { optional = true, version = "0.0.24", package = "uu_base64", path = "src/uu/base64" } +basename = { optional = true, version = "0.0.24", package = "uu_basename", path = "src/uu/basename" } +basenc = { optional = true, version = "0.0.24", package = "uu_basenc", path = "src/uu/basenc" } +cat = { optional = true, version = "0.0.24", package = "uu_cat", path = "src/uu/cat" } +chcon = { optional = true, version = "0.0.24", package = "uu_chcon", path = "src/uu/chcon" } +chgrp = { optional = true, version = "0.0.24", package = "uu_chgrp", path = "src/uu/chgrp" } +chmod = { optional = true, version = "0.0.24", package = "uu_chmod", path = "src/uu/chmod" } +chown = { optional = true, version = "0.0.24", package = "uu_chown", path = "src/uu/chown" } +chroot = { optional = true, version = "0.0.24", package = "uu_chroot", path = "src/uu/chroot" } +cksum = { optional = true, version = "0.0.24", package = "uu_cksum", path = "src/uu/cksum" } +comm = { optional = true, version = "0.0.24", package = "uu_comm", path = "src/uu/comm" } +cp = { optional = true, version = "0.0.24", package = "uu_cp", path = "src/uu/cp" } +csplit = { optional = true, version = "0.0.24", package = "uu_csplit", path = "src/uu/csplit" } +cut = { optional = true, version = "0.0.24", package = "uu_cut", path = "src/uu/cut" } +date = { optional = true, version = "0.0.24", package = "uu_date", path = "src/uu/date" } +dd = { optional = true, version = "0.0.24", package = "uu_dd", path = "src/uu/dd" } +df = { optional = true, version = "0.0.24", package = "uu_df", path = "src/uu/df" } +dir = { optional = true, version = "0.0.24", package = "uu_dir", path = "src/uu/dir" } +dircolors = { optional = true, version = "0.0.24", package = "uu_dircolors", path = "src/uu/dircolors" } +dirname = { optional = true, version = "0.0.24", package = "uu_dirname", path = "src/uu/dirname" } +du = { optional = true, version = "0.0.24", package = "uu_du", path = "src/uu/du" } +echo = { optional = true, version = "0.0.24", package = "uu_echo", path = "src/uu/echo" } +env = { optional = true, version = "0.0.24", package = "uu_env", path = "src/uu/env" } +expand = { optional = true, version = "0.0.24", package = "uu_expand", path = "src/uu/expand" } +expr = { optional = true, version = "0.0.24", package = "uu_expr", path = "src/uu/expr" } +factor = { optional = true, version = "0.0.24", package = "uu_factor", path = "src/uu/factor" } +false = { optional = true, version = "0.0.24", package = "uu_false", path = "src/uu/false" } +fmt = { optional = true, version = "0.0.24", package = "uu_fmt", path = "src/uu/fmt" } +fold = { optional = true, version = "0.0.24", package = "uu_fold", path = "src/uu/fold" } +groups = { optional = true, version = "0.0.24", package = "uu_groups", path = "src/uu/groups" } +hashsum = { optional = true, version = "0.0.24", package = "uu_hashsum", path = "src/uu/hashsum" } +head = { optional = true, version = "0.0.24", package = "uu_head", path = "src/uu/head" } +hostid = { optional = true, version = "0.0.24", package = "uu_hostid", path = "src/uu/hostid" } +hostname = { optional = true, version = "0.0.24", package = "uu_hostname", path = "src/uu/hostname" } +id = { optional = true, version = "0.0.24", package = "uu_id", path = "src/uu/id" } +install = { optional = true, version = "0.0.24", package = "uu_install", path = "src/uu/install" } +join = { optional = true, version = "0.0.24", package = "uu_join", path = "src/uu/join" } +kill = { optional = true, version = "0.0.24", package = "uu_kill", path = "src/uu/kill" } +link = { optional = true, version = "0.0.24", package = "uu_link", path = "src/uu/link" } +ln = { optional = true, version = "0.0.24", package = "uu_ln", path = "src/uu/ln" } +ls = { optional = true, version = "0.0.24", package = "uu_ls", path = "src/uu/ls" } +logname = { optional = true, version = "0.0.24", package = "uu_logname", path = "src/uu/logname" } +mkdir = { optional = true, version = "0.0.24", package = "uu_mkdir", path = "src/uu/mkdir" } +mkfifo = { optional = true, version = "0.0.24", package = "uu_mkfifo", path = "src/uu/mkfifo" } +mknod = { optional = true, version = "0.0.24", package = "uu_mknod", path = "src/uu/mknod" } +mktemp = { optional = true, version = "0.0.24", package = "uu_mktemp", path = "src/uu/mktemp" } +more = { optional = true, version = "0.0.24", package = "uu_more", path = "src/uu/more" } +mv = { optional = true, version = "0.0.24", package = "uu_mv", path = "src/uu/mv" } +nice = { optional = true, version = "0.0.24", package = "uu_nice", path = "src/uu/nice" } +nl = { optional = true, version = "0.0.24", package = "uu_nl", path = "src/uu/nl" } +nohup = { optional = true, version = "0.0.24", package = "uu_nohup", path = "src/uu/nohup" } +nproc = { optional = true, version = "0.0.24", package = "uu_nproc", path = "src/uu/nproc" } +numfmt = { optional = true, version = "0.0.24", package = "uu_numfmt", path = "src/uu/numfmt" } +od = { optional = true, version = "0.0.24", package = "uu_od", path = "src/uu/od" } +paste = { optional = true, version = "0.0.24", package = "uu_paste", path = "src/uu/paste" } +pathchk = { optional = true, version = "0.0.24", package = "uu_pathchk", path = "src/uu/pathchk" } +pinky = { optional = true, version = "0.0.24", package = "uu_pinky", path = "src/uu/pinky" } +pr = { optional = true, version = "0.0.24", package = "uu_pr", path = "src/uu/pr" } +printenv = { optional = true, version = "0.0.24", package = "uu_printenv", path = "src/uu/printenv" } +printf = { optional = true, version = "0.0.24", package = "uu_printf", path = "src/uu/printf" } +ptx = { optional = true, version = "0.0.24", package = "uu_ptx", path = "src/uu/ptx" } +pwd = { optional = true, version = "0.0.24", package = "uu_pwd", path = "src/uu/pwd" } +readlink = { optional = true, version = "0.0.24", package = "uu_readlink", path = "src/uu/readlink" } +realpath = { optional = true, version = "0.0.24", package = "uu_realpath", path = "src/uu/realpath" } +rm = { optional = true, version = "0.0.24", package = "uu_rm", path = "src/uu/rm" } +rmdir = { optional = true, version = "0.0.24", package = "uu_rmdir", path = "src/uu/rmdir" } +runcon = { optional = true, version = "0.0.24", package = "uu_runcon", path = "src/uu/runcon" } +seq = { optional = true, version = "0.0.24", package = "uu_seq", path = "src/uu/seq" } +shred = { optional = true, version = "0.0.24", package = "uu_shred", path = "src/uu/shred" } +shuf = { optional = true, version = "0.0.24", package = "uu_shuf", path = "src/uu/shuf" } +sleep = { optional = true, version = "0.0.24", package = "uu_sleep", path = "src/uu/sleep" } +sort = { optional = true, version = "0.0.24", package = "uu_sort", path = "src/uu/sort" } +split = { optional = true, version = "0.0.24", package = "uu_split", path = "src/uu/split" } +stat = { optional = true, version = "0.0.24", package = "uu_stat", path = "src/uu/stat" } +stdbuf = { optional = true, version = "0.0.24", package = "uu_stdbuf", path = "src/uu/stdbuf" } +stty = { optional = true, version = "0.0.24", package = "uu_stty", path = "src/uu/stty" } +sum = { optional = true, version = "0.0.24", package = "uu_sum", path = "src/uu/sum" } +sync = { optional = true, version = "0.0.24", package = "uu_sync", path = "src/uu/sync" } +tac = { optional = true, version = "0.0.24", package = "uu_tac", path = "src/uu/tac" } +tail = { optional = true, version = "0.0.24", package = "uu_tail", path = "src/uu/tail" } +tee = { optional = true, version = "0.0.24", package = "uu_tee", path = "src/uu/tee" } +timeout = { optional = true, version = "0.0.24", package = "uu_timeout", path = "src/uu/timeout" } +touch = { optional = true, version = "0.0.24", package = "uu_touch", path = "src/uu/touch" } +tr = { optional = true, version = "0.0.24", package = "uu_tr", path = "src/uu/tr" } +true = { optional = true, version = "0.0.24", package = "uu_true", path = "src/uu/true" } +truncate = { optional = true, version = "0.0.24", package = "uu_truncate", path = "src/uu/truncate" } +tsort = { optional = true, version = "0.0.24", package = "uu_tsort", path = "src/uu/tsort" } +tty = { optional = true, version = "0.0.24", package = "uu_tty", path = "src/uu/tty" } +uname = { optional = true, version = "0.0.24", package = "uu_uname", path = "src/uu/uname" } +unexpand = { optional = true, version = "0.0.24", package = "uu_unexpand", path = "src/uu/unexpand" } +uniq = { optional = true, version = "0.0.24", package = "uu_uniq", path = "src/uu/uniq" } +unlink = { optional = true, version = "0.0.24", package = "uu_unlink", path = "src/uu/unlink" } +uptime = { optional = true, version = "0.0.24", package = "uu_uptime", path = "src/uu/uptime" } +users = { optional = true, version = "0.0.24", package = "uu_users", path = "src/uu/users" } +vdir = { optional = true, version = "0.0.24", package = "uu_vdir", path = "src/uu/vdir" } +wc = { optional = true, version = "0.0.24", package = "uu_wc", path = "src/uu/wc" } +who = { optional = true, version = "0.0.24", package = "uu_who", path = "src/uu/who" } +whoami = { optional = true, version = "0.0.24", package = "uu_whoami", path = "src/uu/whoami" } +yes = { optional = true, version = "0.0.24", 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" } @@ -498,6 +500,7 @@ rlimit = "0.10.1" [target.'cfg(unix)'.dev-dependencies] nix = { workspace = true, features = ["process", "signal", "user"] } rand_pcg = "0.3" +xattr = { workspace = true } [build-dependencies] phf_codegen = { workspace = true } diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index 67b201e9cc3..6f1de3b5476 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -1,4 +1,4 @@ - + # Setting up your local development environment @@ -226,8 +226,8 @@ 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 +# Build uutils with release optimizations +bash util/build-gnu.sh --release-build bash util/run-gnu-test.sh # To run a single test: bash util/run-gnu-test.sh tests/touch/not-owner.sh # for example @@ -241,6 +241,12 @@ DEBUG=1 bash util/run-gnu-test.sh tests/misc/sm3sum.pl Note that GNU test suite relies on individual utilities (not the multicall binary). +On FreeBSD, you need to install packages for GNU coreutils and sed (used in shell scripts instead of system commands): + +```shell +pkg install coreutils gsed +``` + ## Code coverage report Code coverage report can be generated using [grcov](https://github.com/mozilla/grcov). diff --git a/LICENSE b/LICENSE index 49fdbd4cf5f..21bd44404e3 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) Jordi Boggiano and many others +Copyright (c) uutils developers Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/README.md b/README.md index 4f341638b3f..c8ca0d8a316 100644 --- a/README.md +++ b/README.md @@ -305,7 +305,7 @@ 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/coreutils/book/test_coverage.html). -See for the main meta bugs +See for the main meta bugs (many are missing). ![Evolution over time](https://github.com/uutils/coreutils-tracking/blob/main/gnu-results.png?raw=true) diff --git a/deny.toml b/deny.toml index 03301ad7cd4..d7c04ad2d5b 100644 --- a/deny.toml +++ b/deny.toml @@ -62,10 +62,14 @@ skip = [ { name = "linux-raw-sys", version = "0.3.8" }, # terminal_size { name = "rustix", version = "0.37.26" }, - # various crates + # notify { name = "windows-sys", version = "0.45.0" }, + # various crates + { name = "windows-sys", version = "0.48.0" }, # windows-sys { name = "windows-targets", version = "0.42.2" }, + # windows-sys + { name = "windows-targets", version = "0.48.0" }, # windows-targets { name = "windows_aarch64_gnullvm", version = "0.42.2" }, # windows-targets @@ -80,12 +84,24 @@ skip = [ { name = "windows_x86_64_gnullvm", version = "0.42.2" }, # windows-targets { name = "windows_x86_64_msvc", version = "0.42.2" }, + # windows-targets + { name = "windows_aarch64_gnullvm", version = "0.48.0" }, + # windows-targets + { name = "windows_aarch64_msvc", version = "0.48.0" }, + # windows-targets + { name = "windows_i686_gnu", version = "0.48.0" }, + # windows-targets + { name = "windows_i686_msvc", version = "0.48.0" }, + # windows-targets + { name = "windows_x86_64_gnu", version = "0.48.0" }, + # windows-targets + { name = "windows_x86_64_gnullvm", version = "0.48.0" }, + # windows-targets + { name = "windows_x86_64_msvc", version = "0.48.0" }, # 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" }, ] diff --git a/docs/src/installation.md b/docs/src/installation.md index da124ead977..dc631d240ca 100644 --- a/docs/src/installation.md +++ b/docs/src/installation.md @@ -1,4 +1,4 @@ - + # Installation @@ -6,13 +6,13 @@ This is a list of uutils packages in various distributions and package managers. Note that these are packaged by third-parties and the packages might contain patches. -You can also [build uutils from source](/build.md). +You can also [build uutils from source](build.md). ## Cargo -[![crates.io package](https://repology.org/badge/version-for-repo/crates_io/uutils-coreutils.svg)](https://repology.org/project/uutils-coreutils/versions) +[![crates.io package](https://repology.org/badge/version-for-repo/crates_io/uutils-coreutils.svg)](https://crates.io/crates/coreutils) ```shell # Linux @@ -53,8 +53,6 @@ apt install rust-coreutils export PATH=/usr/lib/cargo/bin/coreutils:$PATH ``` -> **Note**: Only available from Bookworm (Debian 12) - ### Gentoo [![Gentoo package](https://repology.org/badge/version-for-repo/gentoo/uutils-coreutils.svg)](https://packages.gentoo.org/packages/sys-apps/uutils-coreutils) @@ -65,9 +63,9 @@ emerge -pv sys-apps/uutils-coreutils ### Manjaro -![Manjaro Stable package](https://repology.org/badge/version-for-repo/manjaro_stable/uutils-coreutils.svg) -[![Manjaro Testing package](https://repology.org/badge/version-for-repo/manjaro_testing/uutils-coreutils.svg)](https://repology.org/project/uutils-coreutils/versions) -[![Manjaro Unstable package](https://repology.org/badge/version-for-repo/manjaro_unstable/uutils-coreutils.svg)](https://repology.org/project/uutils-coreutils/versions) +[![Manjaro Stable package](https://repology.org/badge/version-for-repo/manjaro_stable/uutils-coreutils.svg)](https://packages.manjaro.org/?query=uutils-coreutils) +[![Manjaro Testing package](https://repology.org/badge/version-for-repo/manjaro_testing/uutils-coreutils.svg)](https://packages.manjaro.org/?query=uutils-coreutils) +[![Manjaro Unstable package](https://repology.org/badge/version-for-repo/manjaro_unstable/uutils-coreutils.svg)](https://packages.manjaro.org/?query=uutils-coreutils) ```shell pacman -S uutils-coreutils @@ -77,7 +75,7 @@ pamac install uutils-coreutils ### NixOS -[![nixpkgs unstable package](https://repology.org/badge/version-for-repo/nix_unstable/uutils-coreutils.svg)](https://repology.org/project/uutils-coreutils/versions) +[![nixpkgs unstable package](https://repology.org/badge/version-for-repo/nix_unstable/uutils-coreutils.svg)](https://search.nixos.org/packages?query=uutils-coreutils) ```shell nix-env -iA nixos.uutils-coreutils @@ -101,8 +99,6 @@ apt install rust-coreutils export PATH=/usr/lib/cargo/bin/coreutils:$PATH ``` -> **Note**: Only available from Kinetic (Ubuntu 22.10) - ## MacOS ### Homebrew @@ -123,7 +119,7 @@ port install coreutils-uutils ## FreeBSD -[![FreeBSD port](https://repology.org/badge/version-for-repo/freebsd/uutils-coreutils.svg)](https://repology.org/project/uutils-coreutils/versions) +[![FreeBSD port](https://repology.org/badge/version-for-repo/freebsd/rust-coreutils.svg)](https://repology.org/project/rust-coreutils/versions) ```sh pkg install rust-coreutils @@ -131,9 +127,15 @@ pkg install rust-coreutils ## Windows +### Winget + +```shell +winget install uutils.coreutils +``` + ### Scoop -[![Scoop package](https://repology.org/badge/version-for-repo/scoop/uutils-coreutils.svg)](https://scoop.sh/#/apps?q=uutils-coreutils&s=0&d=1&o=true) +[Scoop package](https://scoop.sh/#/apps?q=uutils-coreutils&s=0&d=1&o=true) ```shell scoop install uutils-coreutils @@ -146,7 +148,7 @@ scoop install uutils-coreutils [Conda package](https://anaconda.org/conda-forge/uutils-coreutils) ``` -conda install -c conda-forge uutils-coreutils +conda install -c conda-forge uutils-coreutils ``` ## Non-standard packages diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index 549f9a6b762..dfb62aba5e1 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -10,13 +10,21 @@ cargo-fuzz = true [dependencies] libfuzzer-sys = "0.4" libc = "0.2" +tempfile = "3" rand = { version = "0.8", features = ["small_rng"] } +similar = "2" uucore = { path = "../src/uucore/" } uu_date = { path = "../src/uu/date/" } uu_test = { path = "../src/uu/test/" } uu_expr = { path = "../src/uu/expr/" } - +uu_printf = { path = "../src/uu/printf/" } +uu_echo = { path = "../src/uu/echo/" } +uu_seq = { path = "../src/uu/seq/" } +uu_sort = { path = "../src/uu/sort/" } +uu_wc = { path = "../src/uu/wc/" } +uu_cut = { path = "../src/uu/cut/" } +uu_split = { path = "../src/uu/split/" } # Prevent this from interfering with workspaces [workspace] @@ -28,6 +36,48 @@ path = "fuzz_targets/fuzz_date.rs" test = false doc = false +[[bin]] +name = "fuzz_printf" +path = "fuzz_targets/fuzz_printf.rs" +test = false +doc = false + +[[bin]] +name = "fuzz_echo" +path = "fuzz_targets/fuzz_echo.rs" +test = false +doc = false + +[[bin]] +name = "fuzz_seq" +path = "fuzz_targets/fuzz_seq.rs" +test = false +doc = false + +[[bin]] +name = "fuzz_sort" +path = "fuzz_targets/fuzz_sort.rs" +test = false +doc = false + +[[bin]] +name = "fuzz_split" +path = "fuzz_targets/fuzz_split.rs" +test = false +doc = false + +[[bin]] +name = "fuzz_cut" +path = "fuzz_targets/fuzz_cut.rs" +test = false +doc = false + +[[bin]] +name = "fuzz_wc" +path = "fuzz_targets/fuzz_wc.rs" +test = false +doc = false + [[bin]] name = "fuzz_expr" path = "fuzz_targets/fuzz_expr.rs" diff --git a/fuzz/fuzz_targets/fuzz_common.rs b/fuzz/fuzz_targets/fuzz_common.rs index 2adbb3dd677..cf56268d75a 100644 --- a/fuzz/fuzz_targets/fuzz_common.rs +++ b/fuzz/fuzz_targets/fuzz_common.rs @@ -3,16 +3,18 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. +use libc::STDIN_FILENO; use libc::{close, dup, dup2, pipe, STDERR_FILENO, STDOUT_FILENO}; use rand::prelude::SliceRandom; use rand::Rng; +use similar::TextDiff; use std::ffi::OsString; -use std::io; -use std::io::Write; -use std::os::fd::RawFd; -use std::process::Command; +use std::io::{Seek, SeekFrom, Write}; +use std::os::fd::{AsRawFd, RawFd}; +use std::process::{Command, Stdio}; use std::sync::atomic::Ordering; use std::sync::{atomic::AtomicBool, Once}; +use std::{io, thread}; /// Represents the result of running a command, including its standard output, /// standard error, and exit code. @@ -49,20 +51,25 @@ pub fn is_gnu_cmd(cmd_path: &str) -> Result<(), std::io::Error> { } } -pub fn generate_and_run_uumain(args: &[OsString], uumain_function: F) -> CommandResult +pub fn generate_and_run_uumain( + args: &[OsString], + uumain_function: F, + pipe_input: Option<&str>, +) -> CommandResult where - F: FnOnce(std::vec::IntoIter) -> i32, + F: FnOnce(std::vec::IntoIter) -> i32 + Send + 'static, { // Duplicate the stdout and stderr file descriptors let original_stdout_fd = unsafe { dup(STDOUT_FILENO) }; let original_stderr_fd = unsafe { dup(STDERR_FILENO) }; if original_stdout_fd == -1 || original_stderr_fd == -1 { return CommandResult { - stdout: "Failed to duplicate STDOUT_FILENO or STDERR_FILENO".to_string(), - stderr: "".to_string(), + stdout: "".to_string(), + stderr: "Failed to duplicate STDOUT_FILENO or STDERR_FILENO".to_string(), exit_code: -1, }; } + println!("Running test {:?}", &args[0..]); let mut pipe_stdout_fds = [-1; 2]; let mut pipe_stderr_fds = [-1; 2]; @@ -72,8 +79,8 @@ where || unsafe { pipe(pipe_stderr_fds.as_mut_ptr()) } == -1 { return CommandResult { - stdout: "Failed to create pipes".to_string(), - stderr: "".to_string(), + stdout: "".to_string(), + stderr: "Failed to create pipes".to_string(), exit_code: -1, }; } @@ -89,47 +96,85 @@ where close(pipe_stderr_fds[1]); } return CommandResult { - stdout: "Failed to redirect STDOUT_FILENO or STDERR_FILENO".to_string(), - stderr: "".to_string(), + stdout: "".to_string(), + stderr: "Failed to redirect STDOUT_FILENO or STDERR_FILENO".to_string(), exit_code: -1, }; } - let uumain_exit_status = uumain_function(args.to_owned().into_iter()); + let original_stdin_fd = if let Some(input_str) = pipe_input { + // we have pipe input + let mut input_file = tempfile::tempfile().unwrap(); + write!(input_file, "{}", input_str).unwrap(); + input_file.seek(SeekFrom::Start(0)).unwrap(); + + // Redirect stdin to read from the in-memory file + let original_stdin_fd = unsafe { dup(STDIN_FILENO) }; + if original_stdin_fd == -1 || unsafe { dup2(input_file.as_raw_fd(), STDIN_FILENO) } == -1 { + return CommandResult { + stdout: "".to_string(), + stderr: "Failed to set up stdin redirection".to_string(), + exit_code: -1, + }; + } + Some(original_stdin_fd) + } else { + None + }; - io::stdout().flush().unwrap(); - io::stderr().flush().unwrap(); + let (uumain_exit_status, captured_stdout, captured_stderr) = thread::scope(|s| { + let out = s.spawn(|| read_from_fd(pipe_stdout_fds[0])); + let err = s.spawn(|| read_from_fd(pipe_stderr_fds[0])); + let status = uumain_function(args.to_owned().into_iter()); + // Reset the exit code global variable in case we run another test after this one + // See https://github.com/uutils/coreutils/issues/5777 + uucore::error::set_exit_code(0); + io::stdout().flush().unwrap(); + io::stderr().flush().unwrap(); + unsafe { + close(pipe_stdout_fds[1]); + close(pipe_stderr_fds[1]); + close(STDOUT_FILENO); + close(STDERR_FILENO); + } + (status, out.join().unwrap(), err.join().unwrap()) + }); // Restore the original stdout and stderr if unsafe { dup2(original_stdout_fd, STDOUT_FILENO) } == -1 || unsafe { dup2(original_stderr_fd, STDERR_FILENO) } == -1 { return CommandResult { - stdout: "Failed to restore the original STDOUT_FILENO or STDERR_FILENO".to_string(), - stderr: "".to_string(), + stdout: "".to_string(), + stderr: "Failed to restore the original STDOUT_FILENO or STDERR_FILENO".to_string(), exit_code: -1, }; } unsafe { close(original_stdout_fd); close(original_stderr_fd); - - close(pipe_stdout_fds[1]); - close(pipe_stderr_fds[1]); } - let captured_stdout = read_from_fd(pipe_stdout_fds[0]).trim().to_string(); - let captured_stderr = read_from_fd(pipe_stderr_fds[0]).to_string(); - let captured_stderr = captured_stderr - .split_once(':') - .map(|x| x.1) - .unwrap_or("") - .trim() - .to_string(); + // Restore the original stdin if it was modified + if let Some(fd) = original_stdin_fd { + if unsafe { dup2(fd, STDIN_FILENO) } == -1 { + return CommandResult { + stdout: "".to_string(), + stderr: "Failed to restore the original STDIN".to_string(), + exit_code: -1, + }; + } + unsafe { close(fd) }; + } CommandResult { stdout: captured_stdout, - stderr: captured_stderr, + stderr: captured_stderr + .split_once(':') + .map(|x| x.1) + .unwrap_or("") + .trim() + .to_string(), exit_code: uumain_exit_status, } } @@ -165,6 +210,7 @@ pub fn run_gnu_cmd( cmd_path: &str, args: &[OsString], check_gnu: bool, + pipe_input: Option<&str>, ) -> Result { if check_gnu { match is_gnu_cmd(cmd_path) { @@ -185,18 +231,43 @@ pub fn run_gnu_cmd( command.arg(arg); } - let output = match command.output() { - Ok(output) => output, - Err(e) => { - return Err(CommandResult { - stdout: String::new(), - stderr: e.to_string(), - exit_code: -1, - }); + let output = if let Some(input_str) = pipe_input { + // We have an pipe input + command + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()); + + let mut child = command.spawn().expect("Failed to execute command"); + let child_stdin = child.stdin.as_mut().unwrap(); + child_stdin + .write_all(input_str.as_bytes()) + .expect("Failed to write to stdin"); + + match child.wait_with_output() { + Ok(output) => output, + Err(e) => { + return Err(CommandResult { + stdout: String::new(), + stderr: e.to_string(), + exit_code: -1, + }); + } + } + } else { + // Just run with args + match command.output() { + Ok(output) => output, + Err(e) => { + return Err(CommandResult { + stdout: String::new(), + stderr: e.to_string(), + exit_code: -1, + }); + } } }; let exit_code = output.status.code().unwrap_or(-1); - // Here we get stdout and stderr as Strings let stdout = String::from_utf8_lossy(&output.stdout).to_string(); let stderr = String::from_utf8_lossy(&output.stderr).to_string(); @@ -222,41 +293,51 @@ pub fn run_gnu_cmd( } } +/// Compare results from two different implementations of a command. +/// +/// # Arguments +/// * `test_type` - The command. +/// * `input` - The input provided to the command. +/// * `rust_result` - The result of running the command with the Rust implementation. +/// * `gnu_result` - The result of running the command with the GNU implementation. +/// * `fail_on_stderr_diff` - Whether to fail the test if there is a difference in stderr output. pub fn compare_result( test_type: &str, input: &str, - rust_stdout: &str, - gnu_stdout: &str, - rust_stderr: &str, - gnu_stderr: &str, - rust_exit_code: i32, - gnu_exit_code: i32, + pipe_input: Option<&str>, + rust_result: &CommandResult, + gnu_result: &CommandResult, fail_on_stderr_diff: bool, ) { println!("Test Type: {}", test_type); println!("Input: {}", input); + if let Some(pipe) = pipe_input { + println!("Pipe: {}", pipe); + } let mut discrepancies = Vec::new(); let mut should_panic = false; - if rust_stdout.trim() != gnu_stdout.trim() { + if rust_result.stdout.trim() != gnu_result.stdout.trim() { discrepancies.push("stdout differs"); - println!("Rust stdout: {}", rust_stdout); - println!("GNU stdout: {}", gnu_stdout); + println!("Rust stdout: {}", rust_result.stdout); + println!("GNU stdout: {}", gnu_result.stdout); + print_diff(&rust_result.stdout, &gnu_result.stdout); should_panic = true; } - if rust_stderr.trim() != gnu_stderr.trim() { + if rust_result.stderr.trim() != gnu_result.stderr.trim() { discrepancies.push("stderr differs"); - println!("Rust stderr: {}", rust_stderr); - println!("GNU stderr: {}", gnu_stderr); + println!("Rust stderr: {}", rust_result.stderr); + println!("GNU stderr: {}", gnu_result.stderr); + print_diff(&rust_result.stderr, &gnu_result.stderr); if fail_on_stderr_diff { should_panic = true; } } - if rust_exit_code != gnu_exit_code { + if rust_result.exit_code != gnu_result.exit_code { discrepancies.push("exit code differs"); - println!("Rust exit code: {}", rust_exit_code); - println!("GNU exit code: {}", gnu_exit_code); + println!("Rust exit code: {}", rust_result.exit_code); + println!("GNU exit code: {}", gnu_result.exit_code); should_panic = true; } @@ -275,6 +356,16 @@ pub fn compare_result( } } +/// When we have different outputs, print the diff +fn print_diff(rust_output: &str, gnu_output: &str) { + println!("Diff="); + let diff = TextDiff::from_lines(rust_output, gnu_output); + for change in diff.iter_all_changes() { + print!("{}{}", change.tag(), change); + } + println!(); +} + pub fn generate_random_string(max_length: usize) -> String { let mut rng = rand::thread_rng(); let valid_utf8: Vec = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" diff --git a/fuzz/fuzz_targets/fuzz_cut.rs b/fuzz/fuzz_targets/fuzz_cut.rs new file mode 100644 index 00000000000..fa5f8fcc472 --- /dev/null +++ b/fuzz/fuzz_targets/fuzz_cut.rs @@ -0,0 +1,87 @@ +// 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_cut::uumain; + +use rand::Rng; +use std::ffi::OsString; + +mod fuzz_common; +use crate::fuzz_common::{ + compare_result, generate_and_run_uumain, generate_random_string, run_gnu_cmd, CommandResult, +}; +static CMD_PATH: &str = "cut"; + +fn generate_cut_args() -> String { + let mut rng = rand::thread_rng(); + let arg_count = rng.gen_range(1..=6); + let mut args = Vec::new(); + + for _ in 0..arg_count { + if rng.gen_bool(0.1) { + args.push(generate_random_string(rng.gen_range(1..=20))); + } else { + match rng.gen_range(0..=4) { + 0 => args.push(String::from("-b") + &rng.gen_range(1..=10).to_string()), + 1 => args.push(String::from("-c") + &rng.gen_range(1..=10).to_string()), + 2 => args.push(String::from("-d,") + &generate_random_string(1)), // Using a comma as a default delimiter + 3 => args.push(String::from("-f") + &rng.gen_range(1..=5).to_string()), + _ => (), + } + } + } + + args.join(" ") +} + +fn generate_delimited_data(count: usize) -> String { + let mut rng = rand::thread_rng(); + let mut lines = Vec::new(); + + for _ in 0..count { + let fields = (0..rng.gen_range(1..=5)) + .map(|_| generate_random_string(rng.gen_range(1..=10))) + .collect::>() + .join(","); + lines.push(fields); + } + + lines.join("\n") +} + +fuzz_target!(|_data: &[u8]| { + let cut_args = generate_cut_args(); + let mut args = vec![OsString::from("cut")]; + args.extend(cut_args.split_whitespace().map(OsString::from)); + + let input_lines = generate_delimited_data(10); + + let rust_result = generate_and_run_uumain(&args, uumain, Some(&input_lines)); + let gnu_result = match run_gnu_cmd(CMD_PATH, &args[1..], false, Some(&input_lines)) { + Ok(result) => result, + Err(error_result) => { + eprintln!("Failed to run GNU command:"); + eprintln!("Stderr: {}", error_result.stderr); + eprintln!("Exit Code: {}", error_result.exit_code); + CommandResult { + stdout: String::new(), + stderr: error_result.stderr, + exit_code: error_result.exit_code, + } + } + }; + + compare_result( + "cut", + &format!("{:?}", &args[1..]), + Some(&input_lines), + &rust_result, + &gnu_result, + false, // Set to true if you want to fail on stderr diff + ); +}); diff --git a/fuzz/fuzz_targets/fuzz_echo.rs b/fuzz/fuzz_targets/fuzz_echo.rs new file mode 100644 index 00000000000..3f15b257e6e --- /dev/null +++ b/fuzz/fuzz_targets/fuzz_echo.rs @@ -0,0 +1,86 @@ +#![no_main] +use libfuzzer_sys::fuzz_target; +use uu_echo::uumain; + +use rand::prelude::SliceRandom; +use rand::Rng; +use std::ffi::OsString; + +mod fuzz_common; +use crate::fuzz_common::CommandResult; +use crate::fuzz_common::{ + compare_result, generate_and_run_uumain, generate_random_string, run_gnu_cmd, +}; + +static CMD_PATH: &str = "echo"; + +fn generate_echo() -> String { + let mut rng = rand::thread_rng(); + let mut echo_str = String::new(); + + // Randomly decide whether to include options + let include_n = rng.gen_bool(0.1); // 10% chance + let include_e = rng.gen_bool(0.1); // 10% chance + let include_E = rng.gen_bool(0.1); // 10% chance + + if include_n { + echo_str.push_str("-n "); + } + if include_e { + echo_str.push_str("-e "); + } + if include_E { + echo_str.push_str("-E "); + } + + // Add a random string + echo_str.push_str(&generate_random_string(rng.gen_range(1..=10))); + + // Include escape sequences if -e is enabled + if include_e { + // Add a 10% chance of including an escape sequence + if rng.gen_bool(0.1) { + echo_str.push_str(&generate_escape_sequence(&mut rng)); + } + } + + echo_str +} + +fn generate_escape_sequence(rng: &mut impl Rng) -> String { + let escape_sequences = [ + "\\\\", "\\a", "\\b", "\\c", "\\e", "\\f", "\\n", "\\r", "\\t", "\\v", "\\0NNN", "\\xHH", + ]; + // \0NNN and \xHH need more work + escape_sequences.choose(rng).unwrap().to_string() +} + +fuzz_target!(|_data: &[u8]| { + let echo_input = generate_echo(); + let mut args = vec![OsString::from("echo")]; + args.extend(echo_input.split_whitespace().map(OsString::from)); + let rust_result = generate_and_run_uumain(&args, uumain, None); + + let gnu_result = match run_gnu_cmd(CMD_PATH, &args[1..], false, None) { + Ok(result) => result, + Err(error_result) => { + eprintln!("Failed to run GNU command:"); + eprintln!("Stderr: {}", error_result.stderr); + eprintln!("Exit Code: {}", error_result.exit_code); + CommandResult { + stdout: String::new(), + stderr: error_result.stderr, + exit_code: error_result.exit_code, + } + } + }; + + compare_result( + "echo", + &format!("{:?}", &args[1..]), + None, + &rust_result, + &gnu_result, + true, + ); +}); diff --git a/fuzz/fuzz_targets/fuzz_expr.rs b/fuzz/fuzz_targets/fuzz_expr.rs index 8d1848545ff..8bc18fae47e 100644 --- a/fuzz/fuzz_targets/fuzz_expr.rs +++ b/fuzz/fuzz_targets/fuzz_expr.rs @@ -21,7 +21,9 @@ static CMD_PATH: &str = "expr"; fn generate_expr(max_depth: u32) -> String { let mut rng = rand::thread_rng(); - let ops = ["+", "-", "*", "/", "%", "<", ">", "=", "&", "|"]; + let ops = [ + "+", "-", "*", "/", "%", "<", ">", "=", "&", "|", "!=", "<=", ">=", ":", "index", "length", "substr", + ]; let mut expr = String::new(); let mut depth = 0; @@ -67,9 +69,9 @@ fuzz_target!(|_data: &[u8]| { // because uutils expr doesn't support localization yet // TODO remove once uutils expr supports localization env::set_var("LC_COLLATE", "C"); - let rust_result = generate_and_run_uumain(&args, uumain); + let rust_result = generate_and_run_uumain(&args, uumain, None); - let gnu_result = match run_gnu_cmd(CMD_PATH, &args[1..], false) { + let gnu_result = match run_gnu_cmd(CMD_PATH, &args[1..], false, None) { Ok(result) => result, Err(error_result) => { eprintln!("Failed to run GNU command:"); @@ -86,12 +88,9 @@ fuzz_target!(|_data: &[u8]| { compare_result( "expr", &format!("{:?}", &args[1..]), - &rust_result.stdout, - &gnu_result.stdout, - &rust_result.stderr, - &gnu_result.stderr, - rust_result.exit_code, - gnu_result.exit_code, + None, + &rust_result, + &gnu_result, false, // Set to true if you want to fail on stderr diff ); }); diff --git a/fuzz/fuzz_targets/fuzz_printf.rs b/fuzz/fuzz_targets/fuzz_printf.rs new file mode 100644 index 00000000000..72fac540b17 --- /dev/null +++ b/fuzz/fuzz_targets/fuzz_printf.rs @@ -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. +// spell-checker:ignore parens + +#![no_main] +use libfuzzer_sys::fuzz_target; +use uu_printf::uumain; + +use rand::seq::SliceRandom; +use rand::Rng; +use std::ffi::OsString; + +mod fuzz_common; +use crate::fuzz_common::CommandResult; +use crate::fuzz_common::{ + compare_result, generate_and_run_uumain, generate_random_string, run_gnu_cmd, +}; + +static CMD_PATH: &str = "printf"; + +fn generate_escape_sequence(rng: &mut impl Rng) -> String { + let escape_sequences = [ + "\\\"", + "\\\\", + "\\a", + "\\b", + "\\c", + "\\e", + "\\f", + "\\n", + "\\r", + "\\t", + "\\v", + "\\000", + "\\x00", + "\\u0000", + "\\U00000000", + "%%", + ]; + escape_sequences.choose(rng).unwrap().to_string() +} + +fn generate_printf() -> String { + let mut rng = rand::thread_rng(); + let format_specifiers = ["%s", "%d", "%f", "%x", "%o", "%c", "%b", "%q"]; + let mut printf_str = String::new(); + // Add a 20% chance of generating an invalid format specifier + if rng.gen_bool(0.2) { + printf_str.push_str("%z"); // Invalid format specifier + } else { + let specifier = *format_specifiers.choose(&mut rng).unwrap(); + printf_str.push_str(specifier); + + // Add a 20% chance of introducing complex format strings + if rng.gen_bool(0.2) { + printf_str.push_str(&format!(" %{}", rng.gen_range(1..=1000))); + } else { + // Add a random string or number after the specifier + if specifier == "%s" { + printf_str.push_str(&format!( + " {}", + generate_random_string(rng.gen_range(1..=10)) + )); + } else { + printf_str.push_str(&format!(" {}", rng.gen_range(1..=1000))); + } + } + } + + // Add a 10% chance of including an escape sequence + if rng.gen_bool(0.1) { + printf_str.push_str(&generate_escape_sequence(&mut rng)); + } + printf_str +} + +fuzz_target!(|_data: &[u8]| { + let printf_input = generate_printf(); + let mut args = vec![OsString::from("printf")]; + args.extend(printf_input.split_whitespace().map(OsString::from)); + let rust_result = generate_and_run_uumain(&args, uumain, None); + + let gnu_result = match run_gnu_cmd(CMD_PATH, &args[1..], false, None) { + Ok(result) => result, + Err(error_result) => { + eprintln!("Failed to run GNU command:"); + eprintln!("Stderr: {}", error_result.stderr); + eprintln!("Exit Code: {}", error_result.exit_code); + CommandResult { + stdout: String::new(), + stderr: error_result.stderr, + exit_code: error_result.exit_code, + } + } + }; + + compare_result( + "printf", + &format!("{:?}", &args[1..]), + None, + &rust_result, + &gnu_result, + false, // Set to true if you want to fail on stderr diff + ); +}); diff --git a/fuzz/fuzz_targets/fuzz_seq.rs b/fuzz/fuzz_targets/fuzz_seq.rs new file mode 100644 index 00000000000..7bb4f8af956 --- /dev/null +++ b/fuzz/fuzz_targets/fuzz_seq.rs @@ -0,0 +1,75 @@ +// 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_seq::uumain; + +use rand::Rng; +use std::ffi::OsString; + +mod fuzz_common; +use crate::fuzz_common::CommandResult; +use crate::fuzz_common::{ + compare_result, generate_and_run_uumain, generate_random_string, run_gnu_cmd, +}; +static CMD_PATH: &str = "seq"; + +fn generate_seq() -> String { + let mut rng = rand::thread_rng(); + + // Generate 1 to 3 numbers for seq arguments + let arg_count = rng.gen_range(1..=3); + let mut args = Vec::new(); + + for _ in 0..arg_count { + if rng.gen_ratio(1, 100) { + // 1% chance to add a random string + args.push(generate_random_string(rng.gen_range(1..=10))); + } else { + // 99% chance to add a numeric value + match rng.gen_range(0..=3) { + 0 => args.push(rng.gen_range(-10000..=10000).to_string()), // Large or small integers + 1 => args.push(rng.gen_range(-100.0..100.0).to_string()), // Floating-point numbers + 2 => args.push(rng.gen_range(-100..0).to_string()), // Negative integers + _ => args.push(rng.gen_range(1..=100).to_string()), // Regular integers + } + } + } + + args.join(" ") +} + +fuzz_target!(|_data: &[u8]| { + let seq = generate_seq(); + let mut args = vec![OsString::from("seq")]; + args.extend(seq.split_whitespace().map(OsString::from)); + + let rust_result = generate_and_run_uumain(&args, uumain, None); + + let gnu_result = match run_gnu_cmd(CMD_PATH, &args[1..], false, None) { + Ok(result) => result, + Err(error_result) => { + eprintln!("Failed to run GNU command:"); + eprintln!("Stderr: {}", error_result.stderr); + eprintln!("Exit Code: {}", error_result.exit_code); + CommandResult { + stdout: String::new(), + stderr: error_result.stderr, + exit_code: error_result.exit_code, + } + } + }; + + compare_result( + "seq", + &format!("{:?}", &args[1..]), + None, + &rust_result, + &gnu_result, + false, // Set to true if you want to fail on stderr diff + ); +}); diff --git a/fuzz/fuzz_targets/fuzz_sort.rs b/fuzz/fuzz_targets/fuzz_sort.rs new file mode 100644 index 00000000000..3520bbaefed --- /dev/null +++ b/fuzz/fuzz_targets/fuzz_sort.rs @@ -0,0 +1,86 @@ +// 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_sort::uumain; + +use rand::Rng; +use std::env; +use std::ffi::OsString; + +mod fuzz_common; +use crate::fuzz_common::CommandResult; +use crate::fuzz_common::{ + compare_result, generate_and_run_uumain, generate_random_string, run_gnu_cmd, +}; +static CMD_PATH: &str = "sort"; + +fn generate_sort_args() -> String { + let mut rng = rand::thread_rng(); + + let arg_count = rng.gen_range(1..=5); + let mut args = Vec::new(); + + for _ in 0..arg_count { + match rng.gen_range(0..=4) { + 0 => args.push(String::from("-r")), // Reverse the result of comparisons + 1 => args.push(String::from("-n")), // Compare according to string numerical value + 2 => args.push(String::from("-f")), // Fold lower case to upper case characters + 3 => args.push(generate_random_string(rng.gen_range(1..=10))), // Random string (to simulate file names) + _ => args.push(String::from("-k") + &rng.gen_range(1..=5).to_string()), // Sort via a specified field + } + } + + args.join(" ") +} + +fn generate_random_lines(count: usize) -> String { + let mut rng = rand::thread_rng(); + let mut lines = Vec::new(); + + for _ in 0..count { + lines.push(generate_random_string(rng.gen_range(1..=20))); + } + + lines.join("\n") +} + +fuzz_target!(|_data: &[u8]| { + let sort_args = generate_sort_args(); + let mut args = vec![OsString::from("sort")]; + args.extend(sort_args.split_whitespace().map(OsString::from)); + + // Generate random lines to sort + let input_lines = generate_random_lines(10); + + let rust_result = generate_and_run_uumain(&args, uumain, Some(&input_lines)); + + // TODO remove once uutils sort supports localization + env::set_var("LC_COLLATE", "C"); + let gnu_result = match run_gnu_cmd(CMD_PATH, &args[1..], false, Some(&input_lines)) { + Ok(result) => result, + Err(error_result) => { + eprintln!("Failed to run GNU command:"); + eprintln!("Stderr: {}", error_result.stderr); + eprintln!("Exit Code: {}", error_result.exit_code); + CommandResult { + stdout: String::new(), + stderr: error_result.stderr, + exit_code: error_result.exit_code, + } + } + }; + + compare_result( + "sort", + &format!("{:?}", &args[1..]), + None, + &rust_result, + &gnu_result, + false, // Set to true if you want to fail on stderr diff + ); +}); diff --git a/fuzz/fuzz_targets/fuzz_split.rs b/fuzz/fuzz_targets/fuzz_split.rs new file mode 100644 index 00000000000..876c8dd21d4 --- /dev/null +++ b/fuzz/fuzz_targets/fuzz_split.rs @@ -0,0 +1,105 @@ +// 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_split::uumain; + +use rand::Rng; +use std::ffi::OsString; + +mod fuzz_common; +use crate::fuzz_common::{ + compare_result, generate_and_run_uumain, generate_random_string, run_gnu_cmd, CommandResult, +}; +static CMD_PATH: &str = "split"; + +fn generate_split_args() -> String { + let mut rng = rand::thread_rng(); + let mut args = Vec::new(); + + match rng.gen_range(0..=9) { + 0 => { + args.push(String::from("-a")); // Suffix length + args.push(rng.gen_range(1..=8).to_string()); + } + 1 => { + args.push(String::from("--additional-suffix")); + args.push(generate_random_string(5)); // Random suffix + } + 2 => { + args.push(String::from("-b")); // Bytes per output file + args.push(rng.gen_range(1..=1024).to_string() + "K"); + } + 3 => { + args.push(String::from("-C")); // Line bytes + args.push(rng.gen_range(1..=1024).to_string()); + } + 4 => args.push(String::from("-d")), // Use numeric suffixes + 5 => args.push(String::from("-x")), // Use hex suffixes + 6 => { + args.push(String::from("-l")); // Number of lines per output file + args.push(rng.gen_range(1..=1000).to_string()); + } + 7 => { + args.push(String::from("--filter")); + args.push(String::from("cat > /dev/null")); // Example filter command + } + 8 => { + args.push(String::from("-t")); // Separator + args.push(String::from("\n")); // Newline as separator + } + 9 => args.push(String::from("--verbose")), // Verbose + _ => (), + } + + args.join(" ") +} + +// Function to generate a random string of lines +fn generate_random_lines(count: usize) -> String { + let mut rng = rand::thread_rng(); + let mut lines = Vec::new(); + + for _ in 0..count { + lines.push(generate_random_string(rng.gen_range(1..=20))); + } + + lines.join("\n") +} + +fuzz_target!(|_data: &[u8]| { + let split_args = generate_split_args(); + let mut args = vec![OsString::from("split")]; + args.extend(split_args.split_whitespace().map(OsString::from)); + + let input_lines = generate_random_lines(10); + + let rust_result = generate_and_run_uumain(&args, uumain, Some(&input_lines)); + + let gnu_result = match run_gnu_cmd(CMD_PATH, &args[1..], false, Some(&input_lines)) { + Ok(result) => result, + Err(error_result) => { + eprintln!("Failed to run GNU command:"); + eprintln!("Stderr: {}", error_result.stderr); + eprintln!("Exit Code: {}", error_result.exit_code); + CommandResult { + stdout: String::new(), + stderr: error_result.stderr, + exit_code: error_result.exit_code, + } + } + }; + + compare_result( + "split", + &format!("{:?}", &args[1..]), + None, + &rust_result, + &gnu_result, + false, // Set to true if you want to fail on stderr diff + ); +}); diff --git a/fuzz/fuzz_targets/fuzz_test.rs b/fuzz/fuzz_targets/fuzz_test.rs index 38cd691b389..bed7ca77088 100644 --- a/fuzz/fuzz_targets/fuzz_test.rs +++ b/fuzz/fuzz_targets/fuzz_test.rs @@ -184,9 +184,9 @@ fuzz_target!(|_data: &[u8]| { args.push(OsString::from(generate_test_arg())); } - let rust_result = generate_and_run_uumain(&args, uumain); + let rust_result = generate_and_run_uumain(&args, uumain, None); - let gnu_result = match run_gnu_cmd(CMD_PATH, &args[1..], false) { + let gnu_result = match run_gnu_cmd(CMD_PATH, &args[1..], false, None) { Ok(result) => result, Err(error_result) => { eprintln!("Failed to run GNU command:"); @@ -203,12 +203,9 @@ fuzz_target!(|_data: &[u8]| { compare_result( "test", &format!("{:?}", &args[1..]), - &rust_result.stdout, - &gnu_result.stdout, - &rust_result.stderr, - &gnu_result.stderr, - rust_result.exit_code, - gnu_result.exit_code, + None, + &rust_result, + &gnu_result, false, // Set to true if you want to fail on stderr diff ); }); diff --git a/fuzz/fuzz_targets/fuzz_wc.rs b/fuzz/fuzz_targets/fuzz_wc.rs new file mode 100644 index 00000000000..dc85bbc3541 --- /dev/null +++ b/fuzz/fuzz_targets/fuzz_wc.rs @@ -0,0 +1,99 @@ +// 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_wc::uumain; + +use rand::Rng; +use std::ffi::OsString; + +mod fuzz_common; +use crate::fuzz_common::{ + compare_result, generate_and_run_uumain, generate_random_string, run_gnu_cmd, CommandResult, +}; +static CMD_PATH: &str = "wc"; + +fn generate_wc_args() -> String { + let mut rng = rand::thread_rng(); + let arg_count = rng.gen_range(1..=6); + let mut args = Vec::new(); + + for _ in 0..arg_count { + // Introduce a chance to add invalid arguments + if rng.gen_bool(0.1) { + args.push(generate_random_string(rng.gen_range(1..=20))); + } else { + match rng.gen_range(0..=5) { + 0 => args.push(String::from("-c")), + 1 => args.push(String::from("-m")), + 2 => args.push(String::from("-l")), + 3 => args.push(String::from("-L")), + 4 => args.push(String::from("-w")), + // TODO + 5 => { + args.push(String::from("--files0-from")); + if rng.gen_bool(0.5) { + args.push(generate_random_string(50)); // Longer invalid file name + } else { + args.push(generate_random_string(5)); + } + } + _ => (), + } + } + } + + args.join(" ") +} + +// Function to generate a random string of lines, including invalid ones +fn generate_random_lines(count: usize) -> String { + let mut rng = rand::thread_rng(); + let mut lines = Vec::new(); + + for _ in 0..count { + if rng.gen_bool(0.1) { + lines.push(generate_random_string(rng.gen_range(1000..=5000))); // Very long invalid line + } else { + lines.push(generate_random_string(rng.gen_range(1..=20))); + } + } + + lines.join("\n") +} + +fuzz_target!(|_data: &[u8]| { + let wc_args = generate_wc_args(); + let mut args = vec![OsString::from("wc")]; + args.extend(wc_args.split_whitespace().map(OsString::from)); + + let input_lines = generate_random_lines(10); + + let rust_result = generate_and_run_uumain(&args, uumain, Some(&input_lines)); + let gnu_result = match run_gnu_cmd(CMD_PATH, &args[1..], false, Some(&input_lines)) { + Ok(result) => result, + Err(error_result) => { + eprintln!("Failed to run GNU command:"); + eprintln!("Stderr: {}", error_result.stderr); + eprintln!("Exit Code: {}", error_result.exit_code); + CommandResult { + stdout: String::new(), + stderr: error_result.stderr, + exit_code: error_result.exit_code, + } + } + }; + + compare_result( + "wc", + &format!("{:?}", &args[1..]), + Some(&input_lines), + &rust_result, + &gnu_result, + false, // Set to true if you want to fail on stderr diff + ); +}); diff --git a/src/uu/arch/Cargo.toml b/src/uu/arch/Cargo.toml index edb15f846c2..b4d07b26c59 100644 --- a/src/uu/arch/Cargo.toml +++ b/src/uu/arch/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_arch" -version = "0.0.23" +version = "0.0.24" authors = ["uutils developers"] license = "MIT" description = "arch ~ (uutils) display machine architecture" diff --git a/src/uu/base32/Cargo.toml b/src/uu/base32/Cargo.toml index 71fbe325fe8..1c27e14cf3f 100644 --- a/src/uu/base32/Cargo.toml +++ b/src/uu/base32/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_base32" -version = "0.0.23" +version = "0.0.24" authors = ["uutils developers"] license = "MIT" description = "base32 ~ (uutils) decode/encode input (base32-encoding)" diff --git a/src/uu/base32/src/base_common.rs b/src/uu/base32/src/base_common.rs index 74c3dc80879..68c40287db7 100644 --- a/src/uu/base32/src/base_common.rs +++ b/src/uu/base32/src/base_common.rs @@ -6,7 +6,7 @@ use std::io::{stdout, Read, Write}; use uucore::display::Quotable; -use uucore::encoding::{wrap_print, Data, Format}; +use uucore::encoding::{wrap_print, Data, EncodeError, Format}; use uucore::error::{FromIo, UResult, USimpleError, UUsageError}; use uucore::format_usage; @@ -87,8 +87,7 @@ pub fn parse_base_cmd_args( usage: &str, ) -> UResult { let command = base_app(about, usage); - let arg_list = args.collect_lossy(); - Config::from(&command.try_get_matches_from(arg_list)?) + Config::from(&command.try_get_matches_from(args)?) } pub fn base_app(about: &'static str, usage: &str) -> Command { @@ -175,6 +174,7 @@ pub fn handle_input( wrap_print(&data, &s); Ok(()) } + Err(EncodeError::InvalidInput) => Err(USimpleError::new(1, "error: invalid input")), Err(_) => Err(USimpleError::new( 1, "error: invalid input (length must be multiple of 4 characters)", diff --git a/src/uu/base64/Cargo.toml b/src/uu/base64/Cargo.toml index e52665bb692..204b880bf72 100644 --- a/src/uu/base64/Cargo.toml +++ b/src/uu/base64/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_base64" -version = "0.0.23" +version = "0.0.24" authors = ["uutils developers"] license = "MIT" description = "base64 ~ (uutils) decode/encode input (base64-encoding)" diff --git a/src/uu/basename/Cargo.toml b/src/uu/basename/Cargo.toml index 3de240e04a9..51202235b15 100644 --- a/src/uu/basename/Cargo.toml +++ b/src/uu/basename/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_basename" -version = "0.0.23" +version = "0.0.24" authors = ["uutils developers"] license = "MIT" description = "basename ~ (uutils) display PATHNAME with leading directory components removed" diff --git a/src/uu/basenc/Cargo.toml b/src/uu/basenc/Cargo.toml index 54a5a53a118..26a2364282f 100644 --- a/src/uu/basenc/Cargo.toml +++ b/src/uu/basenc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_basenc" -version = "0.0.23" +version = "0.0.24" authors = ["uutils developers"] license = "MIT" description = "basenc ~ (uutils) decode/encode input" diff --git a/src/uu/basenc/basenc.md b/src/uu/basenc/basenc.md index 17916bd4ab8..001babe9e6b 100644 --- a/src/uu/basenc/basenc.md +++ b/src/uu/basenc/basenc.md @@ -1,7 +1,7 @@ # basenc ``` -basenc [OPTION]... [FILE]" +basenc [OPTION]... [FILE] ``` Encode/decode data and print to standard output diff --git a/src/uu/cat/Cargo.toml b/src/uu/cat/Cargo.toml index 14383895e7d..cce6561c084 100644 --- a/src/uu/cat/Cargo.toml +++ b/src/uu/cat/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_cat" -version = "0.0.23" +version = "0.0.24" authors = ["uutils developers"] license = "MIT" description = "cat ~ (uutils) concatenate and display input" diff --git a/src/uu/cat/src/cat.rs b/src/uu/cat/src/cat.rs index 34eb265129d..af55442ca5e 100644 --- a/src/uu/cat/src/cat.rs +++ b/src/uu/cat/src/cat.rs @@ -174,8 +174,6 @@ mod options { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let args = args.collect_ignore(); - let matches = uu_app().try_get_matches_from(args)?; let number_mode = if matches.get_flag(options::NUMBER_NONBLANK) { @@ -465,7 +463,6 @@ fn write_fast(handle: &mut InputHandle) -> CatResult<()> { /// Outputs file contents to stdout in a line-by-line fashion, /// propagating any errors that might occur. -#[allow(clippy::cognitive_complexity)] fn write_lines( handle: &mut InputHandle, options: &OutputOptions, @@ -484,22 +481,7 @@ fn write_lines( while pos < n { // skip empty line_number enumerating them if needed if in_buf[pos] == b'\n' { - // \r followed by \n is printed as ^M when show_ends is enabled, so that \r\n prints as ^M$ - if state.skipped_carriage_return && options.show_ends { - writer.write_all(b"^M")?; - state.skipped_carriage_return = false; - } - if !state.at_line_start || !options.squeeze_blank || !state.one_blank_kept { - state.one_blank_kept = true; - if state.at_line_start && options.number == NumberingMode::All { - write!(writer, "{0:6}\t", state.line_number)?; - state.line_number += 1; - } - writer.write_all(options.end_of_line().as_bytes())?; - if handle.is_interactive { - writer.flush()?; - } - } + write_new_line(&mut writer, options, state, handle.is_interactive)?; state.at_line_start = true; pos += 1; continue; @@ -516,13 +498,8 @@ fn write_lines( } // print to end of line or end of buffer - let offset = if options.show_nonprint { - write_nonprint_to_end(&in_buf[pos..], &mut writer, options.tab().as_bytes()) - } else if options.show_tabs { - write_tab_to_end(&in_buf[pos..], &mut writer) - } else { - write_to_end(&in_buf[pos..], &mut writer) - }; + let offset = write_end(&mut writer, &in_buf[pos..], options); + // end of buffer? if offset + pos == in_buf.len() { state.at_line_start = false; @@ -533,10 +510,11 @@ fn write_lines( } else { assert_eq!(in_buf[pos + offset], b'\n'); // print suitable end of line - writer.write_all(options.end_of_line().as_bytes())?; - if handle.is_interactive { - writer.flush()?; - } + write_end_of_line( + &mut writer, + options.end_of_line().as_bytes(), + handle.is_interactive, + )?; state.at_line_start = true; } pos += offset + 1; @@ -546,6 +524,41 @@ fn write_lines( Ok(()) } +// \r followed by \n is printed as ^M when show_ends is enabled, so that \r\n prints as ^M$ +fn write_new_line( + writer: &mut W, + options: &OutputOptions, + state: &mut OutputState, + is_interactive: bool, +) -> CatResult<()> { + if state.skipped_carriage_return && options.show_ends { + writer.write_all(b"^M")?; + state.skipped_carriage_return = false; + } + if !state.at_line_start || !options.squeeze_blank || !state.one_blank_kept { + state.one_blank_kept = true; + if state.at_line_start && options.number == NumberingMode::All { + write!(writer, "{0:6}\t", state.line_number)?; + state.line_number += 1; + } + writer.write_all(options.end_of_line().as_bytes())?; + if is_interactive { + writer.flush()?; + } + } + Ok(()) +} + +fn write_end(writer: &mut W, in_buf: &[u8], options: &OutputOptions) -> usize { + if options.show_nonprint { + write_nonprint_to_end(in_buf, writer, options.tab().as_bytes()) + } else if options.show_tabs { + write_tab_to_end(in_buf, writer) + } else { + write_to_end(in_buf, writer) + } +} + // write***_to_end methods // Write all symbols till \n or \r or end of buffer is reached // We need to stop at \r because it may be written as ^M depending on the byte after and settings; @@ -612,6 +625,18 @@ fn write_nonprint_to_end(in_buf: &[u8], writer: &mut W, tab: &[u8]) -> count } +fn write_end_of_line( + writer: &mut W, + end_of_line: &[u8], + is_interactive: bool, +) -> CatResult<()> { + writer.write_all(end_of_line)?; + if is_interactive { + writer.flush()?; + } + Ok(()) +} + #[cfg(test)] mod tests { use std::io::{stdout, BufWriter}; diff --git a/src/uu/chcon/Cargo.toml b/src/uu/chcon/Cargo.toml index d21da4cf04d..021e435823a 100644 --- a/src/uu/chcon/Cargo.toml +++ b/src/uu/chcon/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_chcon" -version = "0.0.23" +version = "0.0.24" authors = ["uutils developers"] license = "MIT" description = "chcon ~ (uutils) change file security context" diff --git a/src/uu/chgrp/Cargo.toml b/src/uu/chgrp/Cargo.toml index 79942033f1e..bbad8d31e6e 100644 --- a/src/uu/chgrp/Cargo.toml +++ b/src/uu/chgrp/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_chgrp" -version = "0.0.23" +version = "0.0.24" authors = ["uutils developers"] license = "MIT" description = "chgrp ~ (uutils) change the group ownership of FILE" diff --git a/src/uu/chmod/Cargo.toml b/src/uu/chmod/Cargo.toml index ebfe00fe1c5..e779469deda 100644 --- a/src/uu/chmod/Cargo.toml +++ b/src/uu/chmod/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_chmod" -version = "0.0.23" +version = "0.0.24" authors = ["uutils developers"] license = "MIT" description = "chmod ~ (uutils) change mode of FILE" diff --git a/src/uu/chown/Cargo.toml b/src/uu/chown/Cargo.toml index aab8f20b6c1..dfa9dba32a1 100644 --- a/src/uu/chown/Cargo.toml +++ b/src/uu/chown/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_chown" -version = "0.0.23" +version = "0.0.24" authors = ["uutils developers"] license = "MIT" description = "chown ~ (uutils) change the ownership of FILE" diff --git a/src/uu/chroot/Cargo.toml b/src/uu/chroot/Cargo.toml index a78c89685bf..12533b6542a 100644 --- a/src/uu/chroot/Cargo.toml +++ b/src/uu/chroot/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_chroot" -version = "0.0.23" +version = "0.0.24" authors = ["uutils developers"] license = "MIT" description = "chroot ~ (uutils) run COMMAND under a new root directory" diff --git a/src/uu/chroot/src/chroot.rs b/src/uu/chroot/src/chroot.rs index 6366775c36d..fb20b0ccc46 100644 --- a/src/uu/chroot/src/chroot.rs +++ b/src/uu/chroot/src/chroot.rs @@ -33,8 +33,6 @@ mod options { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let args = args.collect_lossy(); - let matches = uu_app().try_get_matches_from(args).with_exit_code(125)?; let default_shell: &'static str = "/bin/sh"; @@ -253,7 +251,7 @@ fn set_main_group(group: &str) -> UResult<()> { Ok(()) } -#[cfg(any(target_vendor = "apple", target_os = "freebsd"))] +#[cfg(any(target_vendor = "apple", target_os = "freebsd", target_os = "openbsd"))] fn set_groups(groups: &[libc::gid_t]) -> libc::c_int { unsafe { setgroups(groups.len() as libc::c_int, groups.as_ptr()) } } diff --git a/src/uu/cksum/Cargo.toml b/src/uu/cksum/Cargo.toml index 9a811fafdbc..47b4d73592a 100644 --- a/src/uu/cksum/Cargo.toml +++ b/src/uu/cksum/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_cksum" -version = "0.0.23" +version = "0.0.24" authors = ["uutils developers"] license = "MIT" description = "cksum ~ (uutils) display CRC and size of input" diff --git a/src/uu/cksum/src/cksum.rs b/src/uu/cksum/src/cksum.rs index 629bb457fd8..36dfbbe1e3d 100644 --- a/src/uu/cksum/src/cksum.rs +++ b/src/uu/cksum/src/cksum.rs @@ -4,16 +4,19 @@ // file that was distributed with this source code. // spell-checker:ignore (ToDO) fname, algo -use clap::{crate_version, Arg, ArgAction, Command}; +use clap::{crate_version, value_parser, Arg, ArgAction, Command}; +use hex::decode; use hex::encode; +use std::error::Error; use std::ffi::OsStr; +use std::fmt::Display; use std::fs::File; -use std::io::{self, stdin, BufReader, Read}; +use std::io::{self, stdin, stdout, BufReader, Read, Write}; use std::iter; use std::path::Path; use uucore::{ - error::{FromIo, UResult}, - format_usage, help_about, help_section, help_usage, + error::{FromIo, UError, UResult, USimpleError}, + format_usage, help_about, help_section, help_usage, show, sum::{ div_ceil, Blake2b, Digest, DigestWriter, Md5, Sha1, Sha224, Sha256, Sha384, Sha512, Sm3, BSD, CRC, SYSV, @@ -36,7 +39,35 @@ const ALGORITHM_OPTIONS_SHA512: &str = "sha512"; const ALGORITHM_OPTIONS_BLAKE2B: &str = "blake2b"; const ALGORITHM_OPTIONS_SM3: &str = "sm3"; -fn detect_algo(program: &str) -> (&'static str, Box, usize) { +#[derive(Debug)] +enum CkSumError { + RawMultipleFiles, +} + +impl UError for CkSumError { + fn code(&self) -> i32 { + match self { + Self::RawMultipleFiles => 1, + } + } +} + +impl Error for CkSumError {} + +impl Display for CkSumError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::RawMultipleFiles => { + write!(f, "the --raw option is not supported with multiple files") + } + } + } +} + +fn detect_algo( + program: &str, + length: Option, +) -> (&'static str, Box, usize) { match program { ALGORITHM_OPTIONS_SYSV => ( ALGORITHM_OPTIONS_SYSV, @@ -85,7 +116,11 @@ fn detect_algo(program: &str) -> (&'static str, Box, usize ), ALGORITHM_OPTIONS_BLAKE2B => ( ALGORITHM_OPTIONS_BLAKE2B, - Box::new(Blake2b::new()) as Box, + Box::new(if let Some(length) = length { + Blake2b::with_output_bytes(length) + } else { + Blake2b::new() + }) as Box, 512, ), ALGORITHM_OPTIONS_SM3 => ( @@ -102,6 +137,8 @@ struct Options { digest: Box, output_bits: usize, untagged: bool, + length: Option, + raw: bool, } /// Calculate checksum @@ -115,6 +152,11 @@ fn cksum<'a, I>(mut options: Options, files: I) -> UResult<()> where I: Iterator, { + let files: Vec<_> = files.collect(); + if options.raw && files.len() > 1 { + return Err(Box::new(CkSumError::RawMultipleFiles)); + } + for filename in files { let filename = Path::new(filename); let stdin_buf; @@ -126,13 +168,35 @@ where } else if filename.is_dir() { Box::new(BufReader::new(io::empty())) as Box } else { - file_buf = - File::open(filename).map_err_context(|| filename.to_str().unwrap().to_string())?; + file_buf = match File::open(filename) { + Ok(file) => file, + Err(err) => { + show!(err.map_err_context(|| filename.to_string_lossy().to_string())); + continue; + } + }; Box::new(file_buf) as Box }); let (sum, sz) = digest_read(&mut options.digest, &mut file, options.output_bits) .map_err_context(|| "failed to read input".to_string())?; - + if filename.is_dir() { + show!(USimpleError::new( + 1, + format!("{}: Is a directory", filename.display()) + )); + continue; + } + if options.raw { + let bytes = match options.algo_name { + ALGORITHM_OPTIONS_CRC => sum.parse::().unwrap().to_be_bytes().to_vec(), + ALGORITHM_OPTIONS_SYSV | ALGORITHM_OPTIONS_BSD => { + sum.parse::().unwrap().to_be_bytes().to_vec() + } + _ => decode(sum).unwrap(), + }; + stdout().write_all(&bytes)?; + return Ok(()); + } // The BSD checksum output is 5 digit integer let bsd_width = 5; match (options.algo_name, not_file) { @@ -161,7 +225,12 @@ where (ALGORITHM_OPTIONS_CRC, true) => println!("{sum} {sz}"), (ALGORITHM_OPTIONS_CRC, false) => println!("{sum} {sz} {}", filename.display()), (ALGORITHM_OPTIONS_BLAKE2B, _) if !options.untagged => { - println!("BLAKE2b ({}) = {sum}", filename.display()); + if let Some(length) = options.length { + // Multiply by 8 here, as we want to print the length in bits. + println!("BLAKE2b-{} ({}) = {sum}", length * 8, filename.display()); + } else { + println!("BLAKE2b ({}) = {sum}", filename.display()); + } } _ => { if options.untagged { @@ -217,12 +286,12 @@ mod options { pub const ALGORITHM: &str = "algorithm"; pub const FILE: &str = "file"; pub const UNTAGGED: &str = "untagged"; + pub const LENGTH: &str = "length"; + pub const RAW: &str = "raw"; } #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let args = args.collect_ignore(); - let matches = uu_app().try_get_matches_from(args)?; let algo_name: &str = match matches.get_one::(options::ALGORITHM) { @@ -230,12 +299,56 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { None => ALGORITHM_OPTIONS_CRC, }; - let (name, algo, bits) = detect_algo(algo_name); + let input_length = matches.get_one::(options::LENGTH); + let length = if let Some(length) = input_length { + match length.to_owned() { + 0 => None, + n if n % 8 != 0 => { + // GNU's implementation seem to use these quotation marks + // in their error messages, so we do the same. + uucore::show_error!("invalid length: \u{2018}{length}\u{2019}"); + return Err(io::Error::new( + io::ErrorKind::InvalidInput, + "length is not a multiple of 8", + ) + .into()); + } + n if n > 512 => { + uucore::show_error!("invalid length: \u{2018}{length}\u{2019}"); + + return Err(io::Error::new( + io::ErrorKind::InvalidInput, + "maximum digest length for \u{2018}BLAKE2b\u{2019} is 512 bits", + ) + .into()); + } + n => { + if algo_name != ALGORITHM_OPTIONS_BLAKE2B { + return Err(io::Error::new( + io::ErrorKind::InvalidInput, + "--length is only supported with --algorithm=blake2b", + ) + .into()); + } + + // Divide by 8, as our blake2b implementation expects bytes + // instead of bits. + Some(n / 8) + } + } + } else { + None + }; + + let (name, algo, bits) = detect_algo(algo_name, length); + let opts = Options { algo_name: name, digest: algo, output_bits: bits, + length, untagged: matches.get_flag(options::UNTAGGED), + raw: matches.get_flag(options::RAW), }; match matches.get_many::(options::FILE) { @@ -284,5 +397,19 @@ pub fn uu_app() -> Command { .help("create a reversed style checksum, without digest type") .action(ArgAction::SetTrue), ) + .arg( + Arg::new(options::LENGTH) + .long(options::LENGTH) + .value_parser(value_parser!(usize)) + .short('l') + .help("digest length in bits; must not exceed the max for the blake2 algorithm and must be a multiple of 8") + .action(ArgAction::Set), + ) + .arg( + Arg::new(options::RAW) + .long(options::RAW) + .help("emit a raw binary digest, not hexadecimal") + .action(ArgAction::SetTrue), + ) .after_help(AFTER_HELP) } diff --git a/src/uu/comm/Cargo.toml b/src/uu/comm/Cargo.toml index 71ed0ad717f..cd759aad246 100644 --- a/src/uu/comm/Cargo.toml +++ b/src/uu/comm/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_comm" -version = "0.0.23" +version = "0.0.24" authors = ["uutils developers"] license = "MIT" description = "comm ~ (uutils) compare sorted inputs" diff --git a/src/uu/comm/src/comm.rs b/src/uu/comm/src/comm.rs index e6977142ef5..dd49ef53b02 100644 --- a/src/uu/comm/src/comm.rs +++ b/src/uu/comm/src/comm.rs @@ -145,8 +145,6 @@ fn open_file(name: &str, line_ending: LineEnding) -> io::Result { #[uucore::main] 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_zero_flag(matches.get_flag(options::ZERO_TERMINATED)); let filename1 = matches.get_one::(options::FILE_1).unwrap(); diff --git a/src/uu/cp/Cargo.toml b/src/uu/cp/Cargo.toml index 179b4668e68..7db6b202bb2 100644 --- a/src/uu/cp/Cargo.toml +++ b/src/uu/cp/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_cp" -version = "0.0.23" +version = "0.0.24" authors = [ "Jordy Dickinson ", "Joshua S. Miller ", diff --git a/src/uu/cp/src/copydir.rs b/src/uu/cp/src/copydir.rs index 763d66c0b03..7a9d797e81c 100644 --- a/src/uu/cp/src/copydir.rs +++ b/src/uu/cp/src/copydir.rs @@ -17,7 +17,9 @@ use std::path::{Path, PathBuf, StripPrefixError}; use indicatif::ProgressBar; use uucore::display::Quotable; use uucore::error::UIoError; -use uucore::fs::{canonicalize, FileInformation, MissingHandling, ResolveMode}; +use uucore::fs::{ + canonicalize, path_ends_with_terminator, FileInformation, MissingHandling, ResolveMode, +}; use uucore::show; use uucore::show_error; use uucore::uio_error; @@ -170,7 +172,14 @@ impl Entry { let mut descendant = get_local_to_root_parent(&source_absolute, context.root_parent.as_deref())?; if no_target_dir { - descendant = descendant.strip_prefix(context.root)?.to_path_buf(); + let source_is_dir = direntry.path().is_dir(); + if path_ends_with_terminator(context.target) && source_is_dir { + if let Err(e) = std::fs::create_dir_all(context.target) { + eprintln!("Failed to create directory: {}", e); + } + } else { + descendant = descendant.strip_prefix(context.root)?.to_path_buf(); + } } let local_to_target = context.target.join(descendant); @@ -324,7 +333,7 @@ pub(crate) fn copy_directory( source_in_command_line: bool, ) -> CopyResult<()> { if !options.recursive { - return Err(format!("omitting directory {}", root.quote()).into()); + return Err(format!("-r not specified; omitting directory {}", root.quote()).into()); } // if no-dereference is enabled and this is a symlink, copy it as a file diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index 7265e89f12a..28e9b678471 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -32,8 +32,8 @@ use platform::copy_on_write; 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, + are_hardlinks_to_same_file, canonicalize, is_symlink_loop, path_ends_with_terminator, + paths_refer_to_same_file, FileInformation, MissingHandling, ResolveMode, }; use uucore::{backup_control, update_control}; // These are exposed for projects (e.g. nushell) that want to create an `Options` value, which @@ -150,6 +150,7 @@ pub enum TargetType { } /// Copy action to perform +#[derive(PartialEq)] pub enum CopyMode { Link, SymLink, @@ -825,6 +826,7 @@ impl Attributes { ownership: Preserve::Yes { required: true }, mode: Preserve::Yes { required: true }, timestamps: Preserve::Yes { required: true }, + xattr: Preserve::Yes { required: true }, ..Self::NONE }; @@ -1171,6 +1173,9 @@ pub fn copy(sources: &[PathBuf], target: &Path, options: &Options) -> CopyResult // // key is the source file's information and the value is the destination filepath. let mut copied_files: HashMap = HashMap::with_capacity(sources.len()); + // remember the copied destinations for further usage. + // we can't use copied_files as it is because the key is the source file's information. + let mut copied_destinations: HashSet = HashSet::with_capacity(sources.len()); let progress_bar = if options.progress_bar { let pb = ProgressBar::new(disk_usage(sources, options.recursive)?) @@ -1190,18 +1195,39 @@ pub fn copy(sources: &[PathBuf], target: &Path, options: &Options) -> CopyResult 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 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; + show_warning!("source file {} specified more than once", source.quote()); + } else { + let dest = construct_dest_path(source, target, target_type, options) + .unwrap_or_else(|_| target.to_path_buf()); + + if fs::metadata(&dest).is_ok() && !fs::symlink_metadata(&dest)?.file_type().is_symlink() + { + // There is already a file and it isn't a symlink (managed in a different place) + if copied_destinations.contains(&dest) + && options.backup != BackupMode::NumberedBackup + { + // If the target file was already created in this cp call, do not overwrite + return Err(Error::Error(format!( + "will not overwrite just-created '{}' with '{}'", + dest.display(), + source.display() + ))); + } + } + + 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; + } + copied_destinations.insert(dest.clone()); } seen_sources.insert(source); } @@ -1416,7 +1442,7 @@ pub(crate) fn copy_attributes( })?; handle_preserve(&attributes.xattr, || -> CopyResult<()> { - #[cfg(unix)] + #[cfg(all(unix, not(target_os = "android")))] { let xattrs = xattr::list(source)?; for attr in xattrs { @@ -1425,7 +1451,7 @@ pub(crate) fn copy_attributes( } } } - #[cfg(not(unix))] + #[cfg(not(all(unix, not(target_os = "android"))))] { // The documentation for GNU cp states: // @@ -1472,7 +1498,11 @@ fn context_for(src: &Path, dest: &Path) -> String { /// Implements a simple backup copy for the destination file. /// TODO: for the backup, should this function be replaced by `copy_file(...)`? fn backup_dest(dest: &Path, backup_path: &Path) -> CopyResult { - fs::copy(dest, backup_path)?; + if dest.is_symlink() { + fs::rename(dest, backup_path)?; + } else { + fs::copy(dest, backup_path)?; + } Ok(backup_path.into()) } @@ -1481,7 +1511,7 @@ fn backup_dest(dest: &Path, backup_path: &Path) -> CopyResult { /// /// Copying to the same file is only allowed if both `--backup` and /// `--force` are specified and the file is a regular file. -fn is_forbidden_copy_to_same_file( +fn is_forbidden_to_copy_to_same_file( source: &Path, dest: &Path, options: &Options, @@ -1493,6 +1523,7 @@ fn is_forbidden_copy_to_same_file( options.dereference(source_in_command_line) || !source.is_symlink(); paths_refer_to_same_file(source, dest, dereference_to_compare) && !(options.force() && options.backup != BackupMode::NoBackup) + && !(dest.is_symlink() && options.backup != BackupMode::NoBackup) } /// Back up, remove, or leave intact the destination file, depending on the options. @@ -1504,7 +1535,7 @@ fn handle_existing_dest( ) -> CopyResult<()> { // Disallow copying a file to itself, unless `--force` and // `--backup` are both specified. - if is_forbidden_copy_to_same_file(source, dest, options, source_in_command_line) { + if is_forbidden_to_copy_to_same_file(source, dest, options, source_in_command_line) { return Err(format!("{} and {} are the same file", source.quote(), dest.quote()).into()); } @@ -1672,7 +1703,24 @@ fn copy_file( } } + if are_hardlinks_to_same_file(source, dest) + && matches!( + options.overwrite, + OverwriteMode::Clobber(ClobberMode::RemoveDestination) + ) + { + fs::remove_file(dest)?; + } + if file_or_link_exists(dest) { + if are_hardlinks_to_same_file(source, dest) + && !options.force() + && options.backup == BackupMode::NoBackup + && source != dest + || (source == dest && options.copy_mode == CopyMode::Link) + { + return Ok(()); + } handle_existing_dest(source, dest, options, source_in_command_line)?; } @@ -1815,7 +1863,13 @@ fn copy_file( symlinked_files, )?; } - update_control::UpdateMode::ReplaceNone => return Ok(()), + update_control::UpdateMode::ReplaceNone => { + if options.debug { + println!("skipped {}", dest.quote()); + } + + return Ok(()); + } update_control::UpdateMode::ReplaceIfOlder => { let dest_metadata = fs::symlink_metadata(dest)?; @@ -1896,6 +1950,7 @@ fn handle_no_preserve_mode(options: &Options, org_mode: u32) -> u32 { target_os = "macos", target_os = "macos-12", target_os = "freebsd", + target_os = "redox", )))] { const MODE_RW_UGO: u32 = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH; @@ -1911,6 +1966,7 @@ fn handle_no_preserve_mode(options: &Options, org_mode: u32) -> u32 { target_os = "macos", target_os = "macos-12", target_os = "freebsd", + target_os = "redox", ))] { const MODE_RW_UGO: u32 = @@ -1942,6 +1998,10 @@ fn copy_helper( fs::create_dir_all(parent)?; } + if path_ends_with_terminator(dest) && !dest.is_dir() { + return Err(Error::NotADirectory(dest.to_path_buf())); + } + if source.as_os_str() == "/dev/null" { /* workaround a limitation of fs::copy * https://github.com/rust-lang/rust/issues/79390 diff --git a/src/uu/csplit/Cargo.toml b/src/uu/csplit/Cargo.toml index e68b1258253..5e2f310cb51 100644 --- a/src/uu/csplit/Cargo.toml +++ b/src/uu/csplit/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_csplit" -version = "0.0.23" +version = "0.0.24" 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" diff --git a/src/uu/csplit/src/csplit.rs b/src/uu/csplit/src/csplit.rs index d33be1a5d54..00bebbf4dcb 100644 --- a/src/uu/csplit/src/csplit.rs +++ b/src/uu/csplit/src/csplit.rs @@ -552,8 +552,6 @@ where #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let args = args.collect_ignore(); - let matches = uu_app().try_get_matches_from(args)?; // get the file to split diff --git a/src/uu/csplit/src/patterns.rs b/src/uu/csplit/src/patterns.rs index 8e7b76e6bb4..6e7483b7f9f 100644 --- a/src/uu/csplit/src/patterns.rs +++ b/src/uu/csplit/src/patterns.rs @@ -196,7 +196,7 @@ mod tests { .collect(); let patterns = get_patterns(input.as_slice()).unwrap(); assert_eq!(patterns.len(), 3); - match patterns.get(0) { + match patterns.first() { Some(Pattern::UpToLine(24, ExecutePattern::Times(1))) => (), _ => panic!("expected UpToLine pattern"), }; @@ -227,7 +227,7 @@ mod tests { .collect(); let patterns = get_patterns(input.as_slice()).unwrap(); assert_eq!(patterns.len(), 5); - match patterns.get(0) { + match patterns.first() { Some(Pattern::UpToMatch(reg, 0, ExecutePattern::Times(1))) => { let parsed_reg = format!("{reg}"); assert_eq!(parsed_reg, "test1.*end$"); @@ -281,7 +281,7 @@ mod tests { .collect(); let patterns = get_patterns(input.as_slice()).unwrap(); assert_eq!(patterns.len(), 5); - match patterns.get(0) { + match patterns.first() { Some(Pattern::SkipToMatch(reg, 0, ExecutePattern::Times(1))) => { let parsed_reg = format!("{reg}"); assert_eq!(parsed_reg, "test1.*end$"); diff --git a/src/uu/cut/Cargo.toml b/src/uu/cut/Cargo.toml index e572d987ec4..c98bec5bc42 100644 --- a/src/uu/cut/Cargo.toml +++ b/src/uu/cut/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_cut" -version = "0.0.23" +version = "0.0.24" authors = ["uutils developers"] license = "MIT" description = "cut ~ (uutils) display byte/field columns of input lines" diff --git a/src/uu/cut/cut.md b/src/uu/cut/cut.md index 972fcb6322e..5c21d23dcf9 100644 --- a/src/uu/cut/cut.md +++ b/src/uu/cut/cut.md @@ -3,7 +3,7 @@ ``` -cut [-d|-w] [-s] [-z] [--output-delimiter] ((-f|-b|-c) {{sequence}}) {{sourcefile}}+ +cut OPTION... [FILE]... ``` Prints specified byte or field columns from each line of stdin or the input files diff --git a/src/uu/cut/src/cut.rs b/src/uu/cut/src/cut.rs index 05e8bc6e424..2a3196d002e 100644 --- a/src/uu/cut/src/cut.rs +++ b/src/uu/cut/src/cut.rs @@ -17,7 +17,7 @@ use uucore::line_ending::LineEnding; use self::searcher::Searcher; use matcher::{ExactMatcher, Matcher, WhitespaceMatcher}; use uucore::ranges::Range; -use uucore::{format_usage, help_about, help_section, help_usage, show, show_error, show_if_err}; +use uucore::{format_usage, help_about, help_section, help_usage, show_error, show_if_err}; mod matcher; mod searcher; @@ -426,7 +426,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { delim = ""; } if delim.chars().count() > 1 { - Err("invalid input: The '--delimiter' ('-d') option expects empty or 1 character long, but was provided a value 2 characters or longer".into()) + Err("the delimiter must be a single character".into()) } else { let delim = if delim.is_empty() { "\0".to_owned() @@ -510,6 +510,7 @@ pub fn uu_app() -> Command { .about(ABOUT) .after_help(AFTER_HELP) .infer_long_args(true) + .args_override_self(true) .arg( Arg::new(options::BYTES) .short('b') diff --git a/src/uu/date/Cargo.toml b/src/uu/date/Cargo.toml index c5682f83e5a..11ad64bbef7 100644 --- a/src/uu/date/Cargo.toml +++ b/src/uu/date/Cargo.toml @@ -1,7 +1,7 @@ # spell-checker:ignore datetime [package] name = "uu_date" -version = "0.0.23" +version = "0.0.24" authors = ["uutils developers"] license = "MIT" description = "date ~ (uutils) display or set the current time" diff --git a/src/uu/date/src/date.rs b/src/uu/date/src/date.rs index b5ab8993acd..ee3c7bfdfae 100644 --- a/src/uu/date/src/date.rs +++ b/src/uu/date/src/date.rs @@ -16,7 +16,6 @@ use std::fs::File; use std::io::{BufRead, BufReader}; use std::path::PathBuf; use uucore::display::Quotable; -#[cfg(not(any(target_os = "redox")))] use uucore::error::FromIo; use uucore::error::{UResult, USimpleError}; use uucore::{format_usage, help_about, help_usage, show}; diff --git a/src/uu/dd/Cargo.toml b/src/uu/dd/Cargo.toml index 7db05b422f3..1dbb37bde55 100644 --- a/src/uu/dd/Cargo.toml +++ b/src/uu/dd/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_dd" -version = "0.0.23" +version = "0.0.24" authors = ["uutils developers"] license = "MIT" description = "dd ~ (uutils) copy and convert files" @@ -18,7 +18,7 @@ path = "src/dd.rs" clap = { workspace = true } gcd = { workspace = true } libc = { workspace = true } -uucore = { workspace = true, features = ["memo"] } +uucore = { workspace = true, features = ["format", "quoting-style"] } [target.'cfg(any(target_os = "linux"))'.dependencies] nix = { workspace = true, features = ["fs"] } diff --git a/src/uu/dd/src/bufferedoutput.rs b/src/uu/dd/src/bufferedoutput.rs new file mode 100644 index 00000000000..6ac3b430046 --- /dev/null +++ b/src/uu/dd/src/bufferedoutput.rs @@ -0,0 +1,206 @@ +// 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 wstat towrite cdefg bufferedoutput +//! Buffer partial output blocks until they are completed. +//! +//! Use the [`BufferedOutput`] struct to create a buffered form of the +//! [`Output`] writer. +use crate::{Output, WriteStat}; + +/// Buffer partial output blocks until they are completed. +/// +/// Complete blocks are written immediately to the inner [`Output`], +/// but partial blocks are stored in an internal buffer until they are +/// completed. +pub(crate) struct BufferedOutput<'a> { + /// The unbuffered inner block writer. + inner: Output<'a>, + + /// The internal buffer that stores a partial block. + /// + /// The size of this buffer is always less than the output block + /// size (that is, the value of the `obs` command-line option). + buf: Vec, +} + +impl<'a> BufferedOutput<'a> { + /// Add partial block buffering to the given block writer. + /// + /// The internal buffer size is at most the value of `obs` as + /// defined in `inner`. + pub(crate) fn new(inner: Output<'a>) -> Self { + let obs = inner.settings.obs; + Self { + inner, + buf: Vec::with_capacity(obs), + } + } + + pub(crate) fn discard_cache(&self, offset: libc::off_t, len: libc::off_t) { + self.inner.discard_cache(offset, len); + } + + /// Flush the partial block stored in the internal buffer. + pub(crate) fn flush(&mut self) -> std::io::Result { + let wstat = self.inner.write_blocks(&self.buf)?; + let n = wstat.bytes_total.try_into().unwrap(); + self.buf.drain(0..n); + Ok(wstat) + } + + /// Synchronize the inner block writer. + pub(crate) fn sync(&mut self) -> std::io::Result<()> { + self.inner.sync() + } + + /// Truncate the underlying file to the current stream position, if possible. + pub(crate) fn truncate(&mut self) -> std::io::Result<()> { + self.inner.dst.truncate() + } + + /// Write the given bytes one block at a time. + /// + /// Only complete blocks will be written. Partial blocks will be + /// buffered until enough bytes have been provided to complete a + /// block. The returned [`WriteStat`] object will include the + /// number of blocks written during execution of this function. + pub(crate) fn write_blocks(&mut self, buf: &[u8]) -> std::io::Result { + // Split the incoming buffer into two parts: the bytes to write + // and the bytes to buffer for next time. + // + // If `buf` does not include enough bytes to form a full block, + // just buffer the whole thing and write zero blocks. + let n = self.buf.len() + buf.len(); + let rem = n % self.inner.settings.obs; + let i = buf.len().saturating_sub(rem); + let (to_write, to_buffer) = buf.split_at(i); + + // Concatenate the old partial block with the new bytes to form + // some number of complete blocks. + self.buf.extend_from_slice(to_write); + + // Write all complete blocks to the inner block writer. + // + // For example, if the output block size were 3, the buffered + // partial block were `b"ab"` and the new incoming bytes were + // `b"cdefg"`, then we would write blocks `b"abc"` and + // b`"def"` to the inner block writer. + let wstat = self.inner.write_blocks(&self.buf)?; + + // Buffer any remaining bytes as a partial block. + // + // Continuing the example above, the last byte `b"g"` would be + // buffered as a partial block until the next call to + // `write_blocks()`. + self.buf.clear(); + self.buf.extend_from_slice(to_buffer); + + Ok(wstat) + } +} + +#[cfg(unix)] +#[cfg(test)] +mod tests { + use crate::bufferedoutput::BufferedOutput; + use crate::{Dest, Output, Settings}; + + #[test] + fn test_buffered_output_write_blocks_empty() { + let settings = Settings { + obs: 3, + ..Default::default() + }; + let inner = Output { + dst: Dest::Sink, + settings: &settings, + }; + let mut output = BufferedOutput::new(inner); + let wstat = output.write_blocks(&[]).unwrap(); + assert_eq!(wstat.writes_complete, 0); + assert_eq!(wstat.writes_partial, 0); + assert_eq!(wstat.bytes_total, 0); + assert_eq!(output.buf, vec![]); + } + + #[test] + fn test_buffered_output_write_blocks_partial() { + let settings = Settings { + obs: 3, + ..Default::default() + }; + let inner = Output { + dst: Dest::Sink, + settings: &settings, + }; + let mut output = BufferedOutput::new(inner); + let wstat = output.write_blocks(b"ab").unwrap(); + assert_eq!(wstat.writes_complete, 0); + assert_eq!(wstat.writes_partial, 0); + assert_eq!(wstat.bytes_total, 0); + assert_eq!(output.buf, b"ab"); + } + + #[test] + fn test_buffered_output_write_blocks_complete() { + let settings = Settings { + obs: 3, + ..Default::default() + }; + let inner = Output { + dst: Dest::Sink, + settings: &settings, + }; + let mut output = BufferedOutput::new(inner); + let wstat = output.write_blocks(b"abcd").unwrap(); + assert_eq!(wstat.writes_complete, 1); + assert_eq!(wstat.writes_partial, 0); + assert_eq!(wstat.bytes_total, 3); + assert_eq!(output.buf, b"d"); + } + + #[test] + fn test_buffered_output_write_blocks_append() { + let settings = Settings { + obs: 3, + ..Default::default() + }; + let inner = Output { + dst: Dest::Sink, + settings: &settings, + }; + let mut output = BufferedOutput { + inner, + buf: b"ab".to_vec(), + }; + let wstat = output.write_blocks(b"cdefg").unwrap(); + assert_eq!(wstat.writes_complete, 2); + assert_eq!(wstat.writes_partial, 0); + assert_eq!(wstat.bytes_total, 6); + assert_eq!(output.buf, b"g"); + } + + #[test] + fn test_buffered_output_flush() { + let settings = Settings { + obs: 10, + ..Default::default() + }; + let inner = Output { + dst: Dest::Sink, + settings: &settings, + }; + let mut output = BufferedOutput { + inner, + buf: b"abc".to_vec(), + }; + let wstat = output.flush().unwrap(); + assert_eq!(wstat.writes_complete, 0); + assert_eq!(wstat.writes_partial, 1); + assert_eq!(wstat.bytes_total, 3); + assert_eq!(output.buf, vec![]); + } +} diff --git a/src/uu/dd/src/dd.rs b/src/uu/dd/src/dd.rs index b79ae22da4e..07a754deb51 100644 --- a/src/uu/dd/src/dd.rs +++ b/src/uu/dd/src/dd.rs @@ -3,23 +3,21 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -// 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 +// 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 bufferedoutput +mod blocks; +mod bufferedoutput; +mod conversion_tables; mod datastructures; -use datastructures::*; - +mod numbers; mod parseargs; -use parseargs::Parser; - -mod conversion_tables; - mod progress; -use progress::{gen_prog_updater, ProgUpdate, ReadStat, StatusLevel, WriteStat}; -mod blocks; +use crate::bufferedoutput::BufferedOutput; use blocks::conv_block_unblock_helper; - -mod numbers; +use datastructures::*; +use parseargs::Parser; +use progress::{gen_prog_updater, ProgUpdate, ReadStat, StatusLevel, WriteStat}; use std::cmp; use std::env; @@ -52,9 +50,9 @@ 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")] -use uucore::{show, show_if_err}; +use uucore::show_if_err; +use uucore::{format_usage, help_about, help_section, help_usage, show_error}; const ABOUT: &str = help_about!("dd.md"); const AFTER_HELP: &str = help_section!("after help", "dd.md"); @@ -76,6 +74,8 @@ struct Settings { oconv: OConvFlags, oflags: OFlags, status: Option, + /// Whether the output writer should buffer partial blocks until complete. + buffered: bool, } /// A timer which triggers on a given interval @@ -128,6 +128,12 @@ enum Num { Bytes(u64), } +impl Default for Num { + fn default() -> Self { + Self::Blocks(0) + } +} + impl Num { fn force_bytes_if(self, force: bool) -> Self { match self { @@ -796,6 +802,68 @@ impl<'a> Output<'a> { Ok(()) } } + + /// Truncate the underlying file to the current stream position, if possible. + fn truncate(&mut self) -> std::io::Result<()> { + self.dst.truncate() + } +} + +/// The block writer either with or without partial block buffering. +enum BlockWriter<'a> { + /// Block writer with partial block buffering. + /// + /// Partial blocks are buffered until completed. + Buffered(BufferedOutput<'a>), + + /// Block writer without partial block buffering. + /// + /// Partial blocks are written immediately. + Unbuffered(Output<'a>), +} + +impl<'a> BlockWriter<'a> { + fn discard_cache(&self, offset: libc::off_t, len: libc::off_t) { + match self { + Self::Unbuffered(o) => o.discard_cache(offset, len), + Self::Buffered(o) => o.discard_cache(offset, len), + } + } + + fn flush(&mut self) -> io::Result { + match self { + Self::Unbuffered(_) => Ok(WriteStat::default()), + Self::Buffered(o) => o.flush(), + } + } + + fn sync(&mut self) -> io::Result<()> { + match self { + Self::Unbuffered(o) => o.sync(), + Self::Buffered(o) => o.sync(), + } + } + + /// Truncate the file to the final cursor location. + fn truncate(&mut self) { + // Calling `set_len()` may result in an error (for example, + // when calling it on `/dev/null`), but we don't want to + // terminate the process when that happens. Instead, we + // suppress the error by calling `Result::ok()`. This matches + // the behavior of GNU `dd` when given the command-line + // argument `of=/dev/null`. + match self { + Self::Unbuffered(o) => o.truncate().ok(), + Self::Buffered(o) => o.truncate().ok(), + }; + } + + fn write_blocks(&mut self, buf: &[u8]) -> std::io::Result { + match self { + Self::Unbuffered(o) => o.write_blocks(buf), + Self::Buffered(o) => o.write_blocks(buf), + } + } } /// Copy the given input data to this output, consuming both. @@ -809,7 +877,7 @@ impl<'a> Output<'a> { /// /// If there is a problem reading from the input or writing to /// this output. -fn dd_copy(mut i: Input, mut o: Output) -> std::io::Result<()> { +fn dd_copy(mut i: Input, o: Output) -> std::io::Result<()> { // The read and write statistics. // // These objects are counters, initialized to zero. After each @@ -846,6 +914,9 @@ fn dd_copy(mut i: Input, mut o: Output) -> std::io::Result<()> { let (prog_tx, rx) = mpsc::channel(); let output_thread = thread::spawn(gen_prog_updater(rx, i.settings.status)); + // Whether to truncate the output file after all blocks have been written. + let truncate = !o.settings.oconv.notrunc; + // Optimization: if no blocks are to be written, then don't // bother allocating any buffers. if let Some(Num::Blocks(0) | Num::Bytes(0)) = i.settings.count { @@ -870,7 +941,15 @@ fn dd_copy(mut i: Input, mut o: Output) -> std::io::Result<()> { let len = o.dst.len()?.try_into().unwrap(); o.discard_cache(offset, len); } - return finalize(&mut o, rstat, wstat, start, &prog_tx, output_thread); + return finalize( + BlockWriter::Unbuffered(o), + rstat, + wstat, + start, + &prog_tx, + output_thread, + truncate, + ); }; // Create a common buffer with a capacity of the block size. @@ -890,13 +969,23 @@ fn dd_copy(mut i: Input, mut o: Output) -> std::io::Result<()> { let mut read_offset = 0; let mut write_offset = 0; + let input_nocache = i.settings.iflags.nocache; + let output_nocache = o.settings.oflags.nocache; + + // Add partial block buffering, if needed. + let mut o = if o.settings.buffered { + BlockWriter::Buffered(BufferedOutput::new(o)) + } else { + BlockWriter::Unbuffered(o) + }; + // The main read/write loop. // // Each iteration reads blocks from the input and writes // blocks to this output. Read/write statistics are updated on // each iteration and cumulative statistics are reported to // the progress reporting thread. - while below_count_limit(&i.settings.count, &rstat, &wstat) { + while below_count_limit(&i.settings.count, &rstat) { // Read a block from the input then write the block to the output. // // As an optimization, make an educated guess about the @@ -914,7 +1003,7 @@ fn dd_copy(mut i: Input, mut o: Output) -> std::io::Result<()> { // // TODO Better error handling for overflowing `offset` and `len`. let read_len = rstat_update.bytes_total; - if i.settings.iflags.nocache { + if input_nocache { let offset = read_offset.try_into().unwrap(); let len = read_len.try_into().unwrap(); i.discard_cache(offset, len); @@ -926,7 +1015,7 @@ fn dd_copy(mut i: Input, mut o: Output) -> std::io::Result<()> { // // TODO Better error handling for overflowing `offset` and `len`. let write_len = wstat_update.bytes_total; - if o.settings.oflags.nocache { + if output_nocache { let offset = write_offset.try_into().unwrap(); let len = write_len.try_into().unwrap(); o.discard_cache(offset, len); @@ -946,34 +1035,33 @@ fn dd_copy(mut i: Input, mut o: Output) -> std::io::Result<()> { prog_tx.send(prog_update).unwrap_or(()); } } - finalize(&mut o, rstat, wstat, start, &prog_tx, output_thread) + finalize(o, rstat, wstat, start, &prog_tx, output_thread, truncate) } /// Flush output, print final stats, and join with the progress thread. fn finalize( - output: &mut Output, + mut output: BlockWriter, rstat: ReadStat, wstat: WriteStat, start: Instant, prog_tx: &mpsc::Sender, output_thread: thread::JoinHandle, + truncate: bool, ) -> std::io::Result<()> { - // Flush the output, if configured to do so. + // Flush the output in case a partial write has been buffered but + // not yet written. + let wstat_update = output.flush()?; + + // Sync the output, if configured to do so. output.sync()?; // Truncate the file to the final cursor location. - // - // Calling `set_len()` may result in an error (for example, - // when calling it on `/dev/null`), but we don't want to - // terminate the process when that happens. Instead, we - // suppress the error by calling `Result::ok()`. This matches - // the behavior of GNU `dd` when given the command-line - // argument `of=/dev/null`. - if !output.settings.oconv.notrunc { - output.dst.truncate().ok(); + if truncate { + output.truncate(); } // Print the final read/write statistics. + let wstat = wstat + wstat_update; let prog_update = ProgUpdate::new(rstat, wstat, start.elapsed(), true); prog_tx.send(prog_update).unwrap_or(()); // Wait for the output thread to finish @@ -1093,7 +1181,7 @@ fn calc_loop_bsize( cmp::min(ideal_bsize as u64, rremain * ibs as u64) as usize } Some(Num::Bytes(bmax)) => { - let bmax: u128 = (*bmax).try_into().unwrap(); + let bmax: u128 = (*bmax).into(); let bremain: u128 = bmax - wstat.bytes_total; cmp::min(ideal_bsize as u128, bremain) as usize } @@ -1103,16 +1191,10 @@ fn calc_loop_bsize( // Decide if the current progress is below a count=N limit or return // true if no such limit is set. -fn below_count_limit(count: &Option, rstat: &ReadStat, wstat: &WriteStat) -> bool { +fn below_count_limit(count: &Option, rstat: &ReadStat) -> bool { match count { - Some(Num::Blocks(n)) => { - let n = *n; - rstat.reads_complete + rstat.reads_partial <= n - } - Some(Num::Bytes(n)) => { - let n = (*n).try_into().unwrap(); - wstat.bytes_total <= n - } + Some(Num::Blocks(n)) => rstat.reads_complete + rstat.reads_partial < *n, + Some(Num::Bytes(n)) => rstat.bytes_total < *n, None => true, } } @@ -1185,8 +1267,6 @@ fn is_fifo(filename: &str) -> bool { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let args = args.collect_ignore(); - let matches = uu_app().try_get_matches_from(args)?; let settings: Settings = Parser::new().parse( diff --git a/src/uu/dd/src/parseargs.rs b/src/uu/dd/src/parseargs.rs index 0ff6e752c02..60ce9a6971f 100644 --- a/src/uu/dd/src/parseargs.rs +++ b/src/uu/dd/src/parseargs.rs @@ -35,41 +35,28 @@ pub enum ParseError { } /// Contains a temporary state during parsing of the arguments -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Default)] pub struct Parser { infile: Option, outfile: Option, - ibs: usize, - obs: usize, + /// The block size option specified on the command-line, if any. + bs: Option, + /// The input block size option specified on the command-line, if any. + ibs: Option, + /// The output block size option specified on the command-line, if any. + obs: Option, cbs: Option, skip: Num, seek: Num, count: Option, conv: ConvFlags, + /// Whether a data-transforming `conv` option has been specified. + is_conv_specified: bool, iflag: IFlags, oflag: OFlags, status: Option, } -impl Default for Parser { - fn default() -> Self { - Self { - ibs: 512, - obs: 512, - cbs: None, - infile: None, - outfile: None, - skip: Num::Blocks(0), - seek: Num::Blocks(0), - count: None, - conv: ConvFlags::default(), - iflag: IFlags::default(), - oflag: OFlags::default(), - status: None, - } - } -} - #[derive(Debug, Default, PartialEq, Eq)] pub struct ConvFlags { ascii: bool, @@ -212,15 +199,34 @@ impl Parser { fsync: conv.fsync, }; + // Input and output block sizes. + // + // The `bs` option takes precedence. If either is not + // provided, `ibs` and `obs` are each 512 bytes by default. + let (ibs, obs) = match self.bs { + None => (self.ibs.unwrap_or(512), self.obs.unwrap_or(512)), + Some(bs) => (bs, bs), + }; + + // Whether to buffer partial output blocks until they are completed. + // + // From the GNU `dd` documentation for the `bs=BYTES` option: + // + // > [...] if no data-transforming 'conv' option is specified, + // > input is copied to the output as soon as it's read, even if + // > it is smaller than the block size. + // + let buffered = self.bs.is_none() || self.is_conv_specified; + let skip = self .skip .force_bytes_if(self.iflag.skip_bytes) - .to_bytes(self.ibs as u64); + .to_bytes(ibs as u64); let seek = self .seek .force_bytes_if(self.oflag.seek_bytes) - .to_bytes(self.obs as u64); + .to_bytes(obs as u64); let count = self.count.map(|c| c.force_bytes_if(self.iflag.count_bytes)); @@ -230,8 +236,9 @@ impl Parser { count, iconv, oconv, - ibs: self.ibs, - obs: self.obs, + ibs, + obs, + buffered, infile: self.infile, outfile: self.outfile, iflags: self.iflag, @@ -244,18 +251,17 @@ impl Parser { match operand.split_once('=') { None => return Err(ParseError::UnrecognizedOperand(operand.to_string())), Some((k, v)) => match k { - "bs" => { - let bs = Self::parse_bytes(k, v)?; - self.ibs = bs; - self.obs = bs; - } + "bs" => self.bs = Some(Self::parse_bytes(k, v)?), "cbs" => self.cbs = Some(Self::parse_bytes(k, v)?), - "conv" => self.parse_conv_flags(v)?, + "conv" => { + self.is_conv_specified = true; + self.parse_conv_flags(v)?; + } "count" => self.count = Some(Self::parse_n(v)?), - "ibs" => self.ibs = Self::parse_bytes(k, v)?, + "ibs" => self.ibs = Some(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 = Some(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)?, diff --git a/src/uu/dd/src/parseargs/unit_tests.rs b/src/uu/dd/src/parseargs/unit_tests.rs index 142e49fd0ba..51b0933e926 100644 --- a/src/uu/dd/src/parseargs/unit_tests.rs +++ b/src/uu/dd/src/parseargs/unit_tests.rs @@ -358,6 +358,7 @@ fn parse_icf_tokens_remaining() { fsync: true, ..Default::default() }, + is_conv_specified: true, ..Default::default() }) ); diff --git a/src/uu/dd/src/progress.rs b/src/uu/dd/src/progress.rs index 4fe04cb0e67..238b2ab39a2 100644 --- a/src/uu/dd/src/progress.rs +++ b/src/uu/dd/src/progress.rs @@ -13,8 +13,10 @@ use std::io::Write; use std::sync::mpsc; use std::time::Duration; -use uucore::error::UResult; -use uucore::memo::sprintf; +use uucore::{ + error::UResult, + format::num_format::{FloatVariant, Formatter}, +}; use crate::numbers::{to_magnitude_and_suffix, SuffixType}; @@ -152,7 +154,14 @@ impl ProgUpdate { let (carriage_return, newline) = if rewrite { ("\r", "") } else { ("", "\n") }; // The duration should be formatted as in `printf %g`. - let duration_str = sprintf("%g", &[duration.to_string()])?; + let mut duration_str = Vec::new(); + uucore::format::num_format::Float { + variant: FloatVariant::Shortest, + ..Default::default() + } + .fmt(&mut duration_str, duration)?; + // We assume that printf will output valid UTF-8 + let duration_str = std::str::from_utf8(&duration_str).unwrap(); // If the number of bytes written is sufficiently large, then // print a more concise representation of the number, like @@ -379,6 +388,17 @@ impl std::ops::AddAssign for WriteStat { } } +impl std::ops::Add for WriteStat { + type Output = Self; + fn add(self, other: Self) -> Self { + Self { + writes_complete: self.writes_complete + other.writes_complete, + writes_partial: self.writes_partial + other.writes_partial, + bytes_total: self.bytes_total + other.bytes_total, + } + } +} + /// How much detail to report when printing transfer statistics. /// /// This corresponds to the available settings of the `status` diff --git a/src/uu/df/Cargo.toml b/src/uu/df/Cargo.toml index d074e6be74f..e9aa192e820 100644 --- a/src/uu/df/Cargo.toml +++ b/src/uu/df/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_df" -version = "0.0.23" +version = "0.0.24" authors = ["uutils developers"] license = "MIT" description = "df ~ (uutils) display file system information" diff --git a/src/uu/df/src/df.rs b/src/uu/df/src/df.rs index 78325f3d2ad..c21ba98471a 100644 --- a/src/uu/df/src/df.rs +++ b/src/uu/df/src/df.rs @@ -336,7 +336,7 @@ fn filter_mount_list(vmi: Vec, opt: &Options) -> Vec { fn get_all_filesystems(opt: &Options) -> Result, std::io::Error> { // Run a sync call before any operation if so instructed. if opt.sync { - #[cfg(not(windows))] + #[cfg(not(any(windows, target_os = "redox")))] unsafe { #[cfg(not(target_os = "android"))] uucore::libc::sync(); diff --git a/src/uu/dir/Cargo.toml b/src/uu/dir/Cargo.toml index 628c0fb8cfe..b82298ce319 100644 --- a/src/uu/dir/Cargo.toml +++ b/src/uu/dir/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_dir" -version = "0.0.23" +version = "0.0.24" authors = ["uutils developers"] license = "MIT" description = "shortcut to ls -C -b" diff --git a/src/uu/dircolors/Cargo.toml b/src/uu/dircolors/Cargo.toml index 6099b5a8428..1bf87c22cc1 100644 --- a/src/uu/dircolors/Cargo.toml +++ b/src/uu/dircolors/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_dircolors" -version = "0.0.23" +version = "0.0.24" authors = ["uutils developers"] license = "MIT" description = "dircolors ~ (uutils) display commands to set LS_COLORS" @@ -16,7 +16,7 @@ path = "src/dircolors.rs" [dependencies] clap = { workspace = true } -uucore = { workspace = true } +uucore = { workspace = true, features = ["colors"] } [[bin]] name = "dircolors" diff --git a/src/uu/dircolors/src/colors.rs b/src/uu/dircolors/src/colors.rs deleted file mode 100644 index c0a981db89c..00000000000 --- a/src/uu/dircolors/src/colors.rs +++ /dev/null @@ -1,225 +0,0 @@ -// 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) EIGHTBIT ETERM MULTIHARDLINK cpio dtterm jfbterm konsole kterm mlterm rmvb rxvt stat'able svgz tmux webm xspf COLORTERM tzst avif tzst mjpg mjpeg webp dpkg rpmnew rpmorig rpmsave - -pub const INTERNAL_DB: &str = r#"# Configuration file for dircolors, a utility to help you set the -# LS_COLORS environment variable used by GNU ls with the --color option. -# Copyright (C) 1996-2022 Free Software Foundation, Inc. -# Copying and distribution of this file, with or without modification, -# are permitted provided the copyright notice and this notice are preserved. -# The keywords COLOR, OPTIONS, and EIGHTBIT (honored by the -# slackware version of dircolors) are recognized but ignored. -# Global config options can be specified before TERM or COLORTERM entries -# Below are TERM or COLORTERM entries, which can be glob patterns, which -# restrict following config to systems with matching environment variables. -COLORTERM ?* -TERM Eterm -TERM ansi -TERM *color* -TERM con[0-9]*x[0-9]* -TERM cons25 -TERM console -TERM cygwin -TERM *direct* -TERM dtterm -TERM gnome -TERM hurd -TERM jfbterm -TERM konsole -TERM kterm -TERM linux -TERM linux-c -TERM mlterm -TERM putty -TERM rxvt* -TERM screen* -TERM st -TERM terminator -TERM tmux* -TERM vt100 -TERM xterm* -# Below are the color init strings for the basic file types. -# One can use codes for 256 or more colors supported by modern terminals. -# The default color codes use the capabilities of an 8 color terminal -# with some additional attributes as per the following codes: -# Attribute codes: -# 00=none 01=bold 04=underscore 05=blink 07=reverse 08=concealed -# Text color codes: -# 30=black 31=red 32=green 33=yellow 34=blue 35=magenta 36=cyan 37=white -# Background color codes: -# 40=black 41=red 42=green 43=yellow 44=blue 45=magenta 46=cyan 47=white -#NORMAL 00 # no color code at all -#FILE 00 # regular file: use no color at all -RESET 0 # reset to "normal" color -DIR 01;34 # directory -LINK 01;36 # symbolic link. (If you set this to 'target' instead of a - # numerical value, the color is as for the file pointed to.) -MULTIHARDLINK 00 # regular file with more than one link -FIFO 40;33 # pipe -SOCK 01;35 # socket -DOOR 01;35 # door -BLK 40;33;01 # block device driver -CHR 40;33;01 # character device driver -ORPHAN 40;31;01 # symlink to nonexistent file, or non-stat'able file ... -MISSING 00 # ... and the files they point to -SETUID 37;41 # file that is setuid (u+s) -SETGID 30;43 # file that is setgid (g+s) -CAPABILITY 00 # file with capability (very expensive to lookup) -STICKY_OTHER_WRITABLE 30;42 # dir that is sticky and other-writable (+t,o+w) -OTHER_WRITABLE 34;42 # dir that is other-writable (o+w) and not sticky -STICKY 37;44 # dir with the sticky bit set (+t) and not other-writable -# This is for files with execute permission: -EXEC 01;32 -# List any file extensions like '.gz' or '.tar' that you would like ls -# to color below. Put the extension, a space, and the color init string. -# (and any comments you want to add after a '#') -# If you use DOS-style suffixes, you may want to uncomment the following: -#.cmd 01;32 # executables (bright green) -#.exe 01;32 -#.com 01;32 -#.btm 01;32 -#.bat 01;32 -# Or if you want to color scripts even if they do not have the -# executable bit actually set. -#.sh 01;32 -#.csh 01;32 - # archives or compressed (bright red) -.tar 01;31 -.tgz 01;31 -.arc 01;31 -.arj 01;31 -.taz 01;31 -.lha 01;31 -.lz4 01;31 -.lzh 01;31 -.lzma 01;31 -.tlz 01;31 -.txz 01;31 -.tzo 01;31 -.t7z 01;31 -.zip 01;31 -.z 01;31 -.dz 01;31 -.gz 01;31 -.lrz 01;31 -.lz 01;31 -.lzo 01;31 -.xz 01;31 -.zst 01;31 -.tzst 01;31 -.bz2 01;31 -.bz 01;31 -.tbz 01;31 -.tbz2 01;31 -.tz 01;31 -.deb 01;31 -.rpm 01;31 -.jar 01;31 -.war 01;31 -.ear 01;31 -.sar 01;31 -.rar 01;31 -.alz 01;31 -.ace 01;31 -.zoo 01;31 -.cpio 01;31 -.7z 01;31 -.rz 01;31 -.cab 01;31 -.wim 01;31 -.swm 01;31 -.dwm 01;31 -.esd 01;31 -# image formats -.avif 01;35 -.jpg 01;35 -.jpeg 01;35 -.mjpg 01;35 -.mjpeg 01;35 -.gif 01;35 -.bmp 01;35 -.pbm 01;35 -.pgm 01;35 -.ppm 01;35 -.tga 01;35 -.xbm 01;35 -.xpm 01;35 -.tif 01;35 -.tiff 01;35 -.png 01;35 -.svg 01;35 -.svgz 01;35 -.mng 01;35 -.pcx 01;35 -.mov 01;35 -.mpg 01;35 -.mpeg 01;35 -.m2v 01;35 -.mkv 01;35 -.webm 01;35 -.webp 01;35 -.ogm 01;35 -.mp4 01;35 -.m4v 01;35 -.mp4v 01;35 -.vob 01;35 -.qt 01;35 -.nuv 01;35 -.wmv 01;35 -.asf 01;35 -.rm 01;35 -.rmvb 01;35 -.flc 01;35 -.avi 01;35 -.fli 01;35 -.flv 01;35 -.gl 01;35 -.dl 01;35 -.xcf 01;35 -.xwd 01;35 -.yuv 01;35 -.cgm 01;35 -.emf 01;35 -# https://wiki.xiph.org/MIME_Types_and_File_Extensions -.ogv 01;35 -.ogx 01;35 -# audio formats -.aac 00;36 -.au 00;36 -.flac 00;36 -.m4a 00;36 -.mid 00;36 -.midi 00;36 -.mka 00;36 -.mp3 00;36 -.mpc 00;36 -.ogg 00;36 -.ra 00;36 -.wav 00;36 -# https://wiki.xiph.org/MIME_Types_and_File_Extensions -.oga 00;36 -.opus 00;36 -.spx 00;36 -.xspf 00;36 -# backup files -*~ 00;90 -*# 00;90 -.bak 00;90 -.old 00;90 -.orig 00;90 -.part 00;90 -.rej 00;90 -.swp 00;90 -.tmp 00;90 -.dpkg-dist 00;90 -.dpkg-old 00;90 -.ucf-dist 00;90 -.ucf-new 00;90 -.ucf-old 00;90 -.rpmnew 00;90 -.rpmorig 00;90 -.rpmsave 00;90 -# Subsequent TERM or COLORTERM entries, can be used to add / override -# config specific to those matching environment variables."#; diff --git a/src/uu/dircolors/src/dircolors.rs b/src/uu/dircolors/src/dircolors.rs index 2e3087d810b..531c3ee474c 100644 --- a/src/uu/dircolors/src/dircolors.rs +++ b/src/uu/dircolors/src/dircolors.rs @@ -8,10 +8,12 @@ use std::borrow::Borrow; use std::env; use std::fs::File; +//use std::io::IsTerminal; use std::io::{BufRead, BufReader}; use std::path::Path; use clap::{crate_version, Arg, ArgAction, Command}; +use uucore::colors::{FILE_ATTRIBUTE_CODES, FILE_COLORS, FILE_TYPES, TERMS}; use uucore::display::Quotable; use uucore::error::{UResult, USimpleError, UUsageError}; use uucore::{help_about, help_section, help_usage}; @@ -28,9 +30,6 @@ const USAGE: &str = help_usage!("dircolors.md"); const ABOUT: &str = help_about!("dircolors.md"); const AFTER_HELP: &str = help_section!("after help", "dircolors.md"); -mod colors; -use self::colors::INTERNAL_DB; - #[derive(PartialEq, Eq, Debug)] pub enum OutputFmt { Shell, @@ -57,10 +56,79 @@ pub fn guess_syntax() -> OutputFmt { } } +fn get_colors_format_strings(fmt: &OutputFmt) -> (String, String) { + let prefix = match fmt { + OutputFmt::Shell => "LS_COLORS='".to_string(), + OutputFmt::CShell => "setenv LS_COLORS '".to_string(), + OutputFmt::Display => String::new(), + OutputFmt::Unknown => unreachable!(), + }; + + let suffix = match fmt { + OutputFmt::Shell => "';\nexport LS_COLORS".to_string(), + OutputFmt::CShell => "'".to_string(), + OutputFmt::Display => String::new(), + OutputFmt::Unknown => unreachable!(), + }; + + (prefix, suffix) +} + +pub fn generate_type_output(fmt: &OutputFmt) -> String { + match fmt { + OutputFmt::Display => FILE_TYPES + .iter() + .map(|&(_, key, val)| format!("\x1b[{}m{}\t{}\x1b[0m", val, key, val)) + .collect::>() + .join("\n"), + _ => { + // Existing logic for other formats + FILE_TYPES + .iter() + .map(|&(_, v1, v2)| format!("{}={}", v1, v2)) + .collect::>() + .join(":") + } + } +} + +fn generate_ls_colors(fmt: &OutputFmt, sep: &str) -> String { + match fmt { + OutputFmt::Display => { + let mut display_parts = vec![]; + let type_output = generate_type_output(fmt); + display_parts.push(type_output); + for &(extension, code) in FILE_COLORS { + let prefix = if extension.starts_with('*') { "" } else { "*" }; + let formatted_extension = + format!("\x1b[{}m{}{}\t{}\x1b[0m", code, prefix, extension, code); + display_parts.push(formatted_extension); + } + display_parts.join("\n") + } + _ => { + // existing logic for other formats + let mut parts = vec![]; + for &(extension, code) in FILE_COLORS { + let prefix = if extension.starts_with('*') { "" } else { "*" }; + let formatted_extension = format!("{}{}", prefix, extension); + parts.push(format!("{}={}", formatted_extension, code)); + } + let (prefix, suffix) = get_colors_format_strings(fmt); + let ls_colors = parts.join(sep); + format!( + "{}{}:{}:{}", + prefix, + generate_type_output(fmt), + ls_colors, + suffix + ) + } + } +} + #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let args = args.collect_ignore(); - let matches = uu_app().try_get_matches_from(args)?; let files = matches @@ -97,7 +165,8 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { ), )); } - println!("{INTERNAL_DB}"); + + println!("{}", generate_dircolors_config()); return Ok(()); } @@ -125,7 +194,20 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let result; if files.is_empty() { - result = parse(INTERNAL_DB.lines(), &out_format, ""); + println!("{}", generate_ls_colors(&out_format, ":")); + return Ok(()); + /* + // Check if data is being piped into the program + if std::io::stdin().is_terminal() { + // No data piped, use default behavior + println!("{}", generate_ls_colors(&out_format, ":")); + return Ok(()); + } else { + // Data is piped, process the input from stdin + let fin = BufReader::new(std::io::stdin()); + result = parse(fin.lines().map_while(Result::ok), &out_format, "-"); + } + */ } else if files.len() > 1 { return Err(UUsageError::new( 1, @@ -133,6 +215,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { )); } else if files[0].eq("-") { let fin = BufReader::new(std::io::stdin()); + // For example, for echo "owt 40;33"|dircolors -b - result = parse(fin.lines().map_while(Result::ok), &out_format, files[0]); } else { let path = Path::new(files[0]); @@ -276,69 +359,25 @@ enum ParseState { Pass, } -use std::collections::HashMap; use uucore::{format_usage, parse_glob}; #[allow(clippy::cognitive_complexity)] -fn parse(lines: T, fmt: &OutputFmt, fp: &str) -> Result +fn parse(user_input: T, fmt: &OutputFmt, fp: &str) -> Result where T: IntoIterator, T::Item: Borrow, { - // 1790 > $(dircolors | wc -m) let mut result = String::with_capacity(1790); - match fmt { - OutputFmt::Shell => result.push_str("LS_COLORS='"), - OutputFmt::CShell => result.push_str("setenv LS_COLORS '"), - OutputFmt::Display => (), - OutputFmt::Unknown => unreachable!(), - } + let (prefix, suffix) = get_colors_format_strings(fmt); - let mut table: HashMap<&str, &str> = HashMap::with_capacity(48); - table.insert("normal", "no"); - table.insert("norm", "no"); - table.insert("file", "fi"); - table.insert("reset", "rs"); - table.insert("dir", "di"); - table.insert("lnk", "ln"); - table.insert("link", "ln"); - table.insert("symlink", "ln"); - table.insert("orphan", "or"); - table.insert("missing", "mi"); - table.insert("fifo", "pi"); - table.insert("pipe", "pi"); - table.insert("sock", "so"); - table.insert("blk", "bd"); - table.insert("block", "bd"); - table.insert("chr", "cd"); - table.insert("char", "cd"); - table.insert("door", "do"); - table.insert("exec", "ex"); - table.insert("left", "lc"); - table.insert("leftcode", "lc"); - table.insert("right", "rc"); - table.insert("rightcode", "rc"); - table.insert("end", "ec"); - table.insert("endcode", "ec"); - table.insert("suid", "su"); - table.insert("setuid", "su"); - table.insert("sgid", "sg"); - table.insert("setgid", "sg"); - table.insert("sticky", "st"); - table.insert("other_writable", "ow"); - table.insert("owr", "ow"); - table.insert("sticky_other_writable", "tw"); - table.insert("owt", "tw"); - table.insert("capability", "ca"); - table.insert("multihardlink", "mh"); - table.insert("clrtoeol", "cl"); + result.push_str(&prefix); let term = env::var("TERM").unwrap_or_else(|_| "none".to_owned()); let term = term.as_str(); let mut state = ParseState::Global; - for (num, line) in lines.into_iter().enumerate() { + for (num, line) in user_input.into_iter().enumerate() { let num = num + 1; let line = line.borrow().purify(); if line.is_empty() { @@ -350,13 +389,13 @@ where let (key, val) = line.split_two(); if val.is_empty() { return Err(format!( + // The double space is what GNU is doing "{}:{}: invalid line; missing second token", fp.maybe_quote(), num )); } let lower = key.to_lowercase(); - if lower == "term" || lower == "colorterm" { if term.fnmatch(val) { state = ParseState::Matched; @@ -370,6 +409,8 @@ where state = ParseState::Continue; } if state != ParseState::Pass { + let search_key = lower.as_str(); + if key.starts_with('.') { if *fmt == OutputFmt::Display { result.push_str(format!("\x1b[{val}m*{key}\t{val}\x1b[0m\n").as_str()); @@ -384,7 +425,10 @@ where } } else if lower == "options" || lower == "color" || lower == "eightbit" { // Slackware only. Ignore - } else if let Some(s) = table.get(lower.as_str()) { + } else if let Some((_, s)) = FILE_ATTRIBUTE_CODES + .iter() + .find(|&&(key, _)| key == search_key) + { if *fmt == OutputFmt::Display { result.push_str(format!("\x1b[{val}m{s}\t{val}\x1b[0m\n").as_str()); } else { @@ -402,15 +446,11 @@ where } } - match fmt { - OutputFmt::Shell => result.push_str("';\nexport LS_COLORS"), - OutputFmt::CShell => result.push('\''), - OutputFmt::Display => { - // remove latest "\n" - result.pop(); - } - OutputFmt::Unknown => unreachable!(), + if fmt == &OutputFmt::Display { + // remove latest "\n" + result.pop(); } + result.push_str(&suffix); Ok(result) } @@ -436,6 +476,58 @@ fn escape(s: &str) -> String { result } +pub fn generate_dircolors_config() -> String { + let mut config = String::new(); + + config.push_str( + "\ + # Configuration file for dircolors, a utility to help you set the\n\ + # LS_COLORS environment variable used by GNU ls with the --color option.\n\ + # The keywords COLOR, OPTIONS, and EIGHTBIT (honored by the\n\ + # slackware version of dircolors) are recognized but ignored.\n\ + # Global config options can be specified before TERM or COLORTERM entries\n\ + # Below are TERM or COLORTERM entries, which can be glob patterns, which\n\ + # restrict following config to systems with matching environment variables.\n\ + ", + ); + config.push_str("COLORTERM ?*\n"); + for term in TERMS { + config.push_str(&format!("TERM {}\n", term)); + } + + config.push_str( + "\ + # Below are the color init strings for the basic file types.\n\ + # One can use codes for 256 or more colors supported by modern terminals.\n\ + # The default color codes use the capabilities of an 8 color terminal\n\ + # with some additional attributes as per the following codes:\n\ + # Attribute codes:\n\ + # 00=none 01=bold 04=underscore 05=blink 07=reverse 08=concealed\n\ + # Text color codes:\n\ + # 30=black 31=red 32=green 33=yellow 34=blue 35=magenta 36=cyan 37=white\n\ + # Background color codes:\n\ + # 40=black 41=red 42=green 43=yellow 44=blue 45=magenta 46=cyan 47=white\n\ + #NORMAL 00 # no color code at all\n\ + #FILE 00 # regular file: use no color at all\n\ + ", + ); + + for (name, _, code) in FILE_TYPES { + config.push_str(&format!("{} {}\n", name, code)); + } + + config.push_str("# List any file extensions like '.gz' or '.tar' that you would like ls\n"); + config.push_str("# to color below. Put the extension, a space, and the color init string.\n"); + + for (ext, color) in FILE_COLORS { + config.push_str(&format!("{} {}\n", ext, color)); + } + config.push_str("# Subsequent TERM or COLORTERM entries, can be used to add / override\n"); + config.push_str("# config specific to those matching environment variables."); + + config +} + #[cfg(test)] mod tests { use super::escape; diff --git a/src/uu/dirname/Cargo.toml b/src/uu/dirname/Cargo.toml index b093ce3c55c..85391859663 100644 --- a/src/uu/dirname/Cargo.toml +++ b/src/uu/dirname/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_dirname" -version = "0.0.23" +version = "0.0.24" authors = ["uutils developers"] license = "MIT" description = "dirname ~ (uutils) display parent directory of PATHNAME" diff --git a/src/uu/dirname/src/dirname.rs b/src/uu/dirname/src/dirname.rs index 51935cb7f23..a645b05fd72 100644 --- a/src/uu/dirname/src/dirname.rs +++ b/src/uu/dirname/src/dirname.rs @@ -21,8 +21,6 @@ mod options { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let args = args.collect_lossy(); - let matches = uu_app().after_help(AFTER_HELP).try_get_matches_from(args)?; let line_ending = LineEnding::from_zero_flag(matches.get_flag(options::ZERO)); diff --git a/src/uu/du/Cargo.toml b/src/uu/du/Cargo.toml index 5e87b2f4381..8b9eb062e48 100644 --- a/src/uu/du/Cargo.toml +++ b/src/uu/du/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uu_du" -version = "0.0.23" +version = "0.0.24" authors = ["uutils developers"] license = "MIT" description = "du ~ (uutils) display disk usage" diff --git a/src/uu/du/src/du.rs b/src/uu/du/src/du.rs index 148b197df33..62fcfceda01 100644 --- a/src/uu/du/src/du.rs +++ b/src/uu/du/src/du.rs @@ -3,35 +3,30 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -use chrono::prelude::DateTime; -use chrono::Local; -use clap::ArgAction; -use clap::{crate_version, Arg, ArgMatches, Command}; +use chrono::{DateTime, Local}; +use clap::{crate_version, Arg, ArgAction, ArgMatches, Command}; use glob::Pattern; use std::collections::HashSet; use std::env; -use std::fs; -use std::fs::File; +use std::error::Error; +use std::fmt::Display; #[cfg(not(windows))] use std::fs::Metadata; -use std::io::BufRead; -use std::io::BufReader; -use std::io::Result; -use std::iter; +use std::fs::{self, File}; +use std::io::{BufRead, BufReader}; #[cfg(not(windows))] use std::os::unix::fs::MetadataExt; #[cfg(windows)] use std::os::windows::fs::MetadataExt; #[cfg(windows)] use std::os::windows::io::AsRawHandle; -use std::path::Path; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use std::str::FromStr; +use std::sync::mpsc; +use std::thread; use std::time::{Duration, UNIX_EPOCH}; -use std::{error::Error, fmt::Display}; use uucore::display::{print_verbatim, Quotable}; -use uucore::error::FromIo; -use uucore::error::{set_exit_code, UError, UResult, USimpleError}; +use uucore::error::{set_exit_code, FromIo, UError, UResult, USimpleError}; use uucore::line_ending::LineEnding; use uucore::parse_glob; use uucore::parse_size::{parse_size_u64, ParseSizeError}; @@ -70,6 +65,7 @@ mod options { pub const INODES: &str = "inodes"; pub const EXCLUDE: &str = "exclude"; pub const EXCLUDE_FROM: &str = "exclude-from"; + pub const FILES0_FROM: &str = "files0-from"; pub const VERBOSE: &str = "verbose"; pub const FILE: &str = "FILE"; } @@ -81,25 +77,49 @@ const USAGE: &str = help_usage!("du.md"); // TODO: Support Z & Y (currently limited by size of u64) const UNITS: [(char, u32); 6] = [('E', 6), ('P', 5), ('T', 4), ('G', 3), ('M', 2), ('K', 1)]; -struct Options { +struct TraversalOptions { all: bool, - max_depth: Option, - total: bool, separate_dirs: bool, one_file_system: bool, dereference: Deref, count_links: bool, - inodes: bool, verbose: bool, + excludes: Vec, +} + +struct StatPrinter { + total: bool, + inodes: bool, + max_depth: Option, + threshold: Option, + apparent_size: bool, + size_format: SizeFormat, + time: Option