mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-28 11:37:44 +00:00
Merge branch 'main' into fix_stat_redirect
This commit is contained in:
commit
1de10b8333
35 changed files with 1027 additions and 297 deletions
|
@ -9,3 +9,12 @@ rustflags = [
|
||||||
"-Wclippy::single_char_pattern",
|
"-Wclippy::single_char_pattern",
|
||||||
"-Wclippy::explicit_iter_loop",
|
"-Wclippy::explicit_iter_loop",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[build]
|
||||||
|
# See https://github.com/time-rs/time/issues/293#issuecomment-1005002386. The
|
||||||
|
# unsoundness here is not in the `time` library, but in the Rust stdlib, and as
|
||||||
|
# such it needs to be fixed there.
|
||||||
|
rustflags = "--cfg unsound_local_offset"
|
||||||
|
|
||||||
|
[target.'cfg(target_os = "linux")']
|
||||||
|
rustflags = ["--cfg", "unsound_local_offset"]
|
||||||
|
|
5
.github/dependabot.yml
vendored
5
.github/dependabot.yml
vendored
|
@ -5,3 +5,8 @@ updates:
|
||||||
schedule:
|
schedule:
|
||||||
interval: "daily"
|
interval: "daily"
|
||||||
open-pull-requests-limit: 5
|
open-pull-requests-limit: 5
|
||||||
|
- package-ecosystem: "github-actions"
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: daily
|
||||||
|
open-pull-requests-limit: 5
|
||||||
|
|
46
.github/workflows/CICD.yml
vendored
46
.github/workflows/CICD.yml
vendored
|
@ -24,7 +24,7 @@ jobs:
|
||||||
name: Style/cargo-deny
|
name: Style/cargo-deny
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
- uses: EmbarkStudios/cargo-deny-action@v1
|
- uses: EmbarkStudios/cargo-deny-action@v1
|
||||||
|
|
||||||
style_deps:
|
style_deps:
|
||||||
|
@ -43,7 +43,7 @@ jobs:
|
||||||
- { os: macos-latest , features: "feat_Tier1,feat_require_unix,feat_require_unix_utmpx" }
|
- { os: macos-latest , features: "feat_Tier1,feat_require_unix,feat_require_unix_utmpx" }
|
||||||
- { os: windows-latest , features: feat_os_windows }
|
- { os: windows-latest , features: feat_os_windows }
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
- uses: Swatinem/rust-cache@v1
|
- uses: Swatinem/rust-cache@v1
|
||||||
- name: Initialize workflow variables
|
- name: Initialize workflow variables
|
||||||
id: vars
|
id: vars
|
||||||
|
@ -101,7 +101,7 @@ jobs:
|
||||||
job:
|
job:
|
||||||
- { os: ubuntu-latest , features: feat_os_unix }
|
- { os: ubuntu-latest , features: feat_os_unix }
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
- uses: Swatinem/rust-cache@v1
|
- uses: Swatinem/rust-cache@v1
|
||||||
- name: Initialize workflow variables
|
- name: Initialize workflow variables
|
||||||
id: vars
|
id: vars
|
||||||
|
@ -165,7 +165,7 @@ jobs:
|
||||||
- { os: macos-latest , features: feat_os_macos }
|
- { os: macos-latest , features: feat_os_macos }
|
||||||
- { os: windows-latest , features: feat_os_windows }
|
- { os: windows-latest , features: feat_os_windows }
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
- uses: Swatinem/rust-cache@v1
|
- uses: Swatinem/rust-cache@v1
|
||||||
- name: Initialize workflow variables
|
- name: Initialize workflow variables
|
||||||
id: vars
|
id: vars
|
||||||
|
@ -223,7 +223,7 @@ jobs:
|
||||||
job:
|
job:
|
||||||
- { os: ubuntu-latest , features: feat_os_unix }
|
- { os: ubuntu-latest , features: feat_os_unix }
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
- uses: Swatinem/rust-cache@v1
|
- uses: Swatinem/rust-cache@v1
|
||||||
- name: Initialize workflow variables
|
- name: Initialize workflow variables
|
||||||
id: vars
|
id: vars
|
||||||
|
@ -275,7 +275,7 @@ jobs:
|
||||||
# - { os: macos-latest , features: feat_os_macos }
|
# - { os: macos-latest , features: feat_os_macos }
|
||||||
# - { os: windows-latest , features: feat_os_windows }
|
# - { os: windows-latest , features: feat_os_windows }
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
- uses: Swatinem/rust-cache@v1
|
- uses: Swatinem/rust-cache@v1
|
||||||
- name: Initialize workflow variables
|
- name: Initialize workflow variables
|
||||||
id: vars
|
id: vars
|
||||||
|
@ -320,7 +320,7 @@ jobs:
|
||||||
job:
|
job:
|
||||||
- { os: ubuntu-latest , features: feat_os_unix }
|
- { os: ubuntu-latest , features: feat_os_unix }
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
- uses: Swatinem/rust-cache@v1
|
- uses: Swatinem/rust-cache@v1
|
||||||
- name: Initialize workflow variables
|
- name: Initialize workflow variables
|
||||||
id: vars
|
id: vars
|
||||||
|
@ -384,9 +384,9 @@ jobs:
|
||||||
uses: actions-rs/cargo@v1
|
uses: actions-rs/cargo@v1
|
||||||
with:
|
with:
|
||||||
command: test
|
command: test
|
||||||
args: ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }} -p uucore -p coreutils
|
args: -v ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }} -p uucore -p coreutils
|
||||||
env:
|
env:
|
||||||
RUSTFLAGS: "-Awarnings"
|
RUSTFLAGS: "-Awarnings --cfg unsound_local_offset"
|
||||||
|
|
||||||
deps:
|
deps:
|
||||||
name: Dependencies
|
name: Dependencies
|
||||||
|
@ -397,7 +397,7 @@ jobs:
|
||||||
job:
|
job:
|
||||||
- { os: ubuntu-latest , features: feat_os_unix }
|
- { os: ubuntu-latest , features: feat_os_unix }
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
- uses: Swatinem/rust-cache@v1
|
- uses: Swatinem/rust-cache@v1
|
||||||
- name: Install `rust` toolchain
|
- name: Install `rust` toolchain
|
||||||
uses: actions-rs/toolchain@v1
|
uses: actions-rs/toolchain@v1
|
||||||
|
@ -422,7 +422,7 @@ jobs:
|
||||||
job:
|
job:
|
||||||
- { os: ubuntu-latest , features: feat_os_unix }
|
- { os: ubuntu-latest , features: feat_os_unix }
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
- uses: Swatinem/rust-cache@v1
|
- uses: Swatinem/rust-cache@v1
|
||||||
- name: Install `rust` toolchain
|
- name: Install `rust` toolchain
|
||||||
uses: actions-rs/toolchain@v1
|
uses: actions-rs/toolchain@v1
|
||||||
|
@ -452,7 +452,7 @@ jobs:
|
||||||
- { os: macos-latest , features: feat_os_macos }
|
- { os: macos-latest , features: feat_os_macos }
|
||||||
- { os: windows-latest , features: feat_os_windows }
|
- { os: windows-latest , features: feat_os_windows }
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
- uses: Swatinem/rust-cache@v1
|
- uses: Swatinem/rust-cache@v1
|
||||||
- name: Install `rust` toolchain
|
- name: Install `rust` toolchain
|
||||||
uses: actions-rs/toolchain@v1
|
uses: actions-rs/toolchain@v1
|
||||||
|
@ -478,7 +478,7 @@ jobs:
|
||||||
- { os: macos-latest , features: feat_os_macos }
|
- { os: macos-latest , features: feat_os_macos }
|
||||||
- { os: windows-latest , features: feat_os_windows }
|
- { os: windows-latest , features: feat_os_windows }
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
- uses: Swatinem/rust-cache@v1
|
- uses: Swatinem/rust-cache@v1
|
||||||
- name: Install `rust` toolchain
|
- name: Install `rust` toolchain
|
||||||
uses: actions-rs/toolchain@v1
|
uses: actions-rs/toolchain@v1
|
||||||
|
@ -502,7 +502,7 @@ jobs:
|
||||||
job:
|
job:
|
||||||
- { os: ubuntu-latest , features: feat_os_unix }
|
- { os: ubuntu-latest , features: feat_os_unix }
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
- uses: Swatinem/rust-cache@v1
|
- uses: Swatinem/rust-cache@v1
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
shell: bash
|
shell: bash
|
||||||
|
@ -534,7 +534,7 @@ jobs:
|
||||||
--arg size "$SIZE" \
|
--arg size "$SIZE" \
|
||||||
--arg multisize "$SIZEMULTI" \
|
--arg multisize "$SIZEMULTI" \
|
||||||
'{($date): { sha: $sha, size: $size, multisize: $multisize, }}' > size-result.json
|
'{($date): { sha: $sha, size: $size, multisize: $multisize, }}' > size-result.json
|
||||||
- uses: actions/upload-artifact@v2
|
- uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: size-result
|
name: size-result
|
||||||
path: size-result.json
|
path: size-result.json
|
||||||
|
@ -568,7 +568,7 @@ jobs:
|
||||||
- { os: windows-latest , target: x86_64-pc-windows-gnu , features: feat_os_windows } ## note: requires rust >= 1.43.0 to link correctly
|
- { os: windows-latest , target: x86_64-pc-windows-gnu , features: feat_os_windows } ## note: requires rust >= 1.43.0 to link correctly
|
||||||
- { os: windows-latest , target: x86_64-pc-windows-msvc , features: feat_os_windows }
|
- { os: windows-latest , target: x86_64-pc-windows-msvc , features: feat_os_windows }
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
- uses: Swatinem/rust-cache@v1
|
- uses: Swatinem/rust-cache@v1
|
||||||
- name: Initialize workflow variables
|
- name: Initialize workflow variables
|
||||||
id: vars
|
id: vars
|
||||||
|
@ -762,7 +762,7 @@ jobs:
|
||||||
args: --target=${{ matrix.job.target }} ${{ steps.vars.outputs.CARGO_TEST_OPTIONS}} ${{ matrix.job.cargo-options }} ${{ steps.dep_vars.outputs.CARGO_UTILITY_LIST_OPTIONS }}
|
args: --target=${{ matrix.job.target }} ${{ steps.vars.outputs.CARGO_TEST_OPTIONS}} ${{ matrix.job.cargo-options }} ${{ steps.dep_vars.outputs.CARGO_UTILITY_LIST_OPTIONS }}
|
||||||
toolchain: ${{ env.RUST_MIN_SRV }}
|
toolchain: ${{ env.RUST_MIN_SRV }}
|
||||||
- name: Archive executable artifacts
|
- name: Archive executable artifacts
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: ${{ env.PROJECT_NAME }}-${{ matrix.job.target }}
|
name: ${{ env.PROJECT_NAME }}-${{ matrix.job.target }}
|
||||||
path: target/${{ matrix.job.target }}/release/${{ env.PROJECT_NAME }}${{ steps.vars.outputs.EXE_suffix }}
|
path: target/${{ matrix.job.target }}/release/${{ env.PROJECT_NAME }}${{ steps.vars.outputs.EXE_suffix }}
|
||||||
|
@ -820,7 +820,7 @@ jobs:
|
||||||
job:
|
job:
|
||||||
- { os: ubuntu-latest }
|
- { os: ubuntu-latest }
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
- uses: Swatinem/rust-cache@v1
|
- uses: Swatinem/rust-cache@v1
|
||||||
- name: Install/setup prerequisites
|
- name: Install/setup prerequisites
|
||||||
shell: bash
|
shell: bash
|
||||||
|
@ -857,9 +857,9 @@ jobs:
|
||||||
env:
|
env:
|
||||||
TERMUX: v0.118.0
|
TERMUX: v0.118.0
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
- name: AVD cache
|
- name: AVD cache
|
||||||
uses: actions/cache@v2
|
uses: actions/cache@v3
|
||||||
id: avd-cache
|
id: avd-cache
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
|
@ -911,7 +911,7 @@ jobs:
|
||||||
env:
|
env:
|
||||||
mem: 2048
|
mem: 2048
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
- uses: Swatinem/rust-cache@v1
|
- uses: Swatinem/rust-cache@v1
|
||||||
- name: Prepare, build and test
|
- name: Prepare, build and test
|
||||||
## spell-checker:ignore (ToDO) sshfs usesh vmactions
|
## spell-checker:ignore (ToDO) sshfs usesh vmactions
|
||||||
|
@ -979,7 +979,7 @@ jobs:
|
||||||
- { os: macos-latest , features: macos }
|
- { os: macos-latest , features: macos }
|
||||||
- { os: windows-latest , features: windows }
|
- { os: windows-latest , features: windows }
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
- uses: Swatinem/rust-cache@v1
|
- uses: Swatinem/rust-cache@v1
|
||||||
# - name: Reattach HEAD ## may be needed for accurate code coverage info
|
# - name: Reattach HEAD ## may be needed for accurate code coverage info
|
||||||
# run: git checkout ${{ github.head_ref }}
|
# run: git checkout ${{ github.head_ref }}
|
||||||
|
@ -1100,7 +1100,7 @@ jobs:
|
||||||
grcov . --output-type lcov --output-path "${COVERAGE_REPORT_FILE}" --branch --ignore build.rs --ignore "vendor/*" --ignore "/*" --ignore "[a-zA-Z]:/*" --excl-br-line "^\s*((debug_)?assert(_eq|_ne)?!|#\[derive\()"
|
grcov . --output-type lcov --output-path "${COVERAGE_REPORT_FILE}" --branch --ignore build.rs --ignore "vendor/*" --ignore "/*" --ignore "[a-zA-Z]:/*" --excl-br-line "^\s*((debug_)?assert(_eq|_ne)?!|#\[derive\()"
|
||||||
echo ::set-output name=report::${COVERAGE_REPORT_FILE}
|
echo ::set-output name=report::${COVERAGE_REPORT_FILE}
|
||||||
- name: Upload coverage results (to Codecov.io)
|
- name: Upload coverage results (to Codecov.io)
|
||||||
uses: codecov/codecov-action@v1
|
uses: codecov/codecov-action@v3
|
||||||
# if: steps.vars.outputs.HAS_CODECOV_TOKEN
|
# if: steps.vars.outputs.HAS_CODECOV_TOKEN
|
||||||
with:
|
with:
|
||||||
# token: ${{ secrets.CODECOV_TOKEN }}
|
# token: ${{ secrets.CODECOV_TOKEN }}
|
||||||
|
|
8
.github/workflows/FixPR.yml
vendored
8
.github/workflows/FixPR.yml
vendored
|
@ -28,7 +28,7 @@ jobs:
|
||||||
job:
|
job:
|
||||||
- { os: ubuntu-latest , features: feat_os_unix }
|
- { os: ubuntu-latest , features: feat_os_unix }
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
- uses: Swatinem/rust-cache@v1
|
- uses: Swatinem/rust-cache@v1
|
||||||
- name: Initialize job variables
|
- name: Initialize job variables
|
||||||
id: vars
|
id: vars
|
||||||
|
@ -80,7 +80,7 @@ jobs:
|
||||||
## * using the 'stable' toolchain is necessary to avoid "unexpected '--filter-platform'" errors
|
## * using the 'stable' toolchain is necessary to avoid "unexpected '--filter-platform'" errors
|
||||||
RUSTUP_TOOLCHAIN=stable cargo-tree tree --locked --all --no-dev-dependencies --no-indent --features ${{ matrix.job.features }} | grep -vE "$PWD" | sort --unique
|
RUSTUP_TOOLCHAIN=stable cargo-tree tree --locked --all --no-dev-dependencies --no-indent --features ${{ matrix.job.features }} | grep -vE "$PWD" | sort --unique
|
||||||
- name: Commit any changes (to '${{ env.BRANCH_TARGET }}')
|
- name: Commit any changes (to '${{ env.BRANCH_TARGET }}')
|
||||||
uses: EndBug/add-and-commit@v7
|
uses: EndBug/add-and-commit@v9
|
||||||
with:
|
with:
|
||||||
branch: ${{ env.BRANCH_TARGET }}
|
branch: ${{ env.BRANCH_TARGET }}
|
||||||
default_author: github_actions
|
default_author: github_actions
|
||||||
|
@ -100,7 +100,7 @@ jobs:
|
||||||
job:
|
job:
|
||||||
- { os: ubuntu-latest , features: feat_os_unix }
|
- { os: ubuntu-latest , features: feat_os_unix }
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
- uses: Swatinem/rust-cache@v1
|
- uses: Swatinem/rust-cache@v1
|
||||||
- name: Initialize job variables
|
- name: Initialize job variables
|
||||||
id: vars
|
id: vars
|
||||||
|
@ -130,7 +130,7 @@ jobs:
|
||||||
# `cargo fmt` of tests
|
# `cargo fmt` of tests
|
||||||
find tests -name "*.rs" -print0 | xargs -0 cargo fmt --
|
find tests -name "*.rs" -print0 | xargs -0 cargo fmt --
|
||||||
- name: Commit any changes (to '${{ env.BRANCH_TARGET }}')
|
- name: Commit any changes (to '${{ env.BRANCH_TARGET }}')
|
||||||
uses: EndBug/add-and-commit@v7
|
uses: EndBug/add-and-commit@v9
|
||||||
with:
|
with:
|
||||||
branch: ${{ env.BRANCH_TARGET }}
|
branch: ${{ env.BRANCH_TARGET }}
|
||||||
default_author: github_actions
|
default_author: github_actions
|
||||||
|
|
22
.github/workflows/GnuTests.yml
vendored
22
.github/workflows/GnuTests.yml
vendored
|
@ -41,11 +41,11 @@ jobs:
|
||||||
TEST_FULL_SUMMARY_FILE='gnu-full-result.json'
|
TEST_FULL_SUMMARY_FILE='gnu-full-result.json'
|
||||||
outputs SUITE_LOG_FILE TEST_FILESET_PREFIX TEST_FILESET_SUFFIX TEST_LOGS_GLOB TEST_SUMMARY_FILE TEST_FULL_SUMMARY_FILE
|
outputs SUITE_LOG_FILE TEST_FILESET_PREFIX TEST_FILESET_SUFFIX TEST_LOGS_GLOB TEST_SUMMARY_FILE TEST_FULL_SUMMARY_FILE
|
||||||
- name: Checkout code (uutil)
|
- name: Checkout code (uutil)
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
path: '${{ steps.vars.outputs.path_UUTILS }}'
|
path: '${{ steps.vars.outputs.path_UUTILS }}'
|
||||||
- name: Checkout code (GNU coreutils)
|
- name: Checkout code (GNU coreutils)
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
repository: 'coreutils/coreutils'
|
repository: 'coreutils/coreutils'
|
||||||
path: '${{ steps.vars.outputs.path_GNU }}'
|
path: '${{ steps.vars.outputs.path_GNU }}'
|
||||||
|
@ -146,22 +146,22 @@ jobs:
|
||||||
# Compress logs before upload (fails otherwise)
|
# Compress logs before upload (fails otherwise)
|
||||||
gzip ${{ steps.vars.outputs.TEST_LOGS_GLOB }}
|
gzip ${{ steps.vars.outputs.TEST_LOGS_GLOB }}
|
||||||
- name: Reserve SHA1/ID of 'test-summary'
|
- name: Reserve SHA1/ID of 'test-summary'
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: "${{ steps.summary.outputs.HASH }}"
|
name: "${{ steps.summary.outputs.HASH }}"
|
||||||
path: "${{ steps.vars.outputs.TEST_SUMMARY_FILE }}"
|
path: "${{ steps.vars.outputs.TEST_SUMMARY_FILE }}"
|
||||||
- name: Reserve test results summary
|
- name: Reserve test results summary
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: test-summary
|
name: test-summary
|
||||||
path: "${{ steps.vars.outputs.TEST_SUMMARY_FILE }}"
|
path: "${{ steps.vars.outputs.TEST_SUMMARY_FILE }}"
|
||||||
- name: Reserve test logs
|
- name: Reserve test logs
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: test-logs
|
name: test-logs
|
||||||
path: "${{ steps.vars.outputs.TEST_LOGS_GLOB }}"
|
path: "${{ steps.vars.outputs.TEST_LOGS_GLOB }}"
|
||||||
- name: Upload full json results
|
- name: Upload full json results
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: gnu-full-result.json
|
name: gnu-full-result.json
|
||||||
path: ${{ steps.vars.outputs.TEST_FULL_SUMMARY_FILE }}
|
path: ${{ steps.vars.outputs.TEST_FULL_SUMMARY_FILE }}
|
||||||
|
@ -174,8 +174,8 @@ jobs:
|
||||||
REPO_DEFAULT_BRANCH='${{ steps.vars.outputs.repo_default_branch }}'
|
REPO_DEFAULT_BRANCH='${{ steps.vars.outputs.repo_default_branch }}'
|
||||||
if test -f "${REF_LOG_FILE}"; then
|
if test -f "${REF_LOG_FILE}"; then
|
||||||
echo "Reference SHA1/ID: $(sha1sum -- "${REF_SUMMARY_FILE}")"
|
echo "Reference SHA1/ID: $(sha1sum -- "${REF_SUMMARY_FILE}")"
|
||||||
REF_ERROR=$(sed -n "s/^FAIL: \([[:print:]]\+\).*/\1/p" "${REF_LOG_FILE}" | sort)
|
REF_ERROR=$(sed -n "s/^ERROR: \([[:print:]]\+\).*/\1/p" "${REF_LOG_FILE}" | sort)
|
||||||
NEW_ERROR=$(sed -n "s/^FAIL: \([[:print:]]\+\).*/\1/p" '${{ steps.vars.outputs.path_GNU_tests }}/test-suite.log' | 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)
|
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)
|
NEW_FAILING=$(sed -n "s/^FAIL: \([[:print:]]\+\).*/\1/p" '${{ steps.vars.outputs.path_GNU_tests }}/test-suite.log' | sort)
|
||||||
for LINE in ${REF_FAILING}
|
for LINE in ${REF_FAILING}
|
||||||
|
@ -229,11 +229,11 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code uutil
|
- name: Checkout code uutil
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
path: 'uutils'
|
path: 'uutils'
|
||||||
- name: Checkout GNU coreutils
|
- name: Checkout GNU coreutils
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
repository: 'coreutils/coreutils'
|
repository: 'coreutils/coreutils'
|
||||||
path: 'gnu'
|
path: 'gnu'
|
||||||
|
@ -293,7 +293,7 @@ jobs:
|
||||||
grcov . --output-type lcov --output-path "${COVERAGE_REPORT_FILE}" --branch --ignore build.rs --ignore "vendor/*" --ignore "/*" --ignore "[a-zA-Z]:/*" --excl-br-line "^\s*((debug_)?assert(_eq|_ne)?!|#\[derive\()"
|
grcov . --output-type lcov --output-path "${COVERAGE_REPORT_FILE}" --branch --ignore build.rs --ignore "vendor/*" --ignore "/*" --ignore "[a-zA-Z]:/*" --excl-br-line "^\s*((debug_)?assert(_eq|_ne)?!|#\[derive\()"
|
||||||
echo ::set-output name=report::${COVERAGE_REPORT_FILE}
|
echo ::set-output name=report::${COVERAGE_REPORT_FILE}
|
||||||
- name: Upload coverage results (to Codecov.io)
|
- name: Upload coverage results (to Codecov.io)
|
||||||
uses: codecov/codecov-action@v2
|
uses: codecov/codecov-action@v3
|
||||||
with:
|
with:
|
||||||
file: ${{ steps.coverage.outputs.report }}
|
file: ${{ steps.coverage.outputs.report }}
|
||||||
flags: gnutests
|
flags: gnutests
|
||||||
|
|
69
Cargo.lock
generated
69
Cargo.lock
generated
|
@ -224,7 +224,7 @@ dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"num-integer",
|
"num-integer",
|
||||||
"num-traits",
|
"num-traits",
|
||||||
"time",
|
"time 0.1.44",
|
||||||
"winapi 0.3.9",
|
"winapi 0.3.9",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -273,9 +273,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap_complete"
|
name = "clap_complete"
|
||||||
version = "3.1.3"
|
version = "3.1.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1d7ca9141e27e6ebc52e3c378b0c07f3cea52db46ed1cc5861735fb697b56356"
|
checksum = "da92e6facd8d73c22745a5d3cbb59bdf8e46e3235c923e516527d8e81eec14a4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap 3.1.15",
|
"clap 3.1.15",
|
||||||
]
|
]
|
||||||
|
@ -335,7 +335,7 @@ dependencies = [
|
||||||
"sha1",
|
"sha1",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
"textwrap 0.15.0",
|
"textwrap 0.15.0",
|
||||||
"time",
|
"time 0.3.9",
|
||||||
"unindent",
|
"unindent",
|
||||||
"unix_socket",
|
"unix_socket",
|
||||||
"users",
|
"users",
|
||||||
|
@ -871,7 +871,7 @@ checksum = "418d37c8b1d42553c93648be529cb70f920d3baf8ef469b74b9638df426e0b4c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if 1.0.0",
|
"cfg-if 1.0.0",
|
||||||
"libc",
|
"libc",
|
||||||
"wasi 0.10.2+wasi-snapshot-preview1",
|
"wasi 0.10.0+wasi-snapshot-preview1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -985,6 +985,12 @@ dependencies = [
|
||||||
"either",
|
"either",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "itoa"
|
||||||
|
version = "1.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "keccak"
|
name = "keccak"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
@ -1196,9 +1202,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-traits"
|
name = "num-traits"
|
||||||
version = "0.2.14"
|
version = "0.2.15"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
|
checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"autocfg",
|
"autocfg",
|
||||||
]
|
]
|
||||||
|
@ -1213,6 +1219,15 @@ dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num_threads"
|
||||||
|
version = "0.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "aba1801fb138d8e85e11d0fc70baf4fe1cdfffda7c6cd34a854905df588e5ed0"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "number_prefix"
|
name = "number_prefix"
|
||||||
version = "0.4.0"
|
version = "0.4.0"
|
||||||
|
@ -1944,14 +1959,33 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "time"
|
name = "time"
|
||||||
version = "0.1.43"
|
version = "0.1.44"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438"
|
checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
|
"wasi 0.10.0+wasi-snapshot-preview1",
|
||||||
"winapi 0.3.9",
|
"winapi 0.3.9",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "time"
|
||||||
|
version = "0.3.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c2702e08a7a860f005826c6815dcac101b19b5eb330c27fe4a5928fec1d20ddd"
|
||||||
|
dependencies = [
|
||||||
|
"itoa",
|
||||||
|
"libc",
|
||||||
|
"num_threads",
|
||||||
|
"time-macros",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "time-macros"
|
||||||
|
version = "0.2.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "42657b1a6f4d817cda8e7a0ace261fe0cc946cf3a80314390b22cc61ae080792"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "typenum"
|
name = "typenum"
|
||||||
version = "1.15.0"
|
version = "1.15.0"
|
||||||
|
@ -1993,9 +2027,9 @@ checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unindent"
|
name = "unindent"
|
||||||
version = "0.1.8"
|
version = "0.1.9"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "514672a55d7380da379785a4d70ca8386c8883ff7eaae877be4d2081cebe73d8"
|
checksum = "52fee519a3e570f7df377a06a1a7775cdbfb7aa460be7e08de2b1f0e69973a44"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unix_socket"
|
name = "unix_socket"
|
||||||
|
@ -2400,6 +2434,7 @@ dependencies = [
|
||||||
"file_diff",
|
"file_diff",
|
||||||
"filetime",
|
"filetime",
|
||||||
"libc",
|
"libc",
|
||||||
|
"time 0.3.9",
|
||||||
"uucore",
|
"uucore",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -2875,7 +2910,7 @@ version = "0.0.13"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap 3.1.15",
|
"clap 3.1.15",
|
||||||
"filetime",
|
"filetime",
|
||||||
"time",
|
"time 0.3.9",
|
||||||
"uucore",
|
"uucore",
|
||||||
"winapi 0.3.9",
|
"winapi 0.3.9",
|
||||||
]
|
]
|
||||||
|
@ -3042,7 +3077,7 @@ dependencies = [
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"os_display",
|
"os_display",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"time",
|
"time 0.3.9",
|
||||||
"uucore_procs",
|
"uucore_procs",
|
||||||
"walkdir",
|
"walkdir",
|
||||||
"wild",
|
"wild",
|
||||||
|
@ -3090,9 +3125,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasi"
|
name = "wasi"
|
||||||
version = "0.10.2+wasi-snapshot-preview1"
|
version = "0.10.0+wasi-snapshot-preview1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
|
checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasi"
|
name = "wasi"
|
||||||
|
@ -3208,9 +3243,9 @@ checksum = "504a2476202769977a040c6364301a3f65d0cc9e3fb08600b2bda150a0488316"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "xattr"
|
name = "xattr"
|
||||||
version = "0.2.2"
|
version = "0.2.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "244c3741f4240ef46274860397c7c74e50eb23624996930e484c16679633a54c"
|
checksum = "6d1526bbe5aaeb5eb06885f4d987bcdfa5e23187055de9b83fe00156a821fabc"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
|
@ -391,7 +391,7 @@ rand = "0.8"
|
||||||
regex = "1.5"
|
regex = "1.5"
|
||||||
sha1 = { version="0.10", features=["std"] }
|
sha1 = { version="0.10", features=["std"] }
|
||||||
tempfile = "3"
|
tempfile = "3"
|
||||||
time = "0.1"
|
time = {version="0.3", features=["local-offset"]}
|
||||||
unindent = "0.1"
|
unindent = "0.1"
|
||||||
uucore = { version=">=0.0.11", package="uucore", path="src/uucore", features=["entries", "process"] }
|
uucore = { version=">=0.0.11", package="uucore", path="src/uucore", features=["entries", "process"] }
|
||||||
walkdir = "2.2"
|
walkdir = "2.2"
|
||||||
|
|
|
@ -11,6 +11,8 @@ unmaintained = "warn"
|
||||||
yanked = "warn"
|
yanked = "warn"
|
||||||
notice = "warn"
|
notice = "warn"
|
||||||
ignore = [
|
ignore = [
|
||||||
|
"RUSTSEC-2020-0159",
|
||||||
|
"RUSTSEC-2020-0071",
|
||||||
#"RUSTSEC-0000-0000",
|
#"RUSTSEC-0000-0000",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -62,7 +64,7 @@ highlight = "all"
|
||||||
# spell-checker: disable
|
# spell-checker: disable
|
||||||
skip = [
|
skip = [
|
||||||
# getrandom
|
# getrandom
|
||||||
{ name = "wasi", version="0.10.2+wasi-snapshot-preview1" },
|
{ name = "wasi", version="0.10.0+wasi-snapshot-preview1" },
|
||||||
# blake2d_simd
|
# blake2d_simd
|
||||||
{ name = "arrayvec", version = "=0.7.2" },
|
{ name = "arrayvec", version = "=0.7.2" },
|
||||||
# flimit/unix_socket
|
# flimit/unix_socket
|
||||||
|
@ -84,6 +86,8 @@ skip = [
|
||||||
{ name = "memchr", version = "=1.0.2" },
|
{ name = "memchr", version = "=1.0.2" },
|
||||||
{ name = "quote", version = "=0.3.15" },
|
{ name = "quote", version = "=0.3.15" },
|
||||||
{ name = "unicode-xid", version = "=0.0.4" },
|
{ name = "unicode-xid", version = "=0.0.4" },
|
||||||
|
# chrono
|
||||||
|
{ name = "time", version = "=0.1.44" },
|
||||||
]
|
]
|
||||||
# spell-checker: enable
|
# spell-checker: enable
|
||||||
|
|
||||||
|
|
|
@ -34,7 +34,7 @@ ioctl-sys = "0.8"
|
||||||
winapi = { version="0.3", features=["fileapi"] }
|
winapi = { version="0.3", features=["fileapi"] }
|
||||||
|
|
||||||
[target.'cfg(unix)'.dependencies]
|
[target.'cfg(unix)'.dependencies]
|
||||||
xattr="0.2.1"
|
xattr="0.2.3"
|
||||||
exacl= { version = "0.8.0", optional=true }
|
exacl= { version = "0.8.0", optional=true }
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
|
|
|
@ -5,9 +5,12 @@
|
||||||
//! Types for representing and displaying block sizes.
|
//! Types for representing and displaying block sizes.
|
||||||
use crate::OPT_BLOCKSIZE;
|
use crate::OPT_BLOCKSIZE;
|
||||||
use clap::ArgMatches;
|
use clap::ArgMatches;
|
||||||
use std::fmt;
|
use std::{env, fmt};
|
||||||
|
|
||||||
use uucore::parse_size::{parse_size, ParseSizeError};
|
use uucore::{
|
||||||
|
display::Quotable,
|
||||||
|
parse_size::{parse_size, ParseSizeError},
|
||||||
|
};
|
||||||
|
|
||||||
/// The first ten powers of 1024.
|
/// The first ten powers of 1024.
|
||||||
const IEC_BASES: [u128; 10] = [
|
const IEC_BASES: [u128; 10] = [
|
||||||
|
@ -73,7 +76,7 @@ fn to_magnitude_and_suffix_1024(n: u128) -> Result<String, ()> {
|
||||||
Err(())
|
Err(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convert a number, except multiples of 1024, into a string like "12kB" or "34MB".
|
/// Convert a number into a string like "12kB" or "34MB".
|
||||||
///
|
///
|
||||||
/// Powers of 1000 become "1kB", "1MB", "1GB", etc.
|
/// Powers of 1000 become "1kB", "1MB", "1GB", etc.
|
||||||
///
|
///
|
||||||
|
@ -110,7 +113,7 @@ fn to_magnitude_and_suffix_not_powers_of_1024(n: u128) -> Result<String, ()> {
|
||||||
///
|
///
|
||||||
/// If the number is too large to represent.
|
/// If the number is too large to represent.
|
||||||
fn to_magnitude_and_suffix(n: u128) -> Result<String, ()> {
|
fn to_magnitude_and_suffix(n: u128) -> Result<String, ()> {
|
||||||
if n % 1024 == 0 {
|
if n % 1024 == 0 && n % 1000 != 0 {
|
||||||
to_magnitude_and_suffix_1024(n)
|
to_magnitude_and_suffix_1024(n)
|
||||||
} else {
|
} else {
|
||||||
to_magnitude_and_suffix_not_powers_of_1024(n)
|
to_magnitude_and_suffix_not_powers_of_1024(n)
|
||||||
|
@ -159,6 +162,7 @@ pub(crate) enum HumanReadable {
|
||||||
/// size.
|
/// size.
|
||||||
///
|
///
|
||||||
/// The default variant is `Bytes(1024)`.
|
/// The default variant is `Bytes(1024)`.
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
pub(crate) enum BlockSize {
|
pub(crate) enum BlockSize {
|
||||||
/// A fixed number of bytes.
|
/// A fixed number of bytes.
|
||||||
///
|
///
|
||||||
|
@ -166,16 +170,35 @@ pub(crate) enum BlockSize {
|
||||||
Bytes(u64),
|
Bytes(u64),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl BlockSize {
|
||||||
|
/// Returns the associated value
|
||||||
|
pub(crate) fn as_u64(&self) -> u64 {
|
||||||
|
match *self {
|
||||||
|
Self::Bytes(n) => n,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Default for BlockSize {
|
impl Default for BlockSize {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::Bytes(1024)
|
if env::var("POSIXLY_CORRECT").is_ok() {
|
||||||
|
Self::Bytes(512)
|
||||||
|
} else {
|
||||||
|
Self::Bytes(1024)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn block_size_from_matches(matches: &ArgMatches) -> Result<BlockSize, ParseSizeError> {
|
pub(crate) fn block_size_from_matches(matches: &ArgMatches) -> Result<BlockSize, ParseSizeError> {
|
||||||
if matches.is_present(OPT_BLOCKSIZE) {
|
if matches.is_present(OPT_BLOCKSIZE) {
|
||||||
let s = matches.value_of(OPT_BLOCKSIZE).unwrap();
|
let s = matches.value_of(OPT_BLOCKSIZE).unwrap();
|
||||||
Ok(BlockSize::Bytes(parse_size(s)?))
|
let bytes = parse_size(s)?;
|
||||||
|
|
||||||
|
if bytes > 0 {
|
||||||
|
Ok(BlockSize::Bytes(bytes))
|
||||||
|
} else {
|
||||||
|
Err(ParseSizeError::ParseFailure(format!("{}", s.quote())))
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
Ok(Default::default())
|
Ok(Default::default())
|
||||||
}
|
}
|
||||||
|
@ -195,6 +218,8 @@ impl fmt::Display for BlockSize {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
|
||||||
|
use std::env;
|
||||||
|
|
||||||
use crate::blocks::{to_magnitude_and_suffix, BlockSize};
|
use crate::blocks::{to_magnitude_and_suffix, BlockSize};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -239,10 +264,25 @@ mod tests {
|
||||||
assert_eq!(to_magnitude_and_suffix(1_000_000_001).unwrap(), "1.1GB");
|
assert_eq!(to_magnitude_and_suffix(1_000_000_001).unwrap(), "1.1GB");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_to_magnitude_and_suffix_multiples_of_1000_and_1024() {
|
||||||
|
assert_eq!(to_magnitude_and_suffix(128_000).unwrap(), "128kB");
|
||||||
|
assert_eq!(to_magnitude_and_suffix(1000 * 1024).unwrap(), "1.1MB");
|
||||||
|
assert_eq!(to_magnitude_and_suffix(1_000_000_000_000).unwrap(), "1TB");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_block_size_display() {
|
fn test_block_size_display() {
|
||||||
assert_eq!(format!("{}", BlockSize::Bytes(1024)), "1K");
|
assert_eq!(format!("{}", BlockSize::Bytes(1024)), "1K");
|
||||||
assert_eq!(format!("{}", BlockSize::Bytes(2 * 1024)), "2K");
|
assert_eq!(format!("{}", BlockSize::Bytes(2 * 1024)), "2K");
|
||||||
assert_eq!(format!("{}", BlockSize::Bytes(3 * 1024 * 1024)), "3M");
|
assert_eq!(format!("{}", BlockSize::Bytes(3 * 1024 * 1024)), "3M");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_default_block_size() {
|
||||||
|
assert_eq!(BlockSize::Bytes(1024), BlockSize::default());
|
||||||
|
env::set_var("POSIXLY_CORRECT", "1");
|
||||||
|
assert_eq!(BlockSize::Bytes(512), BlockSize::default());
|
||||||
|
env::remove_var("POSIXLY_CORRECT");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@ mod filesystem;
|
||||||
mod table;
|
mod table;
|
||||||
|
|
||||||
use blocks::{HumanReadable, SizeFormat};
|
use blocks::{HumanReadable, SizeFormat};
|
||||||
|
use table::HeaderMode;
|
||||||
use uucore::display::Quotable;
|
use uucore::display::Quotable;
|
||||||
use uucore::error::{UError, UResult, USimpleError};
|
use uucore::error::{UError, UResult, USimpleError};
|
||||||
use uucore::fsext::{read_fs_list, MountInfo};
|
use uucore::fsext::{read_fs_list, MountInfo};
|
||||||
|
@ -32,6 +33,13 @@ use crate::table::Table;
|
||||||
static ABOUT: &str = "Show information about the file system on which each FILE resides,\n\
|
static ABOUT: &str = "Show information about the file system on which each FILE resides,\n\
|
||||||
or all file systems by default.";
|
or all file systems by default.";
|
||||||
const USAGE: &str = "{} [OPTION]... [FILE]...";
|
const USAGE: &str = "{} [OPTION]... [FILE]...";
|
||||||
|
const LONG_HELP: &str = "Display values are in units of the first available SIZE from --block-size,
|
||||||
|
and the DF_BLOCK_SIZE, BLOCK_SIZE and BLOCKSIZE environment variables.
|
||||||
|
Otherwise, units default to 1024 bytes (or 512 if POSIXLY_CORRECT is set).
|
||||||
|
|
||||||
|
SIZE is an integer and optional unit (example: 10M is 10*1024*1024).
|
||||||
|
Units are K, M, G, T, P, E, Z, Y (powers of 1024) or KB, MB,... (powers
|
||||||
|
of 1000).";
|
||||||
|
|
||||||
static OPT_HELP: &str = "help";
|
static OPT_HELP: &str = "help";
|
||||||
static OPT_ALL: &str = "all";
|
static OPT_ALL: &str = "all";
|
||||||
|
@ -65,6 +73,7 @@ struct Options {
|
||||||
show_all_fs: bool,
|
show_all_fs: bool,
|
||||||
size_format: SizeFormat,
|
size_format: SizeFormat,
|
||||||
block_size: BlockSize,
|
block_size: BlockSize,
|
||||||
|
header_mode: HeaderMode,
|
||||||
|
|
||||||
/// Optional list of filesystem types to include in the output table.
|
/// Optional list of filesystem types to include in the output table.
|
||||||
///
|
///
|
||||||
|
@ -92,6 +101,7 @@ impl Default for Options {
|
||||||
show_all_fs: Default::default(),
|
show_all_fs: Default::default(),
|
||||||
block_size: Default::default(),
|
block_size: Default::default(),
|
||||||
size_format: Default::default(),
|
size_format: Default::default(),
|
||||||
|
header_mode: Default::default(),
|
||||||
include: Default::default(),
|
include: Default::default(),
|
||||||
exclude: Default::default(),
|
exclude: Default::default(),
|
||||||
show_total: Default::default(),
|
show_total: Default::default(),
|
||||||
|
@ -169,6 +179,21 @@ impl Options {
|
||||||
),
|
),
|
||||||
ParseSizeError::ParseFailure(s) => OptionsError::InvalidBlockSize(s),
|
ParseSizeError::ParseFailure(s) => OptionsError::InvalidBlockSize(s),
|
||||||
})?,
|
})?,
|
||||||
|
header_mode: {
|
||||||
|
if matches.is_present(OPT_HUMAN_READABLE_BINARY)
|
||||||
|
|| matches.is_present(OPT_HUMAN_READABLE_DECIMAL)
|
||||||
|
{
|
||||||
|
HeaderMode::HumanReadable
|
||||||
|
} else if matches.is_present(OPT_PORTABILITY) {
|
||||||
|
HeaderMode::PosixPortability
|
||||||
|
// is_present() doesn't work here, it always returns true because OPT_OUTPUT has
|
||||||
|
// default values and hence is always present
|
||||||
|
} else if matches.occurrences_of(OPT_OUTPUT) > 0 {
|
||||||
|
HeaderMode::Output
|
||||||
|
} else {
|
||||||
|
HeaderMode::Default
|
||||||
|
}
|
||||||
|
},
|
||||||
size_format: {
|
size_format: {
|
||||||
if matches.is_present(OPT_HUMAN_READABLE_BINARY) {
|
if matches.is_present(OPT_HUMAN_READABLE_BINARY) {
|
||||||
SizeFormat::HumanReadable(HumanReadable::Binary)
|
SizeFormat::HumanReadable(HumanReadable::Binary)
|
||||||
|
@ -427,6 +452,7 @@ pub fn uu_app<'a>() -> Command<'a> {
|
||||||
.version(crate_version!())
|
.version(crate_version!())
|
||||||
.about(ABOUT)
|
.about(ABOUT)
|
||||||
.override_usage(format_usage(USAGE))
|
.override_usage(format_usage(USAGE))
|
||||||
|
.after_help(LONG_HELP)
|
||||||
.infer_long_args(true)
|
.infer_long_args(true)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::new(OPT_HELP)
|
Arg::new(OPT_HELP)
|
||||||
|
@ -445,6 +471,7 @@ pub fn uu_app<'a>() -> Command<'a> {
|
||||||
.short('B')
|
.short('B')
|
||||||
.long("block-size")
|
.long("block-size")
|
||||||
.takes_value(true)
|
.takes_value(true)
|
||||||
|
.value_name("SIZE")
|
||||||
.overrides_with_all(&[OPT_KILO, OPT_BLOCKSIZE])
|
.overrides_with_all(&[OPT_KILO, OPT_BLOCKSIZE])
|
||||||
.help(
|
.help(
|
||||||
"scale sizes by SIZE before printing them; e.g.\
|
"scale sizes by SIZE before printing them; e.g.\
|
||||||
|
@ -501,6 +528,7 @@ pub fn uu_app<'a>() -> Command<'a> {
|
||||||
Arg::new(OPT_OUTPUT)
|
Arg::new(OPT_OUTPUT)
|
||||||
.long("output")
|
.long("output")
|
||||||
.takes_value(true)
|
.takes_value(true)
|
||||||
|
.value_name("FIELD_LIST")
|
||||||
.min_values(0)
|
.min_values(0)
|
||||||
.require_equals(true)
|
.require_equals(true)
|
||||||
.use_value_delimiter(true)
|
.use_value_delimiter(true)
|
||||||
|
@ -510,7 +538,7 @@ pub fn uu_app<'a>() -> Command<'a> {
|
||||||
.default_values(&["source", "size", "used", "avail", "pcent", "target"])
|
.default_values(&["source", "size", "used", "avail", "pcent", "target"])
|
||||||
.conflicts_with_all(&[OPT_INODES, OPT_PORTABILITY, OPT_PRINT_TYPE])
|
.conflicts_with_all(&[OPT_INODES, OPT_PORTABILITY, OPT_PRINT_TYPE])
|
||||||
.help(
|
.help(
|
||||||
"use the output format defined by FIELD_LIST,\
|
"use the output format defined by FIELD_LIST, \
|
||||||
or print all fields if FIELD_LIST is omitted.",
|
or print all fields if FIELD_LIST is omitted.",
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
@ -533,6 +561,7 @@ pub fn uu_app<'a>() -> Command<'a> {
|
||||||
.long("type")
|
.long("type")
|
||||||
.allow_invalid_utf8(true)
|
.allow_invalid_utf8(true)
|
||||||
.takes_value(true)
|
.takes_value(true)
|
||||||
|
.value_name("TYPE")
|
||||||
.multiple_occurrences(true)
|
.multiple_occurrences(true)
|
||||||
.help("limit listing to file systems of type TYPE"),
|
.help("limit listing to file systems of type TYPE"),
|
||||||
)
|
)
|
||||||
|
@ -549,6 +578,7 @@ pub fn uu_app<'a>() -> Command<'a> {
|
||||||
.long("exclude-type")
|
.long("exclude-type")
|
||||||
.allow_invalid_utf8(true)
|
.allow_invalid_utf8(true)
|
||||||
.takes_value(true)
|
.takes_value(true)
|
||||||
|
.value_name("TYPE")
|
||||||
.use_value_delimiter(true)
|
.use_value_delimiter(true)
|
||||||
.multiple_occurrences(true)
|
.multiple_occurrences(true)
|
||||||
.help("limit listing to file systems not of type TYPE"),
|
.help("limit listing to file systems not of type TYPE"),
|
||||||
|
|
|
@ -289,6 +289,23 @@ impl<'a> RowFormatter<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A HeaderMode defines what header labels should be shown.
|
||||||
|
pub(crate) enum HeaderMode {
|
||||||
|
Default,
|
||||||
|
// the user used -h or -H
|
||||||
|
HumanReadable,
|
||||||
|
// the user used -P
|
||||||
|
PosixPortability,
|
||||||
|
// the user used --output
|
||||||
|
Output,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for HeaderMode {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::Default
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// The data of the header row.
|
/// The data of the header row.
|
||||||
struct Header {}
|
struct Header {}
|
||||||
|
|
||||||
|
@ -302,15 +319,22 @@ impl Header {
|
||||||
for column in &options.columns {
|
for column in &options.columns {
|
||||||
let header = match column {
|
let header = match column {
|
||||||
Column::Source => String::from("Filesystem"),
|
Column::Source => String::from("Filesystem"),
|
||||||
Column::Size => match options.size_format {
|
Column::Size => match options.header_mode {
|
||||||
SizeFormat::HumanReadable(_) => String::from("Size"),
|
HeaderMode::HumanReadable => String::from("Size"),
|
||||||
SizeFormat::StaticBlockSize => {
|
HeaderMode::PosixPortability => {
|
||||||
format!("{}-blocks", options.block_size)
|
format!("{}-blocks", options.block_size.as_u64())
|
||||||
}
|
}
|
||||||
|
_ => format!("{}-blocks", options.block_size),
|
||||||
},
|
},
|
||||||
Column::Used => String::from("Used"),
|
Column::Used => String::from("Used"),
|
||||||
Column::Avail => String::from("Available"),
|
Column::Avail => match options.header_mode {
|
||||||
Column::Pcent => String::from("Use%"),
|
HeaderMode::HumanReadable | HeaderMode::Output => String::from("Avail"),
|
||||||
|
_ => String::from("Available"),
|
||||||
|
},
|
||||||
|
Column::Pcent => match options.header_mode {
|
||||||
|
HeaderMode::PosixPortability => String::from("Capacity"),
|
||||||
|
_ => String::from("Use%"),
|
||||||
|
},
|
||||||
Column::Target => String::from("Mounted on"),
|
Column::Target => String::from("Mounted on"),
|
||||||
Column::Itotal => String::from("Inodes"),
|
Column::Itotal => String::from("Inodes"),
|
||||||
Column::Iused => String::from("IUsed"),
|
Column::Iused => String::from("IUsed"),
|
||||||
|
@ -428,7 +452,7 @@ mod tests {
|
||||||
|
|
||||||
use crate::blocks::{HumanReadable, SizeFormat};
|
use crate::blocks::{HumanReadable, SizeFormat};
|
||||||
use crate::columns::Column;
|
use crate::columns::Column;
|
||||||
use crate::table::{Header, Row, RowFormatter};
|
use crate::table::{Header, HeaderMode, Row, RowFormatter};
|
||||||
use crate::{BlockSize, Options};
|
use crate::{BlockSize, Options};
|
||||||
|
|
||||||
const COLUMNS_WITH_FS_TYPE: [Column; 7] = [
|
const COLUMNS_WITH_FS_TYPE: [Column; 7] = [
|
||||||
|
@ -449,6 +473,30 @@ mod tests {
|
||||||
Column::Target,
|
Column::Target,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
impl Default for Row {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
file: Some("/path/to/file".to_string()),
|
||||||
|
fs_device: "my_device".to_string(),
|
||||||
|
fs_type: "my_type".to_string(),
|
||||||
|
fs_mount: "my_mount".to_string(),
|
||||||
|
|
||||||
|
bytes: 100,
|
||||||
|
bytes_used: 25,
|
||||||
|
bytes_avail: 75,
|
||||||
|
bytes_usage: Some(0.25),
|
||||||
|
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
bytes_capacity: Some(0.5),
|
||||||
|
|
||||||
|
inodes: 10,
|
||||||
|
inodes_used: 2,
|
||||||
|
inodes_free: 8,
|
||||||
|
inodes_usage: Some(0.2),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_default_header() {
|
fn test_default_header() {
|
||||||
let options = Default::default();
|
let options = Default::default();
|
||||||
|
@ -524,37 +572,49 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_header_with_human_readable_binary() {
|
fn test_human_readable_header() {
|
||||||
let options = Options {
|
let options = Options {
|
||||||
size_format: SizeFormat::HumanReadable(HumanReadable::Binary),
|
header_mode: HeaderMode::HumanReadable,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
assert_eq!(
|
||||||
|
Header::get_headers(&options),
|
||||||
|
vec!("Filesystem", "Size", "Used", "Avail", "Use%", "Mounted on")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_posix_portability_header() {
|
||||||
|
let options = Options {
|
||||||
|
header_mode: HeaderMode::PosixPortability,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Header::get_headers(&options),
|
Header::get_headers(&options),
|
||||||
vec!(
|
vec!(
|
||||||
"Filesystem",
|
"Filesystem",
|
||||||
"Size",
|
"1024-blocks",
|
||||||
"Used",
|
"Used",
|
||||||
"Available",
|
"Available",
|
||||||
"Use%",
|
"Capacity",
|
||||||
"Mounted on"
|
"Mounted on"
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_header_with_human_readable_si() {
|
fn test_output_header() {
|
||||||
let options = Options {
|
let options = Options {
|
||||||
size_format: SizeFormat::HumanReadable(HumanReadable::Decimal),
|
header_mode: HeaderMode::Output,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Header::get_headers(&options),
|
Header::get_headers(&options),
|
||||||
vec!(
|
vec!(
|
||||||
"Filesystem",
|
"Filesystem",
|
||||||
"Size",
|
"1K-blocks",
|
||||||
"Used",
|
"Used",
|
||||||
"Available",
|
"Avail",
|
||||||
"Use%",
|
"Use%",
|
||||||
"Mounted on"
|
"Mounted on"
|
||||||
)
|
)
|
||||||
|
@ -568,9 +628,7 @@ mod tests {
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
let row = Row {
|
let row = Row {
|
||||||
file: Some("/path/to/file".to_string()),
|
|
||||||
fs_device: "my_device".to_string(),
|
fs_device: "my_device".to_string(),
|
||||||
fs_type: "my_type".to_string(),
|
|
||||||
fs_mount: "my_mount".to_string(),
|
fs_mount: "my_mount".to_string(),
|
||||||
|
|
||||||
bytes: 100,
|
bytes: 100,
|
||||||
|
@ -578,13 +636,7 @@ mod tests {
|
||||||
bytes_avail: 75,
|
bytes_avail: 75,
|
||||||
bytes_usage: Some(0.25),
|
bytes_usage: Some(0.25),
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
..Default::default()
|
||||||
bytes_capacity: Some(0.5),
|
|
||||||
|
|
||||||
inodes: 10,
|
|
||||||
inodes_used: 2,
|
|
||||||
inodes_free: 8,
|
|
||||||
inodes_usage: Some(0.2),
|
|
||||||
};
|
};
|
||||||
let fmt = RowFormatter::new(&row, &options);
|
let fmt = RowFormatter::new(&row, &options);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -601,7 +653,6 @@ mod tests {
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
let row = Row {
|
let row = Row {
|
||||||
file: Some("/path/to/file".to_string()),
|
|
||||||
fs_device: "my_device".to_string(),
|
fs_device: "my_device".to_string(),
|
||||||
fs_type: "my_type".to_string(),
|
fs_type: "my_type".to_string(),
|
||||||
fs_mount: "my_mount".to_string(),
|
fs_mount: "my_mount".to_string(),
|
||||||
|
@ -611,13 +662,7 @@ mod tests {
|
||||||
bytes_avail: 75,
|
bytes_avail: 75,
|
||||||
bytes_usage: Some(0.25),
|
bytes_usage: Some(0.25),
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
..Default::default()
|
||||||
bytes_capacity: Some(0.5),
|
|
||||||
|
|
||||||
inodes: 10,
|
|
||||||
inodes_used: 2,
|
|
||||||
inodes_free: 8,
|
|
||||||
inodes_usage: Some(0.2),
|
|
||||||
};
|
};
|
||||||
let fmt = RowFormatter::new(&row, &options);
|
let fmt = RowFormatter::new(&row, &options);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -634,23 +679,15 @@ mod tests {
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
let row = Row {
|
let row = Row {
|
||||||
file: Some("/path/to/file".to_string()),
|
|
||||||
fs_device: "my_device".to_string(),
|
fs_device: "my_device".to_string(),
|
||||||
fs_type: "my_type".to_string(),
|
|
||||||
fs_mount: "my_mount".to_string(),
|
fs_mount: "my_mount".to_string(),
|
||||||
|
|
||||||
bytes: 100,
|
|
||||||
bytes_used: 25,
|
|
||||||
bytes_avail: 75,
|
|
||||||
bytes_usage: Some(0.25),
|
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
|
||||||
bytes_capacity: Some(0.5),
|
|
||||||
|
|
||||||
inodes: 10,
|
inodes: 10,
|
||||||
inodes_used: 2,
|
inodes_used: 2,
|
||||||
inodes_free: 8,
|
inodes_free: 8,
|
||||||
inodes_usage: Some(0.2),
|
inodes_usage: Some(0.2),
|
||||||
|
|
||||||
|
..Default::default()
|
||||||
};
|
};
|
||||||
let fmt = RowFormatter::new(&row, &options);
|
let fmt = RowFormatter::new(&row, &options);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -667,23 +704,9 @@ mod tests {
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
let row = Row {
|
let row = Row {
|
||||||
file: Some("/path/to/file".to_string()),
|
|
||||||
fs_device: "my_device".to_string(),
|
|
||||||
fs_type: "my_type".to_string(),
|
|
||||||
fs_mount: "my_mount".to_string(),
|
|
||||||
|
|
||||||
bytes: 100,
|
bytes: 100,
|
||||||
bytes_used: 25,
|
|
||||||
bytes_avail: 75,
|
|
||||||
bytes_usage: Some(0.25),
|
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
|
||||||
bytes_capacity: Some(0.5),
|
|
||||||
|
|
||||||
inodes: 10,
|
inodes: 10,
|
||||||
inodes_used: 2,
|
..Default::default()
|
||||||
inodes_free: 8,
|
|
||||||
inodes_usage: Some(0.2),
|
|
||||||
};
|
};
|
||||||
let fmt = RowFormatter::new(&row, &options);
|
let fmt = RowFormatter::new(&row, &options);
|
||||||
assert_eq!(fmt.get_values(), vec!("1", "10"));
|
assert_eq!(fmt.get_values(), vec!("1", "10"));
|
||||||
|
@ -697,7 +720,6 @@ mod tests {
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
let row = Row {
|
let row = Row {
|
||||||
file: Some("/path/to/file".to_string()),
|
|
||||||
fs_device: "my_device".to_string(),
|
fs_device: "my_device".to_string(),
|
||||||
fs_type: "my_type".to_string(),
|
fs_type: "my_type".to_string(),
|
||||||
fs_mount: "my_mount".to_string(),
|
fs_mount: "my_mount".to_string(),
|
||||||
|
@ -707,13 +729,7 @@ mod tests {
|
||||||
bytes_avail: 3000,
|
bytes_avail: 3000,
|
||||||
bytes_usage: Some(0.25),
|
bytes_usage: Some(0.25),
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
..Default::default()
|
||||||
bytes_capacity: Some(0.5),
|
|
||||||
|
|
||||||
inodes: 10,
|
|
||||||
inodes_used: 2,
|
|
||||||
inodes_free: 8,
|
|
||||||
inodes_usage: Some(0.2),
|
|
||||||
};
|
};
|
||||||
let fmt = RowFormatter::new(&row, &options);
|
let fmt = RowFormatter::new(&row, &options);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -738,7 +754,6 @@ mod tests {
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
let row = Row {
|
let row = Row {
|
||||||
file: Some("/path/to/file".to_string()),
|
|
||||||
fs_device: "my_device".to_string(),
|
fs_device: "my_device".to_string(),
|
||||||
fs_type: "my_type".to_string(),
|
fs_type: "my_type".to_string(),
|
||||||
fs_mount: "my_mount".to_string(),
|
fs_mount: "my_mount".to_string(),
|
||||||
|
@ -748,13 +763,7 @@ mod tests {
|
||||||
bytes_avail: 3072,
|
bytes_avail: 3072,
|
||||||
bytes_usage: Some(0.25),
|
bytes_usage: Some(0.25),
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
..Default::default()
|
||||||
bytes_capacity: Some(0.5),
|
|
||||||
|
|
||||||
inodes: 10,
|
|
||||||
inodes_used: 2,
|
|
||||||
inodes_free: 8,
|
|
||||||
inodes_usage: Some(0.2),
|
|
||||||
};
|
};
|
||||||
let fmt = RowFormatter::new(&row, &options);
|
let fmt = RowFormatter::new(&row, &options);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -774,32 +783,14 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_row_formatter_with_round_up_usage() {
|
fn test_row_formatter_with_round_up_usage() {
|
||||||
let options = Options {
|
let options = Options {
|
||||||
block_size: BlockSize::Bytes(1),
|
columns: vec![Column::Pcent],
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
let row = Row {
|
let row = Row {
|
||||||
file: Some("/path/to/file".to_string()),
|
|
||||||
fs_device: "my_device".to_string(),
|
|
||||||
fs_type: "my_type".to_string(),
|
|
||||||
fs_mount: "my_mount".to_string(),
|
|
||||||
|
|
||||||
bytes: 100,
|
|
||||||
bytes_used: 25,
|
|
||||||
bytes_avail: 75,
|
|
||||||
bytes_usage: Some(0.251),
|
bytes_usage: Some(0.251),
|
||||||
|
..Default::default()
|
||||||
#[cfg(target_os = "macos")]
|
|
||||||
bytes_capacity: Some(0.5),
|
|
||||||
|
|
||||||
inodes: 10,
|
|
||||||
inodes_used: 2,
|
|
||||||
inodes_free: 8,
|
|
||||||
inodes_usage: Some(0.2),
|
|
||||||
};
|
};
|
||||||
let fmt = RowFormatter::new(&row, &options);
|
let fmt = RowFormatter::new(&row, &options);
|
||||||
assert_eq!(
|
assert_eq!(fmt.get_values(), vec!("26%"));
|
||||||
fmt.get_values(),
|
|
||||||
vec!("my_device", "100", "25", "75", "26%", "my_mount")
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,7 @@ path = "src/expr.rs"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
clap = { version = "3.1", features = ["wrap_help", "cargo"] }
|
clap = { version = "3.1", features = ["wrap_help", "cargo"] }
|
||||||
num-bigint = "0.4.0"
|
num-bigint = "0.4.0"
|
||||||
num-traits = "0.2.14"
|
num-traits = "0.2.15"
|
||||||
onig = { version = "~6.3", default-features = false }
|
onig = { version = "~6.3", default-features = false }
|
||||||
uucore = { version=">=0.0.11", package="uucore", path="../../uucore" }
|
uucore = { version=">=0.0.11", package="uucore", path="../../uucore" }
|
||||||
|
|
||||||
|
|
|
@ -12,12 +12,12 @@ categories = ["command-line-utilities"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
num-traits = "0.2.13" # used in src/numerics.rs, which is included by build.rs
|
num-traits = "0.2.15" # used in src/numerics.rs, which is included by build.rs
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
clap = { version = "3.1", features = ["wrap_help", "cargo"] }
|
clap = { version = "3.1", features = ["wrap_help", "cargo"] }
|
||||||
coz = { version = "0.1.3", optional = true }
|
coz = { version = "0.1.3", optional = true }
|
||||||
num-traits = "0.2.13" # Needs at least version 0.2.13 for "OverflowingAdd"
|
num-traits = "0.2.15" # Needs at least version 0.2.15 for "OverflowingAdd"
|
||||||
rand = { version = "0.8", features = ["small_rng"] }
|
rand = { version = "0.8", features = ["small_rng"] }
|
||||||
smallvec = "1.7" # TODO(nicoo): Use `union` feature, requires Rust 1.49 or later.
|
smallvec = "1.7" # TODO(nicoo): Use `union` feature, requires Rust 1.49 or later.
|
||||||
uucore = { version = ">=0.0.8", package = "uucore", path = "../../uucore" }
|
uucore = { version = ">=0.0.8", package = "uucore", path = "../../uucore" }
|
||||||
|
|
|
@ -24,6 +24,9 @@ file_diff = "1.0.0"
|
||||||
libc = ">= 0.2"
|
libc = ">= 0.2"
|
||||||
uucore = { version=">=0.0.11", package="uucore", path="../../uucore", features=["fs", "mode", "perms", "entries"] }
|
uucore = { version=">=0.0.11", package="uucore", path="../../uucore", features=["fs", "mode", "perms", "entries"] }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
time = "0.3"
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "install"
|
name = "install"
|
||||||
path = "src/main.rs"
|
path = "src/main.rs"
|
||||||
|
|
|
@ -41,7 +41,12 @@ enum MkTempError {
|
||||||
PersistError(PathBuf),
|
PersistError(PathBuf),
|
||||||
MustEndInX(String),
|
MustEndInX(String),
|
||||||
TooFewXs(String),
|
TooFewXs(String),
|
||||||
ContainsDirSeparator(String),
|
|
||||||
|
/// The template prefix contains a path separator (e.g. `"a/bXXX"`).
|
||||||
|
PrefixContainsDirSeparator(String),
|
||||||
|
|
||||||
|
/// The template suffix contains a path separator (e.g. `"XXXa/b"`).
|
||||||
|
SuffixContainsDirSeparator(String),
|
||||||
InvalidTemplate(String),
|
InvalidTemplate(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -56,7 +61,14 @@ impl Display for MkTempError {
|
||||||
PersistError(p) => write!(f, "could not persist file {}", p.quote()),
|
PersistError(p) => write!(f, "could not persist file {}", p.quote()),
|
||||||
MustEndInX(s) => write!(f, "with --suffix, template {} must end in X", s.quote()),
|
MustEndInX(s) => write!(f, "with --suffix, template {} must end in X", s.quote()),
|
||||||
TooFewXs(s) => write!(f, "too few X's in template {}", s.quote()),
|
TooFewXs(s) => write!(f, "too few X's in template {}", s.quote()),
|
||||||
ContainsDirSeparator(s) => {
|
PrefixContainsDirSeparator(s) => {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"invalid template, {}, contains directory separator",
|
||||||
|
s.quote()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
SuffixContainsDirSeparator(s) => {
|
||||||
write!(
|
write!(
|
||||||
f,
|
f,
|
||||||
"invalid suffix {}, contains directory separator",
|
"invalid suffix {}, contains directory separator",
|
||||||
|
@ -205,20 +217,41 @@ pub fn uu_app<'a>() -> Command<'a> {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Parse a template string into prefix, suffix, and random components.
|
||||||
|
///
|
||||||
|
/// `temp` is the template string, with three or more consecutive `X`s
|
||||||
|
/// representing a placeholder for randomly generated characters (for
|
||||||
|
/// example, `"abc_XXX.txt"`). If `temp` ends in an `X`, then a suffix
|
||||||
|
/// can be specified by `suffix` instead.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// * If there are fewer than three consecutive `X`s in `temp`.
|
||||||
|
/// * If `suffix` is a [`Some`] object but `temp` does not end in `X`.
|
||||||
|
/// * If the suffix (specified either way) contains a path separator.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```rust,ignore
|
||||||
|
/// assert_eq!(parse_template("XXX", None).unwrap(), ("", 3, ""));
|
||||||
|
/// assert_eq!(parse_template("abcXXX", None).unwrap(), ("abc", 3, ""));
|
||||||
|
/// assert_eq!(parse_template("XXXdef", None).unwrap(), ("", 3, "def"));
|
||||||
|
/// assert_eq!(parse_template("abcXXXdef", None).unwrap(), ("abc", 3, "def"));
|
||||||
|
/// ```
|
||||||
fn parse_template<'a>(
|
fn parse_template<'a>(
|
||||||
temp: &'a str,
|
temp: &'a str,
|
||||||
suffix: Option<&'a str>,
|
suffix: Option<&'a str>,
|
||||||
) -> UResult<(&'a str, usize, &'a str)> {
|
) -> Result<(&'a str, usize, &'a str), MkTempError> {
|
||||||
let right = match temp.rfind('X') {
|
let right = match temp.rfind('X') {
|
||||||
Some(r) => r + 1,
|
Some(r) => r + 1,
|
||||||
None => return Err(MkTempError::TooFewXs(temp.into()).into()),
|
None => return Err(MkTempError::TooFewXs(temp.into())),
|
||||||
};
|
};
|
||||||
let left = temp[..right].rfind(|c| c != 'X').map_or(0, |i| i + 1);
|
let left = temp[..right].rfind(|c| c != 'X').map_or(0, |i| i + 1);
|
||||||
let prefix = &temp[..left];
|
let prefix = &temp[..left];
|
||||||
let rand = right - left;
|
let rand = right - left;
|
||||||
|
|
||||||
if rand < 3 {
|
if rand < 3 {
|
||||||
return Err(MkTempError::TooFewXs(temp.into()).into());
|
return Err(MkTempError::TooFewXs(temp.into()));
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut suf = &temp[right..];
|
let mut suf = &temp[right..];
|
||||||
|
@ -227,12 +260,16 @@ fn parse_template<'a>(
|
||||||
if suf.is_empty() {
|
if suf.is_empty() {
|
||||||
suf = s;
|
suf = s;
|
||||||
} else {
|
} else {
|
||||||
return Err(MkTempError::MustEndInX(temp.into()).into());
|
return Err(MkTempError::MustEndInX(temp.into()));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if prefix.chars().any(is_separator) {
|
||||||
|
return Err(MkTempError::PrefixContainsDirSeparator(temp.into()));
|
||||||
|
}
|
||||||
|
|
||||||
if suf.chars().any(is_separator) {
|
if suf.chars().any(is_separator) {
|
||||||
return Err(MkTempError::ContainsDirSeparator(suf.into()).into());
|
return Err(MkTempError::SuffixContainsDirSeparator(suf.into()));
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok((prefix, rand, suf))
|
Ok((prefix, rand, suf))
|
||||||
|
@ -304,3 +341,37 @@ fn exec(dir: &Path, prefix: &str, rand: usize, suffix: &str, make_dir: bool) ->
|
||||||
|
|
||||||
println_verbatim(path).map_err_context(|| "failed to print directory name".to_owned())
|
println_verbatim(path).map_err_context(|| "failed to print directory name".to_owned())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::parse_template;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_template_no_suffix() {
|
||||||
|
assert_eq!(parse_template("XXX", None).unwrap(), ("", 3, ""));
|
||||||
|
assert_eq!(parse_template("abcXXX", None).unwrap(), ("abc", 3, ""));
|
||||||
|
assert_eq!(parse_template("XXXdef", None).unwrap(), ("", 3, "def"));
|
||||||
|
assert_eq!(
|
||||||
|
parse_template("abcXXXdef", None).unwrap(),
|
||||||
|
("abc", 3, "def")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_template_suffix() {
|
||||||
|
assert_eq!(parse_template("XXX", Some("def")).unwrap(), ("", 3, "def"));
|
||||||
|
assert_eq!(
|
||||||
|
parse_template("abcXXX", Some("def")).unwrap(),
|
||||||
|
("abc", 3, "def")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_template_errors() {
|
||||||
|
assert!(parse_template("a/bXXX", None).is_err());
|
||||||
|
assert!(parse_template("XXXa/b", None).is_err());
|
||||||
|
assert!(parse_template("XX", None).is_err());
|
||||||
|
assert!(parse_template("XXXabc", Some("def")).is_err());
|
||||||
|
assert!(parse_template("XXX", Some("a/b")).is_err());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -221,10 +221,10 @@ impl Capitalize for str {
|
||||||
|
|
||||||
fn idle_string(when: i64) -> String {
|
fn idle_string(when: i64) -> String {
|
||||||
thread_local! {
|
thread_local! {
|
||||||
static NOW: time::Tm = time::now()
|
static NOW: time::OffsetDateTime = time::OffsetDateTime::now_local().unwrap();
|
||||||
}
|
}
|
||||||
NOW.with(|n| {
|
NOW.with(|n| {
|
||||||
let duration = n.to_timespec().sec - when;
|
let duration = n.unix_timestamp() - when;
|
||||||
if duration < 60 {
|
if duration < 60 {
|
||||||
// less than 1min
|
// less than 1min
|
||||||
" ".to_owned()
|
" ".to_owned()
|
||||||
|
@ -242,7 +242,11 @@ fn idle_string(when: i64) -> String {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn time_string(ut: &Utmpx) -> String {
|
fn time_string(ut: &Utmpx) -> String {
|
||||||
time::strftime("%b %e %H:%M", &ut.login_time()).unwrap() // LC_ALL=C
|
// "%b %e %H:%M"
|
||||||
|
let time_format: Vec<time::format_description::FormatItem> =
|
||||||
|
time::format_description::parse("[month repr:short] [day padding:space] [hour]:[minute]")
|
||||||
|
.unwrap();
|
||||||
|
ut.login_time().format(&time_format).unwrap() // LC_ALL=C
|
||||||
}
|
}
|
||||||
|
|
||||||
fn gecos_to_fullname(pw: &Passwd) -> Option<String> {
|
fn gecos_to_fullname(pw: &Passwd) -> Option<String> {
|
||||||
|
|
|
@ -19,7 +19,7 @@ path = "src/seq.rs"
|
||||||
bigdecimal = "0.3"
|
bigdecimal = "0.3"
|
||||||
clap = { version = "3.1", features = ["wrap_help", "cargo"] }
|
clap = { version = "3.1", features = ["wrap_help", "cargo"] }
|
||||||
num-bigint = "0.4.0"
|
num-bigint = "0.4.0"
|
||||||
num-traits = "0.2.14"
|
num-traits = "0.2.15"
|
||||||
uucore = { version=">=0.0.11", package="uucore", path="../../uucore", features=["memo"] }
|
uucore = { version=">=0.0.11", package="uucore", path="../../uucore", features=["memo"] }
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
|
|
|
@ -17,7 +17,7 @@ path = "src/touch.rs"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
filetime = "0.2.1"
|
filetime = "0.2.1"
|
||||||
clap = { version = "3.1", features = ["wrap_help", "cargo"] }
|
clap = { version = "3.1", features = ["wrap_help", "cargo"] }
|
||||||
time = "0.1.40"
|
time = { version = "0.3", features = ["parsing", "formatting", "local-offset", "macros"] }
|
||||||
uucore = { version=">=0.0.11", package="uucore", path="../../uucore", features=["libc"] }
|
uucore = { version=">=0.0.11", package="uucore", path="../../uucore", features=["libc"] }
|
||||||
|
|
||||||
[target.'cfg(target_os = "windows")'.dependencies]
|
[target.'cfg(target_os = "windows")'.dependencies]
|
||||||
|
|
|
@ -6,8 +6,7 @@
|
||||||
// For the full copyright and license information, please view the LICENSE file
|
// For the full copyright and license information, please view the LICENSE file
|
||||||
// that was distributed with this source code.
|
// that was distributed with this source code.
|
||||||
|
|
||||||
// spell-checker:ignore (ToDO) filetime strptime utcoff strs datetime MMDDhhmm clapv PWSTR lpszfilepath hresult
|
// spell-checker:ignore (ToDO) filetime strptime utcoff strs datetime MMDDhhmm clapv PWSTR lpszfilepath hresult mktime YYYYMMDDHHMM YYMMDDHHMM DATETIME YYYYMMDDHHMMS subsecond
|
||||||
|
|
||||||
pub extern crate filetime;
|
pub extern crate filetime;
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
|
@ -17,6 +16,8 @@ use clap::{crate_version, Arg, ArgGroup, Command};
|
||||||
use filetime::*;
|
use filetime::*;
|
||||||
use std::fs::{self, File};
|
use std::fs::{self, File};
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
use time::macros::{format_description, offset, time};
|
||||||
|
use time::Duration;
|
||||||
use uucore::display::Quotable;
|
use uucore::display::Quotable;
|
||||||
use uucore::error::{FromIo, UError, UResult, USimpleError};
|
use uucore::error::{FromIo, UError, UResult, USimpleError};
|
||||||
use uucore::format_usage;
|
use uucore::format_usage;
|
||||||
|
@ -41,14 +42,27 @@ pub mod options {
|
||||||
|
|
||||||
static ARG_FILES: &str = "files";
|
static ARG_FILES: &str = "files";
|
||||||
|
|
||||||
fn to_local(mut tm: time::Tm) -> time::Tm {
|
// Convert a date/time to a date with a TZ offset
|
||||||
tm.tm_utcoff = time::now().tm_utcoff;
|
fn to_local(tm: time::PrimitiveDateTime) -> time::OffsetDateTime {
|
||||||
tm
|
let offset = match time::OffsetDateTime::now_local() {
|
||||||
|
Ok(lo) => lo.offset(),
|
||||||
|
Err(e) => {
|
||||||
|
panic!("error: {}", e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
tm.assume_offset(offset)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn local_tm_to_filetime(tm: time::Tm) -> FileTime {
|
// Convert a date/time with a TZ offset into a FileTime
|
||||||
let ts = tm.to_timespec();
|
fn local_dt_to_filetime(dt: time::OffsetDateTime) -> FileTime {
|
||||||
FileTime::from_unix_time(ts.sec as i64, ts.nsec as u32)
|
FileTime::from_unix_time(dt.unix_timestamp(), dt.nanosecond())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert a date/time, considering that the input is in UTC time
|
||||||
|
// Used for touch -d 1970-01-01 18:43:33.023456789 for example
|
||||||
|
fn dt_to_filename(tm: time::PrimitiveDateTime) -> FileTime {
|
||||||
|
let dt = tm.assume_offset(offset!(UTC));
|
||||||
|
local_dt_to_filetime(dt)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[uucore::main]
|
#[uucore::main]
|
||||||
|
@ -62,7 +76,6 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||||
Try 'touch --help' for more information."##,
|
Try 'touch --help' for more information."##,
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let (mut atime, mut mtime) =
|
let (mut atime, mut mtime) =
|
||||||
if let Some(reference) = matches.value_of_os(options::sources::REFERENCE) {
|
if let Some(reference) = matches.value_of_os(options::sources::REFERENCE) {
|
||||||
stat(Path::new(reference), !matches.is_present(options::NO_DEREF))?
|
stat(Path::new(reference), !matches.is_present(options::NO_DEREF))?
|
||||||
|
@ -72,7 +85,7 @@ Try 'touch --help' for more information."##,
|
||||||
} else if let Some(current) = matches.value_of(options::sources::CURRENT) {
|
} else if let Some(current) = matches.value_of(options::sources::CURRENT) {
|
||||||
parse_timestamp(current)?
|
parse_timestamp(current)?
|
||||||
} else {
|
} else {
|
||||||
local_tm_to_filetime(time::now())
|
local_dt_to_filetime(time::OffsetDateTime::now_local().unwrap())
|
||||||
};
|
};
|
||||||
(timestamp, timestamp)
|
(timestamp, timestamp)
|
||||||
};
|
};
|
||||||
|
@ -248,38 +261,129 @@ fn stat(path: &Path, follow: bool) -> UResult<(FileTime, FileTime)> {
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_date(str: &str) -> UResult<FileTime> {
|
const POSIX_LOCALE_FORMAT: &[time::format_description::FormatItem] = format_description!(
|
||||||
|
"[weekday repr:short] [month repr:short] [day padding:space] \
|
||||||
|
[hour]:[minute]:[second] [year]"
|
||||||
|
);
|
||||||
|
|
||||||
|
const ISO_8601_FORMAT: &[time::format_description::FormatItem] =
|
||||||
|
format_description!("[year]-[month]-[day]");
|
||||||
|
|
||||||
|
// "%Y%m%d%H%M.%S" 15 chars
|
||||||
|
const YYYYMMDDHHMM_DOT_SS_FORMAT: &[time::format_description::FormatItem] = format_description!(
|
||||||
|
"[year repr:full][month repr:numerical padding:zero]\
|
||||||
|
[day][hour][minute].[second]"
|
||||||
|
);
|
||||||
|
|
||||||
|
// "%Y-%m-%d %H:%M:%S.%SS" 12 chars
|
||||||
|
const YYYYMMDDHHMMSS_FORMAT: &[time::format_description::FormatItem] = format_description!(
|
||||||
|
"[year repr:full]-[month repr:numerical padding:zero]-\
|
||||||
|
[day] [hour]:[minute]:[second].[subsecond]"
|
||||||
|
);
|
||||||
|
|
||||||
|
// "%Y-%m-%d %H:%M:%S" 12 chars
|
||||||
|
const YYYYMMDDHHMMS_FORMAT: &[time::format_description::FormatItem] = format_description!(
|
||||||
|
"[year repr:full]-[month repr:numerical padding:zero]-\
|
||||||
|
[day] [hour]:[minute]:[second]"
|
||||||
|
);
|
||||||
|
|
||||||
|
// "%Y-%m-%d %H:%M" 12 chars
|
||||||
|
// Used for example in tests/touch/no-rights.sh
|
||||||
|
const YYYY_MM_DD_HH_MM_FORMAT: &[time::format_description::FormatItem] = format_description!(
|
||||||
|
"[year repr:full]-[month repr:numerical padding:zero]-\
|
||||||
|
[day] [hour]:[minute]"
|
||||||
|
);
|
||||||
|
|
||||||
|
// "%Y%m%d%H%M" 12 chars
|
||||||
|
const YYYYMMDDHHMM_FORMAT: &[time::format_description::FormatItem] = format_description!(
|
||||||
|
"[year repr:full][month repr:numerical padding:zero]\
|
||||||
|
[day][hour][minute]"
|
||||||
|
);
|
||||||
|
|
||||||
|
// "%y%m%d%H%M.%S" 13 chars
|
||||||
|
const YYMMDDHHMM_DOT_SS_FORMAT: &[time::format_description::FormatItem] = format_description!(
|
||||||
|
"[year repr:last_two padding:none][month][day]\
|
||||||
|
[hour][minute].[second]"
|
||||||
|
);
|
||||||
|
|
||||||
|
// "%y%m%d%H%M" 10 chars
|
||||||
|
const YYMMDDHHMM_FORMAT: &[time::format_description::FormatItem] = format_description!(
|
||||||
|
"[year repr:last_two padding:none][month padding:zero][day padding:zero]\
|
||||||
|
[hour repr:24 padding:zero][minute padding:zero]"
|
||||||
|
);
|
||||||
|
|
||||||
|
// "%Y-%m-%d %H:%M +offset"
|
||||||
|
// Used for example in tests/touch/relative.sh
|
||||||
|
const YYYYMMDDHHMM_OFFSET_FORMAT: &[time::format_description::FormatItem] = format_description!(
|
||||||
|
"[year]-[month]-[day] [hour repr:24]:[minute] \
|
||||||
|
[offset_hour sign:mandatory][offset_minute]"
|
||||||
|
);
|
||||||
|
|
||||||
|
fn parse_date(s: &str) -> UResult<FileTime> {
|
||||||
// This isn't actually compatible with GNU touch, but there doesn't seem to
|
// This isn't actually compatible with GNU touch, but there doesn't seem to
|
||||||
// be any simple specification for what format this parameter allows and I'm
|
// be any simple specification for what format this parameter allows and I'm
|
||||||
// not about to implement GNU parse_datetime.
|
// not about to implement GNU parse_datetime.
|
||||||
// http://git.savannah.gnu.org/gitweb/?p=gnulib.git;a=blob_plain;f=lib/parse-datetime.y
|
// http://git.savannah.gnu.org/gitweb/?p=gnulib.git;a=blob_plain;f=lib/parse-datetime.y
|
||||||
let formats = vec!["%c", "%F"];
|
|
||||||
for f in formats {
|
// TODO: match on char count?
|
||||||
if let Ok(tm) = time::strptime(str, f) {
|
|
||||||
return Ok(local_tm_to_filetime(to_local(tm)));
|
// "The preferred date and time representation for the current locale."
|
||||||
|
// "(In the POSIX locale this is equivalent to %a %b %e %H:%M:%S %Y.)"
|
||||||
|
// time 0.1.43 parsed this as 'a b e T Y'
|
||||||
|
// which is equivalent to the POSIX locale: %a %b %e %H:%M:%S %Y
|
||||||
|
// Tue Dec 3 ...
|
||||||
|
// ("%c", POSIX_LOCALE_FORMAT),
|
||||||
|
//
|
||||||
|
if let Ok(parsed) = time::PrimitiveDateTime::parse(s, &POSIX_LOCALE_FORMAT) {
|
||||||
|
return Ok(local_dt_to_filetime(to_local(parsed)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Also support other formats found in the GNU tests like
|
||||||
|
// in tests/misc/stat-nanoseconds.sh
|
||||||
|
// or tests/touch/no-rights.sh
|
||||||
|
for fmt in [
|
||||||
|
YYYYMMDDHHMMS_FORMAT,
|
||||||
|
YYYYMMDDHHMMSS_FORMAT,
|
||||||
|
YYYY_MM_DD_HH_MM_FORMAT,
|
||||||
|
YYYYMMDDHHMM_OFFSET_FORMAT,
|
||||||
|
] {
|
||||||
|
if let Ok(parsed) = time::PrimitiveDateTime::parse(s, &fmt) {
|
||||||
|
return Ok(dt_to_filename(parsed));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Ok(tm) = time::strptime(str, "@%s") {
|
// "Equivalent to %Y-%m-%d (the ISO 8601 date format). (C99)"
|
||||||
// Don't convert to local time in this case - seconds since epoch are not time-zone dependent
|
// ("%F", ISO_8601_FORMAT),
|
||||||
return Ok(local_tm_to_filetime(tm));
|
if let Ok(parsed) = time::Date::parse(s, &ISO_8601_FORMAT) {
|
||||||
|
return Ok(local_dt_to_filetime(to_local(
|
||||||
|
time::PrimitiveDateTime::new(parsed, time!(00:00)),
|
||||||
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
Err(USimpleError::new(
|
// "@%s" is "The number of seconds since the Epoch, 1970-01-01 00:00:00 +0000 (UTC). (TZ) (Calculated from mktime(tm).)"
|
||||||
1,
|
if s.bytes().next() == Some(b'@') {
|
||||||
format!("Unable to parse date: {}", str),
|
if let Ok(ts) = &s[1..].parse::<i64>() {
|
||||||
))
|
// Don't convert to local time in this case - seconds since epoch are not time-zone dependent
|
||||||
|
return Ok(local_dt_to_filetime(
|
||||||
|
time::OffsetDateTime::from_unix_timestamp(*ts).unwrap(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(USimpleError::new(1, format!("Unable to parse date: {}", s)))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_timestamp(s: &str) -> UResult<FileTime> {
|
fn parse_timestamp(s: &str) -> UResult<FileTime> {
|
||||||
let now = time::now();
|
// TODO: handle error
|
||||||
let (format, ts) = match s.chars().count() {
|
let now = time::OffsetDateTime::now_utc();
|
||||||
15 => ("%Y%m%d%H%M.%S", s.to_owned()),
|
|
||||||
12 => ("%Y%m%d%H%M", s.to_owned()),
|
let (mut format, mut ts) = match s.chars().count() {
|
||||||
13 => ("%y%m%d%H%M.%S", s.to_owned()),
|
15 => (YYYYMMDDHHMM_DOT_SS_FORMAT, s.to_owned()),
|
||||||
10 => ("%y%m%d%H%M", s.to_owned()),
|
12 => (YYYYMMDDHHMM_FORMAT, s.to_owned()),
|
||||||
11 => ("%Y%m%d%H%M.%S", format!("{}{}", now.tm_year + 1900, s)),
|
13 => (YYMMDDHHMM_DOT_SS_FORMAT, s.to_owned()),
|
||||||
8 => ("%Y%m%d%H%M", format!("{}{}", now.tm_year + 1900, s)),
|
10 => (YYMMDDHHMM_FORMAT, s.to_owned()),
|
||||||
|
11 => (YYYYMMDDHHMM_DOT_SS_FORMAT, format!("{}{}", now.year(), s)),
|
||||||
|
8 => (YYYYMMDDHHMM_FORMAT, format!("{}{}", now.year(), s)),
|
||||||
_ => {
|
_ => {
|
||||||
return Err(USimpleError::new(
|
return Err(USimpleError::new(
|
||||||
1,
|
1,
|
||||||
|
@ -287,30 +391,53 @@ fn parse_timestamp(s: &str) -> UResult<FileTime> {
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
// workaround time returning Err(TryFromParsed(InsufficientInformation)) for year w/
|
||||||
let tm = time::strptime(&ts, format)
|
// repr:last_two
|
||||||
.map_err(|_| USimpleError::new(1, format!("invalid date format {}", s.quote())))?;
|
// https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=1ccfac7c07c5d1c7887a11decf0e1996
|
||||||
|
if s.chars().count() == 10 {
|
||||||
let mut local = to_local(tm);
|
format = YYYYMMDDHHMM_FORMAT;
|
||||||
local.tm_isdst = -1;
|
ts = "20".to_owned() + &ts;
|
||||||
let ft = local_tm_to_filetime(local);
|
} else if s.chars().count() == 13 {
|
||||||
|
format = YYYYMMDDHHMM_DOT_SS_FORMAT;
|
||||||
// We have to check that ft is valid time. Due to daylight saving
|
ts = "20".to_owned() + &ts;
|
||||||
// time switch, local time can jump from 1:59 AM to 3:00 AM,
|
|
||||||
// in which case any time between 2:00 AM and 2:59 AM is not valid.
|
|
||||||
// Convert back to local time and see if we got the same value back.
|
|
||||||
let ts = time::Timespec {
|
|
||||||
sec: ft.unix_seconds(),
|
|
||||||
nsec: 0,
|
|
||||||
};
|
|
||||||
let tm2 = time::at(ts);
|
|
||||||
if tm.tm_hour != tm2.tm_hour {
|
|
||||||
return Err(USimpleError::new(
|
|
||||||
1,
|
|
||||||
format!("invalid date format {}", s.quote()),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let leap_sec = if (format == YYYYMMDDHHMM_DOT_SS_FORMAT || format == YYMMDDHHMM_DOT_SS_FORMAT)
|
||||||
|
&& ts.ends_with(".60")
|
||||||
|
{
|
||||||
|
// Work around to disable leap seconds
|
||||||
|
// Used in gnu/tests/touch/60-seconds
|
||||||
|
ts = ts.replace(".60", ".59");
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
};
|
||||||
|
|
||||||
|
let tm = time::PrimitiveDateTime::parse(&ts, &format)
|
||||||
|
.map_err(|_| USimpleError::new(1, format!("invalid date ts format {}", ts.quote())))?;
|
||||||
|
let mut local = to_local(tm);
|
||||||
|
if leap_sec {
|
||||||
|
// We are dealing with a leap second, add it
|
||||||
|
local = local.saturating_add(Duration::SECOND);
|
||||||
|
}
|
||||||
|
let ft = local_dt_to_filetime(local);
|
||||||
|
|
||||||
|
// // We have to check that ft is valid time. Due to daylight saving
|
||||||
|
// // time switch, local time can jump from 1:59 AM to 3:00 AM,
|
||||||
|
// // in which case any time between 2:00 AM and 2:59 AM is not valid.
|
||||||
|
// // Convert back to local time and see if we got the same value back.
|
||||||
|
// let ts = time::Timespec {
|
||||||
|
// sec: ft.unix_seconds(),
|
||||||
|
// nsec: 0,
|
||||||
|
// };
|
||||||
|
// let tm2 = time::at(ts);
|
||||||
|
// if tm.tm_hour != tm2.tm_hour {
|
||||||
|
// return Err(USimpleError::new(
|
||||||
|
// 1,
|
||||||
|
// format!("invalid date format {}", s.quote()),
|
||||||
|
// ));
|
||||||
|
// }
|
||||||
|
|
||||||
Ok(ft)
|
Ok(ft)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -111,9 +111,9 @@ fn process_utmpx() -> (Option<time_t>, usize) {
|
||||||
match line.record_type() {
|
match line.record_type() {
|
||||||
USER_PROCESS => nusers += 1,
|
USER_PROCESS => nusers += 1,
|
||||||
BOOT_TIME => {
|
BOOT_TIME => {
|
||||||
let t = line.login_time().to_timespec();
|
let dt = line.login_time();
|
||||||
if t.sec > 0 {
|
if dt.second() > 0 {
|
||||||
boot_time = Some(t.sec as time_t);
|
boot_time = Some(dt.second() as time_t);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => continue,
|
_ => continue,
|
||||||
|
|
|
@ -275,10 +275,10 @@ struct Who {
|
||||||
|
|
||||||
fn idle_string<'a>(when: i64, boottime: i64) -> Cow<'a, str> {
|
fn idle_string<'a>(when: i64, boottime: i64) -> Cow<'a, str> {
|
||||||
thread_local! {
|
thread_local! {
|
||||||
static NOW: time::Tm = time::now()
|
static NOW: time::OffsetDateTime = time::OffsetDateTime::now_local().unwrap();
|
||||||
}
|
}
|
||||||
NOW.with(|n| {
|
NOW.with(|n| {
|
||||||
let now = n.to_timespec().sec;
|
let now = n.unix_timestamp();
|
||||||
if boottime < when && now - 24 * 3600 < when && when <= now {
|
if boottime < when && now - 24 * 3600 < when && when <= now {
|
||||||
let seconds_idle = now - when;
|
let seconds_idle = now - when;
|
||||||
if seconds_idle < 60 {
|
if seconds_idle < 60 {
|
||||||
|
@ -298,7 +298,11 @@ fn idle_string<'a>(when: i64, boottime: i64) -> Cow<'a, str> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn time_string(ut: &Utmpx) -> String {
|
fn time_string(ut: &Utmpx) -> String {
|
||||||
time::strftime("%b %e %H:%M", &ut.login_time()).unwrap() // LC_ALL=C
|
// "%b %e %H:%M"
|
||||||
|
let time_format: Vec<time::format_description::FormatItem> =
|
||||||
|
time::format_description::parse("[month repr:short] [day padding:space] [hour]:[minute]")
|
||||||
|
.unwrap();
|
||||||
|
ut.login_time().format(&time_format).unwrap() // LC_ALL=C
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
|
|
@ -26,7 +26,7 @@ wild = "2.0"
|
||||||
# * optional
|
# * optional
|
||||||
itertools = { version="0.10.0", optional=true }
|
itertools = { version="0.10.0", optional=true }
|
||||||
thiserror = { version="1.0", optional=true }
|
thiserror = { version="1.0", optional=true }
|
||||||
time = { version="<= 0.1.43", optional=true }
|
time = { version="<= 0.3", optional=true, features = ["formatting", "local-offset", "macros"] }
|
||||||
# * "problem" dependencies (pinned)
|
# * "problem" dependencies (pinned)
|
||||||
data-encoding = { version="2.1", optional=true }
|
data-encoding = { version="2.1", optional=true }
|
||||||
data-encoding-macro = { version="0.1.12", optional=true }
|
data-encoding-macro = { version="0.1.12", optional=true }
|
||||||
|
@ -62,6 +62,6 @@ process = ["libc"]
|
||||||
ringbuffer = []
|
ringbuffer = []
|
||||||
signals = []
|
signals = []
|
||||||
utf8 = []
|
utf8 = []
|
||||||
utmpx = ["time", "libc", "dns-lookup"]
|
utmpx = ["time", "time/macros", "libc", "dns-lookup"]
|
||||||
wide = []
|
wide = []
|
||||||
pipes = ["nix"]
|
pipes = ["nix"]
|
||||||
|
|
|
@ -9,9 +9,11 @@
|
||||||
|
|
||||||
//! Set of functions to manage file systems
|
//! Set of functions to manage file systems
|
||||||
|
|
||||||
// spell-checker:ignore (arch) bitrig ; (fs) cifs smbfs
|
// spell-checker:ignore DATETIME subsecond (arch) bitrig ; (fs) cifs smbfs
|
||||||
|
|
||||||
extern crate time;
|
extern crate time;
|
||||||
|
use time::macros::format_description;
|
||||||
|
use time::UtcOffset;
|
||||||
|
|
||||||
pub use crate::*; // import macros from `../../macros.rs`
|
pub use crate::*; // import macros from `../../macros.rs`
|
||||||
|
|
||||||
|
@ -63,7 +65,6 @@ fn LPWSTR2String(buf: &[u16]) -> String {
|
||||||
String::from_utf16(&buf[..len]).unwrap()
|
String::from_utf16(&buf[..len]).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
use self::time::Timespec;
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
use libc::{
|
use libc::{
|
||||||
mode_t, strerror, S_IFBLK, S_IFCHR, S_IFDIR, S_IFIFO, S_IFLNK, S_IFMT, S_IFREG, S_IFSOCK,
|
mode_t, strerror, S_IFBLK, S_IFCHR, S_IFDIR, S_IFIFO, S_IFLNK, S_IFMT, S_IFREG, S_IFSOCK,
|
||||||
|
@ -733,11 +734,42 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// match strftime "%Y-%m-%d %H:%M:%S.%f %z"
|
||||||
|
const PRETTY_DATETIME_FORMAT: &[time::format_description::FormatItem] = format_description!(
|
||||||
|
"\
|
||||||
|
[year]-[month]-[day padding:zero] \
|
||||||
|
[hour]:[minute]:[second].[subsecond digits:9] \
|
||||||
|
[offset_hour sign:mandatory][offset_minute]"
|
||||||
|
);
|
||||||
|
|
||||||
pub fn pretty_time(sec: i64, nsec: i64) -> String {
|
pub fn pretty_time(sec: i64, nsec: i64) -> String {
|
||||||
// sec == seconds since UNIX_EPOCH
|
// sec == seconds since UNIX_EPOCH
|
||||||
// nsec == nanoseconds since (UNIX_EPOCH + sec)
|
// nsec == nanoseconds since (UNIX_EPOCH + sec)
|
||||||
let tm = time::at(Timespec::new(sec, nsec as i32));
|
let ts_nanos: i128 = (sec * 1_000_000_000 + nsec).into();
|
||||||
let res = time::strftime("%Y-%m-%d %H:%M:%S.%f %z", &tm).unwrap();
|
|
||||||
|
// Return the date in UTC
|
||||||
|
let tm = match time::OffsetDateTime::from_unix_timestamp_nanos(ts_nanos) {
|
||||||
|
Ok(tm) => tm,
|
||||||
|
Err(e) => {
|
||||||
|
panic!("error: {}", e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Get the offset to convert to local time
|
||||||
|
// Because of DST (daylight saving), we get the local time back when
|
||||||
|
// the date was set
|
||||||
|
let local_offset = match UtcOffset::local_offset_at(tm) {
|
||||||
|
Ok(lo) => lo,
|
||||||
|
Err(e) => {
|
||||||
|
panic!("error: {}", e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Include the conversion to local time
|
||||||
|
let res = tm
|
||||||
|
.to_offset(local_offset)
|
||||||
|
.format(&PRETTY_DATETIME_FORMAT)
|
||||||
|
.unwrap();
|
||||||
if res.ends_with(" -0000") {
|
if res.ends_with(" -0000") {
|
||||||
res.replace(" -0000", " +0000")
|
res.replace(" -0000", " +0000")
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -32,7 +32,6 @@
|
||||||
//! ```
|
//! ```
|
||||||
|
|
||||||
pub extern crate time;
|
pub extern crate time;
|
||||||
use self::time::{Timespec, Tm};
|
|
||||||
|
|
||||||
use std::ffi::CString;
|
use std::ffi::CString;
|
||||||
use std::io::Result as IOResult;
|
use std::io::Result as IOResult;
|
||||||
|
@ -189,11 +188,14 @@ impl Utmpx {
|
||||||
chars2string!(self.inner.ut_line)
|
chars2string!(self.inner.ut_line)
|
||||||
}
|
}
|
||||||
/// A.K.A. ut.ut_tv
|
/// A.K.A. ut.ut_tv
|
||||||
pub fn login_time(&self) -> Tm {
|
pub fn login_time(&self) -> time::OffsetDateTime {
|
||||||
time::at(Timespec::new(
|
let ts_nanos: i128 = (self.inner.ut_tv.tv_sec as i64 * 1_000_000_000_i64
|
||||||
self.inner.ut_tv.tv_sec as i64,
|
+ self.inner.ut_tv.tv_usec as i64 * 1_000_i64)
|
||||||
self.inner.ut_tv.tv_usec as i32,
|
.into();
|
||||||
))
|
let local_offset = time::OffsetDateTime::now_local().unwrap().offset();
|
||||||
|
time::OffsetDateTime::from_unix_timestamp_nanos(ts_nanos)
|
||||||
|
.unwrap()
|
||||||
|
.to_offset(local_offset)
|
||||||
}
|
}
|
||||||
/// A.K.A. ut.ut_exit
|
/// A.K.A. ut.ut_exit
|
||||||
///
|
///
|
||||||
|
|
|
@ -1032,8 +1032,8 @@ fn test_cp_no_deref_folder_to_folder() {
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
fn test_cp_archive() {
|
fn test_cp_archive() {
|
||||||
let (at, mut ucmd) = at_and_ucmd!();
|
let (at, mut ucmd) = at_and_ucmd!();
|
||||||
let ts = time::now().to_timespec();
|
let ts = time::OffsetDateTime::now_local().unwrap();
|
||||||
let previous = FileTime::from_unix_time(ts.sec as i64 - 3600, ts.nsec as u32);
|
let previous = FileTime::from_unix_time(ts.unix_timestamp() - 3600, ts.nanosecond() as u32);
|
||||||
// set the file creation/modification an hour ago
|
// set the file creation/modification an hour ago
|
||||||
filetime::set_file_times(
|
filetime::set_file_times(
|
||||||
at.plus_as_string(TEST_HELLO_WORLD_SOURCE),
|
at.plus_as_string(TEST_HELLO_WORLD_SOURCE),
|
||||||
|
@ -1135,8 +1135,8 @@ fn test_cp_archive_recursive() {
|
||||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||||
fn test_cp_preserve_timestamps() {
|
fn test_cp_preserve_timestamps() {
|
||||||
let (at, mut ucmd) = at_and_ucmd!();
|
let (at, mut ucmd) = at_and_ucmd!();
|
||||||
let ts = time::now().to_timespec();
|
let ts = time::OffsetDateTime::now_local().unwrap();
|
||||||
let previous = FileTime::from_unix_time(ts.sec as i64 - 3600, ts.nsec as u32);
|
let previous = FileTime::from_unix_time(ts.unix_timestamp() - 3600, ts.nanosecond());
|
||||||
// set the file creation/modification an hour ago
|
// set the file creation/modification an hour ago
|
||||||
filetime::set_file_times(
|
filetime::set_file_times(
|
||||||
at.plus_as_string(TEST_HELLO_WORLD_SOURCE),
|
at.plus_as_string(TEST_HELLO_WORLD_SOURCE),
|
||||||
|
@ -1168,8 +1168,8 @@ fn test_cp_preserve_timestamps() {
|
||||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||||
fn test_cp_no_preserve_timestamps() {
|
fn test_cp_no_preserve_timestamps() {
|
||||||
let (at, mut ucmd) = at_and_ucmd!();
|
let (at, mut ucmd) = at_and_ucmd!();
|
||||||
let ts = time::now().to_timespec();
|
let ts = time::OffsetDateTime::now_local().unwrap();
|
||||||
let previous = FileTime::from_unix_time(ts.sec as i64 - 3600, ts.nsec as u32);
|
let previous = FileTime::from_unix_time(ts.unix_timestamp() - 3600, ts.nanosecond());
|
||||||
// set the file creation/modification an hour ago
|
// set the file creation/modification an hour ago
|
||||||
filetime::set_file_times(
|
filetime::set_file_times(
|
||||||
at.plus_as_string(TEST_HELLO_WORLD_SOURCE),
|
at.plus_as_string(TEST_HELLO_WORLD_SOURCE),
|
||||||
|
|
|
@ -73,7 +73,7 @@ fn test_df_output() {
|
||||||
"Filesystem",
|
"Filesystem",
|
||||||
"Size",
|
"Size",
|
||||||
"Used",
|
"Used",
|
||||||
"Available",
|
"Avail",
|
||||||
"Capacity",
|
"Capacity",
|
||||||
"Use%",
|
"Use%",
|
||||||
"Mounted",
|
"Mounted",
|
||||||
|
@ -84,7 +84,7 @@ fn test_df_output() {
|
||||||
"Filesystem",
|
"Filesystem",
|
||||||
"Size",
|
"Size",
|
||||||
"Used",
|
"Used",
|
||||||
"Available",
|
"Avail",
|
||||||
"Use%",
|
"Use%",
|
||||||
"Mounted",
|
"Mounted",
|
||||||
"on",
|
"on",
|
||||||
|
@ -107,7 +107,7 @@ fn test_df_output_overridden() {
|
||||||
"Filesystem",
|
"Filesystem",
|
||||||
"Size",
|
"Size",
|
||||||
"Used",
|
"Used",
|
||||||
"Available",
|
"Avail",
|
||||||
"Capacity",
|
"Capacity",
|
||||||
"Use%",
|
"Use%",
|
||||||
"Mounted",
|
"Mounted",
|
||||||
|
@ -118,7 +118,7 @@ fn test_df_output_overridden() {
|
||||||
"Filesystem",
|
"Filesystem",
|
||||||
"Size",
|
"Size",
|
||||||
"Used",
|
"Used",
|
||||||
"Available",
|
"Avail",
|
||||||
"Use%",
|
"Use%",
|
||||||
"Mounted",
|
"Mounted",
|
||||||
"on",
|
"on",
|
||||||
|
@ -134,6 +134,46 @@ fn test_df_output_overridden() {
|
||||||
assert_eq!(actual, expected);
|
assert_eq!(actual, expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_default_headers() {
|
||||||
|
let expected = if cfg!(target_os = "macos") {
|
||||||
|
vec![
|
||||||
|
"Filesystem",
|
||||||
|
"1K-blocks",
|
||||||
|
"Used",
|
||||||
|
"Available",
|
||||||
|
"Capacity",
|
||||||
|
"Use%",
|
||||||
|
"Mounted",
|
||||||
|
"on",
|
||||||
|
]
|
||||||
|
} else {
|
||||||
|
vec![
|
||||||
|
"Filesystem",
|
||||||
|
"1K-blocks",
|
||||||
|
"Used",
|
||||||
|
"Available",
|
||||||
|
"Use%",
|
||||||
|
"Mounted",
|
||||||
|
"on",
|
||||||
|
]
|
||||||
|
};
|
||||||
|
let output = new_ucmd!().succeeds().stdout_move_str();
|
||||||
|
let actual = output.lines().take(1).collect::<Vec<&str>>()[0];
|
||||||
|
let actual = actual.split_whitespace().collect::<Vec<_>>();
|
||||||
|
assert_eq!(actual, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_precedence_of_human_readable_header_over_output_header() {
|
||||||
|
let output = new_ucmd!()
|
||||||
|
.args(&["-H", "--output=size"])
|
||||||
|
.succeeds()
|
||||||
|
.stdout_move_str();
|
||||||
|
let header = output.lines().next().unwrap().to_string();
|
||||||
|
assert_eq!(header.trim(), "Size");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_total_option_with_single_dash() {
|
fn test_total_option_with_single_dash() {
|
||||||
// These should fail because `-total` should have two dashes,
|
// These should fail because `-total` should have two dashes,
|
||||||
|
@ -375,6 +415,26 @@ fn test_iuse_percentage() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_default_block_size() {
|
||||||
|
let output = new_ucmd!()
|
||||||
|
.arg("--output=size")
|
||||||
|
.succeeds()
|
||||||
|
.stdout_move_str();
|
||||||
|
let header = output.lines().next().unwrap().to_string();
|
||||||
|
|
||||||
|
assert_eq!(header, "1K-blocks");
|
||||||
|
|
||||||
|
let output = new_ucmd!()
|
||||||
|
.arg("--output=size")
|
||||||
|
.env("POSIXLY_CORRECT", "1")
|
||||||
|
.succeeds()
|
||||||
|
.stdout_move_str();
|
||||||
|
let header = output.lines().next().unwrap().to_string();
|
||||||
|
|
||||||
|
assert_eq!(header, "512B-blocks");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_block_size_1024() {
|
fn test_block_size_1024() {
|
||||||
fn get_header(block_size: u64) -> String {
|
fn get_header(block_size: u64) -> String {
|
||||||
|
@ -392,6 +452,11 @@ fn test_block_size_1024() {
|
||||||
assert_eq!(get_header(2 * 1024 * 1024), "2M-blocks");
|
assert_eq!(get_header(2 * 1024 * 1024), "2M-blocks");
|
||||||
assert_eq!(get_header(1024 * 1024 * 1024), "1G-blocks");
|
assert_eq!(get_header(1024 * 1024 * 1024), "1G-blocks");
|
||||||
assert_eq!(get_header(34 * 1024 * 1024 * 1024), "34G-blocks");
|
assert_eq!(get_header(34 * 1024 * 1024 * 1024), "34G-blocks");
|
||||||
|
|
||||||
|
// multiples of both 1024 and 1000
|
||||||
|
assert_eq!(get_header(128_000), "128kB-blocks");
|
||||||
|
assert_eq!(get_header(1000 * 1024), "1.1MB-blocks");
|
||||||
|
assert_eq!(get_header(1_000_000_000_000), "1TB-blocks");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -418,6 +483,31 @@ fn test_block_size_with_suffix() {
|
||||||
assert_eq!(get_header("1GB"), "1GB-blocks");
|
assert_eq!(get_header("1GB"), "1GB-blocks");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_block_size_in_posix_portability_mode() {
|
||||||
|
fn get_header(block_size: &str) -> String {
|
||||||
|
let output = new_ucmd!()
|
||||||
|
.args(&["-P", "-B", block_size])
|
||||||
|
.succeeds()
|
||||||
|
.stdout_move_str();
|
||||||
|
output
|
||||||
|
.lines()
|
||||||
|
.next()
|
||||||
|
.unwrap()
|
||||||
|
.to_string()
|
||||||
|
.split_whitespace()
|
||||||
|
.nth(1)
|
||||||
|
.unwrap()
|
||||||
|
.to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_eq!(get_header("1024"), "1024-blocks");
|
||||||
|
assert_eq!(get_header("1K"), "1024-blocks");
|
||||||
|
assert_eq!(get_header("1KB"), "1000-blocks");
|
||||||
|
assert_eq!(get_header("1M"), "1048576-blocks");
|
||||||
|
assert_eq!(get_header("1MB"), "1000000-blocks");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_too_large_block_size() {
|
fn test_too_large_block_size() {
|
||||||
fn run_command(size: &str) {
|
fn run_command(size: &str) {
|
||||||
|
@ -440,6 +530,16 @@ fn test_invalid_block_size() {
|
||||||
.arg("--block-size=x")
|
.arg("--block-size=x")
|
||||||
.fails()
|
.fails()
|
||||||
.stderr_contains("invalid --block-size argument 'x'");
|
.stderr_contains("invalid --block-size argument 'x'");
|
||||||
|
|
||||||
|
new_ucmd!()
|
||||||
|
.arg("--block-size=0")
|
||||||
|
.fails()
|
||||||
|
.stderr_contains("invalid --block-size argument '0'");
|
||||||
|
|
||||||
|
new_ucmd!()
|
||||||
|
.arg("--block-size=0K")
|
||||||
|
.fails()
|
||||||
|
.stderr_contains("invalid --block-size argument '0K'");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
@ -2,6 +2,8 @@
|
||||||
|
|
||||||
use crate::common::util::*;
|
use crate::common::util::*;
|
||||||
|
|
||||||
|
use uucore::display::Quotable;
|
||||||
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use tempfile::tempdir;
|
use tempfile::tempdir;
|
||||||
|
|
||||||
|
@ -482,3 +484,15 @@ fn test_respect_template_directory() {
|
||||||
assert_matches_template!(template, filename);
|
assert_matches_template!(template, filename);
|
||||||
assert!(at.file_exists(filename));
|
assert!(at.file_exists(filename));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Test that a template with a path separator is invalid.
|
||||||
|
#[test]
|
||||||
|
fn test_template_path_separator() {
|
||||||
|
new_ucmd!()
|
||||||
|
.args(&["-t", "a/bXXX"])
|
||||||
|
.fails()
|
||||||
|
.stderr_only(format!(
|
||||||
|
"mktemp: invalid template, {}, contains directory separator\n",
|
||||||
|
"a/bXXX".quote()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
|
@ -595,9 +595,9 @@ fn test_mv_update_option() {
|
||||||
|
|
||||||
at.touch(file_a);
|
at.touch(file_a);
|
||||||
at.touch(file_b);
|
at.touch(file_b);
|
||||||
let ts = time::now().to_timespec();
|
let ts = time::OffsetDateTime::now_local().unwrap();
|
||||||
let now = FileTime::from_unix_time(ts.sec as i64, ts.nsec as u32);
|
let now = FileTime::from_unix_time(ts.unix_timestamp(), ts.nanosecond());
|
||||||
let later = FileTime::from_unix_time(ts.sec as i64 + 3600, ts.nsec as u32);
|
let later = FileTime::from_unix_time(ts.unix_timestamp() as i64 + 3600, ts.nanosecond() as u32);
|
||||||
filetime::set_file_times(at.plus_as_string(file_a), now, now).unwrap();
|
filetime::set_file_times(at.plus_as_string(file_a), now, now).unwrap();
|
||||||
filetime::set_file_times(at.plus_as_string(file_b), now, later).unwrap();
|
filetime::set_file_times(at.plus_as_string(file_b), now, later).unwrap();
|
||||||
|
|
||||||
|
|
|
@ -283,6 +283,40 @@ fn test_char() {
|
||||||
ts.ucmd().args(&args).succeeds().stdout_is(expected_stdout);
|
ts.ucmd().args(&args).succeeds().stdout_is(expected_stdout);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(any(target_os = "linux", target_os = "android", target_vendor = "apple"))]
|
||||||
|
#[test]
|
||||||
|
fn test_date() {
|
||||||
|
// Just test the date for the time 0.3 change
|
||||||
|
let args = [
|
||||||
|
"-c",
|
||||||
|
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||||
|
"%z",
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
"/bin/sh",
|
||||||
|
#[cfg(any(target_vendor = "apple"))]
|
||||||
|
"%z",
|
||||||
|
#[cfg(any(target_os = "android", target_vendor = "apple"))]
|
||||||
|
"/bin/sh",
|
||||||
|
];
|
||||||
|
let ts = TestScenario::new(util_name!());
|
||||||
|
let expected_stdout = unwrap_or_return!(expected_result(&ts, &args)).stdout_move_str();
|
||||||
|
ts.ucmd().args(&args).succeeds().stdout_is(expected_stdout);
|
||||||
|
// Just test the date for the time 0.3 change
|
||||||
|
let args = [
|
||||||
|
"-c",
|
||||||
|
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||||
|
"%z",
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
"/dev/ptmx",
|
||||||
|
#[cfg(any(target_vendor = "apple"))]
|
||||||
|
"%z",
|
||||||
|
#[cfg(any(target_os = "android", target_vendor = "apple"))]
|
||||||
|
"/dev/ptmx",
|
||||||
|
];
|
||||||
|
let ts = TestScenario::new(util_name!());
|
||||||
|
let expected_stdout = unwrap_or_return!(expected_result(&ts, &args)).stdout_move_str();
|
||||||
|
ts.ucmd().args(&args).succeeds().stdout_is(expected_stdout);
|
||||||
|
}
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
#[test]
|
#[test]
|
||||||
fn test_multi_files() {
|
fn test_multi_files() {
|
||||||
|
|
|
@ -1,9 +1,16 @@
|
||||||
// spell-checker:ignore (formats) cymdhm cymdhms mdhm mdhms ymdhm ymdhms
|
// spell-checker:ignore (formats) cymdhm cymdhms mdhm mdhms ymdhm ymdhms datetime mktime
|
||||||
|
|
||||||
|
// This test relies on
|
||||||
|
// --cfg unsound_local_offset
|
||||||
|
// https://github.com/time-rs/time/blob/deb8161b84f355b31e39ce09e40c4d6ce3fea837/src/sys/local_offset_at/unix.rs#L112-L120=
|
||||||
|
// See https://github.com/time-rs/time/issues/293#issuecomment-946382614=
|
||||||
|
// Defined in .cargo/config
|
||||||
|
|
||||||
extern crate touch;
|
extern crate touch;
|
||||||
use self::touch::filetime::{self, FileTime};
|
use self::touch::filetime::{self, FileTime};
|
||||||
|
|
||||||
extern crate time;
|
extern crate time;
|
||||||
|
use time::macros::{datetime, format_description};
|
||||||
|
|
||||||
use crate::common::util::*;
|
use crate::common::util::*;
|
||||||
use std::fs::remove_file;
|
use std::fs::remove_file;
|
||||||
|
@ -32,11 +39,24 @@ fn set_file_times(at: &AtPath, path: &str, atime: FileTime, mtime: FileTime) {
|
||||||
|
|
||||||
// Adjusts for local timezone
|
// Adjusts for local timezone
|
||||||
fn str_to_filetime(format: &str, s: &str) -> FileTime {
|
fn str_to_filetime(format: &str, s: &str) -> FileTime {
|
||||||
let mut tm = time::strptime(s, format).unwrap();
|
let format_description = match format {
|
||||||
tm.tm_utcoff = time::now().tm_utcoff;
|
"%y%m%d%H%M" => format_description!("[year repr:last_two][month][day][hour][minute]"),
|
||||||
tm.tm_isdst = -1; // Unknown flag DST
|
"%y%m%d%H%M.%S" => {
|
||||||
let ts = tm.to_timespec();
|
format_description!("[year repr:last_two][month][day][hour][minute].[second]")
|
||||||
FileTime::from_unix_time(ts.sec as i64, ts.nsec as u32)
|
}
|
||||||
|
"%Y%m%d%H%M" => format_description!("[year][month][day][hour][minute]"),
|
||||||
|
"%Y%m%d%H%M.%S" => format_description!("[year][month][day][hour][minute].[second]"),
|
||||||
|
_ => panic!("unexpected dt format"),
|
||||||
|
};
|
||||||
|
let tm = time::PrimitiveDateTime::parse(s, &format_description).unwrap();
|
||||||
|
let d = match time::OffsetDateTime::now_local() {
|
||||||
|
Ok(now) => now,
|
||||||
|
Err(e) => {
|
||||||
|
panic!("Error {} retrieving the OffsetDateTime::now_local", e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let offset_dt = tm.assume_offset(d.offset());
|
||||||
|
FileTime::from_unix_time(offset_dt.unix_timestamp(), tm.nanosecond())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -83,7 +103,10 @@ fn test_touch_set_mdhm_time() {
|
||||||
|
|
||||||
let start_of_year = str_to_filetime(
|
let start_of_year = str_to_filetime(
|
||||||
"%Y%m%d%H%M",
|
"%Y%m%d%H%M",
|
||||||
&format!("{}01010000", 1900 + time::now().tm_year),
|
&format!(
|
||||||
|
"{}01010000",
|
||||||
|
time::OffsetDateTime::now_local().unwrap().year()
|
||||||
|
),
|
||||||
);
|
);
|
||||||
let (atime, mtime) = get_file_times(&at, file);
|
let (atime, mtime) = get_file_times(&at, file);
|
||||||
assert_eq!(atime, mtime);
|
assert_eq!(atime, mtime);
|
||||||
|
@ -104,7 +127,7 @@ fn test_touch_set_mdhms_time() {
|
||||||
|
|
||||||
let start_of_year = str_to_filetime(
|
let start_of_year = str_to_filetime(
|
||||||
"%Y%m%d%H%M.%S",
|
"%Y%m%d%H%M.%S",
|
||||||
&format!("{}01010000.00", 1900 + time::now().tm_year),
|
&format!("{}01010000.00", time::OffsetDateTime::now_utc().year()),
|
||||||
);
|
);
|
||||||
let (atime, mtime) = get_file_times(&at, file);
|
let (atime, mtime) = get_file_times(&at, file);
|
||||||
assert_eq!(atime, mtime);
|
assert_eq!(atime, mtime);
|
||||||
|
@ -123,7 +146,7 @@ fn test_touch_set_ymdhm_time() {
|
||||||
|
|
||||||
assert!(at.file_exists(file));
|
assert!(at.file_exists(file));
|
||||||
|
|
||||||
let start_of_year = str_to_filetime("%y%m%d%H%M", "1501010000");
|
let start_of_year = str_to_filetime("%Y%m%d%H%M", "201501010000");
|
||||||
let (atime, mtime) = get_file_times(&at, file);
|
let (atime, mtime) = get_file_times(&at, file);
|
||||||
assert_eq!(atime, mtime);
|
assert_eq!(atime, mtime);
|
||||||
assert_eq!(atime.unix_seconds() - start_of_year.unix_seconds(), 45240);
|
assert_eq!(atime.unix_seconds() - start_of_year.unix_seconds(), 45240);
|
||||||
|
@ -141,7 +164,7 @@ fn test_touch_set_ymdhms_time() {
|
||||||
|
|
||||||
assert!(at.file_exists(file));
|
assert!(at.file_exists(file));
|
||||||
|
|
||||||
let start_of_year = str_to_filetime("%y%m%d%H%M.%S", "1501010000.00");
|
let start_of_year = str_to_filetime("%Y%m%d%H%M.%S", "201501010000.00");
|
||||||
let (atime, mtime) = get_file_times(&at, file);
|
let (atime, mtime) = get_file_times(&at, file);
|
||||||
assert_eq!(atime, mtime);
|
assert_eq!(atime, mtime);
|
||||||
assert_eq!(atime.unix_seconds() - start_of_year.unix_seconds(), 45296);
|
assert_eq!(atime.unix_seconds() - start_of_year.unix_seconds(), 45296);
|
||||||
|
@ -404,6 +427,86 @@ fn test_touch_set_date3() {
|
||||||
assert_eq!(mtime, expected);
|
assert_eq!(mtime, expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_touch_set_date4() {
|
||||||
|
let (at, mut ucmd) = at_and_ucmd!();
|
||||||
|
let file = "test_touch_set_date";
|
||||||
|
|
||||||
|
ucmd.args(&["-d", "1970-01-01 18:43:33", file])
|
||||||
|
.succeeds()
|
||||||
|
.no_stderr();
|
||||||
|
|
||||||
|
assert!(at.file_exists(file));
|
||||||
|
|
||||||
|
let expected = FileTime::from_unix_time(67413, 0);
|
||||||
|
let (atime, mtime) = get_file_times(&at, file);
|
||||||
|
assert_eq!(atime, mtime);
|
||||||
|
assert_eq!(atime, expected);
|
||||||
|
assert_eq!(mtime, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_touch_set_date5() {
|
||||||
|
let (at, mut ucmd) = at_and_ucmd!();
|
||||||
|
let file = "test_touch_set_date";
|
||||||
|
|
||||||
|
ucmd.args(&["-d", "1970-01-01 18:43:33.023456789", file])
|
||||||
|
.succeeds()
|
||||||
|
.no_stderr();
|
||||||
|
|
||||||
|
assert!(at.file_exists(file));
|
||||||
|
|
||||||
|
// Slightly different result on Windows for nano seconds
|
||||||
|
// TODO: investigate
|
||||||
|
#[cfg(windows)]
|
||||||
|
let expected = FileTime::from_unix_time(67413, 23456700);
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
let expected = FileTime::from_unix_time(67413, 23456789);
|
||||||
|
|
||||||
|
let (atime, mtime) = get_file_times(&at, file);
|
||||||
|
assert_eq!(atime, mtime);
|
||||||
|
assert_eq!(atime, expected);
|
||||||
|
assert_eq!(mtime, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_touch_set_date6() {
|
||||||
|
let (at, mut ucmd) = at_and_ucmd!();
|
||||||
|
let file = "test_touch_set_date";
|
||||||
|
|
||||||
|
ucmd.args(&["-d", "2000-01-01 00:00", file])
|
||||||
|
.succeeds()
|
||||||
|
.no_stderr();
|
||||||
|
|
||||||
|
assert!(at.file_exists(file));
|
||||||
|
|
||||||
|
let expected = FileTime::from_unix_time(946684800, 0);
|
||||||
|
|
||||||
|
let (atime, mtime) = get_file_times(&at, file);
|
||||||
|
assert_eq!(atime, mtime);
|
||||||
|
assert_eq!(atime, expected);
|
||||||
|
assert_eq!(mtime, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_touch_set_date7() {
|
||||||
|
let (at, mut ucmd) = at_and_ucmd!();
|
||||||
|
let file = "test_touch_set_date";
|
||||||
|
|
||||||
|
ucmd.args(&["-d", "2004-01-16 12:00 +0000", file])
|
||||||
|
.succeeds()
|
||||||
|
.no_stderr();
|
||||||
|
|
||||||
|
assert!(at.file_exists(file));
|
||||||
|
|
||||||
|
let expected = FileTime::from_unix_time(1074254400, 0);
|
||||||
|
|
||||||
|
let (atime, mtime) = get_file_times(&at, file);
|
||||||
|
assert_eq!(atime, mtime);
|
||||||
|
assert_eq!(atime, expected);
|
||||||
|
assert_eq!(mtime, expected);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_touch_set_date_wrong_format() {
|
fn test_touch_set_date_wrong_format() {
|
||||||
let (_at, mut ucmd) = at_and_ucmd!();
|
let (_at, mut ucmd) = at_and_ucmd!();
|
||||||
|
@ -430,18 +533,18 @@ fn test_touch_mtime_dst_succeeds() {
|
||||||
assert_eq!(target_time, mtime);
|
assert_eq!(target_time, mtime);
|
||||||
}
|
}
|
||||||
|
|
||||||
// is_dst_switch_hour returns true if timespec ts is just before the switch
|
// // is_dst_switch_hour returns true if timespec ts is just before the switch
|
||||||
// to Daylight Saving Time.
|
// // to Daylight Saving Time.
|
||||||
// For example, in EST (UTC-5), Timespec { sec: 1583647200, nsec: 0 }
|
// // For example, in EST (UTC-5), Timespec { sec: 1583647200, nsec: 0 }
|
||||||
// for March 8 2020 01:00:00 AM
|
// // for March 8 2020 01:00:00 AM
|
||||||
// is just before the switch because on that day clock jumps by 1 hour,
|
// // is just before the switch because on that day clock jumps by 1 hour,
|
||||||
// so 1 minute after 01:59:00 is 03:00:00.
|
// // so 1 minute after 01:59:00 is 03:00:00.
|
||||||
fn is_dst_switch_hour(ts: time::Timespec) -> bool {
|
// fn is_dst_switch_hour(ts: time::Timespec) -> bool {
|
||||||
let ts_after = ts + time::Duration::hours(1);
|
// let ts_after = ts + time::Duration::hours(1);
|
||||||
let tm = time::at(ts);
|
// let tm = time::at(ts);
|
||||||
let tm_after = time::at(ts_after);
|
// let tm_after = time::at(ts_after);
|
||||||
tm_after.tm_hour == tm.tm_hour + 2
|
// tm_after.tm_hour == tm.tm_hour + 2
|
||||||
}
|
// }
|
||||||
|
|
||||||
// get_dst_switch_hour returns date string for which touch -m -t fails.
|
// get_dst_switch_hour returns date string for which touch -m -t fails.
|
||||||
// For example, in EST (UTC-5), that will be "202003080200" so
|
// For example, in EST (UTC-5), that will be "202003080200" so
|
||||||
|
@ -450,23 +553,30 @@ fn is_dst_switch_hour(ts: time::Timespec) -> bool {
|
||||||
// In other locales it will be a different date/time, and in some locales
|
// In other locales it will be a different date/time, and in some locales
|
||||||
// it doesn't exist at all, in which case this function will return None.
|
// it doesn't exist at all, in which case this function will return None.
|
||||||
fn get_dst_switch_hour() -> Option<String> {
|
fn get_dst_switch_hour() -> Option<String> {
|
||||||
let now = time::now();
|
//let now = time::OffsetDateTime::now_local().unwrap();
|
||||||
// Start from January 1, 2020, 00:00.
|
let now = match time::OffsetDateTime::now_local() {
|
||||||
let mut tm = time::strptime("20200101-0000", "%Y%m%d-%H%M").unwrap();
|
Ok(now) => now,
|
||||||
tm.tm_isdst = -1;
|
Err(e) => {
|
||||||
tm.tm_utcoff = now.tm_utcoff;
|
panic!("Error {} retrieving the OffsetDateTime::now_local", e);
|
||||||
let mut ts = tm.to_timespec();
|
|
||||||
// Loop through all hours in year 2020 until we find the hour just
|
|
||||||
// before the switch to DST.
|
|
||||||
for _i in 0..(366 * 24) {
|
|
||||||
if is_dst_switch_hour(ts) {
|
|
||||||
let mut tm = time::at(ts);
|
|
||||||
tm.tm_hour += 1;
|
|
||||||
let s = time::strftime("%Y%m%d%H%M", &tm).unwrap();
|
|
||||||
return Some(s);
|
|
||||||
}
|
}
|
||||||
ts = ts + time::Duration::hours(1);
|
};
|
||||||
}
|
|
||||||
|
// Start from January 1, 2020, 00:00.
|
||||||
|
let tm = datetime!(2020-01-01 00:00 UTC);
|
||||||
|
tm.to_offset(now.offset());
|
||||||
|
|
||||||
|
// let mut ts = tm.to_timespec();
|
||||||
|
// // Loop through all hours in year 2020 until we find the hour just
|
||||||
|
// // before the switch to DST.
|
||||||
|
// for _i in 0..(366 * 24) {
|
||||||
|
// // if is_dst_switch_hour(ts) {
|
||||||
|
// // let mut tm = time::at(ts);
|
||||||
|
// // tm.tm_hour += 1;
|
||||||
|
// // let s = time::strftime("%Y%m%d%H%M", &tm).unwrap();
|
||||||
|
// // return Some(s);
|
||||||
|
// // }
|
||||||
|
// ts = ts + time::Duration::hours(1);
|
||||||
|
// }
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -573,3 +683,21 @@ fn test_no_dereference_no_file() {
|
||||||
.stderr_contains("setting times of 'not-a-file-1': No such file or directory")
|
.stderr_contains("setting times of 'not-a-file-1': No such file or directory")
|
||||||
.stderr_contains("setting times of 'not-a-file-2': No such file or directory");
|
.stderr_contains("setting times of 'not-a-file-2': No such file or directory");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_touch_leap_second() {
|
||||||
|
let (at, mut ucmd) = at_and_ucmd!();
|
||||||
|
let file = "test_touch_leap_sec";
|
||||||
|
|
||||||
|
ucmd.args(&["-t", "197001010000.60", file])
|
||||||
|
.succeeds()
|
||||||
|
.no_stderr();
|
||||||
|
|
||||||
|
assert!(at.file_exists(file));
|
||||||
|
|
||||||
|
let epoch = str_to_filetime("%Y%m%d%H%M", "197001010000");
|
||||||
|
let (atime, mtime) = get_file_times(&at, file);
|
||||||
|
assert_eq!(atime, mtime);
|
||||||
|
assert_eq!(atime.unix_seconds() - epoch.unix_seconds(), 60);
|
||||||
|
assert_eq!(mtime.unix_seconds() - epoch.unix_seconds(), 60);
|
||||||
|
}
|
||||||
|
|
|
@ -680,6 +680,9 @@ fn gnu_tests() {
|
||||||
stderr: None,
|
stderr: None,
|
||||||
exit: None,
|
exit: None,
|
||||||
},
|
},
|
||||||
|
/*
|
||||||
|
Disable as it fails too often. See:
|
||||||
|
https://github.com/uutils/coreutils/issues/3509
|
||||||
TestCase {
|
TestCase {
|
||||||
name: "112",
|
name: "112",
|
||||||
args: &["-D", "-c"],
|
args: &["-D", "-c"],
|
||||||
|
@ -687,7 +690,7 @@ fn gnu_tests() {
|
||||||
stdout: Some(""),
|
stdout: Some(""),
|
||||||
stderr: Some("uniq: printing all duplicated lines and repeat counts is meaningless"),
|
stderr: Some("uniq: printing all duplicated lines and repeat counts is meaningless"),
|
||||||
exit: Some(1),
|
exit: Some(1),
|
||||||
},
|
},*/
|
||||||
TestCase {
|
TestCase {
|
||||||
name: "113",
|
name: "113",
|
||||||
args: &["--all-repeated=separate"],
|
args: &["--all-repeated=separate"],
|
||||||
|
|
|
@ -1362,6 +1362,70 @@ pub fn expected_result(ts: &TestScenario, args: &[&str]) -> std::result::Result<
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// This is a convenience wrapper to run a ucmd with root permissions.
|
||||||
|
/// It can be used to test programs when being root is needed
|
||||||
|
/// This runs 'sudo -E --non-interactive target/debug/coreutils util_name args`
|
||||||
|
/// This is primarily designed to run in an environment where whoami is in $path
|
||||||
|
/// and where non-interactive sudo is possible.
|
||||||
|
/// To check if i) non-interactive sudo is possible and ii) if sudo works, this runs:
|
||||||
|
/// 'sudo -E --non-interactive whoami' first.
|
||||||
|
///
|
||||||
|
/// This return an `Err()` if run inside CICD because there's no 'sudo'.
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
///
|
||||||
|
/// ```no_run
|
||||||
|
/// use crate::common::util::*;
|
||||||
|
/// #[test]
|
||||||
|
/// fn test_xyz() {
|
||||||
|
/// let ts = TestScenario::new("whoami");
|
||||||
|
/// let expected = "root\n".to_string();
|
||||||
|
/// if let Ok(result) = run_ucmd_as_root(&ts, &[]) {
|
||||||
|
/// result.stdout_is(expected);
|
||||||
|
/// } else {
|
||||||
|
/// println!("TEST SKIPPED");
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
///```
|
||||||
|
#[cfg(unix)]
|
||||||
|
pub fn run_ucmd_as_root(
|
||||||
|
ts: &TestScenario,
|
||||||
|
args: &[&str],
|
||||||
|
) -> std::result::Result<CmdResult, String> {
|
||||||
|
if !is_ci() {
|
||||||
|
// check if we can run 'sudo'
|
||||||
|
log_info("run", "sudo -E --non-interactive whoami");
|
||||||
|
match Command::new("sudo")
|
||||||
|
.env("LC_ALL", "C")
|
||||||
|
.args(&["-E", "--non-interactive", "whoami"])
|
||||||
|
.output()
|
||||||
|
{
|
||||||
|
Ok(output) if String::from_utf8_lossy(&output.stdout).eq("root\n") => {
|
||||||
|
// we can run sudo and we're root
|
||||||
|
// run ucmd as root:
|
||||||
|
Ok(ts
|
||||||
|
.cmd_keepenv("sudo")
|
||||||
|
.env("LC_ALL", "C")
|
||||||
|
.arg("-E")
|
||||||
|
.arg("--non-interactive")
|
||||||
|
.arg(&ts.bin_path)
|
||||||
|
.arg(&ts.util_name)
|
||||||
|
.args(args)
|
||||||
|
.run())
|
||||||
|
}
|
||||||
|
Ok(output)
|
||||||
|
if String::from_utf8_lossy(&output.stderr).eq("sudo: a password is required\n") =>
|
||||||
|
{
|
||||||
|
Err("Cannot run non-interactive sudo".to_string())
|
||||||
|
}
|
||||||
|
Ok(_output) => Err("\"sudo whoami\" didn't return \"root\"".to_string()),
|
||||||
|
Err(e) => Err(format!("{}: {}", UUTILS_WARNING, e)),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Err(format!("{}: {}", UUTILS_INFO, "cannot run inside CI"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Sanity checks for test utils
|
/// Sanity checks for test utils
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
@ -1712,4 +1776,32 @@ mod tests {
|
||||||
std::assert_eq!(host_name_for("gwho"), "gwho");
|
std::assert_eq!(host_name_for("gwho"), "gwho");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(unix)]
|
||||||
|
#[cfg(feature = "whoami")]
|
||||||
|
fn test_run_ucmd_as_root() {
|
||||||
|
if !is_ci() {
|
||||||
|
// Skip test if we can't guarantee non-interactive `sudo`, or if we're not "root"
|
||||||
|
if let Ok(output) = Command::new("sudo")
|
||||||
|
.env("LC_ALL", "C")
|
||||||
|
.args(&["-E", "--non-interactive", "whoami"])
|
||||||
|
.output()
|
||||||
|
{
|
||||||
|
if output.status.success() && String::from_utf8_lossy(&output.stdout).eq("root\n") {
|
||||||
|
let ts = TestScenario::new("whoami");
|
||||||
|
std::assert_eq!(
|
||||||
|
run_ucmd_as_root(&ts, &[]).unwrap().stdout_str().trim(),
|
||||||
|
"root"
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
println!("TEST SKIPPED (we're not root)");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
println!("TEST SKIPPED (cannot run sudo)");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
println!("TEST SKIPPED (cannot run inside CI)");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
#!/bin/bash
|
||||||
# spell-checker:ignore termux keyevent sdcard binutils unmatch adb's dumpsys logcat pkill
|
# spell-checker:ignore termux keyevent sdcard binutils unmatch adb's dumpsys logcat pkill
|
||||||
|
|
||||||
# There are three shells: the host's, adb, and termux. Only adb lets us run
|
# There are three shells: the host's, adb, and termux. Only adb lets us run
|
||||||
|
|
|
@ -137,7 +137,8 @@ sed -i 's|chmod |/usr/bin/chmod |' tests/du/inacc-dir.sh tests/tail-2/tail-n0f.s
|
||||||
sed -i 's|sort |/usr/bin/sort |' tests/ls/hyperlink.sh tests/misc/test-N.sh
|
sed -i 's|sort |/usr/bin/sort |' tests/ls/hyperlink.sh tests/misc/test-N.sh
|
||||||
sed -i 's|split |/usr/bin/split |' tests/misc/factor-parallel.sh
|
sed -i 's|split |/usr/bin/split |' tests/misc/factor-parallel.sh
|
||||||
sed -i 's|id -|/usr/bin/id -|' tests/misc/runcon-no-reorder.sh
|
sed -i 's|id -|/usr/bin/id -|' tests/misc/runcon-no-reorder.sh
|
||||||
sed -i 's|touch |/usr/bin/touch |' tests/cp/preserve-link.sh tests/cp/reflink-perm.sh tests/ls/block-size.sh tests/mv/update.sh tests/misc/ls-time.sh tests/misc/stat-nanoseconds.sh tests/misc/time-style.sh tests/misc/test-N.sh
|
# tests/ls/abmon-align.sh - https://github.com/uutils/coreutils/issues/3505
|
||||||
|
sed -i 's|touch |/usr/bin/touch |' tests/cp/preserve-link.sh tests/cp/reflink-perm.sh tests/ls/block-size.sh tests/mv/update.sh tests/misc/ls-time.sh tests/misc/stat-nanoseconds.sh tests/misc/time-style.sh tests/misc/test-N.sh tests/ls/abmon-align.sh
|
||||||
sed -i 's|ln -|/usr/bin/ln -|' tests/cp/link-deref.sh
|
sed -i 's|ln -|/usr/bin/ln -|' tests/cp/link-deref.sh
|
||||||
sed -i 's|cp |/usr/bin/cp |' tests/mv/hard-2.sh
|
sed -i 's|cp |/usr/bin/cp |' tests/mv/hard-2.sh
|
||||||
sed -i 's|paste |/usr/bin/paste |' tests/misc/od-endian.sh
|
sed -i 's|paste |/usr/bin/paste |' tests/misc/od-endian.sh
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue