mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-28 11:37:44 +00:00
Merge branch 'main' into hotfix-mktemp
This commit is contained in:
commit
59490e4a17
80 changed files with 1585 additions and 751 deletions
5
.github/workflows/CICD.yml
vendored
5
.github/workflows/CICD.yml
vendored
|
@ -20,6 +20,11 @@ on: [push, pull_request]
|
||||||
permissions:
|
permissions:
|
||||||
contents: read # to fetch code (actions/checkout)
|
contents: read # to fetch code (actions/checkout)
|
||||||
|
|
||||||
|
# End the current execution if there is a new changeset in the PR.
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
|
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
cargo-deny:
|
cargo-deny:
|
||||||
name: Style/cargo-deny
|
name: Style/cargo-deny
|
||||||
|
|
24
.github/workflows/GnuTests.yml
vendored
24
.github/workflows/GnuTests.yml
vendored
|
@ -14,6 +14,11 @@ on: [push, pull_request]
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
|
|
||||||
|
# End the current execution if there is a new changeset in the PR.
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
|
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
gnu:
|
gnu:
|
||||||
permissions:
|
permissions:
|
||||||
|
@ -196,6 +201,9 @@ jobs:
|
||||||
REF_LOG_FILE='${{ steps.vars.outputs.path_reference }}/test-logs/test-suite.log'
|
REF_LOG_FILE='${{ steps.vars.outputs.path_reference }}/test-logs/test-suite.log'
|
||||||
REF_SUMMARY_FILE='${{ steps.vars.outputs.path_reference }}/test-summary/gnu-result.json'
|
REF_SUMMARY_FILE='${{ steps.vars.outputs.path_reference }}/test-summary/gnu-result.json'
|
||||||
REPO_DEFAULT_BRANCH='${{ steps.vars.outputs.repo_default_branch }}'
|
REPO_DEFAULT_BRANCH='${{ steps.vars.outputs.repo_default_branch }}'
|
||||||
|
# https://github.com/uutils/coreutils/issues/4294
|
||||||
|
# https://github.com/uutils/coreutils/issues/4295
|
||||||
|
IGNORE_INTERMITTENT='tests/tail-2/inotify-dir-recreate tests/misc/timeout tests/rm/rm1'
|
||||||
|
|
||||||
mkdir -p ${{ steps.vars.outputs.path_reference }}
|
mkdir -p ${{ steps.vars.outputs.path_reference }}
|
||||||
|
|
||||||
|
@ -227,10 +235,18 @@ jobs:
|
||||||
do
|
do
|
||||||
if ! grep -Fxq ${LINE}<<<"${REF_FAILING}"
|
if ! grep -Fxq ${LINE}<<<"${REF_FAILING}"
|
||||||
then
|
then
|
||||||
MSG="GNU test failed: ${LINE}. ${LINE} is passing on '${{ steps.vars.outputs.repo_default_branch }}'. Maybe you have to rebase?"
|
if ! grep ${LINE} ${IGNORE_INTERMITTENT}
|
||||||
echo "::error ::$MSG"
|
then
|
||||||
echo $MSG >> ${COMMENT_LOG}
|
MSG="GNU test failed: ${LINE}. ${LINE} is passing on '${{ steps.vars.outputs.repo_default_branch }}'. Maybe you have to rebase?"
|
||||||
have_new_failures="true"
|
echo "::error ::$MSG"
|
||||||
|
echo $MSG >> ${COMMENT_LOG}
|
||||||
|
have_new_failures="true"
|
||||||
|
else
|
||||||
|
MSG="Skip an intermittent issue ${LINE}"
|
||||||
|
echo "::warning ::$MSG"
|
||||||
|
echo $MSG >> ${COMMENT_LOG}
|
||||||
|
echo ""
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
for LINE in ${REF_ERROR}
|
for LINE in ${REF_ERROR}
|
||||||
|
|
171
Cargo.lock
generated
171
Cargo.lock
generated
|
@ -67,7 +67,7 @@ version = "0.2.14"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
|
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"hermit-abi",
|
"hermit-abi 0.1.19",
|
||||||
"libc",
|
"libc",
|
||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
@ -293,7 +293,7 @@ dependencies = [
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"libc",
|
"libc",
|
||||||
"unicode-width",
|
"unicode-width",
|
||||||
"windows-sys",
|
"windows-sys 0.42.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -321,7 +321,6 @@ checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc"
|
||||||
name = "coreutils"
|
name = "coreutils"
|
||||||
version = "0.0.17"
|
version = "0.0.17"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"atty",
|
|
||||||
"chrono",
|
"chrono",
|
||||||
"clap",
|
"clap",
|
||||||
"clap_complete",
|
"clap_complete",
|
||||||
|
@ -329,6 +328,7 @@ dependencies = [
|
||||||
"filetime",
|
"filetime",
|
||||||
"glob",
|
"glob",
|
||||||
"hex-literal",
|
"hex-literal",
|
||||||
|
"is-terminal",
|
||||||
"libc",
|
"libc",
|
||||||
"nix",
|
"nix",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
|
@ -635,7 +635,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1631ca6e3c59112501a9d87fd86f21591ff77acd31331e8a73f8d80a65bbdd71"
|
checksum = "1631ca6e3c59112501a9d87fd86f21591ff77acd31331e8a73f8d80a65bbdd71"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"nix",
|
"nix",
|
||||||
"windows-sys",
|
"windows-sys 0.42.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -834,7 +834,7 @@ dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"libc",
|
"libc",
|
||||||
"redox_syscall",
|
"redox_syscall",
|
||||||
"windows-sys",
|
"windows-sys 0.42.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -855,9 +855,9 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fs_extra"
|
name = "fs_extra"
|
||||||
version = "1.2.0"
|
version = "1.3.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2022715d62ab30faffd124d40b76f4134a550a87792276512b18d63272333394"
|
checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fsevent-sys"
|
name = "fsevent-sys"
|
||||||
|
@ -1045,6 +1045,12 @@ dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hermit-abi"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hex"
|
name = "hex"
|
||||||
version = "0.4.3"
|
version = "0.4.3"
|
||||||
|
@ -1139,6 +1145,28 @@ version = "0.7.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "59ce5ef949d49ee85593fc4d3f3f95ad61657076395cbbce23e2121fc5542074"
|
checksum = "59ce5ef949d49ee85593fc4d3f3f95ad61657076395cbbce23e2121fc5542074"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "io-lifetimes"
|
||||||
|
version = "1.0.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1abeb7a0dd0f8181267ff8adc397075586500b81b28a73e8a0208b00fc170fb3"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"windows-sys 0.45.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "is-terminal"
|
||||||
|
version = "0.4.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "22e18b0a45d56fe973d6db23972bf5bc46f988a4a2385deac9cc29572f09daef"
|
||||||
|
dependencies = [
|
||||||
|
"hermit-abi 0.3.1",
|
||||||
|
"io-lifetimes 1.0.5",
|
||||||
|
"rustix 0.36.8",
|
||||||
|
"windows-sys 0.45.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itertools"
|
name = "itertools"
|
||||||
version = "0.10.5"
|
version = "0.10.5"
|
||||||
|
@ -1235,6 +1263,12 @@ version = "0.0.46"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d4d2456c373231a208ad294c33dc5bff30051eafd954cd4caae83a712b12854d"
|
checksum = "d4d2456c373231a208ad294c33dc5bff30051eafd954cd4caae83a712b12854d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "linux-raw-sys"
|
||||||
|
version = "0.1.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lock_api"
|
name = "lock_api"
|
||||||
version = "0.4.9"
|
version = "0.4.9"
|
||||||
|
@ -1326,7 +1360,7 @@ dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"log",
|
"log",
|
||||||
"wasi",
|
"wasi",
|
||||||
"windows-sys",
|
"windows-sys 0.42.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -1415,7 +1449,7 @@ version = "1.14.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f6058e64324c71e02bc2b150e4f3bc8286db6c83092132ffa3f6b1eab0f9def5"
|
checksum = "f6058e64324c71e02bc2b150e4f3bc8286db6c83092132ffa3f6b1eab0f9def5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"hermit-abi",
|
"hermit-abi 0.1.19",
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -1545,7 +1579,7 @@ dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"redox_syscall",
|
"redox_syscall",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
"windows-sys",
|
"windows-sys 0.42.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -1693,7 +1727,7 @@ dependencies = [
|
||||||
"byteorder",
|
"byteorder",
|
||||||
"hex",
|
"hex",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"rustix",
|
"rustix 0.35.13",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -1833,9 +1867,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rlimit"
|
name = "rlimit"
|
||||||
version = "0.8.3"
|
version = "0.9.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f7278a1ec8bfd4a4e07515c589f5ff7b309a373f987393aef44813d9dcf87aa3"
|
checksum = "f8a29d87a652dc4d43c586328706bb5cdff211f3f39a530f240b53f7221dab8e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
@ -1899,10 +1933,24 @@ checksum = "727a1a6d65f786ec22df8a81ca3121107f235970dc1705ed681d3e6e8b9cd5f9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags",
|
||||||
"errno",
|
"errno",
|
||||||
"io-lifetimes",
|
"io-lifetimes 0.7.5",
|
||||||
"libc",
|
"libc",
|
||||||
"linux-raw-sys",
|
"linux-raw-sys 0.0.46",
|
||||||
"windows-sys",
|
"windows-sys 0.42.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustix"
|
||||||
|
version = "0.36.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f43abb88211988493c1abb44a70efa56ff0ce98f233b7b276146f1f3f7ba9644"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"errno",
|
||||||
|
"io-lifetimes 1.0.5",
|
||||||
|
"libc",
|
||||||
|
"linux-raw-sys 0.1.4",
|
||||||
|
"windows-sys 0.45.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -2161,8 +2209,8 @@ version = "0.2.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "40ca90c434fd12083d1a6bdcbe9f92a14f96c8a1ba600ba451734ac334521f7a"
|
checksum = "40ca90c434fd12083d1a6bdcbe9f92a14f96c8a1ba600ba451734ac334521f7a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"rustix",
|
"rustix 0.35.13",
|
||||||
"windows-sys",
|
"windows-sys 0.42.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -2334,8 +2382,8 @@ dependencies = [
|
||||||
name = "uu_cat"
|
name = "uu_cat"
|
||||||
version = "0.0.17"
|
version = "0.0.17"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"atty",
|
|
||||||
"clap",
|
"clap",
|
||||||
|
"is-terminal",
|
||||||
"nix",
|
"nix",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"uucore",
|
"uucore",
|
||||||
|
@ -2432,9 +2480,9 @@ dependencies = [
|
||||||
name = "uu_cut"
|
name = "uu_cut"
|
||||||
version = "0.0.17"
|
version = "0.0.17"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"atty",
|
|
||||||
"bstr",
|
"bstr",
|
||||||
"clap",
|
"clap",
|
||||||
|
"is-terminal",
|
||||||
"memchr",
|
"memchr",
|
||||||
"uucore",
|
"uucore",
|
||||||
]
|
]
|
||||||
|
@ -2447,7 +2495,7 @@ dependencies = [
|
||||||
"clap",
|
"clap",
|
||||||
"libc",
|
"libc",
|
||||||
"uucore",
|
"uucore",
|
||||||
"windows-sys",
|
"windows-sys 0.42.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -2503,7 +2551,7 @@ dependencies = [
|
||||||
"clap",
|
"clap",
|
||||||
"glob",
|
"glob",
|
||||||
"uucore",
|
"uucore",
|
||||||
"windows-sys",
|
"windows-sys 0.42.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -2634,7 +2682,7 @@ dependencies = [
|
||||||
"clap",
|
"clap",
|
||||||
"hostname",
|
"hostname",
|
||||||
"uucore",
|
"uucore",
|
||||||
"windows-sys",
|
"windows-sys 0.42.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -2705,10 +2753,10 @@ dependencies = [
|
||||||
name = "uu_ls"
|
name = "uu_ls"
|
||||||
version = "0.0.17"
|
version = "0.0.17"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"atty",
|
|
||||||
"chrono",
|
"chrono",
|
||||||
"clap",
|
"clap",
|
||||||
"glob",
|
"glob",
|
||||||
|
"is-terminal",
|
||||||
"lscolors",
|
"lscolors",
|
||||||
"number_prefix",
|
"number_prefix",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
|
@ -2759,9 +2807,9 @@ dependencies = [
|
||||||
name = "uu_more"
|
name = "uu_more"
|
||||||
version = "0.0.17"
|
version = "0.0.17"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"atty",
|
|
||||||
"clap",
|
"clap",
|
||||||
"crossterm",
|
"crossterm",
|
||||||
|
"is-terminal",
|
||||||
"nix",
|
"nix",
|
||||||
"unicode-segmentation",
|
"unicode-segmentation",
|
||||||
"unicode-width",
|
"unicode-width",
|
||||||
|
@ -2801,8 +2849,8 @@ dependencies = [
|
||||||
name = "uu_nohup"
|
name = "uu_nohup"
|
||||||
version = "0.0.17"
|
version = "0.0.17"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"atty",
|
|
||||||
"clap",
|
"clap",
|
||||||
|
"is-terminal",
|
||||||
"libc",
|
"libc",
|
||||||
"uucore",
|
"uucore",
|
||||||
]
|
]
|
||||||
|
@ -2936,7 +2984,7 @@ dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"uucore",
|
"uucore",
|
||||||
"walkdir",
|
"walkdir",
|
||||||
"windows-sys",
|
"windows-sys 0.42.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -3079,7 +3127,7 @@ dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"nix",
|
"nix",
|
||||||
"uucore",
|
"uucore",
|
||||||
"windows-sys",
|
"windows-sys 0.42.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -3097,17 +3145,16 @@ dependencies = [
|
||||||
name = "uu_tail"
|
name = "uu_tail"
|
||||||
version = "0.0.17"
|
version = "0.0.17"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"atty",
|
|
||||||
"clap",
|
"clap",
|
||||||
"fundu",
|
"fundu",
|
||||||
|
"is-terminal",
|
||||||
"libc",
|
"libc",
|
||||||
"memchr",
|
"memchr",
|
||||||
"nix",
|
|
||||||
"notify",
|
"notify",
|
||||||
"same-file",
|
"same-file",
|
||||||
"uucore",
|
"uucore",
|
||||||
"winapi-util",
|
"winapi-util",
|
||||||
"windows-sys",
|
"windows-sys 0.42.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -3147,7 +3194,7 @@ dependencies = [
|
||||||
"filetime",
|
"filetime",
|
||||||
"time",
|
"time",
|
||||||
"uucore",
|
"uucore",
|
||||||
"windows-sys",
|
"windows-sys 0.42.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -3187,8 +3234,8 @@ dependencies = [
|
||||||
name = "uu_tty"
|
name = "uu_tty"
|
||||||
version = "0.0.17"
|
version = "0.0.17"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"atty",
|
|
||||||
"clap",
|
"clap",
|
||||||
|
"is-terminal",
|
||||||
"nix",
|
"nix",
|
||||||
"uucore",
|
"uucore",
|
||||||
]
|
]
|
||||||
|
@ -3283,7 +3330,7 @@ dependencies = [
|
||||||
"clap",
|
"clap",
|
||||||
"libc",
|
"libc",
|
||||||
"uucore",
|
"uucore",
|
||||||
"windows-sys",
|
"windows-sys 0.42.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -3317,7 +3364,7 @@ dependencies = [
|
||||||
"walkdir",
|
"walkdir",
|
||||||
"wild",
|
"wild",
|
||||||
"winapi-util",
|
"winapi-util",
|
||||||
"windows-sys",
|
"windows-sys 0.42.0",
|
||||||
"z85",
|
"z85",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -3479,46 +3526,70 @@ dependencies = [
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_aarch64_gnullvm"
|
name = "windows-sys"
|
||||||
version = "0.42.0"
|
version = "0.45.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e"
|
checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
|
||||||
|
dependencies = [
|
||||||
|
"windows-targets",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-targets"
|
||||||
|
version = "0.42.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8e2522491fbfcd58cc84d47aeb2958948c4b8982e9a2d8a2a35bbaed431390e7"
|
||||||
|
dependencies = [
|
||||||
|
"windows_aarch64_gnullvm",
|
||||||
|
"windows_aarch64_msvc",
|
||||||
|
"windows_i686_gnu",
|
||||||
|
"windows_i686_msvc",
|
||||||
|
"windows_x86_64_gnu",
|
||||||
|
"windows_x86_64_gnullvm",
|
||||||
|
"windows_x86_64_msvc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_aarch64_gnullvm"
|
||||||
|
version = "0.42.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_aarch64_msvc"
|
name = "windows_aarch64_msvc"
|
||||||
version = "0.42.0"
|
version = "0.42.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4"
|
checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_i686_gnu"
|
name = "windows_i686_gnu"
|
||||||
version = "0.42.0"
|
version = "0.42.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7"
|
checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_i686_msvc"
|
name = "windows_i686_msvc"
|
||||||
version = "0.42.0"
|
version = "0.42.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246"
|
checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_x86_64_gnu"
|
name = "windows_x86_64_gnu"
|
||||||
version = "0.42.0"
|
version = "0.42.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed"
|
checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_x86_64_gnullvm"
|
name = "windows_x86_64_gnullvm"
|
||||||
version = "0.42.0"
|
version = "0.42.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028"
|
checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_x86_64_msvc"
|
name = "windows_x86_64_msvc"
|
||||||
version = "0.42.0"
|
version = "0.42.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5"
|
checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "xattr"
|
name = "xattr"
|
||||||
|
|
|
@ -263,7 +263,6 @@ feat_os_windows_legacy = [
|
||||||
test = [ "uu_test" ]
|
test = [ "uu_test" ]
|
||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
atty = "0.2"
|
|
||||||
bigdecimal = "0.3"
|
bigdecimal = "0.3"
|
||||||
binary-heap-plus = "0.5.0"
|
binary-heap-plus = "0.5.0"
|
||||||
bstr = "1.0"
|
bstr = "1.0"
|
||||||
|
@ -287,6 +286,7 @@ gcd = "2.2"
|
||||||
glob = "0.3.0"
|
glob = "0.3.0"
|
||||||
half = "2.1"
|
half = "2.1"
|
||||||
indicatif = "0.17"
|
indicatif = "0.17"
|
||||||
|
is-terminal = "0.4.3"
|
||||||
itertools = "0.10.0"
|
itertools = "0.10.0"
|
||||||
libc = "0.2.139"
|
libc = "0.2.139"
|
||||||
lscolors = { version = "0.13.0", default-features=false, features = ["nu-ansi-term"] }
|
lscolors = { version = "0.13.0", default-features=false, features = ["nu-ansi-term"] }
|
||||||
|
@ -476,13 +476,13 @@ time = { workspace=true, features=["local-offset"] }
|
||||||
unindent = "0.1"
|
unindent = "0.1"
|
||||||
uucore = { workspace=true, features=["entries", "process", "signals"] }
|
uucore = { workspace=true, features=["entries", "process", "signals"] }
|
||||||
walkdir = { workspace=true }
|
walkdir = { workspace=true }
|
||||||
atty = { workspace=true }
|
is-terminal = { workspace=true }
|
||||||
hex-literal = "0.3.1"
|
hex-literal = "0.3.1"
|
||||||
rstest = "0.16.0"
|
rstest = "0.16.0"
|
||||||
|
|
||||||
[target.'cfg(any(target_os = "linux", target_os = "android"))'.dev-dependencies]
|
[target.'cfg(any(target_os = "linux", target_os = "android"))'.dev-dependencies]
|
||||||
procfs = { version = "0.14", default-features = false }
|
procfs = { version = "0.14", default-features = false }
|
||||||
rlimit = "0.8.3"
|
rlimit = "0.9.1"
|
||||||
|
|
||||||
[target.'cfg(unix)'.dev-dependencies]
|
[target.'cfg(unix)'.dev-dependencies]
|
||||||
nix = { workspace=true, features=["process", "signal", "user"] }
|
nix = { workspace=true, features=["process", "signal", "user"] }
|
||||||
|
|
14
deny.toml
14
deny.toml
|
@ -58,12 +58,24 @@ highlight = "all"
|
||||||
# For each duplicate dependency, indicate the name of the dependency which
|
# For each duplicate dependency, indicate the name of the dependency which
|
||||||
# introduces it.
|
# introduces it.
|
||||||
# spell-checker: disable
|
# spell-checker: disable
|
||||||
skip = []
|
skip = [
|
||||||
|
# is-terminal
|
||||||
|
{ name = "hermit-abi", version = "0.3.1" },
|
||||||
|
# is-terminal
|
||||||
|
{ name = "rustix", version = "0.36.8" },
|
||||||
|
# is-terminal (via rustix)
|
||||||
|
{ name = "io-lifetimes", version = "1.0.5" },
|
||||||
|
# is-terminal
|
||||||
|
{ name = "linux-raw-sys", version = "0.1.4" },
|
||||||
|
# is-terminal
|
||||||
|
{ name = "windows-sys", version = "0.45.0" },
|
||||||
|
]
|
||||||
# spell-checker: enable
|
# spell-checker: enable
|
||||||
|
|
||||||
# This section is considered when running `cargo deny check sources`.
|
# This section is considered when running `cargo deny check sources`.
|
||||||
# More documentation about the 'sources' section can be found here:
|
# More documentation about the 'sources' section can be found here:
|
||||||
# https://embarkstudios.github.io/cargo-deny/checks/sources/cfg.html
|
# https://embarkstudios.github.io/cargo-deny/checks/sources/cfg.html
|
||||||
|
|
||||||
[sources]
|
[sources]
|
||||||
unknown-registry = "warn"
|
unknown-registry = "warn"
|
||||||
unknown-git = "warn"
|
unknown-git = "warn"
|
||||||
|
|
|
@ -43,13 +43,23 @@ pacman -S uutils-coreutils
|
||||||
|
|
||||||
### Debian
|
### Debian
|
||||||
|
|
||||||
[](https://packages.debian.org/sid/source/rust-coreutils)
|
[](https://packages.debian.org/sid/source/rust-coreutils)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
apt install rust-coreutils
|
apt install rust-coreutils
|
||||||
|
# To use it:
|
||||||
|
export PATH=/usr/lib/cargo/bin/coreutils:$PATH
|
||||||
```
|
```
|
||||||
|
|
||||||
> **Note**: Requires the `unstable` repository.
|
> **Note**: Only available from Bookworm (Debian 12)
|
||||||
|
|
||||||
|
### Gentoo
|
||||||
|
|
||||||
|
[](https://packages.gentoo.org/packages/sys-apps/uutils)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
emerge -pv sys-apps/uutils
|
||||||
|
```
|
||||||
|
|
||||||
### Manjaro
|
### Manjaro
|
||||||

|

|
||||||
|
@ -69,6 +79,18 @@ pamac install uutils-coreutils
|
||||||
nix-env -iA nixos.uutils-coreutils
|
nix-env -iA nixos.uutils-coreutils
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Ubuntu
|
||||||
|
|
||||||
|
[](https://packages.ubuntu.com/source/lunar/rust-coreutils)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
apt install rust-coreutils
|
||||||
|
# To use it:
|
||||||
|
export PATH=/usr/lib/cargo/bin/coreutils:$PATH
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Note**: Only available from Kinetic (Ubuntu 22.10)
|
||||||
|
|
||||||
## MacOS
|
## MacOS
|
||||||
|
|
||||||
### Homebrew
|
### Homebrew
|
||||||
|
@ -85,6 +107,13 @@ brew install uutils-coreutils
|
||||||
port install coreutils-uutils
|
port install coreutils-uutils
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## FreeBSD
|
||||||
|
[](https://repology.org/project/uutils-coreutils/versions)
|
||||||
|
|
||||||
|
```sh
|
||||||
|
pkg install uutils
|
||||||
|
```
|
||||||
|
|
||||||
## Windows
|
## Windows
|
||||||
|
|
||||||
### Scoop
|
### Scoop
|
||||||
|
|
14
src/uu/arch/arch.md
Normal file
14
src/uu/arch/arch.md
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
# arch
|
||||||
|
|
||||||
|
```
|
||||||
|
arch
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
Display machine architecture
|
||||||
|
|
||||||
|
|
||||||
|
## After Help
|
||||||
|
|
||||||
|
Determine architecture name for current machine.
|
||||||
|
|
|
@ -10,9 +10,10 @@ use platform_info::*;
|
||||||
|
|
||||||
use clap::{crate_version, Command};
|
use clap::{crate_version, Command};
|
||||||
use uucore::error::{FromIo, UResult};
|
use uucore::error::{FromIo, UResult};
|
||||||
|
use uucore::{help_about, help_section};
|
||||||
|
|
||||||
static ABOUT: &str = "Display machine architecture";
|
static ABOUT: &str = help_about!("arch.md");
|
||||||
static SUMMARY: &str = "Determine architecture name for current machine.";
|
static SUMMARY: &str = help_section!("after help", "arch.md");
|
||||||
|
|
||||||
#[uucore::main]
|
#[uucore::main]
|
||||||
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||||
|
|
|
@ -1,12 +1,9 @@
|
||||||
# base32
|
# base32
|
||||||
|
|
||||||
## Usage
|
|
||||||
```
|
```
|
||||||
base32 [OPTION]... [FILE]
|
base32 [OPTION]... [FILE]
|
||||||
```
|
```
|
||||||
|
|
||||||
## About
|
|
||||||
|
|
||||||
encode/decode data and print to standard output
|
encode/decode data and print to standard output
|
||||||
With no FILE, or when FILE is -, read standard input.
|
With no FILE, or when FILE is -, read standard input.
|
||||||
|
|
||||||
|
|
|
@ -8,11 +8,11 @@
|
||||||
use std::io::{stdin, Read};
|
use std::io::{stdin, Read};
|
||||||
|
|
||||||
use clap::Command;
|
use clap::Command;
|
||||||
use uucore::{encoding::Format, error::UResult, help_section, help_usage};
|
use uucore::{encoding::Format, error::UResult, help_about, help_usage};
|
||||||
|
|
||||||
pub mod base_common;
|
pub mod base_common;
|
||||||
|
|
||||||
const ABOUT: &str = help_section!("about", "base32.md");
|
const ABOUT: &str = help_about!("base32.md");
|
||||||
const USAGE: &str = help_usage!("base32.md");
|
const USAGE: &str = help_usage!("base32.md");
|
||||||
|
|
||||||
#[uucore::main]
|
#[uucore::main]
|
||||||
|
|
|
@ -1,12 +1,9 @@
|
||||||
# base64
|
# base64
|
||||||
|
|
||||||
## Usage
|
|
||||||
```
|
```
|
||||||
base64 [OPTION]... [FILE]
|
base64 [OPTION]... [FILE]
|
||||||
```
|
```
|
||||||
|
|
||||||
## About
|
|
||||||
|
|
||||||
encode/decode data and print to standard output
|
encode/decode data and print to standard output
|
||||||
With no FILE, or when FILE is -, read standard input.
|
With no FILE, or when FILE is -, read standard input.
|
||||||
|
|
||||||
|
|
|
@ -9,11 +9,11 @@
|
||||||
use uu_base32::base_common;
|
use uu_base32::base_common;
|
||||||
pub use uu_base32::uu_app;
|
pub use uu_base32::uu_app;
|
||||||
|
|
||||||
use uucore::{encoding::Format, error::UResult, help_section, help_usage};
|
use uucore::{encoding::Format, error::UResult, help_about, help_usage};
|
||||||
|
|
||||||
use std::io::{stdin, Read};
|
use std::io::{stdin, Read};
|
||||||
|
|
||||||
const ABOUT: &str = help_section!("about", "base64.md");
|
const ABOUT: &str = help_about!("base64.md");
|
||||||
const USAGE: &str = help_usage!("base64.md");
|
const USAGE: &str = help_usage!("base64.md");
|
||||||
|
|
||||||
#[uucore::main]
|
#[uucore::main]
|
||||||
|
|
12
src/uu/basenc/basenc.md
Normal file
12
src/uu/basenc/basenc.md
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
# basenc
|
||||||
|
|
||||||
|
```
|
||||||
|
basenc [OPTION]... [FILE]"
|
||||||
|
```
|
||||||
|
|
||||||
|
Encode/decode data and print to standard output
|
||||||
|
With no FILE, or when FILE is -, read standard input.
|
||||||
|
|
||||||
|
When decoding, the input may contain newlines in addition to the bytes of
|
||||||
|
the formal alphabet. Use --ignore-garbage to attempt to recover
|
||||||
|
from any other non-alphabet bytes in the encoded stream.
|
|
@ -19,14 +19,10 @@ use uucore::{
|
||||||
use std::io::{stdin, Read};
|
use std::io::{stdin, Read};
|
||||||
use uucore::error::UClapError;
|
use uucore::error::UClapError;
|
||||||
|
|
||||||
static ABOUT: &str = "\
|
use uucore::{help_about, help_usage};
|
||||||
Encode/decode data and print to standard output
|
|
||||||
With no FILE, or when FILE is -, read standard input.
|
|
||||||
|
|
||||||
When decoding, the input may contain newlines in addition to the bytes of
|
const ABOUT: &str = help_about!("basenc.md");
|
||||||
the formal alphabet. Use --ignore-garbage to attempt to recover
|
const USAGE: &str = help_usage!("basenc.md");
|
||||||
from any other non-alphabet bytes in the encoded stream.
|
|
||||||
";
|
|
||||||
|
|
||||||
const ENCODINGS: &[(&str, Format)] = &[
|
const ENCODINGS: &[(&str, Format)] = &[
|
||||||
("base64", Format::Base64),
|
("base64", Format::Base64),
|
||||||
|
@ -39,8 +35,6 @@ const ENCODINGS: &[(&str, Format)] = &[
|
||||||
("z85", Format::Z85),
|
("z85", Format::Z85),
|
||||||
];
|
];
|
||||||
|
|
||||||
const USAGE: &str = "{} [OPTION]... [FILE]";
|
|
||||||
|
|
||||||
pub fn uu_app() -> Command {
|
pub fn uu_app() -> Command {
|
||||||
let mut command = base_common::base_app(ABOUT, USAGE);
|
let mut command = base_common::base_app(ABOUT, USAGE);
|
||||||
for encoding in ENCODINGS {
|
for encoding in ENCODINGS {
|
||||||
|
|
|
@ -17,7 +17,7 @@ path = "src/cat.rs"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
clap = { workspace=true }
|
clap = { workspace=true }
|
||||||
thiserror = { workspace = true }
|
thiserror = { workspace = true }
|
||||||
atty = { workspace=true }
|
is-terminal = { workspace = true }
|
||||||
uucore = { workspace=true, features=["fs", "pipes"] }
|
uucore = { workspace=true, features=["fs", "pipes"] }
|
||||||
|
|
||||||
[target.'cfg(unix)'.dependencies]
|
[target.'cfg(unix)'.dependencies]
|
||||||
|
|
|
@ -1,11 +1,8 @@
|
||||||
# cat
|
# cat
|
||||||
|
|
||||||
## Usage
|
|
||||||
```
|
```
|
||||||
cat [OPTION]... [FILE]...
|
cat [OPTION]... [FILE]...
|
||||||
```
|
```
|
||||||
|
|
||||||
## About
|
|
||||||
|
|
||||||
Concatenate FILE(s), or standard input, to standard output
|
Concatenate FILE(s), or standard input, to standard output
|
||||||
With no FILE, or when FILE is -, read standard input.
|
With no FILE, or when FILE is -, read standard input.
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
|
|
||||||
// last synced with: cat (GNU coreutils) 8.13
|
// last synced with: cat (GNU coreutils) 8.13
|
||||||
use clap::{crate_version, Arg, ArgAction, Command};
|
use clap::{crate_version, Arg, ArgAction, Command};
|
||||||
|
use is_terminal::IsTerminal;
|
||||||
use std::fs::{metadata, File};
|
use std::fs::{metadata, File};
|
||||||
use std::io::{self, Read, Write};
|
use std::io::{self, Read, Write};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
@ -33,10 +34,10 @@ use std::net::Shutdown;
|
||||||
use std::os::unix::fs::FileTypeExt;
|
use std::os::unix::fs::FileTypeExt;
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
use std::os::unix::net::UnixStream;
|
use std::os::unix::net::UnixStream;
|
||||||
use uucore::{format_usage, help_section, help_usage};
|
use uucore::{format_usage, help_about, help_usage};
|
||||||
|
|
||||||
const USAGE: &str = help_usage!("cat.md");
|
const USAGE: &str = help_usage!("cat.md");
|
||||||
const ABOUT: &str = help_section!("about", "cat.md");
|
const ABOUT: &str = help_about!("cat.md");
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
enum CatError {
|
enum CatError {
|
||||||
|
@ -332,7 +333,7 @@ fn cat_path(
|
||||||
let stdin = io::stdin();
|
let stdin = io::stdin();
|
||||||
let mut handle = InputHandle {
|
let mut handle = InputHandle {
|
||||||
reader: stdin,
|
reader: stdin,
|
||||||
is_interactive: atty::is(atty::Stream::Stdin),
|
is_interactive: std::io::stdin().is_terminal(),
|
||||||
};
|
};
|
||||||
cat_handle(&mut handle, options, state)
|
cat_handle(&mut handle, options, state)
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,12 +12,12 @@ use std::fs;
|
||||||
use std::os::unix::fs::{MetadataExt, PermissionsExt};
|
use std::os::unix::fs::{MetadataExt, PermissionsExt};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use uucore::display::Quotable;
|
use uucore::display::Quotable;
|
||||||
use uucore::error::{ExitCode, UResult, USimpleError, UUsageError};
|
use uucore::error::{set_exit_code, ExitCode, UResult, USimpleError, UUsageError};
|
||||||
use uucore::fs::display_permissions_unix;
|
use uucore::fs::display_permissions_unix;
|
||||||
use uucore::libc::mode_t;
|
use uucore::libc::mode_t;
|
||||||
#[cfg(not(windows))]
|
#[cfg(not(windows))]
|
||||||
use uucore::mode;
|
use uucore::mode;
|
||||||
use uucore::{format_usage, show_error};
|
use uucore::{format_usage, show, show_error};
|
||||||
|
|
||||||
const ABOUT: &str = "Change the mode of each FILE to MODE.\n\
|
const ABOUT: &str = "Change the mode of each FILE to MODE.\n\
|
||||||
With --reference, change the mode of each FILE to that of RFILE.";
|
With --reference, change the mode of each FILE to that of RFILE.";
|
||||||
|
@ -190,26 +190,31 @@ impl Chmoder {
|
||||||
let file = Path::new(filename);
|
let file = Path::new(filename);
|
||||||
if !file.exists() {
|
if !file.exists() {
|
||||||
if file.is_symlink() {
|
if file.is_symlink() {
|
||||||
println!(
|
|
||||||
"failed to change mode of {} from 0000 (---------) to 0000 (---------)",
|
|
||||||
filename.quote()
|
|
||||||
);
|
|
||||||
if !self.quiet {
|
if !self.quiet {
|
||||||
return Err(USimpleError::new(
|
show!(USimpleError::new(
|
||||||
1,
|
1,
|
||||||
format!("cannot operate on dangling symlink {}", filename.quote()),
|
format!("cannot operate on dangling symlink {}", filename.quote()),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
if self.verbose {
|
||||||
|
println!(
|
||||||
|
"failed to change mode of {} from 0000 (---------) to 1500 (r-x-----T)",
|
||||||
|
filename.quote()
|
||||||
|
);
|
||||||
|
}
|
||||||
} else if !self.quiet {
|
} else if !self.quiet {
|
||||||
return Err(USimpleError::new(
|
show!(USimpleError::new(
|
||||||
1,
|
1,
|
||||||
format!(
|
format!(
|
||||||
"cannot access {}: No such file or directory",
|
"cannot access {}: No such file or directory",
|
||||||
filename.quote()
|
filename.quote()
|
||||||
),
|
)
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
return Err(ExitCode::new(1));
|
// GNU exits with exit code 1 even if -q or --quiet are passed
|
||||||
|
// So we set the exit code, because it hasn't been set yet if `self.quiet` is true.
|
||||||
|
set_exit_code(1);
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
if self.recursive && self.preserve_root && filename == "/" {
|
if self.recursive && self.preserve_root && filename == "/" {
|
||||||
return Err(USimpleError::new(
|
return Err(USimpleError::new(
|
||||||
|
|
|
@ -1,12 +1,9 @@
|
||||||
# cp
|
# cp
|
||||||
|
|
||||||
## Usage
|
|
||||||
```
|
```
|
||||||
cp [OPTION]... [-T] SOURCE DEST
|
cp [OPTION]... [-T] SOURCE DEST
|
||||||
cp [OPTION]... SOURCE... DIRECTORY
|
cp [OPTION]... SOURCE... DIRECTORY
|
||||||
cp [OPTION]... -t DIRECTORY SOURCE...
|
cp [OPTION]... -t DIRECTORY SOURCE...
|
||||||
```
|
```
|
||||||
|
|
||||||
## About
|
|
||||||
|
|
||||||
Copy SOURCE to DEST, or multiple SOURCE(s) to DIRECTORY.
|
Copy SOURCE to DEST, or multiple SOURCE(s) to DIRECTORY.
|
||||||
|
|
|
@ -40,7 +40,7 @@ use uucore::error::{set_exit_code, UClapError, UError, UResult, UUsageError};
|
||||||
use uucore::fs::{
|
use uucore::fs::{
|
||||||
canonicalize, paths_refer_to_same_file, FileInformation, MissingHandling, ResolveMode,
|
canonicalize, paths_refer_to_same_file, FileInformation, MissingHandling, ResolveMode,
|
||||||
};
|
};
|
||||||
use uucore::{crash, format_usage, help_section, help_usage, prompt_yes, show_error, show_warning};
|
use uucore::{crash, format_usage, help_about, help_usage, prompt_yes, show_error, show_warning};
|
||||||
|
|
||||||
use crate::copydir::copy_directory;
|
use crate::copydir::copy_directory;
|
||||||
|
|
||||||
|
@ -228,11 +228,11 @@ pub struct Options {
|
||||||
progress_bar: bool,
|
progress_bar: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
const ABOUT: &str = help_section!("about", "cp.md");
|
const ABOUT: &str = help_about!("cp.md");
|
||||||
static EXIT_ERR: i32 = 1;
|
|
||||||
|
|
||||||
const USAGE: &str = help_usage!("cp.md");
|
const USAGE: &str = help_usage!("cp.md");
|
||||||
|
|
||||||
|
static EXIT_ERR: i32 = 1;
|
||||||
|
|
||||||
// Argument constants
|
// Argument constants
|
||||||
mod options {
|
mod options {
|
||||||
pub const ARCHIVE: &str = "archive";
|
pub const ARCHIVE: &str = "archive";
|
||||||
|
|
|
@ -19,7 +19,7 @@ clap = { workspace=true }
|
||||||
uucore = { workspace=true }
|
uucore = { workspace=true }
|
||||||
memchr = { workspace=true }
|
memchr = { workspace=true }
|
||||||
bstr = { workspace=true }
|
bstr = { workspace=true }
|
||||||
atty = { workspace=true }
|
is-terminal = { workspace=true }
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "cut"
|
name = "cut"
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
|
|
||||||
use bstr::io::BufReadExt;
|
use bstr::io::BufReadExt;
|
||||||
use clap::{crate_version, Arg, ArgAction, Command};
|
use clap::{crate_version, Arg, ArgAction, Command};
|
||||||
|
use is_terminal::IsTerminal;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::{stdin, stdout, BufReader, BufWriter, Read, Write};
|
use std::io::{stdin, stdout, BufReader, BufWriter, Read, Write};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
@ -136,7 +137,7 @@ enum Mode {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn stdout_writer() -> Box<dyn Write> {
|
fn stdout_writer() -> Box<dyn Write> {
|
||||||
if atty::is(atty::Stream::Stdout) {
|
if std::io::stdout().is_terminal() {
|
||||||
Box::new(stdout())
|
Box::new(stdout())
|
||||||
} else {
|
} else {
|
||||||
Box::new(BufWriter::new(stdout())) as Box<dyn Write>
|
Box::new(BufWriter::new(stdout())) as Box<dyn Write>
|
||||||
|
|
|
@ -1,7 +1,11 @@
|
||||||
<!-- spell-checker:ignore convs iseek oseek -->
|
<!-- spell-checker:ignore convs iseek oseek -->
|
||||||
# dd
|
# dd
|
||||||
|
|
||||||
## About
|
```
|
||||||
|
dd [OPERAND]...
|
||||||
|
dd OPTION
|
||||||
|
```
|
||||||
|
|
||||||
Copy, and optionally convert, a file system resource
|
Copy, and optionally convert, a file system resource
|
||||||
|
|
||||||
## After Help
|
## After Help
|
||||||
|
|
|
@ -27,7 +27,9 @@ use std::cmp;
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::ffi::OsString;
|
use std::ffi::OsString;
|
||||||
use std::fs::{File, OpenOptions};
|
use std::fs::{File, OpenOptions};
|
||||||
use std::io::{self, Read, Seek, SeekFrom, Stdout, Write};
|
use std::io::{self, Read, Seek, SeekFrom, Stdin, Stdout, Write};
|
||||||
|
#[cfg(unix)]
|
||||||
|
use std::os::unix::fs::FileTypeExt;
|
||||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||||
use std::os::unix::fs::OpenOptionsExt;
|
use std::os::unix::fs::OpenOptionsExt;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
@ -39,11 +41,11 @@ use clap::{crate_version, Arg, Command};
|
||||||
use gcd::Gcd;
|
use gcd::Gcd;
|
||||||
use uucore::display::Quotable;
|
use uucore::display::Quotable;
|
||||||
use uucore::error::{FromIo, UResult};
|
use uucore::error::{FromIo, UResult};
|
||||||
use uucore::help_section;
|
use uucore::{format_usage, help_about, help_section, help_usage, show_error};
|
||||||
use uucore::show_error;
|
|
||||||
|
|
||||||
const ABOUT: &str = help_section!("about", "dd.md");
|
const ABOUT: &str = help_about!("dd.md");
|
||||||
const AFTER_HELP: &str = help_section!("after help", "dd.md");
|
const AFTER_HELP: &str = help_section!("after help", "dd.md");
|
||||||
|
const USAGE: &str = help_usage!("dd.md");
|
||||||
const BUF_INIT_BYTE: u8 = 0xDD;
|
const BUF_INIT_BYTE: u8 = 0xDD;
|
||||||
|
|
||||||
/// Final settings after parsing
|
/// Final settings after parsing
|
||||||
|
@ -90,30 +92,106 @@ impl Num {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Input<'a, R: Read> {
|
/// Data sources.
|
||||||
src: R,
|
enum Source {
|
||||||
|
/// Input from stdin.
|
||||||
|
Stdin(Stdin),
|
||||||
|
|
||||||
|
/// Input from a file.
|
||||||
|
File(File),
|
||||||
|
|
||||||
|
/// Input from a named pipe, also known as a FIFO.
|
||||||
|
#[cfg(unix)]
|
||||||
|
Fifo(File),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Source {
|
||||||
|
fn skip(&mut self, n: u64) -> io::Result<u64> {
|
||||||
|
match self {
|
||||||
|
Self::Stdin(stdin) => match io::copy(&mut stdin.take(n), &mut io::sink()) {
|
||||||
|
Ok(m) if m < n => {
|
||||||
|
show_error!("'standard input': cannot skip to specified offset");
|
||||||
|
Ok(m)
|
||||||
|
}
|
||||||
|
Ok(m) => Ok(m),
|
||||||
|
Err(e) => Err(e),
|
||||||
|
},
|
||||||
|
Self::File(f) => f.seek(io::SeekFrom::Start(n)),
|
||||||
|
#[cfg(unix)]
|
||||||
|
Self::Fifo(f) => io::copy(&mut f.take(n), &mut io::sink()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Read for Source {
|
||||||
|
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||||
|
match self {
|
||||||
|
Self::Stdin(stdin) => stdin.read(buf),
|
||||||
|
Self::File(f) => f.read(buf),
|
||||||
|
#[cfg(unix)]
|
||||||
|
Self::Fifo(f) => f.read(buf),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The source of the data, configured with the given settings.
|
||||||
|
///
|
||||||
|
/// Use the [`Input::new_stdin`] or [`Input::new_file`] functions to
|
||||||
|
/// construct a new instance of this struct. Then pass the instance to
|
||||||
|
/// the [`Output::dd_out`] function to execute the main copy operation
|
||||||
|
/// for `dd`.
|
||||||
|
struct Input<'a> {
|
||||||
|
/// The source from which bytes will be read.
|
||||||
|
src: Source,
|
||||||
|
|
||||||
|
/// Configuration settings for how to read the data.
|
||||||
settings: &'a Settings,
|
settings: &'a Settings,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Input<'a, io::Stdin> {
|
impl<'a> Input<'a> {
|
||||||
fn new(settings: &'a Settings) -> UResult<Self> {
|
/// Instantiate this struct with stdin as a source.
|
||||||
let mut input = Self {
|
fn new_stdin(settings: &'a Settings) -> UResult<Self> {
|
||||||
src: io::stdin(),
|
let mut src = Source::Stdin(io::stdin());
|
||||||
settings,
|
if settings.skip > 0 {
|
||||||
|
src.skip(settings.skip)?;
|
||||||
|
}
|
||||||
|
Ok(Self { src, settings })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Instantiate this struct with the named file as a source.
|
||||||
|
fn new_file(filename: &Path, settings: &'a Settings) -> UResult<Self> {
|
||||||
|
let src = {
|
||||||
|
let mut opts = OpenOptions::new();
|
||||||
|
opts.read(true);
|
||||||
|
|
||||||
|
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||||
|
if let Some(libc_flags) = make_linux_iflags(&settings.iflags) {
|
||||||
|
opts.custom_flags(libc_flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
opts.open(filename)
|
||||||
|
.map_err_context(|| format!("failed to open {}", filename.quote()))?
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let mut src = Source::File(src);
|
||||||
if settings.skip > 0 {
|
if settings.skip > 0 {
|
||||||
if let Err(e) = input.read_skip(settings.skip) {
|
src.skip(settings.skip)?;
|
||||||
if let io::ErrorKind::UnexpectedEof = e.kind() {
|
|
||||||
show_error!("'standard input': cannot skip to specified offset");
|
|
||||||
} else {
|
|
||||||
return io::Result::Err(e)
|
|
||||||
.map_err_context(|| "I/O error while skipping".to_string());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
Ok(Self { src, settings })
|
||||||
|
}
|
||||||
|
|
||||||
Ok(input)
|
/// Instantiate this struct with the named pipe as a source.
|
||||||
|
#[cfg(unix)]
|
||||||
|
fn new_fifo(filename: &Path, settings: &'a Settings) -> UResult<Self> {
|
||||||
|
let mut opts = OpenOptions::new();
|
||||||
|
opts.read(true);
|
||||||
|
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||||
|
opts.custom_flags(make_linux_iflags(&settings.iflags).unwrap_or(0));
|
||||||
|
let mut src = Source::Fifo(opts.open(filename)?);
|
||||||
|
if settings.skip > 0 {
|
||||||
|
src.skip(settings.skip)?;
|
||||||
|
}
|
||||||
|
Ok(Self { src, settings })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -153,31 +231,7 @@ fn make_linux_iflags(iflags: &IFlags) -> Option<libc::c_int> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Input<'a, File> {
|
impl<'a> Read for Input<'a> {
|
||||||
fn new(filename: &Path, settings: &'a Settings) -> UResult<Self> {
|
|
||||||
let mut src = {
|
|
||||||
let mut opts = OpenOptions::new();
|
|
||||||
opts.read(true);
|
|
||||||
|
|
||||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
|
||||||
if let Some(libc_flags) = make_linux_iflags(&settings.iflags) {
|
|
||||||
opts.custom_flags(libc_flags);
|
|
||||||
}
|
|
||||||
|
|
||||||
opts.open(filename)
|
|
||||||
.map_err_context(|| format!("failed to open {}", filename.quote()))?
|
|
||||||
};
|
|
||||||
|
|
||||||
if settings.skip > 0 {
|
|
||||||
src.seek(io::SeekFrom::Start(settings.skip))
|
|
||||||
.map_err_context(|| "failed to seek in input file".to_string())?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(Self { src, settings })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, R: Read> Read for Input<'a, R> {
|
|
||||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||||
let mut base_idx = 0;
|
let mut base_idx = 0;
|
||||||
let target_len = buf.len();
|
let target_len = buf.len();
|
||||||
|
@ -200,7 +254,7 @@ impl<'a, R: Read> Read for Input<'a, R> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, R: Read> Input<'a, R> {
|
impl<'a> Input<'a> {
|
||||||
/// Fills a given buffer.
|
/// Fills a given buffer.
|
||||||
/// Reads in increments of 'self.ibs'.
|
/// Reads in increments of 'self.ibs'.
|
||||||
/// The start of each ibs-sized read follows the previous one.
|
/// The start of each ibs-sized read follows the previous one.
|
||||||
|
@ -266,20 +320,6 @@ impl<'a, R: Read> Input<'a, R> {
|
||||||
records_truncated: 0,
|
records_truncated: 0,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Skips amount_to_read bytes from the Input by copying into a sink
|
|
||||||
fn read_skip(&mut self, amount_to_read: u64) -> std::io::Result<()> {
|
|
||||||
let copy_result = io::copy(&mut self.src.by_ref().take(amount_to_read), &mut io::sink());
|
|
||||||
if let Ok(n) = copy_result {
|
|
||||||
if n != amount_to_read {
|
|
||||||
io::Result::Err(io::Error::new(io::ErrorKind::UnexpectedEof, ""))
|
|
||||||
} else {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
io::Result::Err(copy_result.unwrap_err())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
enum Density {
|
enum Density {
|
||||||
|
@ -297,6 +337,14 @@ enum Dest {
|
||||||
/// The [`Density`] component indicates whether to attempt to
|
/// The [`Density`] component indicates whether to attempt to
|
||||||
/// write a sparse file when all-zero blocks are encountered.
|
/// write a sparse file when all-zero blocks are encountered.
|
||||||
File(File, Density),
|
File(File, Density),
|
||||||
|
|
||||||
|
/// Output to a named pipe, also known as a FIFO.
|
||||||
|
#[cfg(unix)]
|
||||||
|
Fifo(File),
|
||||||
|
|
||||||
|
/// Output to nothing, dropping each byte written to the output.
|
||||||
|
#[cfg(unix)]
|
||||||
|
Sink,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Dest {
|
impl Dest {
|
||||||
|
@ -307,6 +355,13 @@ impl Dest {
|
||||||
f.flush()?;
|
f.flush()?;
|
||||||
f.sync_all()
|
f.sync_all()
|
||||||
}
|
}
|
||||||
|
#[cfg(unix)]
|
||||||
|
Self::Fifo(f) => {
|
||||||
|
f.flush()?;
|
||||||
|
f.sync_all()
|
||||||
|
}
|
||||||
|
#[cfg(unix)]
|
||||||
|
Self::Sink => Ok(()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -317,6 +372,13 @@ impl Dest {
|
||||||
f.flush()?;
|
f.flush()?;
|
||||||
f.sync_data()
|
f.sync_data()
|
||||||
}
|
}
|
||||||
|
#[cfg(unix)]
|
||||||
|
Self::Fifo(f) => {
|
||||||
|
f.flush()?;
|
||||||
|
f.sync_data()
|
||||||
|
}
|
||||||
|
#[cfg(unix)]
|
||||||
|
Self::Sink => Ok(()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -324,17 +386,24 @@ impl Dest {
|
||||||
match self {
|
match self {
|
||||||
Self::Stdout(stdout) => io::copy(&mut io::repeat(0).take(n), stdout),
|
Self::Stdout(stdout) => io::copy(&mut io::repeat(0).take(n), stdout),
|
||||||
Self::File(f, _) => f.seek(io::SeekFrom::Start(n)),
|
Self::File(f, _) => f.seek(io::SeekFrom::Start(n)),
|
||||||
|
#[cfg(unix)]
|
||||||
|
Self::Fifo(f) => {
|
||||||
|
// Seeking in a named pipe means *reading* from the pipe.
|
||||||
|
io::copy(&mut f.take(n), &mut io::sink())
|
||||||
|
}
|
||||||
|
#[cfg(unix)]
|
||||||
|
Self::Sink => Ok(0),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Truncate the underlying file to the current stream position, if possible.
|
/// Truncate the underlying file to the current stream position, if possible.
|
||||||
fn truncate(&mut self) -> io::Result<()> {
|
fn truncate(&mut self) -> io::Result<()> {
|
||||||
match self {
|
match self {
|
||||||
Self::Stdout(_) => Ok(()),
|
|
||||||
Self::File(f, _) => {
|
Self::File(f, _) => {
|
||||||
let pos = f.stream_position()?;
|
let pos = f.stream_position()?;
|
||||||
f.set_len(pos)
|
f.set_len(pos)
|
||||||
}
|
}
|
||||||
|
_ => Ok(()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -357,6 +426,10 @@ impl Write for Dest {
|
||||||
}
|
}
|
||||||
Self::File(f, _) => f.write(buf),
|
Self::File(f, _) => f.write(buf),
|
||||||
Self::Stdout(stdout) => stdout.write(buf),
|
Self::Stdout(stdout) => stdout.write(buf),
|
||||||
|
#[cfg(unix)]
|
||||||
|
Self::Fifo(f) => f.write(buf),
|
||||||
|
#[cfg(unix)]
|
||||||
|
Self::Sink => Ok(buf.len()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -364,6 +437,10 @@ impl Write for Dest {
|
||||||
match self {
|
match self {
|
||||||
Self::Stdout(stdout) => stdout.flush(),
|
Self::Stdout(stdout) => stdout.flush(),
|
||||||
Self::File(f, _) => f.flush(),
|
Self::File(f, _) => f.flush(),
|
||||||
|
#[cfg(unix)]
|
||||||
|
Self::Fifo(f) => f.flush(),
|
||||||
|
#[cfg(unix)]
|
||||||
|
Self::Sink => Ok(()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -433,6 +510,35 @@ impl<'a> Output<'a> {
|
||||||
Ok(Self { dst, settings })
|
Ok(Self { dst, settings })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Instantiate this struct with the given named pipe as a destination.
|
||||||
|
#[cfg(unix)]
|
||||||
|
fn new_fifo(filename: &Path, settings: &'a Settings) -> UResult<Self> {
|
||||||
|
// We simulate seeking in a FIFO by *reading*, so we open the
|
||||||
|
// file for reading. But then we need to close the file and
|
||||||
|
// re-open it for writing.
|
||||||
|
if settings.seek > 0 {
|
||||||
|
Dest::Fifo(File::open(filename)?).seek(settings.seek)?;
|
||||||
|
}
|
||||||
|
// If `count=0`, then we don't bother opening the file for
|
||||||
|
// writing because that would cause this process to block
|
||||||
|
// indefinitely.
|
||||||
|
if let Some(Num::Blocks(0) | Num::Bytes(0)) = settings.count {
|
||||||
|
let dst = Dest::Sink;
|
||||||
|
return Ok(Self { dst, settings });
|
||||||
|
}
|
||||||
|
// At this point, we know there is at least one block to write
|
||||||
|
// to the output, so we open the file for writing.
|
||||||
|
let mut opts = OpenOptions::new();
|
||||||
|
opts.write(true)
|
||||||
|
.create(!settings.oconv.nocreat)
|
||||||
|
.create_new(settings.oconv.excl)
|
||||||
|
.append(settings.oflags.append);
|
||||||
|
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||||
|
opts.custom_flags(make_linux_oflags(&settings.oflags).unwrap_or(0));
|
||||||
|
let dst = Dest::Fifo(opts.open(filename)?);
|
||||||
|
Ok(Self { dst, settings })
|
||||||
|
}
|
||||||
|
|
||||||
/// Write the given bytes one block at a time.
|
/// Write the given bytes one block at a time.
|
||||||
///
|
///
|
||||||
/// This may write partial blocks (for example, if the underlying
|
/// This may write partial blocks (for example, if the underlying
|
||||||
|
@ -485,7 +591,7 @@ impl<'a> Output<'a> {
|
||||||
///
|
///
|
||||||
/// If there is a problem reading from the input or writing to
|
/// If there is a problem reading from the input or writing to
|
||||||
/// this output.
|
/// this output.
|
||||||
fn dd_out<R: Read>(mut self, mut i: Input<R>) -> std::io::Result<()> {
|
fn dd_out(mut self, mut i: Input) -> std::io::Result<()> {
|
||||||
// The read and write statistics.
|
// The read and write statistics.
|
||||||
//
|
//
|
||||||
// These objects are counters, initialized to zero. After each
|
// These objects are counters, initialized to zero. After each
|
||||||
|
@ -645,12 +751,13 @@ fn make_linux_oflags(oflags: &OFlags) -> Option<libc::c_int> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Read helper performs read operations common to all dd reads, and dispatches the buffer to relevant helper functions as dictated by the operations requested by the user.
|
/// Read from an input (that is, a source of bytes) into the given buffer.
|
||||||
fn read_helper<R: Read>(
|
///
|
||||||
i: &mut Input<R>,
|
/// This function also performs any conversions as specified by
|
||||||
buf: &mut Vec<u8>,
|
/// `conv=swab` or `conv=block` command-line arguments. This function
|
||||||
bsize: usize,
|
/// mutates the `buf` argument in-place. The returned [`ReadStat`]
|
||||||
) -> std::io::Result<ReadStat> {
|
/// indicates how many blocks were read.
|
||||||
|
fn read_helper(i: &mut Input, buf: &mut Vec<u8>, bsize: usize) -> std::io::Result<ReadStat> {
|
||||||
// Local Helper Fns -------------------------------------------------
|
// Local Helper Fns -------------------------------------------------
|
||||||
fn perform_swab(buf: &mut [u8]) {
|
fn perform_swab(buf: &mut [u8]) {
|
||||||
for base in (1..buf.len()).step_by(2) {
|
for base in (1..buf.len()).step_by(2) {
|
||||||
|
@ -778,6 +885,17 @@ fn is_stdout_redirected_to_seekable_file() -> bool {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Decide whether the named file is a named pipe, also known as a FIFO.
|
||||||
|
#[cfg(unix)]
|
||||||
|
fn is_fifo(filename: &str) -> bool {
|
||||||
|
if let Ok(metadata) = std::fs::metadata(filename) {
|
||||||
|
if metadata.file_type().is_fifo() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
#[uucore::main]
|
#[uucore::main]
|
||||||
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||||
let args = args.collect_ignore();
|
let args = args.collect_ignore();
|
||||||
|
@ -792,46 +910,29 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||||
.collect::<Vec<_>>()[..],
|
.collect::<Vec<_>>()[..],
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
match (&settings.infile, &settings.outfile) {
|
let i = match settings.infile {
|
||||||
(Some(infile), Some(outfile)) => {
|
#[cfg(unix)]
|
||||||
let i = Input::<File>::new(Path::new(&infile), &settings)?;
|
Some(ref infile) if is_fifo(infile) => Input::new_fifo(Path::new(&infile), &settings)?,
|
||||||
let o = Output::new_file(Path::new(&outfile), &settings)?;
|
Some(ref infile) => Input::new_file(Path::new(&infile), &settings)?,
|
||||||
o.dd_out(i).map_err_context(|| "IO error".to_string())
|
None => Input::new_stdin(&settings)?,
|
||||||
|
};
|
||||||
|
let o = match settings.outfile {
|
||||||
|
#[cfg(unix)]
|
||||||
|
Some(ref outfile) if is_fifo(outfile) => Output::new_fifo(Path::new(&outfile), &settings)?,
|
||||||
|
Some(ref outfile) => Output::new_file(Path::new(&outfile), &settings)?,
|
||||||
|
None if is_stdout_redirected_to_seekable_file() => {
|
||||||
|
Output::new_file(Path::new(&stdout_canonicalized()), &settings)?
|
||||||
}
|
}
|
||||||
(None, Some(outfile)) => {
|
None => Output::new_stdout(&settings)?,
|
||||||
let i = Input::<io::Stdin>::new(&settings)?;
|
};
|
||||||
let o = Output::new_file(Path::new(&outfile), &settings)?;
|
o.dd_out(i).map_err_context(|| "IO error".to_string())
|
||||||
o.dd_out(i).map_err_context(|| "IO error".to_string())
|
|
||||||
}
|
|
||||||
(Some(infile), None) => {
|
|
||||||
let i = Input::<File>::new(Path::new(&infile), &settings)?;
|
|
||||||
if is_stdout_redirected_to_seekable_file() {
|
|
||||||
let filename = stdout_canonicalized();
|
|
||||||
let o = Output::new_file(Path::new(&filename), &settings)?;
|
|
||||||
o.dd_out(i).map_err_context(|| "IO error".to_string())
|
|
||||||
} else {
|
|
||||||
let o = Output::new_stdout(&settings)?;
|
|
||||||
o.dd_out(i).map_err_context(|| "IO error".to_string())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
(None, None) => {
|
|
||||||
let i = Input::<io::Stdin>::new(&settings)?;
|
|
||||||
if is_stdout_redirected_to_seekable_file() {
|
|
||||||
let filename = stdout_canonicalized();
|
|
||||||
let o = Output::new_file(Path::new(&filename), &settings)?;
|
|
||||||
o.dd_out(i).map_err_context(|| "IO error".to_string())
|
|
||||||
} else {
|
|
||||||
let o = Output::new_stdout(&settings)?;
|
|
||||||
o.dd_out(i).map_err_context(|| "IO error".to_string())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn uu_app() -> Command {
|
pub fn uu_app() -> Command {
|
||||||
Command::new(uucore::util_name())
|
Command::new(uucore::util_name())
|
||||||
.version(crate_version!())
|
.version(crate_version!())
|
||||||
.about(ABOUT)
|
.about(ABOUT)
|
||||||
|
.override_usage(format_usage(USAGE))
|
||||||
.after_help(AFTER_HELP)
|
.after_help(AFTER_HELP)
|
||||||
.infer_long_args(true)
|
.infer_long_args(true)
|
||||||
.arg(Arg::new(options::OPERANDS).num_args(1..))
|
.arg(Arg::new(options::OPERANDS).num_args(1..))
|
||||||
|
|
24
src/uu/du/du.md
Normal file
24
src/uu/du/du.md
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
# du
|
||||||
|
|
||||||
|
```
|
||||||
|
du [OPTION]... [FILE]...
|
||||||
|
du [OPTION]... --files0-from=F
|
||||||
|
```
|
||||||
|
|
||||||
|
Estimate file space usage
|
||||||
|
|
||||||
|
## After Help
|
||||||
|
|
||||||
|
Display values are in units of the first available SIZE from --block-size,
|
||||||
|
and the DU_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).
|
||||||
|
|
||||||
|
PATTERN allows some advanced exclusions. For example, the following syntaxes
|
||||||
|
are supported:
|
||||||
|
? will match only one character
|
||||||
|
* will match zero or more characters
|
||||||
|
{a,b} will match a or b
|
|
@ -36,7 +36,9 @@ use uucore::error::FromIo;
|
||||||
use uucore::error::{UError, UResult};
|
use uucore::error::{UError, UResult};
|
||||||
use uucore::parse_glob;
|
use uucore::parse_glob;
|
||||||
use uucore::parse_size::{parse_size, ParseSizeError};
|
use uucore::parse_size::{parse_size, ParseSizeError};
|
||||||
use uucore::{crash, format_usage, show, show_error, show_warning};
|
use uucore::{
|
||||||
|
crash, format_usage, help_about, help_section, help_usage, show, show_error, show_warning,
|
||||||
|
};
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
use windows_sys::Win32::Foundation::HANDLE;
|
use windows_sys::Win32::Foundation::HANDLE;
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
|
@ -73,25 +75,9 @@ mod options {
|
||||||
pub const FILE: &str = "FILE";
|
pub const FILE: &str = "FILE";
|
||||||
}
|
}
|
||||||
|
|
||||||
const ABOUT: &str = "Estimate file space usage";
|
const ABOUT: &str = help_about!("du.md");
|
||||||
const LONG_HELP: &str = "
|
const AFTER_HELP: &str = help_section!("after help", "du.md");
|
||||||
Display values are in units of the first available SIZE from --block-size,
|
const USAGE: &str = help_usage!("du.md");
|
||||||
and the DU_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).
|
|
||||||
|
|
||||||
PATTERN allows some advanced exclusions. For example, the following syntaxes
|
|
||||||
are supported:
|
|
||||||
? will match only one character
|
|
||||||
* will match zero or more characters
|
|
||||||
{a,b} will match a or b
|
|
||||||
";
|
|
||||||
const USAGE: &str = "\
|
|
||||||
{} [OPTION]... [FILE]...
|
|
||||||
{} [OPTION]... --files0-from=F";
|
|
||||||
|
|
||||||
// TODO: Support Z & Y (currently limited by size of u64)
|
// TODO: Support Z & Y (currently limited by size of u64)
|
||||||
const UNITS: [(char, u32); 6] = [('E', 6), ('P', 5), ('T', 4), ('G', 3), ('M', 2), ('K', 1)];
|
const UNITS: [(char, u32); 6] = [('E', 6), ('P', 5), ('T', 4), ('G', 3), ('M', 2), ('K', 1)];
|
||||||
|
@ -705,7 +691,7 @@ pub fn uu_app() -> Command {
|
||||||
Command::new(uucore::util_name())
|
Command::new(uucore::util_name())
|
||||||
.version(crate_version!())
|
.version(crate_version!())
|
||||||
.about(ABOUT)
|
.about(ABOUT)
|
||||||
.after_help(LONG_HELP)
|
.after_help(AFTER_HELP)
|
||||||
.override_usage(format_usage(USAGE))
|
.override_usage(format_usage(USAGE))
|
||||||
.infer_long_args(true)
|
.infer_long_args(true)
|
||||||
.disable_help_flag(true)
|
.disable_help_flag(true)
|
||||||
|
|
|
@ -1,15 +1,12 @@
|
||||||
# expr
|
# expr
|
||||||
|
|
||||||
## About
|
|
||||||
|
|
||||||
Print the value of `EXPRESSION` to standard output
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
```
|
```
|
||||||
expr [EXPRESSION]
|
expr [EXPRESSION]
|
||||||
expr [OPTIONS]
|
expr [OPTIONS]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Print the value of `EXPRESSION` to standard output
|
||||||
|
|
||||||
## After help
|
## After help
|
||||||
|
|
||||||
Print the value of `EXPRESSION` to standard output. A blank line below
|
Print the value of `EXPRESSION` to standard output. A blank line below
|
||||||
|
@ -58,4 +55,4 @@ Environment variables:
|
||||||
- `EXPR_DEBUG_TOKENS=1`: dump expression's tokens
|
- `EXPR_DEBUG_TOKENS=1`: dump expression's tokens
|
||||||
- `EXPR_DEBUG_RPN=1`: dump expression represented in reverse polish notation
|
- `EXPR_DEBUG_RPN=1`: dump expression represented in reverse polish notation
|
||||||
- `EXPR_DEBUG_SYA_STEP=1`: dump each parser step
|
- `EXPR_DEBUG_SYA_STEP=1`: dump each parser step
|
||||||
- `EXPR_DEBUG_AST=1`: dump expression represented abstract syntax tree
|
- `EXPR_DEBUG_AST=1`: dump expression represented abstract syntax tree
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
use clap::{crate_version, Arg, ArgAction, Command};
|
use clap::{crate_version, Arg, ArgAction, Command};
|
||||||
use uucore::{
|
use uucore::{
|
||||||
error::{UResult, USimpleError},
|
error::{UResult, USimpleError},
|
||||||
format_usage, help_section, help_usage,
|
format_usage, help_about, help_section, help_usage,
|
||||||
};
|
};
|
||||||
|
|
||||||
mod syntax_tree;
|
mod syntax_tree;
|
||||||
|
@ -23,7 +23,7 @@ mod options {
|
||||||
pub fn uu_app() -> Command {
|
pub fn uu_app() -> Command {
|
||||||
Command::new(uucore::util_name())
|
Command::new(uucore::util_name())
|
||||||
.version(crate_version!())
|
.version(crate_version!())
|
||||||
.about(help_section!("about", "expr.md"))
|
.about(help_about!("expr.md"))
|
||||||
.override_usage(format_usage(help_usage!("expr.md")))
|
.override_usage(format_usage(help_usage!("expr.md")))
|
||||||
.after_help(help_section!("after help", "expr.md"))
|
.after_help(help_section!("after help", "expr.md"))
|
||||||
.infer_long_args(true)
|
.infer_long_args(true)
|
||||||
|
|
|
@ -41,8 +41,8 @@ pub struct Behavior {
|
||||||
specified_mode: Option<u32>,
|
specified_mode: Option<u32>,
|
||||||
backup_mode: BackupMode,
|
backup_mode: BackupMode,
|
||||||
suffix: String,
|
suffix: String,
|
||||||
owner: String,
|
owner_id: Option<u32>,
|
||||||
group: String,
|
group_id: Option<u32>,
|
||||||
verbose: bool,
|
verbose: bool,
|
||||||
preserve_timestamps: bool,
|
preserve_timestamps: bool,
|
||||||
compare: bool,
|
compare: bool,
|
||||||
|
@ -58,14 +58,15 @@ enum InstallError {
|
||||||
DirNeedsArg(),
|
DirNeedsArg(),
|
||||||
CreateDirFailed(PathBuf, std::io::Error),
|
CreateDirFailed(PathBuf, std::io::Error),
|
||||||
ChmodFailed(PathBuf),
|
ChmodFailed(PathBuf),
|
||||||
|
ChownFailed(PathBuf, String),
|
||||||
InvalidTarget(PathBuf),
|
InvalidTarget(PathBuf),
|
||||||
TargetDirIsntDir(PathBuf),
|
TargetDirIsntDir(PathBuf),
|
||||||
BackupFailed(PathBuf, PathBuf, std::io::Error),
|
BackupFailed(PathBuf, PathBuf, std::io::Error),
|
||||||
InstallFailed(PathBuf, PathBuf, std::io::Error),
|
InstallFailed(PathBuf, PathBuf, std::io::Error),
|
||||||
StripProgramFailed(String),
|
StripProgramFailed(String),
|
||||||
MetadataFailed(std::io::Error),
|
MetadataFailed(std::io::Error),
|
||||||
NoSuchUser(String),
|
InvalidUser(String),
|
||||||
NoSuchGroup(String),
|
InvalidGroup(String),
|
||||||
OmittingDirectory(PathBuf),
|
OmittingDirectory(PathBuf),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -99,6 +100,7 @@ impl Display for InstallError {
|
||||||
Display::fmt(&uio_error!(e, "failed to create {}", dir.quote()), f)
|
Display::fmt(&uio_error!(e, "failed to create {}", dir.quote()), f)
|
||||||
}
|
}
|
||||||
Self::ChmodFailed(file) => write!(f, "failed to chmod {}", file.quote()),
|
Self::ChmodFailed(file) => write!(f, "failed to chmod {}", file.quote()),
|
||||||
|
Self::ChownFailed(file, msg) => write!(f, "failed to chown {}: {}", file.quote(), msg),
|
||||||
Self::InvalidTarget(target) => write!(
|
Self::InvalidTarget(target) => write!(
|
||||||
f,
|
f,
|
||||||
"invalid target {}: No such file or directory",
|
"invalid target {}: No such file or directory",
|
||||||
|
@ -117,8 +119,8 @@ impl Display for InstallError {
|
||||||
),
|
),
|
||||||
Self::StripProgramFailed(msg) => write!(f, "strip program failed: {msg}"),
|
Self::StripProgramFailed(msg) => write!(f, "strip program failed: {msg}"),
|
||||||
Self::MetadataFailed(e) => Display::fmt(&uio_error!(e, ""), f),
|
Self::MetadataFailed(e) => Display::fmt(&uio_error!(e, ""), f),
|
||||||
Self::NoSuchUser(user) => write!(f, "no such user: {}", user.maybe_quote()),
|
Self::InvalidUser(user) => write!(f, "invalid user: {}", user.quote()),
|
||||||
Self::NoSuchGroup(group) => write!(f, "no such group: {}", group.maybe_quote()),
|
Self::InvalidGroup(group) => write!(f, "invalid group: {}", group.quote()),
|
||||||
Self::OmittingDirectory(dir) => write!(f, "omitting directory {}", dir.quote()),
|
Self::OmittingDirectory(dir) => write!(f, "omitting directory {}", dir.quote()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -391,21 +393,44 @@ fn behavior(matches: &ArgMatches) -> UResult<Behavior> {
|
||||||
show_error!("Options --compare and --strip are mutually exclusive");
|
show_error!("Options --compare and --strip are mutually exclusive");
|
||||||
return Err(1.into());
|
return Err(1.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let owner = matches
|
||||||
|
.get_one::<String>(OPT_OWNER)
|
||||||
|
.map(|s| s.as_str())
|
||||||
|
.unwrap_or("")
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
let owner_id = if !owner.is_empty() {
|
||||||
|
match usr2uid(&owner) {
|
||||||
|
Ok(u) => Some(u),
|
||||||
|
Err(_) => return Err(InstallError::InvalidUser(owner.clone()).into()),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
let group = matches
|
||||||
|
.get_one::<String>(OPT_GROUP)
|
||||||
|
.map(|s| s.as_str())
|
||||||
|
.unwrap_or("")
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
let group_id = if !group.is_empty() {
|
||||||
|
match grp2gid(&group) {
|
||||||
|
Ok(g) => Some(g),
|
||||||
|
Err(_) => return Err(InstallError::InvalidGroup(group.clone()).into()),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
Ok(Behavior {
|
Ok(Behavior {
|
||||||
main_function,
|
main_function,
|
||||||
specified_mode,
|
specified_mode,
|
||||||
backup_mode,
|
backup_mode,
|
||||||
suffix: backup_control::determine_backup_suffix(matches),
|
suffix: backup_control::determine_backup_suffix(matches),
|
||||||
owner: matches
|
owner_id,
|
||||||
.get_one::<String>(OPT_OWNER)
|
group_id,
|
||||||
.map(|s| s.as_str())
|
|
||||||
.unwrap_or("")
|
|
||||||
.to_string(),
|
|
||||||
group: matches
|
|
||||||
.get_one::<String>(OPT_GROUP)
|
|
||||||
.map(|s| s.as_str())
|
|
||||||
.unwrap_or("")
|
|
||||||
.to_string(),
|
|
||||||
verbose: matches.get_flag(OPT_VERBOSE),
|
verbose: matches.get_flag(OPT_VERBOSE),
|
||||||
preserve_timestamps,
|
preserve_timestamps,
|
||||||
compare,
|
compare,
|
||||||
|
@ -466,6 +491,8 @@ fn directory(paths: &[String], b: &Behavior) -> UResult<()> {
|
||||||
uucore::error::set_exit_code(1);
|
uucore::error::set_exit_code(1);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
show_if_err!(chown_optional_user_group(path, b));
|
||||||
}
|
}
|
||||||
// If the exit code was set, or show! has been called at least once
|
// If the exit code was set, or show! has been called at least once
|
||||||
// (which sets the exit code as well), function execution will end after
|
// (which sets the exit code as well), function execution will end after
|
||||||
|
@ -558,12 +585,6 @@ fn standard(mut paths: Vec<String>, b: &Behavior) -> UResult<()> {
|
||||||
if let Err(e) = fs::create_dir_all(to_create) {
|
if let Err(e) = fs::create_dir_all(to_create) {
|
||||||
return Err(InstallError::CreateDirFailed(to_create.to_path_buf(), e).into());
|
return Err(InstallError::CreateDirFailed(to_create.to_path_buf(), e).into());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Silent the warning as we want to the error message
|
|
||||||
#[allow(clippy::question_mark)]
|
|
||||||
if mode::chmod(to_create, b.mode()).is_err() {
|
|
||||||
return Err(InstallError::ChmodFailed(to_create.to_path_buf()).into());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -626,6 +647,42 @@ fn copy_files_into_dir(files: &[PathBuf], target_dir: &Path, b: &Behavior) -> UR
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Handle incomplete user/group parings for chown.
|
||||||
|
///
|
||||||
|
/// Returns a Result type with the Err variant containing the error message.
|
||||||
|
///
|
||||||
|
/// # Parameters
|
||||||
|
///
|
||||||
|
/// _path_ must exist.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// If the owner or group are invalid or copy system call fails, we print a verbose error and
|
||||||
|
/// return an empty error value.
|
||||||
|
///
|
||||||
|
fn chown_optional_user_group(path: &Path, b: &Behavior) -> UResult<()> {
|
||||||
|
if b.owner_id.is_some() || b.group_id.is_some() {
|
||||||
|
let meta = match fs::metadata(path) {
|
||||||
|
Ok(meta) => meta,
|
||||||
|
Err(e) => return Err(InstallError::MetadataFailed(e).into()),
|
||||||
|
};
|
||||||
|
|
||||||
|
// GNU coreutils doesn't print chown operations during install with verbose flag.
|
||||||
|
let verbosity = Verbosity {
|
||||||
|
groups_only: b.owner_id.is_none(),
|
||||||
|
level: VerbosityLevel::Normal,
|
||||||
|
};
|
||||||
|
|
||||||
|
match wrap_chown(path, &meta, b.owner_id, b.group_id, false, verbosity) {
|
||||||
|
Ok(msg) if b.verbose && !msg.is_empty() => println!("chown: {msg}"),
|
||||||
|
Ok(_) => {}
|
||||||
|
Err(e) => return Err(InstallError::ChownFailed(path.to_path_buf(), e).into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Copy one file to a new location, changing metadata.
|
/// Copy one file to a new location, changing metadata.
|
||||||
///
|
///
|
||||||
/// Returns a Result type with the Err variant containing the error message.
|
/// Returns a Result type with the Err variant containing the error message.
|
||||||
|
@ -708,66 +765,7 @@ fn copy(from: &Path, to: &Path, b: &Behavior) -> UResult<()> {
|
||||||
return Err(InstallError::ChmodFailed(to.to_path_buf()).into());
|
return Err(InstallError::ChmodFailed(to.to_path_buf()).into());
|
||||||
}
|
}
|
||||||
|
|
||||||
if !b.owner.is_empty() {
|
chown_optional_user_group(to, b)?;
|
||||||
let meta = match fs::metadata(to) {
|
|
||||||
Ok(meta) => meta,
|
|
||||||
Err(e) => return Err(InstallError::MetadataFailed(e).into()),
|
|
||||||
};
|
|
||||||
|
|
||||||
let owner_id = match usr2uid(&b.owner) {
|
|
||||||
Ok(g) => g,
|
|
||||||
_ => return Err(InstallError::NoSuchUser(b.owner.clone()).into()),
|
|
||||||
};
|
|
||||||
let gid = meta.gid();
|
|
||||||
match wrap_chown(
|
|
||||||
to,
|
|
||||||
&meta,
|
|
||||||
Some(owner_id),
|
|
||||||
Some(gid),
|
|
||||||
false,
|
|
||||||
Verbosity {
|
|
||||||
groups_only: false,
|
|
||||||
level: VerbosityLevel::Normal,
|
|
||||||
},
|
|
||||||
) {
|
|
||||||
Ok(n) => {
|
|
||||||
if !n.is_empty() {
|
|
||||||
show_error!("{}", n);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(e) => show_error!("{}", e),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !b.group.is_empty() {
|
|
||||||
let meta = match fs::metadata(to) {
|
|
||||||
Ok(meta) => meta,
|
|
||||||
Err(e) => return Err(InstallError::MetadataFailed(e).into()),
|
|
||||||
};
|
|
||||||
|
|
||||||
let group_id = match grp2gid(&b.group) {
|
|
||||||
Ok(g) => g,
|
|
||||||
_ => return Err(InstallError::NoSuchGroup(b.group.clone()).into()),
|
|
||||||
};
|
|
||||||
match wrap_chown(
|
|
||||||
to,
|
|
||||||
&meta,
|
|
||||||
Some(group_id),
|
|
||||||
None,
|
|
||||||
false,
|
|
||||||
Verbosity {
|
|
||||||
groups_only: true,
|
|
||||||
level: VerbosityLevel::Normal,
|
|
||||||
},
|
|
||||||
) {
|
|
||||||
Ok(n) => {
|
|
||||||
if !n.is_empty() {
|
|
||||||
show_error!("{}", n);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(e) => show_error!("{}", e),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if b.preserve_timestamps {
|
if b.preserve_timestamps {
|
||||||
let meta = match fs::metadata(from) {
|
let meta = match fs::metadata(from) {
|
||||||
|
@ -847,19 +845,11 @@ fn need_copy(from: &Path, to: &Path, b: &Behavior) -> UResult<bool> {
|
||||||
|
|
||||||
// TODO: if -P (#1809) and from/to contexts mismatch, return true.
|
// TODO: if -P (#1809) and from/to contexts mismatch, return true.
|
||||||
|
|
||||||
if !b.owner.is_empty() {
|
if let Some(owner_id) = b.owner_id {
|
||||||
let owner_id = match usr2uid(&b.owner) {
|
|
||||||
Ok(id) => id,
|
|
||||||
_ => return Err(InstallError::NoSuchUser(b.owner.clone()).into()),
|
|
||||||
};
|
|
||||||
if owner_id != to_meta.uid() {
|
if owner_id != to_meta.uid() {
|
||||||
return Ok(true);
|
return Ok(true);
|
||||||
}
|
}
|
||||||
} else if !b.group.is_empty() {
|
} else if let Some(group_id) = b.group_id {
|
||||||
let group_id = match grp2gid(&b.group) {
|
|
||||||
Ok(id) => id,
|
|
||||||
_ => return Err(InstallError::NoSuchGroup(b.group.clone()).into()),
|
|
||||||
};
|
|
||||||
if group_id != to_meta.gid() {
|
if group_id != to_meta.gid() {
|
||||||
return Ok(true);
|
return Ok(true);
|
||||||
}
|
}
|
||||||
|
|
7
src/uu/kill/kill.md
Normal file
7
src/uu/kill/kill.md
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
# kill
|
||||||
|
|
||||||
|
```
|
||||||
|
kill [OPTIONS]... PID...
|
||||||
|
```
|
||||||
|
|
||||||
|
Send signal to processes or list information about signals.
|
|
@ -14,10 +14,10 @@ use std::io::Error;
|
||||||
use uucore::display::Quotable;
|
use uucore::display::Quotable;
|
||||||
use uucore::error::{FromIo, UError, UResult, USimpleError};
|
use uucore::error::{FromIo, UError, UResult, USimpleError};
|
||||||
use uucore::signals::{signal_by_name_or_value, ALL_SIGNALS};
|
use uucore::signals::{signal_by_name_or_value, ALL_SIGNALS};
|
||||||
use uucore::{format_usage, show};
|
use uucore::{format_usage, help_about, help_usage, show};
|
||||||
|
|
||||||
static ABOUT: &str = "Send signal to processes or list information about signals.";
|
static ABOUT: &str = help_about!("kill.md");
|
||||||
const USAGE: &str = "{} [OPTIONS]... PID...";
|
const USAGE: &str = help_usage!("kill.md");
|
||||||
|
|
||||||
pub mod options {
|
pub mod options {
|
||||||
pub static PIDS_OR_SIGNALS: &str = "pids_or_signals";
|
pub static PIDS_OR_SIGNALS: &str = "pids_or_signals";
|
||||||
|
|
|
@ -25,7 +25,7 @@ glob = { workspace=true }
|
||||||
lscolors = { workspace=true }
|
lscolors = { workspace=true }
|
||||||
uucore = { workspace=true, features = ["entries", "fs"] }
|
uucore = { workspace=true, features = ["entries", "fs"] }
|
||||||
once_cell = { workspace=true }
|
once_cell = { workspace=true }
|
||||||
atty = { workspace=true }
|
is-terminal = { workspace=true }
|
||||||
selinux = { workspace=true, optional = true }
|
selinux = { workspace=true, optional = true }
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
|
|
|
@ -12,6 +12,7 @@ use clap::{
|
||||||
crate_version, Arg, ArgAction, Command,
|
crate_version, Arg, ArgAction, Command,
|
||||||
};
|
};
|
||||||
use glob::{MatchOptions, Pattern};
|
use glob::{MatchOptions, Pattern};
|
||||||
|
use is_terminal::IsTerminal;
|
||||||
use lscolors::LsColors;
|
use lscolors::LsColors;
|
||||||
use number_prefix::NumberPrefix;
|
use number_prefix::NumberPrefix;
|
||||||
use once_cell::unsync::OnceCell;
|
use once_cell::unsync::OnceCell;
|
||||||
|
@ -451,7 +452,7 @@ impl Config {
|
||||||
(Format::Commas, Some(options::format::COMMAS))
|
(Format::Commas, Some(options::format::COMMAS))
|
||||||
} else if options.get_flag(options::format::COLUMNS) {
|
} else if options.get_flag(options::format::COLUMNS) {
|
||||||
(Format::Columns, Some(options::format::COLUMNS))
|
(Format::Columns, Some(options::format::COLUMNS))
|
||||||
} else if atty::is(atty::Stream::Stdout) {
|
} else if std::io::stdout().is_terminal() {
|
||||||
(Format::Columns, None)
|
(Format::Columns, None)
|
||||||
} else {
|
} else {
|
||||||
(Format::OneLine, None)
|
(Format::OneLine, None)
|
||||||
|
@ -557,7 +558,7 @@ impl Config {
|
||||||
None => options.contains_id(options::COLOR),
|
None => options.contains_id(options::COLOR),
|
||||||
Some(val) => match val.as_str() {
|
Some(val) => match val.as_str() {
|
||||||
"" | "always" | "yes" | "force" => true,
|
"" | "always" | "yes" | "force" => true,
|
||||||
"auto" | "tty" | "if-tty" => atty::is(atty::Stream::Stdout),
|
"auto" | "tty" | "if-tty" => std::io::stdout().is_terminal(),
|
||||||
/* "never" | "no" | "none" | */ _ => false,
|
/* "never" | "no" | "none" | */ _ => false,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -678,7 +679,7 @@ impl Config {
|
||||||
} else if options.get_flag(options::SHOW_CONTROL_CHARS) {
|
} else if options.get_flag(options::SHOW_CONTROL_CHARS) {
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
!atty::is(atty::Stream::Stdout)
|
!std::io::stdout().is_terminal()
|
||||||
};
|
};
|
||||||
|
|
||||||
let opt_quoting_style = options
|
let opt_quoting_style = options
|
||||||
|
@ -750,7 +751,7 @@ impl Config {
|
||||||
"never" | "no" | "none" => IndicatorStyle::None,
|
"never" | "no" | "none" => IndicatorStyle::None,
|
||||||
"always" | "yes" | "force" => IndicatorStyle::Classify,
|
"always" | "yes" | "force" => IndicatorStyle::Classify,
|
||||||
"auto" | "tty" | "if-tty" => {
|
"auto" | "tty" | "if-tty" => {
|
||||||
if atty::is(atty::Stream::Stdout) {
|
if std::io::stdout().is_terminal() {
|
||||||
IndicatorStyle::Classify
|
IndicatorStyle::Classify
|
||||||
} else {
|
} else {
|
||||||
IndicatorStyle::None
|
IndicatorStyle::None
|
||||||
|
|
12
src/uu/mkdir/mkdir.md
Normal file
12
src/uu/mkdir/mkdir.md
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
<!-- spell-checker:ignore ugoa -->
|
||||||
|
# mkdir
|
||||||
|
|
||||||
|
```
|
||||||
|
mkdir [OPTION]... [USER]
|
||||||
|
```
|
||||||
|
|
||||||
|
Create the given DIRECTORY(ies) if they do not exist
|
||||||
|
|
||||||
|
## After Help
|
||||||
|
|
||||||
|
Each MODE is of the form '[ugoa]*([-+=]([rwxXst]*|[ugo]))+|[-+=]?[0-7]+'.
|
|
@ -18,14 +18,13 @@ use uucore::error::{UResult, USimpleError};
|
||||||
#[cfg(not(windows))]
|
#[cfg(not(windows))]
|
||||||
use uucore::mode;
|
use uucore::mode;
|
||||||
use uucore::{display::Quotable, fs::dir_strip_dot_for_creation};
|
use uucore::{display::Quotable, fs::dir_strip_dot_for_creation};
|
||||||
use uucore::{format_usage, show, show_if_err};
|
use uucore::{format_usage, help_about, help_section, help_usage, show, show_if_err};
|
||||||
|
|
||||||
static DEFAULT_PERM: u32 = 0o755;
|
static DEFAULT_PERM: u32 = 0o755;
|
||||||
|
|
||||||
const ABOUT: &str = "Create the given DIRECTORY(ies) if they do not exist";
|
const ABOUT: &str = help_about!("mkdir.md");
|
||||||
const USAGE: &str = "{} [OPTION]... [USER]";
|
const USAGE: &str = help_usage!("mkdir.md");
|
||||||
const LONG_USAGE: &str =
|
const AFTER_HELP: &str = help_section!("after help", "mkdir.md");
|
||||||
"Each MODE is of the form '[ugoa]*([-+=]([rwxXst]*|[ugo]))+|[-+=]?[0-7]+'.";
|
|
||||||
|
|
||||||
mod options {
|
mod options {
|
||||||
pub const MODE: &str = "mode";
|
pub const MODE: &str = "mode";
|
||||||
|
@ -90,7 +89,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||||
// Linux-specific options, not implemented
|
// Linux-specific options, not implemented
|
||||||
// opts.optflag("Z", "context", "set SELinux security context" +
|
// opts.optflag("Z", "context", "set SELinux security context" +
|
||||||
// " of each created directory to CTX"),
|
// " of each created directory to CTX"),
|
||||||
let matches = uu_app().after_help(LONG_USAGE).try_get_matches_from(args)?;
|
let matches = uu_app().after_help(AFTER_HELP).try_get_matches_from(args)?;
|
||||||
|
|
||||||
let dirs = matches
|
let dirs = matches
|
||||||
.get_many::<OsString>(options::DIRS)
|
.get_many::<OsString>(options::DIRS)
|
||||||
|
|
|
@ -18,7 +18,7 @@ path = "src/more.rs"
|
||||||
clap = { workspace=true }
|
clap = { workspace=true }
|
||||||
uucore = { workspace=true }
|
uucore = { workspace=true }
|
||||||
crossterm = { workspace=true }
|
crossterm = { workspace=true }
|
||||||
atty = { workspace=true }
|
is-terminal = { workspace=true }
|
||||||
unicode-width = { workspace=true }
|
unicode-width = { workspace=true }
|
||||||
unicode-segmentation = { workspace=true }
|
unicode-segmentation = { workspace=true }
|
||||||
|
|
||||||
|
|
|
@ -23,6 +23,7 @@ use crossterm::{
|
||||||
terminal,
|
terminal,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use is_terminal::IsTerminal;
|
||||||
use unicode_segmentation::UnicodeSegmentation;
|
use unicode_segmentation::UnicodeSegmentation;
|
||||||
use unicode_width::UnicodeWidthStr;
|
use unicode_width::UnicodeWidthStr;
|
||||||
use uucore::display::Quotable;
|
use uucore::display::Quotable;
|
||||||
|
@ -83,7 +84,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||||
buff.clear();
|
buff.clear();
|
||||||
}
|
}
|
||||||
reset_term(&mut stdout);
|
reset_term(&mut stdout);
|
||||||
} else if atty::isnt(atty::Stream::Stdin) {
|
} else if !std::io::stdin().is_terminal() {
|
||||||
stdin().read_to_string(&mut buff).unwrap();
|
stdin().read_to_string(&mut buff).unwrap();
|
||||||
let mut stdout = setup_term();
|
let mut stdout = setup_term();
|
||||||
more(&buff, &mut stdout, None, silent)?;
|
more(&buff, &mut stdout, None, silent)?;
|
||||||
|
|
|
@ -17,7 +17,7 @@ path = "src/nohup.rs"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
clap = { workspace=true }
|
clap = { workspace=true }
|
||||||
libc = { workspace=true }
|
libc = { workspace=true }
|
||||||
atty = { workspace=true }
|
is-terminal = { workspace=true }
|
||||||
uucore = { workspace=true, features=["fs"] }
|
uucore = { workspace=true, features=["fs"] }
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
|
|
15
src/uu/nohup/nohup.md
Normal file
15
src/uu/nohup/nohup.md
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
# nohup
|
||||||
|
|
||||||
|
```
|
||||||
|
nohup COMMAND [ARG]...
|
||||||
|
nohup FLAG
|
||||||
|
```
|
||||||
|
|
||||||
|
Run COMMAND ignoring hangup signals.
|
||||||
|
|
||||||
|
## After Help
|
||||||
|
|
||||||
|
If standard input is terminal, it'll be replaced with /dev/null.
|
||||||
|
If standard output is terminal, it'll be appended to nohup.out instead,
|
||||||
|
or $HOME/nohup.out, if nohup.out open failed.
|
||||||
|
If standard error is terminal, it'll be redirected to stdout.
|
|
@ -8,6 +8,7 @@
|
||||||
// spell-checker:ignore (ToDO) execvp SIGHUP cproc vprocmgr cstrs homeout
|
// spell-checker:ignore (ToDO) execvp SIGHUP cproc vprocmgr cstrs homeout
|
||||||
|
|
||||||
use clap::{crate_version, Arg, ArgAction, Command};
|
use clap::{crate_version, Arg, ArgAction, Command};
|
||||||
|
use is_terminal::IsTerminal;
|
||||||
use libc::{c_char, dup2, execvp, signal};
|
use libc::{c_char, dup2, execvp, signal};
|
||||||
use libc::{SIGHUP, SIG_IGN};
|
use libc::{SIGHUP, SIG_IGN};
|
||||||
use std::env;
|
use std::env;
|
||||||
|
@ -19,18 +20,11 @@ use std::os::unix::prelude::*;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use uucore::display::Quotable;
|
use uucore::display::Quotable;
|
||||||
use uucore::error::{set_exit_code, UClapError, UError, UResult};
|
use uucore::error::{set_exit_code, UClapError, UError, UResult};
|
||||||
use uucore::{format_usage, show_error};
|
use uucore::{format_usage, help_about, help_section, help_usage, show_error};
|
||||||
|
|
||||||
static ABOUT: &str = "Run COMMAND ignoring hangup signals.";
|
const ABOUT: &str = help_about!("nohup.md");
|
||||||
static LONG_HELP: &str = "
|
const AFTER_HELP: &str = help_section!("after help", "nohup.md");
|
||||||
If standard input is terminal, it'll be replaced with /dev/null.
|
const USAGE: &str = help_usage!("nohup.md");
|
||||||
If standard output is terminal, it'll be appended to nohup.out instead,
|
|
||||||
or $HOME/nohup.out, if nohup.out open failed.
|
|
||||||
If standard error is terminal, it'll be redirected to stdout.
|
|
||||||
";
|
|
||||||
const USAGE: &str = "\
|
|
||||||
{} COMMAND [ARG]...
|
|
||||||
{} FLAG";
|
|
||||||
static NOHUP_OUT: &str = "nohup.out";
|
static NOHUP_OUT: &str = "nohup.out";
|
||||||
// exit codes that match the GNU implementation
|
// exit codes that match the GNU implementation
|
||||||
static EXIT_CANCELED: i32 = 125;
|
static EXIT_CANCELED: i32 = 125;
|
||||||
|
@ -115,7 +109,7 @@ pub fn uu_app() -> Command {
|
||||||
Command::new(uucore::util_name())
|
Command::new(uucore::util_name())
|
||||||
.version(crate_version!())
|
.version(crate_version!())
|
||||||
.about(ABOUT)
|
.about(ABOUT)
|
||||||
.after_help(LONG_HELP)
|
.after_help(AFTER_HELP)
|
||||||
.override_usage(format_usage(USAGE))
|
.override_usage(format_usage(USAGE))
|
||||||
.arg(
|
.arg(
|
||||||
Arg::new(options::CMD)
|
Arg::new(options::CMD)
|
||||||
|
@ -129,7 +123,7 @@ pub fn uu_app() -> Command {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn replace_fds() -> UResult<()> {
|
fn replace_fds() -> UResult<()> {
|
||||||
if atty::is(atty::Stream::Stdin) {
|
if std::io::stdin().is_terminal() {
|
||||||
let new_stdin = File::open(Path::new("/dev/null"))
|
let new_stdin = File::open(Path::new("/dev/null"))
|
||||||
.map_err(|e| NohupError::CannotReplace("STDIN", e))?;
|
.map_err(|e| NohupError::CannotReplace("STDIN", e))?;
|
||||||
if unsafe { dup2(new_stdin.as_raw_fd(), 0) } != 0 {
|
if unsafe { dup2(new_stdin.as_raw_fd(), 0) } != 0 {
|
||||||
|
@ -137,7 +131,7 @@ fn replace_fds() -> UResult<()> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if atty::is(atty::Stream::Stdout) {
|
if std::io::stdout().is_terminal() {
|
||||||
let new_stdout = find_stdout()?;
|
let new_stdout = find_stdout()?;
|
||||||
let fd = new_stdout.as_raw_fd();
|
let fd = new_stdout.as_raw_fd();
|
||||||
|
|
||||||
|
@ -146,7 +140,7 @@ fn replace_fds() -> UResult<()> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if atty::is(atty::Stream::Stderr) && unsafe { dup2(1, 2) } != 2 {
|
if std::io::stderr().is_terminal() && unsafe { dup2(1, 2) } != 2 {
|
||||||
return Err(NohupError::CannotReplace("STDERR", Error::last_os_error()).into());
|
return Err(NohupError::CannotReplace("STDERR", Error::last_os_error()).into());
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -1,13 +1,10 @@
|
||||||
<!-- spell-checker:ignore N'th M'th -->
|
<!-- spell-checker:ignore N'th M'th -->
|
||||||
# numfmt
|
# numfmt
|
||||||
|
|
||||||
## Usage
|
|
||||||
```
|
```
|
||||||
numfmt [OPTION]... [NUMBER]...
|
numfmt [OPTION]... [NUMBER]...
|
||||||
```
|
```
|
||||||
|
|
||||||
## About
|
|
||||||
|
|
||||||
Convert numbers from/to human-readable strings
|
Convert numbers from/to human-readable strings
|
||||||
|
|
||||||
## After Help
|
## After Help
|
||||||
|
|
|
@ -14,16 +14,15 @@ use std::io::{BufRead, Write};
|
||||||
use units::{IEC_BASES, SI_BASES};
|
use units::{IEC_BASES, SI_BASES};
|
||||||
use uucore::display::Quotable;
|
use uucore::display::Quotable;
|
||||||
use uucore::error::UResult;
|
use uucore::error::UResult;
|
||||||
use uucore::format_usage;
|
|
||||||
use uucore::ranges::Range;
|
use uucore::ranges::Range;
|
||||||
use uucore::{help_section, help_usage};
|
use uucore::{format_usage, help_about, help_section, help_usage};
|
||||||
|
|
||||||
pub mod errors;
|
pub mod errors;
|
||||||
pub mod format;
|
pub mod format;
|
||||||
pub mod options;
|
pub mod options;
|
||||||
mod units;
|
mod units;
|
||||||
|
|
||||||
const ABOUT: &str = help_section!("about", "numfmt.md");
|
const ABOUT: &str = help_about!("numfmt.md");
|
||||||
const AFTER_HELP: &str = help_section!("after help", "numfmt.md");
|
const AFTER_HELP: &str = help_section!("after help", "numfmt.md");
|
||||||
const USAGE: &str = help_usage!("numfmt.md");
|
const USAGE: &str = help_usage!("numfmt.md");
|
||||||
|
|
||||||
|
|
49
src/uu/od/od.md
Normal file
49
src/uu/od/od.md
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
# od
|
||||||
|
|
||||||
|
```
|
||||||
|
od [OPTION]... [--] [FILENAME]...
|
||||||
|
od [-abcdDefFhHiIlLoOsxX] [FILENAME] [[+][0x]OFFSET[.][b]]
|
||||||
|
od --traditional [OPTION]... [FILENAME] [[+][0x]OFFSET[.][b] [[+][0x]LABEL[.][b]]]
|
||||||
|
```
|
||||||
|
|
||||||
|
Dump files in octal and other formats
|
||||||
|
|
||||||
|
## After Help
|
||||||
|
|
||||||
|
Displays data in various human-readable formats. If multiple formats are
|
||||||
|
specified, the output will contain all formats in the order they appear on the
|
||||||
|
command line. Each format will be printed on a new line. Only the line
|
||||||
|
containing the first format will be prefixed with the offset.
|
||||||
|
|
||||||
|
If no filename is specified, or it is "-", stdin will be used. After a "--", no
|
||||||
|
more options will be recognized. This allows for filenames starting with a "-".
|
||||||
|
|
||||||
|
If a filename is a valid number which can be used as an offset in the second
|
||||||
|
form, you can force it to be recognized as a filename if you include an option
|
||||||
|
like "-j0", which is only valid in the first form.
|
||||||
|
|
||||||
|
RADIX is one of o,d,x,n for octal, decimal, hexadecimal or none.
|
||||||
|
|
||||||
|
BYTES is decimal by default, octal if prefixed with a "0", or hexadecimal if
|
||||||
|
prefixed with "0x". The suffixes b, KB, K, MB, M, GB, G, will multiply the
|
||||||
|
number with 512, 1000, 1024, 1000^2, 1024^2, 1000^3, 1024^3, 1000^2, 1024^2.
|
||||||
|
|
||||||
|
OFFSET and LABEL are octal by default, hexadecimal if prefixed with "0x" or
|
||||||
|
decimal if a "." suffix is added. The "b" suffix will multiply with 512.
|
||||||
|
|
||||||
|
TYPE contains one or more format specifications consisting of:
|
||||||
|
a for printable 7-bits ASCII
|
||||||
|
c for utf-8 characters or octal for undefined characters
|
||||||
|
d[SIZE] for signed decimal
|
||||||
|
f[SIZE] for floating point
|
||||||
|
o[SIZE] for octal
|
||||||
|
u[SIZE] for unsigned decimal
|
||||||
|
x[SIZE] for hexadecimal
|
||||||
|
SIZE is the number of bytes which can be the number 1, 2, 4, 8 or 16,
|
||||||
|
or C, I, S, L for 1, 2, 4, 8 bytes for integer types,
|
||||||
|
or F, D, L for 4, 8, 16 bytes for floating point.
|
||||||
|
Any type specification can have a "z" suffix, which will add a ASCII dump at
|
||||||
|
the end of the line.
|
||||||
|
|
||||||
|
If an error occurred, a diagnostic message will be printed to stderr, and the
|
||||||
|
exit code will be non-zero.
|
|
@ -44,57 +44,15 @@ use clap::ArgAction;
|
||||||
use clap::{crate_version, parser::ValueSource, Arg, ArgMatches, Command};
|
use clap::{crate_version, parser::ValueSource, Arg, ArgMatches, Command};
|
||||||
use uucore::display::Quotable;
|
use uucore::display::Quotable;
|
||||||
use uucore::error::{UResult, USimpleError};
|
use uucore::error::{UResult, USimpleError};
|
||||||
use uucore::format_usage;
|
|
||||||
use uucore::parse_size::ParseSizeError;
|
use uucore::parse_size::ParseSizeError;
|
||||||
use uucore::show_error;
|
use uucore::{format_usage, help_about, help_section, help_usage, show_error, show_warning};
|
||||||
use uucore::show_warning;
|
|
||||||
|
|
||||||
const PEEK_BUFFER_SIZE: usize = 4; // utf-8 can be 4 bytes
|
const PEEK_BUFFER_SIZE: usize = 4; // utf-8 can be 4 bytes
|
||||||
static ABOUT: &str = "Dump files in octal and other formats";
|
const ABOUT: &str = help_about!("od.md");
|
||||||
|
|
||||||
static USAGE: &str = "\
|
const USAGE: &str = help_usage!("od.md");
|
||||||
{} [OPTION]... [--] [FILENAME]...
|
|
||||||
{} [-abcdDefFhHiIlLoOsxX] [FILENAME] [[+][0x]OFFSET[.][b]]
|
|
||||||
{} --traditional [OPTION]... [FILENAME] [[+][0x]OFFSET[.][b] [[+][0x]LABEL[.][b]]]";
|
|
||||||
|
|
||||||
static LONG_HELP: &str = r#"
|
const AFTER_HELP: &str = help_section!("after help", "od.md");
|
||||||
Displays data in various human-readable formats. If multiple formats are
|
|
||||||
specified, the output will contain all formats in the order they appear on the
|
|
||||||
command line. Each format will be printed on a new line. Only the line
|
|
||||||
containing the first format will be prefixed with the offset.
|
|
||||||
|
|
||||||
If no filename is specified, or it is "-", stdin will be used. After a "--", no
|
|
||||||
more options will be recognized. This allows for filenames starting with a "-".
|
|
||||||
|
|
||||||
If a filename is a valid number which can be used as an offset in the second
|
|
||||||
form, you can force it to be recognized as a filename if you include an option
|
|
||||||
like "-j0", which is only valid in the first form.
|
|
||||||
|
|
||||||
RADIX is one of o,d,x,n for octal, decimal, hexadecimal or none.
|
|
||||||
|
|
||||||
BYTES is decimal by default, octal if prefixed with a "0", or hexadecimal if
|
|
||||||
prefixed with "0x". The suffixes b, KB, K, MB, M, GB, G, will multiply the
|
|
||||||
number with 512, 1000, 1024, 1000^2, 1024^2, 1000^3, 1024^3, 1000^2, 1024^2.
|
|
||||||
|
|
||||||
OFFSET and LABEL are octal by default, hexadecimal if prefixed with "0x" or
|
|
||||||
decimal if a "." suffix is added. The "b" suffix will multiply with 512.
|
|
||||||
|
|
||||||
TYPE contains one or more format specifications consisting of:
|
|
||||||
a for printable 7-bits ASCII
|
|
||||||
c for utf-8 characters or octal for undefined characters
|
|
||||||
d[SIZE] for signed decimal
|
|
||||||
f[SIZE] for floating point
|
|
||||||
o[SIZE] for octal
|
|
||||||
u[SIZE] for unsigned decimal
|
|
||||||
x[SIZE] for hexadecimal
|
|
||||||
SIZE is the number of bytes which can be the number 1, 2, 4, 8 or 16,
|
|
||||||
or C, I, S, L for 1, 2, 4, 8 bytes for integer types,
|
|
||||||
or F, D, L for 4, 8, 16 bytes for floating point.
|
|
||||||
Any type specification can have a "z" suffix, which will add a ASCII dump at
|
|
||||||
the end of the line.
|
|
||||||
|
|
||||||
If an error occurred, a diagnostic message will be printed to stderr, and the
|
|
||||||
exitcode will be non-zero."#;
|
|
||||||
|
|
||||||
pub(crate) mod options {
|
pub(crate) mod options {
|
||||||
pub const HELP: &str = "help";
|
pub const HELP: &str = "help";
|
||||||
|
@ -295,7 +253,7 @@ pub fn uu_app() -> Command {
|
||||||
.version(crate_version!())
|
.version(crate_version!())
|
||||||
.about(ABOUT)
|
.about(ABOUT)
|
||||||
.override_usage(format_usage(USAGE))
|
.override_usage(format_usage(USAGE))
|
||||||
.after_help(LONG_HELP)
|
.after_help(AFTER_HELP)
|
||||||
.trailing_var_arg(true)
|
.trailing_var_arg(true)
|
||||||
.dont_delimit_trailing_values(true)
|
.dont_delimit_trailing_values(true)
|
||||||
.infer_long_args(true)
|
.infer_long_args(true)
|
||||||
|
|
22
src/uu/rm/rm.md
Normal file
22
src/uu/rm/rm.md
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
# rm
|
||||||
|
|
||||||
|
```
|
||||||
|
rm [OPTION]... FILE...
|
||||||
|
```
|
||||||
|
|
||||||
|
Remove (unlink) the FILE(s)
|
||||||
|
|
||||||
|
## After Help
|
||||||
|
|
||||||
|
By default, rm does not remove directories. Use the --recursive (-r or -R)
|
||||||
|
option to remove each listed directory, too, along with all of its contents
|
||||||
|
|
||||||
|
To remove a file whose name starts with a '-', for example '-foo',
|
||||||
|
use one of these commands:
|
||||||
|
rm -- -foo
|
||||||
|
|
||||||
|
rm ./-foo
|
||||||
|
|
||||||
|
Note that if you use rm to remove a file, it might be possible to recover
|
||||||
|
some of its contents, given sufficient expertise and/or time. For greater
|
||||||
|
assurance that the contents are truly unrecoverable, consider using shred.
|
|
@ -15,7 +15,7 @@ use std::ops::BitOr;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use uucore::display::Quotable;
|
use uucore::display::Quotable;
|
||||||
use uucore::error::{UResult, USimpleError, UUsageError};
|
use uucore::error::{UResult, USimpleError, UUsageError};
|
||||||
use uucore::{format_usage, prompt_yes, show_error};
|
use uucore::{format_usage, help_about, help_section, help_usage, prompt_yes, show_error};
|
||||||
use walkdir::{DirEntry, WalkDir};
|
use walkdir::{DirEntry, WalkDir};
|
||||||
|
|
||||||
#[derive(Eq, PartialEq, Clone, Copy)]
|
#[derive(Eq, PartialEq, Clone, Copy)]
|
||||||
|
@ -37,21 +37,9 @@ struct Options {
|
||||||
verbose: bool,
|
verbose: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
const ABOUT: &str = "Remove (unlink) the FILE(s)";
|
const ABOUT: &str = help_about!("rm.md");
|
||||||
const USAGE: &str = "{} [OPTION]... FILE...";
|
const USAGE: &str = help_usage!("rm.md");
|
||||||
const LONG_USAGE: &str = "\
|
const AFTER_HELP: &str = help_section!("after help", "rm.md");
|
||||||
By default, rm does not remove directories. Use the --recursive (-r or -R)
|
|
||||||
option to remove each listed directory, too, along with all of its contents
|
|
||||||
|
|
||||||
To remove a file whose name starts with a '-', for example '-foo',
|
|
||||||
use one of these commands:
|
|
||||||
rm -- -foo
|
|
||||||
|
|
||||||
rm ./-foo
|
|
||||||
|
|
||||||
Note that if you use rm to remove a file, it might be possible to recover
|
|
||||||
some of its contents, given sufficient expertise and/or time. For greater
|
|
||||||
assurance that the contents are truly unrecoverable, consider using shred.";
|
|
||||||
|
|
||||||
static OPT_DIR: &str = "dir";
|
static OPT_DIR: &str = "dir";
|
||||||
static OPT_INTERACTIVE: &str = "interactive";
|
static OPT_INTERACTIVE: &str = "interactive";
|
||||||
|
@ -69,7 +57,7 @@ static ARG_FILES: &str = "files";
|
||||||
|
|
||||||
#[uucore::main]
|
#[uucore::main]
|
||||||
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||||
let matches = uu_app().after_help(LONG_USAGE).try_get_matches_from(args)?;
|
let matches = uu_app().after_help(AFTER_HELP).try_get_matches_from(args)?;
|
||||||
|
|
||||||
let files: Vec<String> = matches
|
let files: Vec<String> = matches
|
||||||
.get_many::<String>(ARG_FILES)
|
.get_many::<String>(ARG_FILES)
|
||||||
|
|
7
src/uu/rmdir/rmdir.md
Normal file
7
src/uu/rmdir/rmdir.md
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
# rmdir
|
||||||
|
|
||||||
|
```
|
||||||
|
rmdir [OPTION]... DIRECTORY...
|
||||||
|
```
|
||||||
|
|
||||||
|
Remove the DIRECTORY(ies), if they are empty.
|
|
@ -16,10 +16,10 @@ use std::path::Path;
|
||||||
use uucore::display::Quotable;
|
use uucore::display::Quotable;
|
||||||
use uucore::error::{set_exit_code, strip_errno, UResult};
|
use uucore::error::{set_exit_code, strip_errno, UResult};
|
||||||
|
|
||||||
use uucore::{format_usage, show_error, util_name};
|
use uucore::{format_usage, help_about, help_usage, show_error, util_name};
|
||||||
|
|
||||||
static ABOUT: &str = "Remove the DIRECTORY(ies), if they are empty.";
|
static ABOUT: &str = help_about!("rmdir.md");
|
||||||
const USAGE: &str = "{} [OPTION]... DIRECTORY...";
|
const USAGE: &str = help_usage!("rmdir.md");
|
||||||
static OPT_IGNORE_FAIL_NON_EMPTY: &str = "ignore-fail-on-non-empty";
|
static OPT_IGNORE_FAIL_NON_EMPTY: &str = "ignore-fail-on-non-empty";
|
||||||
static OPT_PARENTS: &str = "parents";
|
static OPT_PARENTS: &str = "parents";
|
||||||
static OPT_VERBOSE: &str = "verbose";
|
static OPT_VERBOSE: &str = "verbose";
|
||||||
|
|
16
src/uu/sleep/sleep.md
Normal file
16
src/uu/sleep/sleep.md
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
# sleep
|
||||||
|
|
||||||
|
```
|
||||||
|
sleep NUMBER[SUFFIX]...
|
||||||
|
sleep OPTION
|
||||||
|
```
|
||||||
|
|
||||||
|
Pause for NUMBER seconds.
|
||||||
|
|
||||||
|
## After Help
|
||||||
|
|
||||||
|
Pause for NUMBER seconds. SUFFIX may be 's' for seconds (the default),
|
||||||
|
'm' for minutes, 'h' for hours or 'd' for days. Unlike most implementations
|
||||||
|
that require NUMBER be an integer, here NUMBER may be an arbitrary floating
|
||||||
|
point number. Given two or more arguments, pause for the amount of time
|
||||||
|
specified by the sum of their values.
|
|
@ -10,20 +10,14 @@ use std::time::Duration;
|
||||||
|
|
||||||
use uucore::{
|
use uucore::{
|
||||||
error::{UResult, USimpleError, UUsageError},
|
error::{UResult, USimpleError, UUsageError},
|
||||||
format_usage, show,
|
format_usage, help_about, help_section, help_usage, show,
|
||||||
};
|
};
|
||||||
|
|
||||||
use clap::{crate_version, Arg, ArgAction, Command};
|
use clap::{crate_version, Arg, ArgAction, Command};
|
||||||
|
|
||||||
static ABOUT: &str = "Pause for NUMBER seconds.";
|
static ABOUT: &str = help_about!("sleep.md");
|
||||||
const USAGE: &str = "\
|
const USAGE: &str = help_usage!("sleep.md");
|
||||||
{} NUMBER[SUFFIX]...
|
static AFTER_HELP: &str = help_section!("after help", "sleep.md");
|
||||||
{} OPTION";
|
|
||||||
static LONG_HELP: &str = "Pause for NUMBER seconds. SUFFIX may be 's' for seconds (the default),
|
|
||||||
'm' for minutes, 'h' for hours or 'd' for days. Unlike most implementations
|
|
||||||
that require NUMBER be an integer, here NUMBER may be an arbitrary floating
|
|
||||||
point number. Given two or more arguments, pause for the amount of time
|
|
||||||
specified by the sum of their values.";
|
|
||||||
|
|
||||||
mod options {
|
mod options {
|
||||||
pub const NUMBER: &str = "NUMBER";
|
pub const NUMBER: &str = "NUMBER";
|
||||||
|
@ -54,7 +48,7 @@ pub fn uu_app() -> Command {
|
||||||
Command::new(uucore::util_name())
|
Command::new(uucore::util_name())
|
||||||
.version(crate_version!())
|
.version(crate_version!())
|
||||||
.about(ABOUT)
|
.about(ABOUT)
|
||||||
.after_help(LONG_HELP)
|
.after_help(AFTER_HELP)
|
||||||
.override_usage(format_usage(USAGE))
|
.override_usage(format_usage(USAGE))
|
||||||
.infer_long_args(true)
|
.infer_long_args(true)
|
||||||
.arg(
|
.arg(
|
||||||
|
|
|
@ -13,7 +13,9 @@ use uucore::fsext::{
|
||||||
pretty_filetype, pretty_fstype, pretty_time, read_fs_list, statfs, BirthTime, FsMeta,
|
pretty_filetype, pretty_fstype, pretty_time, read_fs_list, statfs, BirthTime, FsMeta,
|
||||||
};
|
};
|
||||||
use uucore::libc::mode_t;
|
use uucore::libc::mode_t;
|
||||||
use uucore::{entries, format_usage, help_section, help_usage, show_error, show_warning};
|
use uucore::{
|
||||||
|
entries, format_usage, help_about, help_section, help_usage, show_error, show_warning,
|
||||||
|
};
|
||||||
|
|
||||||
use clap::{crate_version, Arg, ArgAction, ArgMatches, Command};
|
use clap::{crate_version, Arg, ArgAction, ArgMatches, Command};
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
@ -24,7 +26,7 @@ use std::os::unix::fs::{FileTypeExt, MetadataExt};
|
||||||
use std::os::unix::prelude::OsStrExt;
|
use std::os::unix::prelude::OsStrExt;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
const ABOUT: &str = help_section!("about", "stat.md");
|
const ABOUT: &str = help_about!("stat.md");
|
||||||
const USAGE: &str = help_usage!("stat.md");
|
const USAGE: &str = help_usage!("stat.md");
|
||||||
const LONG_USAGE: &str = help_section!("long usage", "stat.md");
|
const LONG_USAGE: &str = help_section!("long usage", "stat.md");
|
||||||
|
|
||||||
|
|
|
@ -1,14 +1,11 @@
|
||||||
# stat
|
# stat
|
||||||
|
|
||||||
## About
|
|
||||||
|
|
||||||
Display file or file system status.
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
```
|
```
|
||||||
stat [OPTION]... FILE...
|
stat [OPTION]... FILE...
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Display file or file system status.
|
||||||
|
|
||||||
## Long Usage
|
## Long Usage
|
||||||
|
|
||||||
The valid format sequences for files (without `--file-system`):
|
The valid format sequences for files (without `--file-system`):
|
||||||
|
|
|
@ -19,12 +19,12 @@ use std::{
|
||||||
use uucore::display::Quotable;
|
use uucore::display::Quotable;
|
||||||
use uucore::error::UError;
|
use uucore::error::UError;
|
||||||
use uucore::error::UResult;
|
use uucore::error::UResult;
|
||||||
use uucore::{format_usage, show};
|
use uucore::{format_usage, help_about, help_usage, show};
|
||||||
|
|
||||||
use crate::error::TacError;
|
use crate::error::TacError;
|
||||||
|
|
||||||
static USAGE: &str = "{} [OPTION]... [FILE]...";
|
static USAGE: &str = help_usage!("tac.md");
|
||||||
static ABOUT: &str = "Write each file to standard output, last line first.";
|
static ABOUT: &str = help_about!("tac.md");
|
||||||
|
|
||||||
mod options {
|
mod options {
|
||||||
pub static BEFORE: &str = "before";
|
pub static BEFORE: &str = "before";
|
||||||
|
|
7
src/uu/tac/tac.md
Normal file
7
src/uu/tac/tac.md
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
# tac
|
||||||
|
|
||||||
|
```
|
||||||
|
tac [OPTION]... [FILE]...
|
||||||
|
```
|
||||||
|
|
||||||
|
Write each file to standard output, last line first.
|
|
@ -20,18 +20,15 @@ clap = { workspace=true }
|
||||||
libc = { workspace=true }
|
libc = { workspace=true }
|
||||||
memchr = { workspace=true }
|
memchr = { workspace=true }
|
||||||
notify = { workspace=true }
|
notify = { workspace=true }
|
||||||
uucore = { workspace=true, features=["ringbuffer", "lines"] }
|
uucore = { workspace=true }
|
||||||
same-file = { workspace=true }
|
same-file = { workspace=true }
|
||||||
atty = { workspace=true }
|
is-terminal = { workspace=true }
|
||||||
fundu = { workspace=true }
|
fundu = { workspace=true }
|
||||||
|
|
||||||
[target.'cfg(windows)'.dependencies]
|
[target.'cfg(windows)'.dependencies]
|
||||||
windows-sys = { workspace=true, features = ["Win32_System_Threading", "Win32_Foundation"] }
|
windows-sys = { workspace=true, features = ["Win32_System_Threading", "Win32_Foundation"] }
|
||||||
winapi-util = { workspace=true }
|
winapi-util = { workspace=true }
|
||||||
|
|
||||||
[target.'cfg(unix)'.dependencies]
|
|
||||||
nix = { workspace=true, features = ["fs"] }
|
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "tail"
|
name = "tail"
|
||||||
path = "src/main.rs"
|
path = "src/main.rs"
|
||||||
|
|
|
@ -7,10 +7,10 @@
|
||||||
|
|
||||||
use crate::paths::Input;
|
use crate::paths::Input;
|
||||||
use crate::{parse, platform, Quotable};
|
use crate::{parse, platform, Quotable};
|
||||||
use atty::Stream;
|
|
||||||
use clap::crate_version;
|
use clap::crate_version;
|
||||||
use clap::{parser::ValueSource, Arg, ArgAction, ArgMatches, Command};
|
use clap::{parser::ValueSource, Arg, ArgAction, ArgMatches, Command};
|
||||||
use fundu::DurationParser;
|
use fundu::DurationParser;
|
||||||
|
use is_terminal::IsTerminal;
|
||||||
use same_file::Handle;
|
use same_file::Handle;
|
||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
use std::ffi::OsString;
|
use std::ffi::OsString;
|
||||||
|
@ -274,7 +274,7 @@ impl Settings {
|
||||||
.map_or(false, |meta| !meta.is_file())
|
.map_or(false, |meta| !meta.is_file())
|
||||||
});
|
});
|
||||||
|
|
||||||
if !blocking_stdin && atty::is(Stream::Stdin) {
|
if !blocking_stdin && std::io::stdin().is_terminal() {
|
||||||
show_warning!("following standard input indefinitely is ineffective");
|
show_warning!("following standard input indefinitely is ineffective");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,7 @@ path = "src/tty.rs"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
clap = { workspace=true }
|
clap = { workspace=true }
|
||||||
nix = { workspace=true, features=["term"] }
|
nix = { workspace=true, features=["term"] }
|
||||||
atty = { workspace=true }
|
is-terminal = { workspace=true }
|
||||||
uucore = { workspace=true, features=["fs"] }
|
uucore = { workspace=true, features=["fs"] }
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
// spell-checker:ignore (ToDO) ttyname filedesc
|
// spell-checker:ignore (ToDO) ttyname filedesc
|
||||||
|
|
||||||
use clap::{crate_version, Arg, ArgAction, Command};
|
use clap::{crate_version, Arg, ArgAction, Command};
|
||||||
|
use is_terminal::IsTerminal;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::os::unix::io::AsRawFd;
|
use std::os::unix::io::AsRawFd;
|
||||||
use uucore::error::{set_exit_code, UResult};
|
use uucore::error::{set_exit_code, UResult};
|
||||||
|
@ -30,7 +31,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||||
|
|
||||||
// If silent, we don't need the name, only whether or not stdin is a tty.
|
// If silent, we don't need the name, only whether or not stdin is a tty.
|
||||||
if silent {
|
if silent {
|
||||||
return if atty::is(atty::Stream::Stdin) {
|
return if std::io::stdin().is_terminal() {
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
Err(1.into())
|
Err(1.into())
|
||||||
|
|
|
@ -12,13 +12,13 @@ use std::io::{self, Result, Write};
|
||||||
|
|
||||||
use clap::{Arg, ArgAction, Command};
|
use clap::{Arg, ArgAction, Command};
|
||||||
use uucore::error::{UResult, USimpleError};
|
use uucore::error::{UResult, USimpleError};
|
||||||
use uucore::format_usage;
|
use uucore::{format_usage, help_about, help_usage};
|
||||||
|
|
||||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||||
mod splice;
|
mod splice;
|
||||||
|
|
||||||
const ABOUT: &str = "Repeatedly display a line with STRING (or 'y')";
|
const ABOUT: &str = help_about!("yes.md");
|
||||||
const USAGE: &str = "{} [STRING]...";
|
const USAGE: &str = help_usage!("yes.md");
|
||||||
|
|
||||||
// it's possible that using a smaller or larger buffer might provide better performance on some
|
// it's possible that using a smaller or larger buffer might provide better performance on some
|
||||||
// systems, but honestly this is good enough
|
// systems, but honestly this is good enough
|
||||||
|
|
7
src/uu/yes/yes.md
Normal file
7
src/uu/yes/yes.md
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
# yes
|
||||||
|
|
||||||
|
```
|
||||||
|
yes [STRING]...
|
||||||
|
```
|
||||||
|
|
||||||
|
Repeatedly display a line with STRING (or 'y')
|
|
@ -192,13 +192,9 @@ impl Utmpx {
|
||||||
}
|
}
|
||||||
/// A.K.A. ut.ut_tv
|
/// A.K.A. ut.ut_tv
|
||||||
pub fn login_time(&self) -> time::OffsetDateTime {
|
pub fn login_time(&self) -> time::OffsetDateTime {
|
||||||
#[cfg(all(not(target_os = "freebsd"), not(target_vendor = "apple")))]
|
#[allow(clippy::unnecessary_cast)]
|
||||||
let ts_nanos: i128 = (self.inner.ut_tv.tv_sec as i64 * 1_000_000_000_i64
|
let ts_nanos: i128 = (1_000_000_000_i64 * self.inner.ut_tv.tv_sec as i64
|
||||||
+ self.inner.ut_tv.tv_usec as i64 * 1_000_i64)
|
+ 1_000_i64 * self.inner.ut_tv.tv_usec as i64)
|
||||||
.into();
|
|
||||||
#[cfg(any(target_os = "freebsd", target_vendor = "apple"))]
|
|
||||||
let ts_nanos: i128 = (self.inner.ut_tv.tv_sec * 1_000_000_000_i64
|
|
||||||
+ self.inner.ut_tv.tv_usec as i64 * 1_000_i64)
|
|
||||||
.into();
|
.into();
|
||||||
let local_offset = time::OffsetDateTime::now_local().unwrap().offset();
|
let local_offset = time::OffsetDateTime::now_local().unwrap().offset();
|
||||||
time::OffsetDateTime::from_unix_timestamp_nanos(ts_nanos)
|
time::OffsetDateTime::from_unix_timestamp_nanos(ts_nanos)
|
||||||
|
|
|
@ -51,7 +51,10 @@ pub fn from_str(string: &str) -> Result<Duration, String> {
|
||||||
if len == 0 {
|
if len == 0 {
|
||||||
return Err("empty string".to_owned());
|
return Err("empty string".to_owned());
|
||||||
}
|
}
|
||||||
let slice = &string[..len - 1];
|
let slice = match string.get(..len - 1) {
|
||||||
|
Some(s) => s,
|
||||||
|
None => return Err(format!("invalid time interval {}", string.quote())),
|
||||||
|
};
|
||||||
let (numstr, times) = match string.chars().next_back().unwrap() {
|
let (numstr, times) = match string.chars().next_back().unwrap() {
|
||||||
's' => (slice, 1),
|
's' => (slice, 1),
|
||||||
'm' => (slice, 60),
|
'm' => (slice, 60),
|
||||||
|
@ -112,6 +115,11 @@ mod tests {
|
||||||
assert!(from_str("123X").is_err());
|
assert!(from_str("123X").is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_error_multi_bytes_characters() {
|
||||||
|
assert!(from_str("10€").is_err());
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_error_invalid_magnitude() {
|
fn test_error_invalid_magnitude() {
|
||||||
assert!(from_str("12abc3s").is_err());
|
assert!(from_str("12abc3s").is_err());
|
||||||
|
|
|
@ -7,6 +7,8 @@ use std::{fs::File, io::Read, path::PathBuf};
|
||||||
use proc_macro::{Literal, TokenStream, TokenTree};
|
use proc_macro::{Literal, TokenStream, TokenTree};
|
||||||
use quote::quote;
|
use quote::quote;
|
||||||
|
|
||||||
|
const MARKDOWN_CODE_FENCES: &str = "```";
|
||||||
|
|
||||||
//## rust proc-macro background info
|
//## rust proc-macro background info
|
||||||
//* ref: <https://dev.to/naufraghi/procedural-macro-in-rust-101-k3f> @@ <http://archive.is/Vbr5e>
|
//* ref: <https://dev.to/naufraghi/procedural-macro-in-rust-101-k3f> @@ <http://archive.is/Vbr5e>
|
||||||
//* ref: [path construction from LitStr](https://oschwald.github.io/maxminddb-rust/syn/struct.LitStr.html) @@ <http://archive.is/8YDua>
|
//* ref: [path construction from LitStr](https://oschwald.github.io/maxminddb-rust/syn/struct.LitStr.html) @@ <http://archive.is/8YDua>
|
||||||
|
@ -51,7 +53,19 @@ fn render_markdown(s: &str) -> String {
|
||||||
s.replace('`', "")
|
s.replace('`', "")
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the usage from the "Usage" section in the help file.
|
/// Get the about text from the help file.
|
||||||
|
///
|
||||||
|
/// The about text is assumed to be the text between the first markdown
|
||||||
|
/// code block and the next header, if any. It may span multiple lines.
|
||||||
|
#[proc_macro]
|
||||||
|
pub fn help_about(input: TokenStream) -> TokenStream {
|
||||||
|
let input: Vec<TokenTree> = input.into_iter().collect();
|
||||||
|
let filename = get_argument(&input, 0, "filename");
|
||||||
|
let text: String = parse_about(&read_help(&filename));
|
||||||
|
TokenTree::Literal(Literal::string(&text)).into()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the usage from the help file.
|
||||||
///
|
///
|
||||||
/// The usage is assumed to be surrounded by markdown code fences. It may span
|
/// The usage is assumed to be surrounded by markdown code fences. It may span
|
||||||
/// multiple lines. The first word of each line is assumed to be the name of
|
/// multiple lines. The first word of each line is assumed to be the name of
|
||||||
|
@ -61,7 +75,7 @@ fn render_markdown(s: &str) -> String {
|
||||||
pub fn help_usage(input: TokenStream) -> TokenStream {
|
pub fn help_usage(input: TokenStream) -> TokenStream {
|
||||||
let input: Vec<TokenTree> = input.into_iter().collect();
|
let input: Vec<TokenTree> = input.into_iter().collect();
|
||||||
let filename = get_argument(&input, 0, "filename");
|
let filename = get_argument(&input, 0, "filename");
|
||||||
let text: String = parse_usage(&parse_help("usage", &filename));
|
let text: String = parse_usage(&read_help(&filename));
|
||||||
TokenTree::Literal(Literal::string(&text)).into()
|
TokenTree::Literal(Literal::string(&text)).into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -94,7 +108,7 @@ pub fn help_section(input: TokenStream) -> TokenStream {
|
||||||
let input: Vec<TokenTree> = input.into_iter().collect();
|
let input: Vec<TokenTree> = input.into_iter().collect();
|
||||||
let section = get_argument(&input, 0, "section");
|
let section = get_argument(&input, 0, "section");
|
||||||
let filename = get_argument(&input, 1, "filename");
|
let filename = get_argument(&input, 1, "filename");
|
||||||
let text = parse_help(§ion, &filename);
|
let text = parse_help_section(§ion, &read_help(&filename));
|
||||||
let rendered = render_markdown(&text);
|
let rendered = render_markdown(&text);
|
||||||
TokenTree::Literal(Literal::string(&rendered)).into()
|
TokenTree::Literal(Literal::string(&rendered)).into()
|
||||||
}
|
}
|
||||||
|
@ -121,13 +135,11 @@ fn get_argument(input: &[TokenTree], index: usize, name: &str) -> String {
|
||||||
.to_string()
|
.to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Read the help file and extract a section
|
/// Read the help file
|
||||||
fn parse_help(section: &str, filename: &str) -> String {
|
fn read_help(filename: &str) -> String {
|
||||||
let section = section.to_lowercase();
|
|
||||||
let section = section.trim_matches('"');
|
|
||||||
let mut content = String::new();
|
let mut content = String::new();
|
||||||
let mut path = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap());
|
|
||||||
|
|
||||||
|
let mut path = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap());
|
||||||
path.push(filename);
|
path.push(filename);
|
||||||
|
|
||||||
File::open(path)
|
File::open(path)
|
||||||
|
@ -135,7 +147,7 @@ fn parse_help(section: &str, filename: &str) -> String {
|
||||||
.read_to_string(&mut content)
|
.read_to_string(&mut content)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
parse_help_section(section, &content)
|
content
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a single section from content
|
/// Get a single section from content
|
||||||
|
@ -147,6 +159,8 @@ fn parse_help_section(section: &str, content: &str) -> String {
|
||||||
.map_or(false, |l| l.trim().to_lowercase() == section)
|
.map_or(false, |l| l.trim().to_lowercase() == section)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let section = §ion.to_lowercase();
|
||||||
|
|
||||||
// We cannot distinguish between an empty or non-existing section below,
|
// We cannot distinguish between an empty or non-existing section below,
|
||||||
// so we do a quick test to check whether the section exists to provide
|
// so we do a quick test to check whether the section exists to provide
|
||||||
// a nice error message.
|
// a nice error message.
|
||||||
|
@ -167,17 +181,17 @@ fn parse_help_section(section: &str, content: &str) -> String {
|
||||||
.to_string()
|
.to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parses a markdown code block into a usage string
|
/// Parses the first markdown code block into a usage string
|
||||||
///
|
///
|
||||||
/// The code fences are removed and the name of the util is replaced
|
/// The code fences are removed and the name of the util is replaced
|
||||||
/// with `{}` so that it can be replaced with the appropriate name
|
/// with `{}` so that it can be replaced with the appropriate name
|
||||||
/// at runtime.
|
/// at runtime.
|
||||||
fn parse_usage(content: &str) -> String {
|
fn parse_usage(content: &str) -> String {
|
||||||
content
|
content
|
||||||
.strip_suffix("```")
|
|
||||||
.unwrap()
|
|
||||||
.lines()
|
.lines()
|
||||||
.skip(1) // Skip the "```" of markdown syntax
|
.skip_while(|l| !l.starts_with(MARKDOWN_CODE_FENCES))
|
||||||
|
.skip(1)
|
||||||
|
.take_while(|l| !l.starts_with(MARKDOWN_CODE_FENCES))
|
||||||
.map(|l| {
|
.map(|l| {
|
||||||
// Replace the util name (assumed to be the first word) with "{}"
|
// Replace the util name (assumed to be the first word) with "{}"
|
||||||
// to be replaced with the runtime value later.
|
// to be replaced with the runtime value later.
|
||||||
|
@ -187,12 +201,31 @@ fn parse_usage(content: &str) -> String {
|
||||||
"{}\n".to_string()
|
"{}\n".to_string()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect()
|
.collect::<Vec<_>>()
|
||||||
|
.join("")
|
||||||
|
.trim()
|
||||||
|
.to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parses the text between the first markdown code block and the next header, if any,
|
||||||
|
/// into an about string.
|
||||||
|
fn parse_about(content: &str) -> String {
|
||||||
|
content
|
||||||
|
.lines()
|
||||||
|
.skip_while(|l| !l.starts_with(MARKDOWN_CODE_FENCES))
|
||||||
|
.skip(1)
|
||||||
|
.skip_while(|l| !l.starts_with(MARKDOWN_CODE_FENCES))
|
||||||
|
.skip(1)
|
||||||
|
.take_while(|l| !l.starts_with('#'))
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join("\n")
|
||||||
|
.trim()
|
||||||
|
.to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::{parse_help_section, parse_usage};
|
use super::{parse_about, parse_help_section, parse_usage};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn section_parsing() {
|
fn section_parsing() {
|
||||||
|
@ -209,6 +242,10 @@ mod tests {
|
||||||
parse_help_section("some section", input),
|
parse_help_section("some section", input),
|
||||||
"This is some section"
|
"This is some section"
|
||||||
);
|
);
|
||||||
|
assert_eq!(
|
||||||
|
parse_help_section("SOME SECTION", input),
|
||||||
|
"This is some section"
|
||||||
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parse_help_section("another section", input),
|
parse_help_section("another section", input),
|
||||||
"This is the other section\nwith multiple lines"
|
"This is the other section\nwith multiple lines"
|
||||||
|
@ -233,7 +270,6 @@ mod tests {
|
||||||
fn usage_parsing() {
|
fn usage_parsing() {
|
||||||
let input = "\
|
let input = "\
|
||||||
# ls\n\
|
# ls\n\
|
||||||
## Usage\n\
|
|
||||||
```\n\
|
```\n\
|
||||||
ls -l\n\
|
ls -l\n\
|
||||||
```\n\
|
```\n\
|
||||||
|
@ -244,17 +280,55 @@ mod tests {
|
||||||
This is the other section\n\
|
This is the other section\n\
|
||||||
with multiple lines\n";
|
with multiple lines\n";
|
||||||
|
|
||||||
assert_eq!(parse_usage(&parse_help_section("usage", input)), "{} -l",);
|
assert_eq!(parse_usage(input), "{} -l");
|
||||||
|
}
|
||||||
|
|
||||||
assert_eq!(
|
#[test]
|
||||||
parse_usage(
|
fn multi_line_usage_parsing() {
|
||||||
"\
|
let input = "\
|
||||||
```\n\
|
# ls\n\
|
||||||
util [some] [options]\n\
|
```\n\
|
||||||
```\
|
ls -a\n\
|
||||||
"
|
ls -b\n\
|
||||||
),
|
ls -c\n\
|
||||||
"{} [some] [options]"
|
```\n\
|
||||||
);
|
## some section\n\
|
||||||
|
This is some section\n";
|
||||||
|
|
||||||
|
assert_eq!(parse_usage(input), "{} -a\n{} -b\n{} -c");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn about_parsing() {
|
||||||
|
let input = "\
|
||||||
|
# ls\n\
|
||||||
|
```\n\
|
||||||
|
ls -l\n\
|
||||||
|
```\n\
|
||||||
|
\n\
|
||||||
|
This is the about section\n\
|
||||||
|
\n\
|
||||||
|
## some section\n\
|
||||||
|
This is some section\n";
|
||||||
|
|
||||||
|
assert_eq!(parse_about(input), "This is the about section");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn multi_line_about_parsing() {
|
||||||
|
let input = "\
|
||||||
|
# ls\n\
|
||||||
|
```\n\
|
||||||
|
ls -l\n\
|
||||||
|
```\n\
|
||||||
|
\n\
|
||||||
|
about a\n\
|
||||||
|
\n\
|
||||||
|
about b\n\
|
||||||
|
\n\
|
||||||
|
## some section\n\
|
||||||
|
This is some section\n";
|
||||||
|
|
||||||
|
assert_eq!(parse_about(input), "about a\n\nabout b");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -103,7 +103,7 @@ fn test_closes_file_descriptors() {
|
||||||
"alpha.txt",
|
"alpha.txt",
|
||||||
"alpha.txt",
|
"alpha.txt",
|
||||||
])
|
])
|
||||||
.with_limit(Resource::NOFILE, 9, 9)
|
.limit(Resource::NOFILE, 9, 9)
|
||||||
.succeeds();
|
.succeeds();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -48,15 +48,12 @@ fn run_single_test(test: &TestCase, at: &AtPath, mut ucmd: UCommand) {
|
||||||
let r = ucmd.run();
|
let r = ucmd.run();
|
||||||
if !r.succeeded() {
|
if !r.succeeded() {
|
||||||
println!("{}", r.stderr_str());
|
println!("{}", r.stderr_str());
|
||||||
panic!("{:?}: failed", ucmd.raw);
|
panic!("{ucmd}: failed");
|
||||||
}
|
}
|
||||||
|
|
||||||
let perms = at.metadata(TEST_FILE).permissions().mode();
|
let perms = at.metadata(TEST_FILE).permissions().mode();
|
||||||
if perms != test.after {
|
if perms != test.after {
|
||||||
panic!(
|
panic!("{}: expected: {:o} got: {:o}", ucmd, test.after, perms);
|
||||||
"{:?}: expected: {:o} got: {:o}",
|
|
||||||
ucmd.raw, test.after, perms
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -414,7 +411,7 @@ fn test_chmod_symlink_non_existing_file() {
|
||||||
let non_existing = "test_chmod_symlink_non_existing_file";
|
let non_existing = "test_chmod_symlink_non_existing_file";
|
||||||
let test_symlink = "test_chmod_symlink_non_existing_file_symlink";
|
let test_symlink = "test_chmod_symlink_non_existing_file_symlink";
|
||||||
let expected_stdout = &format!(
|
let expected_stdout = &format!(
|
||||||
"failed to change mode of '{test_symlink}' from 0000 (---------) to 0000 (---------)"
|
"failed to change mode of '{test_symlink}' from 0000 (---------) to 1500 (r-x-----T)"
|
||||||
);
|
);
|
||||||
let expected_stderr = &format!("cannot operate on dangling symlink '{test_symlink}'");
|
let expected_stderr = &format!("cannot operate on dangling symlink '{test_symlink}'");
|
||||||
|
|
||||||
|
@ -442,6 +439,17 @@ fn test_chmod_symlink_non_existing_file() {
|
||||||
.code_is(1)
|
.code_is(1)
|
||||||
.no_stderr()
|
.no_stderr()
|
||||||
.stdout_contains(expected_stdout);
|
.stdout_contains(expected_stdout);
|
||||||
|
|
||||||
|
// this should only include the dangling symlink message
|
||||||
|
// NOT the failure to change mode
|
||||||
|
scene
|
||||||
|
.ucmd()
|
||||||
|
.arg("755")
|
||||||
|
.arg(test_symlink)
|
||||||
|
.run()
|
||||||
|
.code_is(1)
|
||||||
|
.no_stdout()
|
||||||
|
.stderr_contains(expected_stderr);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -574,3 +582,71 @@ fn test_mode_after_dash_dash() {
|
||||||
ucmd,
|
ucmd,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_chmod_file_after_non_existing_file() {
|
||||||
|
let scene = TestScenario::new(util_name!());
|
||||||
|
let at = &scene.fixtures;
|
||||||
|
at.touch(TEST_FILE);
|
||||||
|
at.touch("file2");
|
||||||
|
set_permissions(at.plus(TEST_FILE), Permissions::from_mode(0o664)).unwrap();
|
||||||
|
set_permissions(at.plus("file2"), Permissions::from_mode(0o664)).unwrap();
|
||||||
|
scene
|
||||||
|
.ucmd()
|
||||||
|
.arg("u+x")
|
||||||
|
.arg("does-not-exist")
|
||||||
|
.arg(TEST_FILE)
|
||||||
|
.fails()
|
||||||
|
.stderr_contains("chmod: cannot access 'does-not-exist': No such file or directory")
|
||||||
|
.code_is(1);
|
||||||
|
|
||||||
|
assert_eq!(at.metadata(TEST_FILE).permissions().mode(), 0o100764);
|
||||||
|
|
||||||
|
scene
|
||||||
|
.ucmd()
|
||||||
|
.arg("u+x")
|
||||||
|
.arg("--q")
|
||||||
|
.arg("does-not-exist")
|
||||||
|
.arg("file2")
|
||||||
|
.fails()
|
||||||
|
.no_stderr()
|
||||||
|
.code_is(1);
|
||||||
|
assert_eq!(at.metadata("file2").permissions().mode(), 0o100764);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_chmod_file_symlink_after_non_existing_file() {
|
||||||
|
let scene = TestScenario::new(util_name!());
|
||||||
|
let at = &scene.fixtures;
|
||||||
|
let existing = "file";
|
||||||
|
let test_existing_symlink = "file_symlink";
|
||||||
|
|
||||||
|
let non_existing = "test_chmod_symlink_non_existing_file";
|
||||||
|
let test_dangling_symlink = "test_chmod_symlink_non_existing_file_symlink";
|
||||||
|
let expected_stdout = &format!(
|
||||||
|
"failed to change mode of '{test_dangling_symlink}' from 0000 (---------) to 1500 (r-x-----T)"
|
||||||
|
);
|
||||||
|
let expected_stderr = &format!("cannot operate on dangling symlink '{test_dangling_symlink}'");
|
||||||
|
|
||||||
|
at.touch(existing);
|
||||||
|
set_permissions(at.plus(existing), Permissions::from_mode(0o664)).unwrap();
|
||||||
|
at.symlink_file(non_existing, test_dangling_symlink);
|
||||||
|
at.symlink_file(existing, test_existing_symlink);
|
||||||
|
|
||||||
|
// this cannot succeed since the symbolic link dangles
|
||||||
|
// but the metadata for the existing target should change
|
||||||
|
scene
|
||||||
|
.ucmd()
|
||||||
|
.arg("u+x")
|
||||||
|
.arg("-v")
|
||||||
|
.arg(test_dangling_symlink)
|
||||||
|
.arg(test_existing_symlink)
|
||||||
|
.fails()
|
||||||
|
.code_is(1)
|
||||||
|
.stdout_contains(expected_stdout)
|
||||||
|
.stderr_contains(expected_stderr);
|
||||||
|
assert_eq!(
|
||||||
|
at.metadata(test_existing_symlink).permissions().mode(),
|
||||||
|
0o100764
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
@ -396,7 +396,7 @@ fn test_chown_only_user_id() {
|
||||||
let scene = TestScenario::new(util_name!());
|
let scene = TestScenario::new(util_name!());
|
||||||
let at = &scene.fixtures;
|
let at = &scene.fixtures;
|
||||||
|
|
||||||
let result = scene.cmd_keepenv("id").arg("-u").run();
|
let result = scene.cmd("id").keep_env().arg("-u").run();
|
||||||
if skipping_test_is_okay(&result, "id: cannot find name for group ID") {
|
if skipping_test_is_okay(&result, "id: cannot find name for group ID") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -430,7 +430,7 @@ fn test_chown_fail_id() {
|
||||||
let scene = TestScenario::new(util_name!());
|
let scene = TestScenario::new(util_name!());
|
||||||
let at = &scene.fixtures;
|
let at = &scene.fixtures;
|
||||||
|
|
||||||
let result = scene.cmd_keepenv("id").arg("-u").run();
|
let result = scene.cmd("id").keep_env().arg("-u").run();
|
||||||
if skipping_test_is_okay(&result, "id: cannot find name for group ID") {
|
if skipping_test_is_okay(&result, "id: cannot find name for group ID") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -487,7 +487,7 @@ fn test_chown_only_group_id() {
|
||||||
let scene = TestScenario::new(util_name!());
|
let scene = TestScenario::new(util_name!());
|
||||||
let at = &scene.fixtures;
|
let at = &scene.fixtures;
|
||||||
|
|
||||||
let result = scene.cmd_keepenv("id").arg("-g").run();
|
let result = scene.cmd("id").keep_env().arg("-g").run();
|
||||||
if skipping_test_is_okay(&result, "id: cannot find name for group ID") {
|
if skipping_test_is_okay(&result, "id: cannot find name for group ID") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -551,14 +551,14 @@ fn test_chown_owner_group_id() {
|
||||||
let scene = TestScenario::new(util_name!());
|
let scene = TestScenario::new(util_name!());
|
||||||
let at = &scene.fixtures;
|
let at = &scene.fixtures;
|
||||||
|
|
||||||
let result = scene.cmd_keepenv("id").arg("-u").run();
|
let result = scene.cmd("id").keep_env().arg("-u").run();
|
||||||
if skipping_test_is_okay(&result, "id: cannot find name for group ID") {
|
if skipping_test_is_okay(&result, "id: cannot find name for group ID") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let user_id = String::from(result.stdout_str().trim());
|
let user_id = String::from(result.stdout_str().trim());
|
||||||
assert!(!user_id.is_empty());
|
assert!(!user_id.is_empty());
|
||||||
|
|
||||||
let result = scene.cmd_keepenv("id").arg("-g").run();
|
let result = scene.cmd("id").keep_env().arg("-g").run();
|
||||||
if skipping_test_is_okay(&result, "id: cannot find name for group ID") {
|
if skipping_test_is_okay(&result, "id: cannot find name for group ID") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -612,14 +612,14 @@ fn test_chown_owner_group_mix() {
|
||||||
let scene = TestScenario::new(util_name!());
|
let scene = TestScenario::new(util_name!());
|
||||||
let at = &scene.fixtures;
|
let at = &scene.fixtures;
|
||||||
|
|
||||||
let result = scene.cmd_keepenv("id").arg("-u").run();
|
let result = scene.cmd("id").keep_env().arg("-u").run();
|
||||||
if skipping_test_is_okay(&result, "id: cannot find name for group ID") {
|
if skipping_test_is_okay(&result, "id: cannot find name for group ID") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let user_id = String::from(result.stdout_str().trim());
|
let user_id = String::from(result.stdout_str().trim());
|
||||||
assert!(!user_id.is_empty());
|
assert!(!user_id.is_empty());
|
||||||
|
|
||||||
let result = scene.cmd_keepenv("id").arg("-gn").run();
|
let result = scene.cmd("id").keep_env().arg("-gn").run();
|
||||||
if skipping_test_is_okay(&result, "id: cannot find name for group ID") {
|
if skipping_test_is_okay(&result, "id: cannot find name for group ID") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1545,7 +1545,7 @@ fn test_closes_file_descriptors() {
|
||||||
.arg("--reflink=auto")
|
.arg("--reflink=auto")
|
||||||
.arg("dir_with_10_files/")
|
.arg("dir_with_10_files/")
|
||||||
.arg("dir_with_10_files_new/")
|
.arg("dir_with_10_files_new/")
|
||||||
.with_limit(Resource::NOFILE, limit_fd, limit_fd)
|
.limit(Resource::NOFILE, limit_fd, limit_fd)
|
||||||
.succeeds();
|
.succeeds();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1692,7 +1692,8 @@ fn test_cp_reflink_always_override() {
|
||||||
.succeeds();
|
.succeeds();
|
||||||
|
|
||||||
if !scene
|
if !scene
|
||||||
.cmd_keepenv("env")
|
.cmd("env")
|
||||||
|
.keep_env()
|
||||||
.args(&["mkfs.btrfs", "--rootdir", ROOTDIR, DISK])
|
.args(&["mkfs.btrfs", "--rootdir", ROOTDIR, DISK])
|
||||||
.run()
|
.run()
|
||||||
.succeeded()
|
.succeeded()
|
||||||
|
@ -1704,7 +1705,8 @@ fn test_cp_reflink_always_override() {
|
||||||
scene.fixtures.mkdir(MOUNTPOINT);
|
scene.fixtures.mkdir(MOUNTPOINT);
|
||||||
|
|
||||||
let mount = scene
|
let mount = scene
|
||||||
.cmd_keepenv("sudo")
|
.cmd("sudo")
|
||||||
|
.keep_env()
|
||||||
.args(&["-E", "--non-interactive", "mount", DISK, MOUNTPOINT])
|
.args(&["-E", "--non-interactive", "mount", DISK, MOUNTPOINT])
|
||||||
.run();
|
.run();
|
||||||
|
|
||||||
|
@ -1730,7 +1732,8 @@ fn test_cp_reflink_always_override() {
|
||||||
.succeeds();
|
.succeeds();
|
||||||
|
|
||||||
scene
|
scene
|
||||||
.cmd_keepenv("sudo")
|
.cmd("sudo")
|
||||||
|
.keep_env()
|
||||||
.args(&["-E", "--non-interactive", "umount", MOUNTPOINT])
|
.args(&["-E", "--non-interactive", "umount", MOUNTPOINT])
|
||||||
.succeeds();
|
.succeeds();
|
||||||
}
|
}
|
||||||
|
@ -2524,9 +2527,9 @@ fn test_src_base_dot() {
|
||||||
let at = ts.fixtures.clone();
|
let at = ts.fixtures.clone();
|
||||||
at.mkdir("x");
|
at.mkdir("x");
|
||||||
at.mkdir("y");
|
at.mkdir("y");
|
||||||
let mut ucmd = UCommand::new(ts.bin_path, &Some(ts.util_name), at.plus("y"), true);
|
ts.ucmd()
|
||||||
|
.current_dir(at.plus("y"))
|
||||||
ucmd.args(&["--verbose", "-r", "../x/.", "."])
|
.args(&["--verbose", "-r", "../x/.", "."])
|
||||||
.succeeds()
|
.succeeds()
|
||||||
.no_stderr()
|
.no_stderr()
|
||||||
.no_stdout();
|
.no_stdout();
|
||||||
|
|
|
@ -5,6 +5,8 @@ use crate::common::util::*;
|
||||||
use std::fs::{File, OpenOptions};
|
use std::fs::{File, OpenOptions};
|
||||||
use std::io::{BufReader, Read, Write};
|
use std::io::{BufReader, Read, Write};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
#[cfg(all(not(windows), not(target_os = "macos")))]
|
||||||
|
use std::process::{Command, Stdio};
|
||||||
#[cfg(not(windows))]
|
#[cfg(not(windows))]
|
||||||
use std::thread::sleep;
|
use std::thread::sleep;
|
||||||
#[cfg(not(windows))]
|
#[cfg(not(windows))]
|
||||||
|
@ -1442,3 +1444,73 @@ fn test_sparse() {
|
||||||
// number of blocks stored on disk may be zero.
|
// number of blocks stored on disk may be zero.
|
||||||
assert_eq!(at.metadata("infile").len(), at.metadata("outfile").len());
|
assert_eq!(at.metadata("infile").len(), at.metadata("outfile").len());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO These FIFO tests should work on macos, but some issue is
|
||||||
|
// causing our implementation of dd to wait indefinitely when it
|
||||||
|
// shouldn't.
|
||||||
|
|
||||||
|
/// Test that a seek on an output FIFO results in a read.
|
||||||
|
#[test]
|
||||||
|
#[cfg(all(unix, not(target_os = "macos"), not(target_os = "freebsd")))]
|
||||||
|
fn test_seek_output_fifo() {
|
||||||
|
let ts = TestScenario::new(util_name!());
|
||||||
|
let at = &ts.fixtures;
|
||||||
|
at.mkfifo("fifo");
|
||||||
|
|
||||||
|
// TODO When `dd` is a bit more advanced, we could use the uutils
|
||||||
|
// version of dd here as well.
|
||||||
|
let child = Command::new("dd")
|
||||||
|
.current_dir(&at.subdir)
|
||||||
|
.args([
|
||||||
|
"count=1",
|
||||||
|
"if=/dev/zero",
|
||||||
|
&format!("of={}", at.plus_as_string("fifo")),
|
||||||
|
"status=noxfer",
|
||||||
|
])
|
||||||
|
.stderr(Stdio::piped())
|
||||||
|
.spawn()
|
||||||
|
.expect("failed to execute child process");
|
||||||
|
|
||||||
|
ts.ucmd()
|
||||||
|
.args(&["count=0", "seek=1", "of=fifo", "status=noxfer"])
|
||||||
|
.succeeds()
|
||||||
|
.stderr_only("0+0 records in\n0+0 records out\n");
|
||||||
|
|
||||||
|
let output = child.wait_with_output().unwrap();
|
||||||
|
assert!(output.status.success());
|
||||||
|
assert!(output.stdout.is_empty());
|
||||||
|
assert_eq!(&output.stderr, b"1+0 records in\n1+0 records out\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Test that a skip on an input FIFO results in a read.
|
||||||
|
#[test]
|
||||||
|
#[cfg(all(unix, not(target_os = "macos"), not(target_os = "freebsd")))]
|
||||||
|
fn test_skip_input_fifo() {
|
||||||
|
let ts = TestScenario::new(util_name!());
|
||||||
|
let at = &ts.fixtures;
|
||||||
|
at.mkfifo("fifo");
|
||||||
|
|
||||||
|
// TODO When `dd` is a bit more advanced, we could use the uutils
|
||||||
|
// version of dd here as well.
|
||||||
|
let child = Command::new("dd")
|
||||||
|
.current_dir(&at.subdir)
|
||||||
|
.args([
|
||||||
|
"count=1",
|
||||||
|
"if=/dev/zero",
|
||||||
|
&format!("of={}", at.plus_as_string("fifo")),
|
||||||
|
"status=noxfer",
|
||||||
|
])
|
||||||
|
.stderr(Stdio::piped())
|
||||||
|
.spawn()
|
||||||
|
.expect("failed to execute child process");
|
||||||
|
|
||||||
|
ts.ucmd()
|
||||||
|
.args(&["count=0", "skip=1", "if=fifo", "status=noxfer"])
|
||||||
|
.succeeds()
|
||||||
|
.stderr_only("0+0 records in\n0+0 records out\n");
|
||||||
|
|
||||||
|
let output = child.wait_with_output().unwrap();
|
||||||
|
assert!(output.status.success());
|
||||||
|
assert!(output.stdout.is_empty());
|
||||||
|
assert_eq!(&output.stderr, b"1+0 records in\n1+0 records out\n");
|
||||||
|
}
|
||||||
|
|
|
@ -156,7 +156,8 @@ fn test_unset_variable() {
|
||||||
// This test depends on the HOME variable being pre-defined by the
|
// This test depends on the HOME variable being pre-defined by the
|
||||||
// default shell
|
// default shell
|
||||||
let out = TestScenario::new(util_name!())
|
let out = TestScenario::new(util_name!())
|
||||||
.ucmd_keepenv()
|
.ucmd()
|
||||||
|
.keep_env()
|
||||||
.arg("-u")
|
.arg("-u")
|
||||||
.arg("HOME")
|
.arg("HOME")
|
||||||
.succeeds()
|
.succeeds()
|
||||||
|
|
|
@ -124,6 +124,37 @@ fn test_install_ancestors_mode_directories() {
|
||||||
assert_eq!(0o40_200_u32, at.metadata(target_dir).permissions().mode());
|
assert_eq!(0o40_200_u32, at.metadata(target_dir).permissions().mode());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_install_ancestors_mode_directories_with_file() {
|
||||||
|
let (at, mut ucmd) = at_and_ucmd!();
|
||||||
|
let ancestor1 = "ancestor1";
|
||||||
|
let ancestor2 = "ancestor1/ancestor2";
|
||||||
|
let target_file = "ancestor1/ancestor2/target_file";
|
||||||
|
let directories_arg = "-D";
|
||||||
|
let mode_arg = "--mode=200";
|
||||||
|
let file = "file";
|
||||||
|
let probe = "probe";
|
||||||
|
|
||||||
|
at.mkdir(probe);
|
||||||
|
let default_perms = at.metadata(probe).permissions().mode();
|
||||||
|
|
||||||
|
at.touch(file);
|
||||||
|
|
||||||
|
ucmd.args(&[mode_arg, directories_arg, file, target_file])
|
||||||
|
.succeeds()
|
||||||
|
.no_stderr();
|
||||||
|
|
||||||
|
assert!(at.dir_exists(ancestor1));
|
||||||
|
assert!(at.dir_exists(ancestor2));
|
||||||
|
assert!(at.file_exists(target_file));
|
||||||
|
|
||||||
|
assert_eq!(default_perms, at.metadata(ancestor1).permissions().mode());
|
||||||
|
assert_eq!(default_perms, at.metadata(ancestor2).permissions().mode());
|
||||||
|
|
||||||
|
// Expected mode only on the target_file.
|
||||||
|
assert_eq!(0o100_200_u32, at.metadata(target_file).permissions().mode());
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_install_parent_directories() {
|
fn test_install_parent_directories() {
|
||||||
let (at, mut ucmd) = at_and_ucmd!();
|
let (at, mut ucmd) = at_and_ucmd!();
|
||||||
|
@ -1369,6 +1400,100 @@ fn test_install_dir_req_verbose() {
|
||||||
.stdout_contains("install: creating directory 'sub5/a'\ninstall: creating directory 'sub5/a/b'\ninstall: creating directory 'sub5/a/b/c'\n'source_file1' -> 'sub5/a/b/c/file'");
|
.stdout_contains("install: creating directory 'sub5/a'\ninstall: creating directory 'sub5/a/b'\ninstall: creating directory 'sub5/a/b/c'\n'source_file1' -> 'sub5/a/b/c/file'");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_install_chown_file_invalid() {
|
||||||
|
let scene = TestScenario::new(util_name!());
|
||||||
|
let at = &scene.fixtures;
|
||||||
|
|
||||||
|
let file_1 = "source_file1";
|
||||||
|
at.touch(file_1);
|
||||||
|
|
||||||
|
scene
|
||||||
|
.ucmd()
|
||||||
|
.arg("-o")
|
||||||
|
.arg("test_invalid_user")
|
||||||
|
.arg(file_1)
|
||||||
|
.arg("target_file1")
|
||||||
|
.fails()
|
||||||
|
.stderr_contains("install: invalid user: 'test_invalid_user'");
|
||||||
|
|
||||||
|
scene
|
||||||
|
.ucmd()
|
||||||
|
.arg("-g")
|
||||||
|
.arg("test_invalid_group")
|
||||||
|
.arg(file_1)
|
||||||
|
.arg("target_file1")
|
||||||
|
.fails()
|
||||||
|
.stderr_contains("install: invalid group: 'test_invalid_group'");
|
||||||
|
|
||||||
|
scene
|
||||||
|
.ucmd()
|
||||||
|
.arg("-o")
|
||||||
|
.arg("test_invalid_user")
|
||||||
|
.arg("-g")
|
||||||
|
.arg("test_invalid_group")
|
||||||
|
.arg(file_1)
|
||||||
|
.arg("target_file1")
|
||||||
|
.fails()
|
||||||
|
.stderr_contains("install: invalid user: 'test_invalid_user'");
|
||||||
|
|
||||||
|
scene
|
||||||
|
.ucmd()
|
||||||
|
.arg("-g")
|
||||||
|
.arg("test_invalid_group")
|
||||||
|
.arg("-o")
|
||||||
|
.arg("test_invalid_user")
|
||||||
|
.arg(file_1)
|
||||||
|
.arg("target_file1")
|
||||||
|
.fails()
|
||||||
|
.stderr_contains("install: invalid user: 'test_invalid_user'");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_install_chown_directory_invalid() {
|
||||||
|
let scene = TestScenario::new(util_name!());
|
||||||
|
|
||||||
|
scene
|
||||||
|
.ucmd()
|
||||||
|
.arg("-o")
|
||||||
|
.arg("test_invalid_user")
|
||||||
|
.arg("-d")
|
||||||
|
.arg("dir1/dir2")
|
||||||
|
.fails()
|
||||||
|
.stderr_contains("install: invalid user: 'test_invalid_user'");
|
||||||
|
|
||||||
|
scene
|
||||||
|
.ucmd()
|
||||||
|
.arg("-g")
|
||||||
|
.arg("test_invalid_group")
|
||||||
|
.arg("-d")
|
||||||
|
.arg("dir1/dir2")
|
||||||
|
.fails()
|
||||||
|
.stderr_contains("install: invalid group: 'test_invalid_group'");
|
||||||
|
|
||||||
|
scene
|
||||||
|
.ucmd()
|
||||||
|
.arg("-o")
|
||||||
|
.arg("test_invalid_user")
|
||||||
|
.arg("-g")
|
||||||
|
.arg("test_invalid_group")
|
||||||
|
.arg("-d")
|
||||||
|
.arg("dir1/dir2")
|
||||||
|
.fails()
|
||||||
|
.stderr_contains("install: invalid user: 'test_invalid_user'");
|
||||||
|
|
||||||
|
scene
|
||||||
|
.ucmd()
|
||||||
|
.arg("-g")
|
||||||
|
.arg("test_invalid_group")
|
||||||
|
.arg("-o")
|
||||||
|
.arg("test_invalid_user")
|
||||||
|
.arg("-d")
|
||||||
|
.arg("dir1/dir2")
|
||||||
|
.fails()
|
||||||
|
.stderr_contains("install: invalid user: 'test_invalid_user'");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_install_compare_option() {
|
fn test_install_compare_option() {
|
||||||
let scene = TestScenario::new(util_name!());
|
let scene = TestScenario::new(util_name!());
|
||||||
|
|
|
@ -426,7 +426,8 @@ fn test_mktemp_tmpdir_one_arg() {
|
||||||
let scene = TestScenario::new(util_name!());
|
let scene = TestScenario::new(util_name!());
|
||||||
|
|
||||||
let result = scene
|
let result = scene
|
||||||
.ucmd_keepenv()
|
.ucmd()
|
||||||
|
.keep_env()
|
||||||
.arg("--tmpdir")
|
.arg("--tmpdir")
|
||||||
.arg("apt-key-gpghome.XXXXXXXXXX")
|
.arg("apt-key-gpghome.XXXXXXXXXX")
|
||||||
.succeeds();
|
.succeeds();
|
||||||
|
@ -439,7 +440,8 @@ fn test_mktemp_directory_tmpdir() {
|
||||||
let scene = TestScenario::new(util_name!());
|
let scene = TestScenario::new(util_name!());
|
||||||
|
|
||||||
let result = scene
|
let result = scene
|
||||||
.ucmd_keepenv()
|
.ucmd()
|
||||||
|
.keep_env()
|
||||||
.arg("--directory")
|
.arg("--directory")
|
||||||
.arg("--tmpdir")
|
.arg("--tmpdir")
|
||||||
.arg("apt-key-gpghome.XXXXXXXXXX")
|
.arg("apt-key-gpghome.XXXXXXXXXX")
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
use crate::common::util::*;
|
use crate::common::util::*;
|
||||||
|
use is_terminal::IsTerminal;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_more_no_arg() {
|
fn test_more_no_arg() {
|
||||||
// Reading from stdin is now supported, so this must succeed
|
// Reading from stdin is now supported, so this must succeed
|
||||||
if atty::is(atty::Stream::Stdout) {
|
if std::io::stdout().is_terminal() {
|
||||||
new_ucmd!().succeeds();
|
new_ucmd!().succeeds();
|
||||||
} else {
|
} else {
|
||||||
}
|
}
|
||||||
|
@ -14,7 +15,7 @@ fn test_more_dir_arg() {
|
||||||
// Run the test only if there's a valid terminal, else do nothing
|
// Run the test only if there's a valid terminal, else do nothing
|
||||||
// Maybe we could capture the error, i.e. "Device not found" in that case
|
// Maybe we could capture the error, i.e. "Device not found" in that case
|
||||||
// but I am leaving this for later
|
// but I am leaving this for later
|
||||||
if atty::is(atty::Stream::Stdout) {
|
if std::io::stdout().is_terminal() {
|
||||||
new_ucmd!()
|
new_ucmd!()
|
||||||
.arg(".")
|
.arg(".")
|
||||||
.fails()
|
.fails()
|
||||||
|
|
|
@ -20,7 +20,8 @@ fn test_nproc_all_omp() {
|
||||||
assert!(nproc > 0);
|
assert!(nproc > 0);
|
||||||
|
|
||||||
let result = TestScenario::new(util_name!())
|
let result = TestScenario::new(util_name!())
|
||||||
.ucmd_keepenv()
|
.ucmd()
|
||||||
|
.keep_env()
|
||||||
.env("OMP_NUM_THREADS", "60")
|
.env("OMP_NUM_THREADS", "60")
|
||||||
.succeeds();
|
.succeeds();
|
||||||
|
|
||||||
|
@ -28,7 +29,8 @@ fn test_nproc_all_omp() {
|
||||||
assert_eq!(nproc_omp, 60);
|
assert_eq!(nproc_omp, 60);
|
||||||
|
|
||||||
let result = TestScenario::new(util_name!())
|
let result = TestScenario::new(util_name!())
|
||||||
.ucmd_keepenv()
|
.ucmd()
|
||||||
|
.keep_env()
|
||||||
.env("OMP_NUM_THREADS", "1") // Has no effect
|
.env("OMP_NUM_THREADS", "1") // Has no effect
|
||||||
.arg("--all")
|
.arg("--all")
|
||||||
.succeeds();
|
.succeeds();
|
||||||
|
@ -37,7 +39,8 @@ fn test_nproc_all_omp() {
|
||||||
|
|
||||||
// If the parsing fails, returns the number of CPU
|
// If the parsing fails, returns the number of CPU
|
||||||
let result = TestScenario::new(util_name!())
|
let result = TestScenario::new(util_name!())
|
||||||
.ucmd_keepenv()
|
.ucmd()
|
||||||
|
.keep_env()
|
||||||
.env("OMP_NUM_THREADS", "incorrectnumber") // returns the number CPU
|
.env("OMP_NUM_THREADS", "incorrectnumber") // returns the number CPU
|
||||||
.succeeds();
|
.succeeds();
|
||||||
let nproc_omp: u8 = result.stdout_str().trim().parse().unwrap();
|
let nproc_omp: u8 = result.stdout_str().trim().parse().unwrap();
|
||||||
|
@ -51,7 +54,8 @@ fn test_nproc_ignore() {
|
||||||
if nproc_total > 1 {
|
if nproc_total > 1 {
|
||||||
// Ignore all CPU but one
|
// Ignore all CPU but one
|
||||||
let result = TestScenario::new(util_name!())
|
let result = TestScenario::new(util_name!())
|
||||||
.ucmd_keepenv()
|
.ucmd()
|
||||||
|
.keep_env()
|
||||||
.arg("--ignore")
|
.arg("--ignore")
|
||||||
.arg((nproc_total - 1).to_string())
|
.arg((nproc_total - 1).to_string())
|
||||||
.succeeds();
|
.succeeds();
|
||||||
|
@ -59,7 +63,8 @@ fn test_nproc_ignore() {
|
||||||
assert_eq!(nproc, 1);
|
assert_eq!(nproc, 1);
|
||||||
// Ignore all CPU but one with a string
|
// Ignore all CPU but one with a string
|
||||||
let result = TestScenario::new(util_name!())
|
let result = TestScenario::new(util_name!())
|
||||||
.ucmd_keepenv()
|
.ucmd()
|
||||||
|
.keep_env()
|
||||||
.arg("--ignore= 1")
|
.arg("--ignore= 1")
|
||||||
.succeeds();
|
.succeeds();
|
||||||
let nproc: u8 = result.stdout_str().trim().parse().unwrap();
|
let nproc: u8 = result.stdout_str().trim().parse().unwrap();
|
||||||
|
@ -70,7 +75,8 @@ fn test_nproc_ignore() {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_nproc_ignore_all_omp() {
|
fn test_nproc_ignore_all_omp() {
|
||||||
let result = TestScenario::new(util_name!())
|
let result = TestScenario::new(util_name!())
|
||||||
.ucmd_keepenv()
|
.ucmd()
|
||||||
|
.keep_env()
|
||||||
.env("OMP_NUM_THREADS", "42")
|
.env("OMP_NUM_THREADS", "42")
|
||||||
.arg("--ignore=40")
|
.arg("--ignore=40")
|
||||||
.succeeds();
|
.succeeds();
|
||||||
|
@ -81,7 +87,8 @@ fn test_nproc_ignore_all_omp() {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_nproc_omp_limit() {
|
fn test_nproc_omp_limit() {
|
||||||
let result = TestScenario::new(util_name!())
|
let result = TestScenario::new(util_name!())
|
||||||
.ucmd_keepenv()
|
.ucmd()
|
||||||
|
.keep_env()
|
||||||
.env("OMP_NUM_THREADS", "42")
|
.env("OMP_NUM_THREADS", "42")
|
||||||
.env("OMP_THREAD_LIMIT", "0")
|
.env("OMP_THREAD_LIMIT", "0")
|
||||||
.succeeds();
|
.succeeds();
|
||||||
|
@ -89,7 +96,8 @@ fn test_nproc_omp_limit() {
|
||||||
assert_eq!(nproc, 42);
|
assert_eq!(nproc, 42);
|
||||||
|
|
||||||
let result = TestScenario::new(util_name!())
|
let result = TestScenario::new(util_name!())
|
||||||
.ucmd_keepenv()
|
.ucmd()
|
||||||
|
.keep_env()
|
||||||
.env("OMP_NUM_THREADS", "42")
|
.env("OMP_NUM_THREADS", "42")
|
||||||
.env("OMP_THREAD_LIMIT", "2")
|
.env("OMP_THREAD_LIMIT", "2")
|
||||||
.succeeds();
|
.succeeds();
|
||||||
|
@ -97,7 +105,8 @@ fn test_nproc_omp_limit() {
|
||||||
assert_eq!(nproc, 2);
|
assert_eq!(nproc, 2);
|
||||||
|
|
||||||
let result = TestScenario::new(util_name!())
|
let result = TestScenario::new(util_name!())
|
||||||
.ucmd_keepenv()
|
.ucmd()
|
||||||
|
.keep_env()
|
||||||
.env("OMP_NUM_THREADS", "42")
|
.env("OMP_NUM_THREADS", "42")
|
||||||
.env("OMP_THREAD_LIMIT", "2bad")
|
.env("OMP_THREAD_LIMIT", "2bad")
|
||||||
.succeeds();
|
.succeeds();
|
||||||
|
@ -109,14 +118,16 @@ fn test_nproc_omp_limit() {
|
||||||
assert!(nproc_system > 0);
|
assert!(nproc_system > 0);
|
||||||
|
|
||||||
let result = TestScenario::new(util_name!())
|
let result = TestScenario::new(util_name!())
|
||||||
.ucmd_keepenv()
|
.ucmd()
|
||||||
|
.keep_env()
|
||||||
.env("OMP_THREAD_LIMIT", "1")
|
.env("OMP_THREAD_LIMIT", "1")
|
||||||
.succeeds();
|
.succeeds();
|
||||||
let nproc: u8 = result.stdout_str().trim().parse().unwrap();
|
let nproc: u8 = result.stdout_str().trim().parse().unwrap();
|
||||||
assert_eq!(nproc, 1);
|
assert_eq!(nproc, 1);
|
||||||
|
|
||||||
let result = TestScenario::new(util_name!())
|
let result = TestScenario::new(util_name!())
|
||||||
.ucmd_keepenv()
|
.ucmd()
|
||||||
|
.keep_env()
|
||||||
.env("OMP_NUM_THREADS", "0")
|
.env("OMP_NUM_THREADS", "0")
|
||||||
.env("OMP_THREAD_LIMIT", "")
|
.env("OMP_THREAD_LIMIT", "")
|
||||||
.succeeds();
|
.succeeds();
|
||||||
|
@ -124,7 +135,8 @@ fn test_nproc_omp_limit() {
|
||||||
assert_eq!(nproc, nproc_system);
|
assert_eq!(nproc, nproc_system);
|
||||||
|
|
||||||
let result = TestScenario::new(util_name!())
|
let result = TestScenario::new(util_name!())
|
||||||
.ucmd_keepenv()
|
.ucmd()
|
||||||
|
.keep_env()
|
||||||
.env("OMP_NUM_THREADS", "")
|
.env("OMP_NUM_THREADS", "")
|
||||||
.env("OMP_THREAD_LIMIT", "")
|
.env("OMP_THREAD_LIMIT", "")
|
||||||
.succeeds();
|
.succeeds();
|
||||||
|
@ -132,7 +144,8 @@ fn test_nproc_omp_limit() {
|
||||||
assert_eq!(nproc, nproc_system);
|
assert_eq!(nproc, nproc_system);
|
||||||
|
|
||||||
let result = TestScenario::new(util_name!())
|
let result = TestScenario::new(util_name!())
|
||||||
.ucmd_keepenv()
|
.ucmd()
|
||||||
|
.keep_env()
|
||||||
.env("OMP_NUM_THREADS", "2,2,1")
|
.env("OMP_NUM_THREADS", "2,2,1")
|
||||||
.env("OMP_THREAD_LIMIT", "")
|
.env("OMP_THREAD_LIMIT", "")
|
||||||
.succeeds();
|
.succeeds();
|
||||||
|
@ -140,7 +153,8 @@ fn test_nproc_omp_limit() {
|
||||||
assert_eq!(2, nproc);
|
assert_eq!(2, nproc);
|
||||||
|
|
||||||
let result = TestScenario::new(util_name!())
|
let result = TestScenario::new(util_name!())
|
||||||
.ucmd_keepenv()
|
.ucmd()
|
||||||
|
.keep_env()
|
||||||
.env("OMP_NUM_THREADS", "2,ignored")
|
.env("OMP_NUM_THREADS", "2,ignored")
|
||||||
.env("OMP_THREAD_LIMIT", "")
|
.env("OMP_THREAD_LIMIT", "")
|
||||||
.succeeds();
|
.succeeds();
|
||||||
|
@ -148,7 +162,8 @@ fn test_nproc_omp_limit() {
|
||||||
assert_eq!(2, nproc);
|
assert_eq!(2, nproc);
|
||||||
|
|
||||||
let result = TestScenario::new(util_name!())
|
let result = TestScenario::new(util_name!())
|
||||||
.ucmd_keepenv()
|
.ucmd()
|
||||||
|
.keep_env()
|
||||||
.env("OMP_NUM_THREADS", "2,2,1")
|
.env("OMP_NUM_THREADS", "2,2,1")
|
||||||
.env("OMP_THREAD_LIMIT", "0")
|
.env("OMP_THREAD_LIMIT", "0")
|
||||||
.succeeds();
|
.succeeds();
|
||||||
|
@ -156,7 +171,8 @@ fn test_nproc_omp_limit() {
|
||||||
assert_eq!(2, nproc);
|
assert_eq!(2, nproc);
|
||||||
|
|
||||||
let result = TestScenario::new(util_name!())
|
let result = TestScenario::new(util_name!())
|
||||||
.ucmd_keepenv()
|
.ucmd()
|
||||||
|
.keep_env()
|
||||||
.env("OMP_NUM_THREADS", "2,2,1")
|
.env("OMP_NUM_THREADS", "2,2,1")
|
||||||
.env("OMP_THREAD_LIMIT", "1bad")
|
.env("OMP_THREAD_LIMIT", "1bad")
|
||||||
.succeeds();
|
.succeeds();
|
||||||
|
@ -164,7 +180,8 @@ fn test_nproc_omp_limit() {
|
||||||
assert_eq!(2, nproc);
|
assert_eq!(2, nproc);
|
||||||
|
|
||||||
let result = TestScenario::new(util_name!())
|
let result = TestScenario::new(util_name!())
|
||||||
.ucmd_keepenv()
|
.ucmd()
|
||||||
|
.keep_env()
|
||||||
.env("OMP_NUM_THREADS", "29,2,1")
|
.env("OMP_NUM_THREADS", "29,2,1")
|
||||||
.env("OMP_THREAD_LIMIT", "1bad")
|
.env("OMP_THREAD_LIMIT", "1bad")
|
||||||
.succeeds();
|
.succeeds();
|
||||||
|
|
|
@ -8,7 +8,8 @@ fn test_get_all() {
|
||||||
assert_eq!(env::var(key), Ok("VALUE".to_string()));
|
assert_eq!(env::var(key), Ok("VALUE".to_string()));
|
||||||
|
|
||||||
TestScenario::new(util_name!())
|
TestScenario::new(util_name!())
|
||||||
.ucmd_keepenv()
|
.ucmd()
|
||||||
|
.keep_env()
|
||||||
.succeeds()
|
.succeeds()
|
||||||
.stdout_contains("HOME=")
|
.stdout_contains("HOME=")
|
||||||
.stdout_contains("KEY=VALUE");
|
.stdout_contains("KEY=VALUE");
|
||||||
|
@ -21,7 +22,8 @@ fn test_get_var() {
|
||||||
assert_eq!(env::var(key), Ok("VALUE".to_string()));
|
assert_eq!(env::var(key), Ok("VALUE".to_string()));
|
||||||
|
|
||||||
let result = TestScenario::new(util_name!())
|
let result = TestScenario::new(util_name!())
|
||||||
.ucmd_keepenv()
|
.ucmd()
|
||||||
|
.keep_env()
|
||||||
.arg("KEY")
|
.arg("KEY")
|
||||||
.succeeds();
|
.succeeds();
|
||||||
|
|
||||||
|
|
|
@ -60,7 +60,7 @@ fn symlinked_env() -> Env {
|
||||||
// Note: on Windows this requires admin permissions
|
// Note: on Windows this requires admin permissions
|
||||||
at.symlink_dir("subdir", "symdir");
|
at.symlink_dir("subdir", "symdir");
|
||||||
let root = PathBuf::from(at.root_dir_resolved());
|
let root = PathBuf::from(at.root_dir_resolved());
|
||||||
ucmd.raw.current_dir(root.join("symdir"));
|
ucmd.current_dir(root.join("symdir"));
|
||||||
#[cfg(not(windows))]
|
#[cfg(not(windows))]
|
||||||
ucmd.env("PWD", root.join("symdir"));
|
ucmd.env("PWD", root.join("symdir"));
|
||||||
Env {
|
Env {
|
||||||
|
|
|
@ -31,7 +31,8 @@ fn test_buffer_sizes() {
|
||||||
let buffer_sizes = ["0", "50K", "50k", "1M", "100M"];
|
let buffer_sizes = ["0", "50K", "50k", "1M", "100M"];
|
||||||
for buffer_size in &buffer_sizes {
|
for buffer_size in &buffer_sizes {
|
||||||
TestScenario::new(util_name!())
|
TestScenario::new(util_name!())
|
||||||
.ucmd_keepenv()
|
.ucmd()
|
||||||
|
.keep_env()
|
||||||
.arg("-n")
|
.arg("-n")
|
||||||
.arg("-S")
|
.arg("-S")
|
||||||
.arg(buffer_size)
|
.arg(buffer_size)
|
||||||
|
@ -44,7 +45,8 @@ fn test_buffer_sizes() {
|
||||||
let buffer_sizes = ["1000G", "10T"];
|
let buffer_sizes = ["1000G", "10T"];
|
||||||
for buffer_size in &buffer_sizes {
|
for buffer_size in &buffer_sizes {
|
||||||
TestScenario::new(util_name!())
|
TestScenario::new(util_name!())
|
||||||
.ucmd_keepenv()
|
.ucmd()
|
||||||
|
.keep_env()
|
||||||
.arg("-n")
|
.arg("-n")
|
||||||
.arg("-S")
|
.arg("-S")
|
||||||
.arg(buffer_size)
|
.arg(buffer_size)
|
||||||
|
@ -918,7 +920,8 @@ fn test_compress_merge() {
|
||||||
fn test_compress_fail() {
|
fn test_compress_fail() {
|
||||||
#[cfg(not(windows))]
|
#[cfg(not(windows))]
|
||||||
TestScenario::new(util_name!())
|
TestScenario::new(util_name!())
|
||||||
.ucmd_keepenv()
|
.ucmd()
|
||||||
|
.keep_env()
|
||||||
.args(&[
|
.args(&[
|
||||||
"ext_sort.txt",
|
"ext_sort.txt",
|
||||||
"-n",
|
"-n",
|
||||||
|
@ -934,7 +937,8 @@ fn test_compress_fail() {
|
||||||
// So, don't check the output
|
// So, don't check the output
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
TestScenario::new(util_name!())
|
TestScenario::new(util_name!())
|
||||||
.ucmd_keepenv()
|
.ucmd()
|
||||||
|
.keep_env()
|
||||||
.args(&[
|
.args(&[
|
||||||
"ext_sort.txt",
|
"ext_sort.txt",
|
||||||
"-n",
|
"-n",
|
||||||
|
@ -949,7 +953,8 @@ fn test_compress_fail() {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_merge_batches() {
|
fn test_merge_batches() {
|
||||||
TestScenario::new(util_name!())
|
TestScenario::new(util_name!())
|
||||||
.ucmd_keepenv()
|
.ucmd()
|
||||||
|
.keep_env()
|
||||||
.timeout(Duration::from_secs(120))
|
.timeout(Duration::from_secs(120))
|
||||||
.args(&["ext_sort.txt", "-n", "-S", "150b"])
|
.args(&["ext_sort.txt", "-n", "-S", "150b"])
|
||||||
.succeeds()
|
.succeeds()
|
||||||
|
@ -959,7 +964,8 @@ fn test_merge_batches() {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_merge_batch_size() {
|
fn test_merge_batch_size() {
|
||||||
TestScenario::new(util_name!())
|
TestScenario::new(util_name!())
|
||||||
.ucmd_keepenv()
|
.ucmd()
|
||||||
|
.keep_env()
|
||||||
.arg("--batch-size=2")
|
.arg("--batch-size=2")
|
||||||
.arg("-m")
|
.arg("-m")
|
||||||
.arg("--unique")
|
.arg("--unique")
|
||||||
|
@ -1067,7 +1073,8 @@ fn test_output_is_input() {
|
||||||
at.touch("file");
|
at.touch("file");
|
||||||
at.append("file", input);
|
at.append("file", input);
|
||||||
scene
|
scene
|
||||||
.ucmd_keepenv()
|
.ucmd()
|
||||||
|
.keep_env()
|
||||||
.args(&["-m", "-u", "-o", "file", "file", "file", "file"])
|
.args(&["-m", "-u", "-o", "file", "file", "file", "file"])
|
||||||
.succeeds();
|
.succeeds();
|
||||||
assert_eq!(at.read("file"), input);
|
assert_eq!(at.read("file"), input);
|
||||||
|
|
|
@ -300,19 +300,15 @@ fn test_invalid_utf8_integer_compare() {
|
||||||
let source = [0x66, 0x6f, 0x80, 0x6f];
|
let source = [0x66, 0x6f, 0x80, 0x6f];
|
||||||
let arg = OsStr::from_bytes(&source[..]);
|
let arg = OsStr::from_bytes(&source[..]);
|
||||||
|
|
||||||
let mut cmd = new_ucmd!();
|
new_ucmd!()
|
||||||
cmd.arg("123").arg("-ne");
|
.args(&[OsStr::new("123"), OsStr::new("-ne"), arg])
|
||||||
cmd.raw.arg(arg);
|
.run()
|
||||||
|
|
||||||
cmd.run()
|
|
||||||
.code_is(2)
|
.code_is(2)
|
||||||
.stderr_is("test: invalid integer $'fo\\x80o'\n");
|
.stderr_is("test: invalid integer $'fo\\x80o'\n");
|
||||||
|
|
||||||
let mut cmd = new_ucmd!();
|
new_ucmd!()
|
||||||
cmd.raw.arg(arg);
|
.args(&[arg, OsStr::new("-eq"), OsStr::new("456")])
|
||||||
cmd.arg("-eq").arg("456");
|
.run()
|
||||||
|
|
||||||
cmd.run()
|
|
||||||
.code_is(2)
|
.code_is(2)
|
||||||
.stderr_is("test: invalid integer $'fo\\x80o'\n");
|
.stderr_is("test: invalid integer $'fo\\x80o'\n");
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,8 @@ fn test_invalid_arg() {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_uptime() {
|
fn test_uptime() {
|
||||||
TestScenario::new(util_name!())
|
TestScenario::new(util_name!())
|
||||||
.ucmd_keepenv()
|
.ucmd()
|
||||||
|
.keep_env()
|
||||||
.succeeds()
|
.succeeds()
|
||||||
.stdout_contains("load average:")
|
.stdout_contains("load average:")
|
||||||
.stdout_contains(" up ");
|
.stdout_contains(" up ");
|
||||||
|
|
|
@ -22,7 +22,8 @@ fn test_users_check_name() {
|
||||||
// note: clippy::needless_borrow *false positive*
|
// note: clippy::needless_borrow *false positive*
|
||||||
#[allow(clippy::needless_borrow)]
|
#[allow(clippy::needless_borrow)]
|
||||||
let expected = TestScenario::new(&util_name)
|
let expected = TestScenario::new(&util_name)
|
||||||
.cmd_keepenv(util_name)
|
.cmd(util_name)
|
||||||
|
.keep_env()
|
||||||
.env("LC_ALL", "C")
|
.env("LC_ALL", "C")
|
||||||
.succeeds()
|
.succeeds()
|
||||||
.stdout_move_str();
|
.stdout_move_str();
|
||||||
|
|
|
@ -3,19 +3,20 @@
|
||||||
// * For the full copyright and license information, please view the LICENSE
|
// * For the full copyright and license information, please view the LICENSE
|
||||||
// * file that was distributed with this source code.
|
// * file that was distributed with this source code.
|
||||||
|
|
||||||
//spell-checker: ignore (linux) rlimit prlimit coreutil ggroups uchild uncaptured scmd
|
//spell-checker: ignore (linux) rlimit prlimit coreutil ggroups uchild uncaptured scmd SHLVL canonicalized
|
||||||
|
|
||||||
#![allow(dead_code)]
|
#![allow(dead_code)]
|
||||||
|
|
||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::assert_eq;
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||||
use rlimit::prlimit;
|
use rlimit::prlimit;
|
||||||
use rstest::rstest;
|
use rstest::rstest;
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
use std::collections::VecDeque;
|
||||||
#[cfg(not(windows))]
|
#[cfg(not(windows))]
|
||||||
use std::ffi::CString;
|
use std::ffi::CString;
|
||||||
use std::ffi::OsStr;
|
use std::ffi::{OsStr, OsString};
|
||||||
use std::fs::{self, hard_link, remove_file, File, OpenOptions};
|
use std::fs::{self, hard_link, remove_file, File, OpenOptions};
|
||||||
use std::io::{self, BufWriter, Read, Result, Write};
|
use std::io::{self, BufWriter, Read, Result, Write};
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
|
@ -34,7 +35,6 @@ use std::thread::{sleep, JoinHandle};
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
use std::{env, hint, thread};
|
use std::{env, hint, thread};
|
||||||
use tempfile::{Builder, TempDir};
|
use tempfile::{Builder, TempDir};
|
||||||
use uucore::Args;
|
|
||||||
|
|
||||||
static TESTS_DIR: &str = "tests";
|
static TESTS_DIR: &str = "tests";
|
||||||
static FIXTURES_DIR: &str = "fixtures";
|
static FIXTURES_DIR: &str = "fixtures";
|
||||||
|
@ -46,6 +46,8 @@ static MULTIPLE_STDIN_MEANINGLESS: &str = "Ucommand is designed around a typical
|
||||||
|
|
||||||
static NO_STDIN_MEANINGLESS: &str = "Setting this flag has no effect if there is no stdin";
|
static NO_STDIN_MEANINGLESS: &str = "Setting this flag has no effect if there is no stdin";
|
||||||
|
|
||||||
|
pub const TESTS_BINARY: &str = env!("CARGO_BIN_EXE_coreutils");
|
||||||
|
|
||||||
/// Test if the program is running under CI
|
/// Test if the program is running under CI
|
||||||
pub fn is_ci() -> bool {
|
pub fn is_ci() -> bool {
|
||||||
std::env::var("CI")
|
std::env::var("CI")
|
||||||
|
@ -64,7 +66,7 @@ fn read_scenario_fixture<S: AsRef<OsStr>>(tmpd: &Option<Rc<TempDir>>, file_rel_p
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct CmdResult {
|
pub struct CmdResult {
|
||||||
/// bin_path provided by `TestScenario` or `UCommand`
|
/// bin_path provided by `TestScenario` or `UCommand`
|
||||||
bin_path: String,
|
bin_path: PathBuf,
|
||||||
/// util_name provided by `TestScenario` or `UCommand`
|
/// util_name provided by `TestScenario` or `UCommand`
|
||||||
util_name: Option<String>,
|
util_name: Option<String>,
|
||||||
//tmpd is used for convenience functions for asserts against fixtures
|
//tmpd is used for convenience functions for asserts against fixtures
|
||||||
|
@ -78,21 +80,23 @@ pub struct CmdResult {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CmdResult {
|
impl CmdResult {
|
||||||
pub fn new<T, U>(
|
pub fn new<S, T, U, V>(
|
||||||
bin_path: String,
|
bin_path: S,
|
||||||
util_name: Option<String>,
|
util_name: Option<T>,
|
||||||
tmpd: Option<Rc<TempDir>>,
|
tmpd: Option<Rc<TempDir>>,
|
||||||
exit_status: Option<ExitStatus>,
|
exit_status: Option<ExitStatus>,
|
||||||
stdout: T,
|
stdout: U,
|
||||||
stderr: U,
|
stderr: V,
|
||||||
) -> Self
|
) -> Self
|
||||||
where
|
where
|
||||||
T: Into<Vec<u8>>,
|
S: Into<PathBuf>,
|
||||||
|
T: AsRef<str>,
|
||||||
U: Into<Vec<u8>>,
|
U: Into<Vec<u8>>,
|
||||||
|
V: Into<Vec<u8>>,
|
||||||
{
|
{
|
||||||
Self {
|
Self {
|
||||||
bin_path,
|
bin_path: bin_path.into(),
|
||||||
util_name,
|
util_name: util_name.map(|s| s.as_ref().into()),
|
||||||
tmpd,
|
tmpd,
|
||||||
exit_status,
|
exit_status,
|
||||||
stdout: stdout.into(),
|
stdout: stdout.into(),
|
||||||
|
@ -634,7 +638,7 @@ impl CmdResult {
|
||||||
self.stderr_only(format!(
|
self.stderr_only(format!(
|
||||||
"{0}: {2}\nTry '{1} {0} --help' for more information.\n",
|
"{0}: {2}\nTry '{1} {0} --help' for more information.\n",
|
||||||
self.util_name.as_ref().unwrap(), // This shouldn't be called using a normal command
|
self.util_name.as_ref().unwrap(), // This shouldn't be called using a normal command
|
||||||
self.bin_path,
|
self.bin_path.display(),
|
||||||
msg.as_ref()
|
msg.as_ref()
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
@ -1093,18 +1097,21 @@ pub struct TestScenario {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TestScenario {
|
impl TestScenario {
|
||||||
pub fn new(util_name: &str) -> Self {
|
pub fn new<T>(util_name: T) -> Self
|
||||||
|
where
|
||||||
|
T: AsRef<str>,
|
||||||
|
{
|
||||||
let tmpd = Rc::new(TempDir::new().unwrap());
|
let tmpd = Rc::new(TempDir::new().unwrap());
|
||||||
let ts = Self {
|
let ts = Self {
|
||||||
bin_path: PathBuf::from(env!("CARGO_BIN_EXE_coreutils")),
|
bin_path: PathBuf::from(TESTS_BINARY),
|
||||||
util_name: String::from(util_name),
|
util_name: util_name.as_ref().into(),
|
||||||
fixtures: AtPath::new(tmpd.as_ref().path()),
|
fixtures: AtPath::new(tmpd.as_ref().path()),
|
||||||
tmpd,
|
tmpd,
|
||||||
};
|
};
|
||||||
let mut fixture_path_builder = env::current_dir().unwrap();
|
let mut fixture_path_builder = env::current_dir().unwrap();
|
||||||
fixture_path_builder.push(TESTS_DIR);
|
fixture_path_builder.push(TESTS_DIR);
|
||||||
fixture_path_builder.push(FIXTURES_DIR);
|
fixture_path_builder.push(FIXTURES_DIR);
|
||||||
fixture_path_builder.push(util_name);
|
fixture_path_builder.push(util_name.as_ref());
|
||||||
if let Ok(m) = fs::metadata(&fixture_path_builder) {
|
if let Ok(m) = fs::metadata(&fixture_path_builder) {
|
||||||
if m.is_dir() {
|
if m.is_dir() {
|
||||||
recursive_copy(&fixture_path_builder, &ts.fixtures.subdir).unwrap();
|
recursive_copy(&fixture_path_builder, &ts.fixtures.subdir).unwrap();
|
||||||
|
@ -1116,58 +1123,50 @@ impl TestScenario {
|
||||||
/// Returns builder for invoking the target uutils binary. Paths given are
|
/// Returns builder for invoking the target uutils binary. Paths given are
|
||||||
/// treated relative to the environment's unique temporary test directory.
|
/// treated relative to the environment's unique temporary test directory.
|
||||||
pub fn ucmd(&self) -> UCommand {
|
pub fn ucmd(&self) -> UCommand {
|
||||||
self.composite_cmd(&self.bin_path, &self.util_name, true)
|
UCommand::from_test_scenario(self)
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns builder for invoking the target uutils binary. Paths given are
|
|
||||||
/// treated relative to the environment's unique temporary test directory.
|
|
||||||
pub fn composite_cmd<S: AsRef<OsStr>, T: AsRef<OsStr>>(
|
|
||||||
&self,
|
|
||||||
bin: S,
|
|
||||||
util_name: T,
|
|
||||||
env_clear: bool,
|
|
||||||
) -> UCommand {
|
|
||||||
UCommand::new_from_tmp(bin, &Some(util_name), self.tmpd.clone(), env_clear)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns builder for invoking any system command. Paths given are treated
|
/// Returns builder for invoking any system command. Paths given are treated
|
||||||
/// relative to the environment's unique temporary test directory.
|
/// relative to the environment's unique temporary test directory.
|
||||||
pub fn cmd<S: AsRef<OsStr>>(&self, bin: S) -> UCommand {
|
pub fn cmd<S: Into<PathBuf>>(&self, bin_path: S) -> UCommand {
|
||||||
UCommand::new_from_tmp::<S, S>(bin, &None, self.tmpd.clone(), true)
|
let mut command = UCommand::new();
|
||||||
|
command.bin_path(bin_path);
|
||||||
|
command.temp_dir(self.tmpd.clone());
|
||||||
|
command
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns builder for invoking any uutils command. Paths given are treated
|
/// Returns builder for invoking any uutils command. Paths given are treated
|
||||||
/// relative to the environment's unique temporary test directory.
|
/// relative to the environment's unique temporary test directory.
|
||||||
pub fn ccmd<S: AsRef<OsStr>>(&self, bin: S) -> UCommand {
|
pub fn ccmd<S: AsRef<str>>(&self, util_name: S) -> UCommand {
|
||||||
self.composite_cmd(&self.bin_path, bin, true)
|
UCommand::with_util(util_name, self.tmpd.clone())
|
||||||
}
|
|
||||||
|
|
||||||
// different names are used rather than an argument
|
|
||||||
// because the need to keep the environment is exceedingly rare.
|
|
||||||
pub fn ucmd_keepenv(&self) -> UCommand {
|
|
||||||
self.composite_cmd(&self.bin_path, &self.util_name, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns builder for invoking any system command. Paths given are treated
|
|
||||||
/// relative to the environment's unique temporary test directory.
|
|
||||||
/// Differs from the builder returned by `cmd` in that `cmd_keepenv` does not call
|
|
||||||
/// `Command::env_clear` (Clears the entire environment map for the child process.)
|
|
||||||
pub fn cmd_keepenv<S: AsRef<OsStr>>(&self, bin: S) -> UCommand {
|
|
||||||
UCommand::new_from_tmp::<S, S>(bin, &None, self.tmpd.clone(), false)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A `UCommand` is a wrapper around an individual Command that provides several additional features
|
/// A `UCommand` is a builder wrapping an individual Command that provides several additional features:
|
||||||
/// 1. it has convenience functions that are more ergonomic to use for piping in stdin, spawning the command
|
/// 1. it has convenience functions that are more ergonomic to use for piping in stdin, spawning the command
|
||||||
/// and asserting on the results.
|
/// and asserting on the results.
|
||||||
/// 2. it tracks arguments provided so that in test cases which may provide variations of an arg in loops
|
/// 2. it tracks arguments provided so that in test cases which may provide variations of an arg in loops
|
||||||
/// the test failure can display the exact call which preceded an assertion failure.
|
/// the test failure can display the exact call which preceded an assertion failure.
|
||||||
/// 3. it provides convenience construction arguments to set the Command working directory and/or clear its environment.
|
/// 3. it provides convenience construction methods to set the Command uutils utility and temporary directory.
|
||||||
#[derive(Debug)]
|
///
|
||||||
|
/// Per default `UCommand` runs a command given as an argument in a shell, platform independently.
|
||||||
|
/// It does so with safety in mind, so the working directory is set to an individual temporary
|
||||||
|
/// directory and the environment variables are cleared per default.
|
||||||
|
///
|
||||||
|
/// The default behavior can be changed with builder methods:
|
||||||
|
/// * [`UCommand::with_util`]: Run `coreutils UTIL_NAME` instead of the shell
|
||||||
|
/// * [`UCommand::from_test_scenario`]: Run `coreutils UTIL_NAME` instead of the shell in the
|
||||||
|
/// temporary directory of the [`TestScenario`]
|
||||||
|
/// * [`UCommand::current_dir`]: Sets the working directory
|
||||||
|
/// * [`UCommand::keep_env`]: Keep environment variables instead of clearing them
|
||||||
|
/// * ...
|
||||||
|
#[derive(Debug, Default)]
|
||||||
pub struct UCommand {
|
pub struct UCommand {
|
||||||
pub raw: Command,
|
args: VecDeque<OsString>,
|
||||||
comm_string: String,
|
env_vars: Vec<(OsString, OsString)>,
|
||||||
bin_path: String,
|
current_dir: Option<PathBuf>,
|
||||||
|
env_clear: bool,
|
||||||
|
bin_path: Option<PathBuf>,
|
||||||
util_name: Option<String>,
|
util_name: Option<String>,
|
||||||
has_run: bool,
|
has_run: bool,
|
||||||
ignore_stdin_write_error: bool,
|
ignore_stdin_write_error: bool,
|
||||||
|
@ -1183,72 +1182,80 @@ pub struct UCommand {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UCommand {
|
impl UCommand {
|
||||||
pub fn new<T: AsRef<OsStr>, S: AsRef<OsStr>, U: AsRef<OsStr>>(
|
/// Create a new plain [`UCommand`].
|
||||||
bin_path: T,
|
///
|
||||||
util_name: &Option<S>,
|
/// Executes a command that must be given as argument (for example with [`UCommand::arg`] in a
|
||||||
curdir: U,
|
/// shell (`sh -c` on unix platforms or `cmd /C` on windows).
|
||||||
env_clear: bool,
|
///
|
||||||
) -> Self {
|
/// Per default the environment is cleared and the working directory is set to an individual
|
||||||
let bin_path = bin_path.as_ref();
|
/// temporary directory for safety purposes.
|
||||||
let util_name = util_name.as_ref().map(std::convert::AsRef::as_ref);
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
let mut ucmd = Self {
|
env_clear: true,
|
||||||
tmpd: None,
|
..Default::default()
|
||||||
has_run: false,
|
|
||||||
raw: {
|
|
||||||
let mut cmd = Command::new(bin_path);
|
|
||||||
cmd.current_dir(curdir.as_ref());
|
|
||||||
if env_clear {
|
|
||||||
cmd.env_clear();
|
|
||||||
if cfg!(windows) {
|
|
||||||
// spell-checker:ignore (dll) rsaenh
|
|
||||||
// %SYSTEMROOT% is required on Windows to initialize crypto provider
|
|
||||||
// ... and crypto provider is required for std::rand
|
|
||||||
// From `procmon`: RegQueryValue HKLM\SOFTWARE\Microsoft\Cryptography\Defaults\Provider\Microsoft Strong Cryptographic Provider\Image Path
|
|
||||||
// SUCCESS Type: REG_SZ, Length: 66, Data: %SystemRoot%\system32\rsaenh.dll"
|
|
||||||
if let Some(systemroot) = env::var_os("SYSTEMROOT") {
|
|
||||||
cmd.env("SYSTEMROOT", systemroot);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// if someone is setting LD_PRELOAD, there's probably a good reason for it
|
|
||||||
if let Some(ld_preload) = env::var_os("LD_PRELOAD") {
|
|
||||||
cmd.env("LD_PRELOAD", ld_preload);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
cmd
|
|
||||||
},
|
|
||||||
comm_string: String::from(bin_path.to_str().unwrap()),
|
|
||||||
bin_path: bin_path.to_str().unwrap().to_string(),
|
|
||||||
util_name: util_name.map(|un| un.to_str().unwrap().to_string()),
|
|
||||||
ignore_stdin_write_error: false,
|
|
||||||
bytes_into_stdin: None,
|
|
||||||
stdin: None,
|
|
||||||
stdout: None,
|
|
||||||
stderr: None,
|
|
||||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
|
||||||
limits: vec![],
|
|
||||||
stderr_to_stdout: false,
|
|
||||||
timeout: Some(Duration::from_secs(30)),
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(un) = util_name {
|
|
||||||
ucmd.arg(un);
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a [`UCommand`] for a specific uutils utility.
|
||||||
|
///
|
||||||
|
/// Sets the temporary directory to `tmpd` and the execution binary to the path where
|
||||||
|
/// `coreutils` is found.
|
||||||
|
pub fn with_util<T>(util_name: T, tmpd: Rc<TempDir>) -> Self
|
||||||
|
where
|
||||||
|
T: AsRef<str>,
|
||||||
|
{
|
||||||
|
let mut ucmd = Self::new();
|
||||||
|
ucmd.util_name = Some(util_name.as_ref().into());
|
||||||
|
ucmd.bin_path(TESTS_BINARY).temp_dir(tmpd);
|
||||||
ucmd
|
ucmd
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_from_tmp<T: AsRef<OsStr>, S: AsRef<OsStr>>(
|
/// Create a [`UCommand`] from a [`TestScenario`].
|
||||||
bin_path: T,
|
///
|
||||||
util_name: &Option<S>,
|
/// The temporary directory and uutils utility are inherited from the [`TestScenario`] and the
|
||||||
tmpd: Rc<TempDir>,
|
/// execution binary is set to `coreutils`.
|
||||||
env_clear: bool,
|
pub fn from_test_scenario(scene: &TestScenario) -> Self {
|
||||||
) -> Self {
|
Self::with_util(&scene.util_name, scene.tmpd.clone())
|
||||||
let tmpd_path_buf = String::from(tmpd.as_ref().path().to_str().unwrap());
|
}
|
||||||
let mut ucmd: Self = Self::new(bin_path, util_name, tmpd_path_buf, env_clear);
|
|
||||||
ucmd.tmpd = Some(tmpd);
|
/// Set the execution binary.
|
||||||
ucmd
|
///
|
||||||
|
/// Make sure the binary found at this path is executable. It's safest to provide the
|
||||||
|
/// canonicalized path instead of just the name of the executable, since path resolution is not
|
||||||
|
/// guaranteed to work on all platforms.
|
||||||
|
fn bin_path<T>(&mut self, bin_path: T) -> &mut Self
|
||||||
|
where
|
||||||
|
T: Into<PathBuf>,
|
||||||
|
{
|
||||||
|
self.bin_path = Some(bin_path.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the temporary directory.
|
||||||
|
///
|
||||||
|
/// Per default an individual temporary directory is created for every [`UCommand`]. If not
|
||||||
|
/// specified otherwise with [`UCommand::current_dir`] the working directory is set to this
|
||||||
|
/// temporary directory.
|
||||||
|
fn temp_dir(&mut self, temp_dir: Rc<TempDir>) -> &mut Self {
|
||||||
|
self.tmpd = Some(temp_dir);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Keep the environment variables instead of clearing them before running the command.
|
||||||
|
pub fn keep_env(&mut self) -> &mut Self {
|
||||||
|
self.env_clear = false;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the working directory for this [`UCommand`]
|
||||||
|
///
|
||||||
|
/// Per default the working directory is set to the [`UCommands`] temporary directory.
|
||||||
|
pub fn current_dir<T>(&mut self, current_dir: T) -> &mut Self
|
||||||
|
where
|
||||||
|
T: Into<PathBuf>,
|
||||||
|
{
|
||||||
|
self.current_dir = Some(current_dir.into());
|
||||||
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_stdin<T: Into<Stdio>>(&mut self, stdin: T) -> &mut Self {
|
pub fn set_stdin<T: Into<Stdio>>(&mut self, stdin: T) -> &mut Self {
|
||||||
|
@ -1274,29 +1281,14 @@ impl UCommand {
|
||||||
/// Add a parameter to the invocation. Path arguments are treated relative
|
/// Add a parameter to the invocation. Path arguments are treated relative
|
||||||
/// to the test environment directory.
|
/// to the test environment directory.
|
||||||
pub fn arg<S: AsRef<OsStr>>(&mut self, arg: S) -> &mut Self {
|
pub fn arg<S: AsRef<OsStr>>(&mut self, arg: S) -> &mut Self {
|
||||||
assert!(!self.has_run, "{}", ALREADY_RUN);
|
self.args.push_back(arg.as_ref().into());
|
||||||
self.comm_string.push(' ');
|
|
||||||
self.comm_string
|
|
||||||
.push_str(arg.as_ref().to_str().unwrap_or_default());
|
|
||||||
self.raw.arg(arg.as_ref());
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add multiple parameters to the invocation. Path arguments are treated relative
|
/// Add multiple parameters to the invocation. Path arguments are treated relative
|
||||||
/// to the test environment directory.
|
/// to the test environment directory.
|
||||||
pub fn args<S: AsRef<OsStr>>(&mut self, args: &[S]) -> &mut Self {
|
pub fn args<S: AsRef<OsStr>>(&mut self, args: &[S]) -> &mut Self {
|
||||||
assert!(!self.has_run, "{}", MULTIPLE_STDIN_MEANINGLESS);
|
self.args.extend(args.iter().map(|s| s.as_ref().into()));
|
||||||
let strings = args
|
|
||||||
.iter()
|
|
||||||
.map(|s| s.as_ref().to_os_string())
|
|
||||||
.collect_ignore();
|
|
||||||
|
|
||||||
for s in strings {
|
|
||||||
self.comm_string.push(' ');
|
|
||||||
self.comm_string.push_str(&s);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.raw.args(args.as_ref());
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1331,13 +1323,13 @@ impl UCommand {
|
||||||
K: AsRef<OsStr>,
|
K: AsRef<OsStr>,
|
||||||
V: AsRef<OsStr>,
|
V: AsRef<OsStr>,
|
||||||
{
|
{
|
||||||
assert!(!self.has_run, "{}", ALREADY_RUN);
|
self.env_vars
|
||||||
self.raw.env(key, val);
|
.push((key.as_ref().into(), val.as_ref().into()));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||||
pub fn with_limit(
|
pub fn limit(
|
||||||
&mut self,
|
&mut self,
|
||||||
resource: rlimit::Resource,
|
resource: rlimit::Resource,
|
||||||
soft_limit: u64,
|
soft_limit: u64,
|
||||||
|
@ -1359,26 +1351,113 @@ impl UCommand {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Spawns the command, feeds the stdin if any, and returns the
|
/// Build the `std::process::Command` and apply the defaults on fields which were not specified
|
||||||
/// child process immediately.
|
/// by the user.
|
||||||
pub fn run_no_wait(&mut self) -> UChild {
|
///
|
||||||
assert!(!self.has_run, "{}", ALREADY_RUN);
|
/// These __defaults__ are:
|
||||||
self.has_run = true;
|
/// * `bin_path`: Depending on the platform and os, the native shell (unix -> `/bin/sh` etc.).
|
||||||
log_info("run", &self.comm_string);
|
/// This default also requires to set the first argument to `-c` on unix (`/C` on windows) if
|
||||||
|
/// this argument wasn't specified explicitly by the user.
|
||||||
|
/// * `util_name`: `None`. If neither `bin_path` nor `util_name` were given the arguments are
|
||||||
|
/// run in a shell (See `bin_path` above).
|
||||||
|
/// * `temp_dir`: If `current_dir` was not set, a new temporary directory will be created in
|
||||||
|
/// which this command will be run and `current_dir` will be set to this `temp_dir`.
|
||||||
|
/// * `current_dir`: The temporary directory given by `temp_dir`.
|
||||||
|
/// * `timeout`: `30 seconds`
|
||||||
|
/// * `env_clear`: `true`. (Almost) all environment variables will be cleared.
|
||||||
|
/// * `stdin`: `Stdio::null()`
|
||||||
|
/// * `ignore_stdin_write_error`: `false`
|
||||||
|
/// * `stdout`, `stderr`: If not specified the output will be captured with [`CapturedOutput`]
|
||||||
|
/// * `stderr_to_stdout`: `false`
|
||||||
|
/// * `bytes_into_stdin`: `None`
|
||||||
|
/// * `limits`: `None`.
|
||||||
|
fn build(&mut self) -> (Command, Option<CapturedOutput>, Option<CapturedOutput>) {
|
||||||
|
if self.bin_path.is_some() {
|
||||||
|
if let Some(util_name) = &self.util_name {
|
||||||
|
self.args.push_front(util_name.into());
|
||||||
|
}
|
||||||
|
} else if let Some(util_name) = &self.util_name {
|
||||||
|
self.bin_path = Some(PathBuf::from(TESTS_BINARY));
|
||||||
|
self.args.push_front(util_name.into());
|
||||||
|
// neither `bin_path` nor `util_name` was set so we apply the default to run the arguments
|
||||||
|
// in a platform specific shell
|
||||||
|
} else if cfg!(unix) {
|
||||||
|
#[cfg(target_os = "android")]
|
||||||
|
let bin_path = PathBuf::from("/system/bin/sh");
|
||||||
|
#[cfg(not(target_os = "android"))]
|
||||||
|
let bin_path = PathBuf::from("/bin/sh");
|
||||||
|
|
||||||
|
self.bin_path = Some(bin_path);
|
||||||
|
let c_arg = OsString::from("-c");
|
||||||
|
if !self.args.contains(&c_arg) {
|
||||||
|
self.args.push_front(c_arg);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.bin_path = Some(PathBuf::from("cmd"));
|
||||||
|
let c_arg = OsString::from("/C");
|
||||||
|
let k_arg = OsString::from("/K");
|
||||||
|
if !self
|
||||||
|
.args
|
||||||
|
.iter()
|
||||||
|
.any(|s| s.eq_ignore_ascii_case(&c_arg) || s.eq_ignore_ascii_case(&k_arg))
|
||||||
|
{
|
||||||
|
self.args.push_front(c_arg);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// unwrap is safe here because we have set `self.bin_path` before
|
||||||
|
let mut command = Command::new(self.bin_path.as_ref().unwrap());
|
||||||
|
command.args(&self.args);
|
||||||
|
|
||||||
|
// We use a temporary directory as working directory if not specified otherwise with
|
||||||
|
// `current_dir()`. If neither `current_dir` nor a temporary directory is available, then we
|
||||||
|
// create our own.
|
||||||
|
if let Some(current_dir) = &self.current_dir {
|
||||||
|
command.current_dir(current_dir);
|
||||||
|
} else if let Some(temp_dir) = &self.tmpd {
|
||||||
|
command.current_dir(temp_dir.path());
|
||||||
|
} else {
|
||||||
|
let temp_dir = tempfile::tempdir().unwrap();
|
||||||
|
self.current_dir = Some(temp_dir.path().into());
|
||||||
|
command.current_dir(temp_dir.path());
|
||||||
|
self.tmpd = Some(Rc::new(temp_dir));
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.env_clear {
|
||||||
|
command.env_clear();
|
||||||
|
if cfg!(windows) {
|
||||||
|
// spell-checker:ignore (dll) rsaenh
|
||||||
|
// %SYSTEMROOT% is required on Windows to initialize crypto provider
|
||||||
|
// ... and crypto provider is required for std::rand
|
||||||
|
// From `procmon`: RegQueryValue HKLM\SOFTWARE\Microsoft\Cryptography\Defaults\Provider\Microsoft Strong Cryptographic Provider\Image Path
|
||||||
|
// SUCCESS Type: REG_SZ, Length: 66, Data: %SystemRoot%\system32\rsaenh.dll"
|
||||||
|
if let Some(systemroot) = env::var_os("SYSTEMROOT") {
|
||||||
|
command.env("SYSTEMROOT", systemroot);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// if someone is setting LD_PRELOAD, there's probably a good reason for it
|
||||||
|
if let Some(ld_preload) = env::var_os("LD_PRELOAD") {
|
||||||
|
command.env("LD_PRELOAD", ld_preload);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
command.envs(self.env_vars.iter().cloned());
|
||||||
|
|
||||||
|
if self.timeout.is_none() {
|
||||||
|
self.timeout = Some(Duration::from_secs(30));
|
||||||
|
}
|
||||||
|
|
||||||
let mut captured_stdout = None;
|
let mut captured_stdout = None;
|
||||||
let mut captured_stderr = None;
|
let mut captured_stderr = None;
|
||||||
let command = if self.stderr_to_stdout {
|
if self.stderr_to_stdout {
|
||||||
let mut output = CapturedOutput::default();
|
let mut output = CapturedOutput::default();
|
||||||
|
|
||||||
let command = self
|
command
|
||||||
.raw
|
|
||||||
.stdin(self.stdin.take().unwrap_or_else(Stdio::null))
|
.stdin(self.stdin.take().unwrap_or_else(Stdio::null))
|
||||||
.stdout(Stdio::from(output.try_clone().unwrap()))
|
.stdout(Stdio::from(output.try_clone().unwrap()))
|
||||||
.stderr(Stdio::from(output.try_clone().unwrap()));
|
.stderr(Stdio::from(output.try_clone().unwrap()));
|
||||||
captured_stdout = Some(output);
|
captured_stdout = Some(output);
|
||||||
|
|
||||||
command
|
|
||||||
} else {
|
} else {
|
||||||
let stdout = if self.stdout.is_some() {
|
let stdout = if self.stdout.is_some() {
|
||||||
self.stdout.take().unwrap()
|
self.stdout.take().unwrap()
|
||||||
|
@ -1398,15 +1477,27 @@ impl UCommand {
|
||||||
stdio
|
stdio
|
||||||
};
|
};
|
||||||
|
|
||||||
self.raw
|
command
|
||||||
.stdin(self.stdin.take().unwrap_or_else(Stdio::null))
|
.stdin(self.stdin.take().unwrap_or_else(Stdio::null))
|
||||||
.stdout(stdout)
|
.stdout(stdout)
|
||||||
.stderr(stderr)
|
.stderr(stderr);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
(command, captured_stdout, captured_stderr)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Spawns the command, feeds the stdin if any, and returns the
|
||||||
|
/// child process immediately.
|
||||||
|
pub fn run_no_wait(&mut self) -> UChild {
|
||||||
|
assert!(!self.has_run, "{}", ALREADY_RUN);
|
||||||
|
self.has_run = true;
|
||||||
|
|
||||||
|
let (mut command, captured_stdout, captured_stderr) = self.build();
|
||||||
|
log_info("run", self.to_string());
|
||||||
|
|
||||||
let child = command.spawn().unwrap();
|
let child = command.spawn().unwrap();
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||||
for &(resource, soft_limit, hard_limit) in &self.limits {
|
for &(resource, soft_limit, hard_limit) in &self.limits {
|
||||||
prlimit(
|
prlimit(
|
||||||
child.id() as i32,
|
child.id() as i32,
|
||||||
|
@ -1465,6 +1556,17 @@ impl UCommand {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for UCommand {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
let mut comm_string: Vec<String> = vec![self
|
||||||
|
.bin_path
|
||||||
|
.as_ref()
|
||||||
|
.map_or(String::new(), |p| p.display().to_string())];
|
||||||
|
comm_string.extend(self.args.iter().map(|s| s.to_string_lossy().to_string()));
|
||||||
|
f.write_str(&comm_string.join(" "))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Stored the captured output in a temporary file. The file is deleted as soon as
|
/// Stored the captured output in a temporary file. The file is deleted as soon as
|
||||||
/// [`CapturedOutput`] is dropped.
|
/// [`CapturedOutput`] is dropped.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -1597,14 +1699,14 @@ impl<'a> UChildAssertion<'a> {
|
||||||
self.uchild.stderr_exact_bytes(expected_stderr_size),
|
self.uchild.stderr_exact_bytes(expected_stderr_size),
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
CmdResult {
|
CmdResult::new(
|
||||||
bin_path: self.uchild.bin_path.clone(),
|
self.uchild.bin_path.clone(),
|
||||||
util_name: self.uchild.util_name.clone(),
|
self.uchild.util_name.clone(),
|
||||||
tmpd: self.uchild.tmpd.clone(),
|
self.uchild.tmpd.clone(),
|
||||||
exit_status,
|
exit_status,
|
||||||
stdout,
|
stdout,
|
||||||
stderr,
|
stderr,
|
||||||
}
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make assertions of [`CmdResult`] with all output from start of the process until now.
|
// Make assertions of [`CmdResult`] with all output from start of the process until now.
|
||||||
|
@ -1684,7 +1786,7 @@ impl<'a> UChildAssertion<'a> {
|
||||||
/// Abstraction for a [`std::process::Child`] to handle the child process.
|
/// Abstraction for a [`std::process::Child`] to handle the child process.
|
||||||
pub struct UChild {
|
pub struct UChild {
|
||||||
raw: Child,
|
raw: Child,
|
||||||
bin_path: String,
|
bin_path: PathBuf,
|
||||||
util_name: Option<String>,
|
util_name: Option<String>,
|
||||||
captured_stdout: Option<CapturedOutput>,
|
captured_stdout: Option<CapturedOutput>,
|
||||||
captured_stderr: Option<CapturedOutput>,
|
captured_stderr: Option<CapturedOutput>,
|
||||||
|
@ -1704,7 +1806,7 @@ impl UChild {
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
raw: child,
|
raw: child,
|
||||||
bin_path: ucommand.bin_path.clone(),
|
bin_path: ucommand.bin_path.clone().unwrap(),
|
||||||
util_name: ucommand.util_name.clone(),
|
util_name: ucommand.util_name.clone(),
|
||||||
captured_stdout,
|
captured_stdout,
|
||||||
captured_stderr,
|
captured_stderr,
|
||||||
|
@ -2335,11 +2437,13 @@ fn parse_coreutil_version(version_string: &str) -> f32 {
|
||||||
///```
|
///```
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
pub fn expected_result(ts: &TestScenario, args: &[&str]) -> std::result::Result<CmdResult, String> {
|
pub fn expected_result(ts: &TestScenario, args: &[&str]) -> std::result::Result<CmdResult, String> {
|
||||||
println!("{}", check_coreutil_version(&ts.util_name, VERSION_MIN)?);
|
let util_name = ts.util_name.as_str();
|
||||||
let util_name = &host_name_for(&ts.util_name);
|
println!("{}", check_coreutil_version(util_name, VERSION_MIN)?);
|
||||||
|
let util_name = host_name_for(util_name);
|
||||||
|
|
||||||
let result = ts
|
let result = ts
|
||||||
.cmd_keepenv(util_name.as_ref())
|
.cmd(util_name.as_ref())
|
||||||
|
.keep_env()
|
||||||
.env("LC_ALL", "C")
|
.env("LC_ALL", "C")
|
||||||
.args(args)
|
.args(args)
|
||||||
.run();
|
.run();
|
||||||
|
@ -2411,7 +2515,8 @@ pub fn run_ucmd_as_root(
|
||||||
// we can run sudo and we're root
|
// we can run sudo and we're root
|
||||||
// run ucmd as root:
|
// run ucmd as root:
|
||||||
Ok(ts
|
Ok(ts
|
||||||
.cmd_keepenv("sudo")
|
.cmd("sudo")
|
||||||
|
.keep_env()
|
||||||
.env("LC_ALL", "C")
|
.env("LC_ALL", "C")
|
||||||
.arg("-E")
|
.arg("-E")
|
||||||
.arg("--non-interactive")
|
.arg("--non-interactive")
|
||||||
|
@ -2439,30 +2544,8 @@ mod tests {
|
||||||
// spell-checker:ignore (tests) asdfsadfa
|
// spell-checker:ignore (tests) asdfsadfa
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[cfg(unix)]
|
|
||||||
pub fn run_cmd<T: AsRef<OsStr>>(cmd: T) -> CmdResult {
|
pub fn run_cmd<T: AsRef<OsStr>>(cmd: T) -> CmdResult {
|
||||||
let mut ucmd = UCommand::new_from_tmp::<&str, String>(
|
UCommand::new().arg(cmd).run()
|
||||||
"sh",
|
|
||||||
&None,
|
|
||||||
Rc::new(tempfile::tempdir().unwrap()),
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
ucmd.arg("-c");
|
|
||||||
ucmd.arg(cmd);
|
|
||||||
ucmd.run()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(windows)]
|
|
||||||
pub fn run_cmd<T: AsRef<OsStr>>(cmd: T) -> CmdResult {
|
|
||||||
let mut ucmd = UCommand::new_from_tmp::<&str, String>(
|
|
||||||
"cmd",
|
|
||||||
&None,
|
|
||||||
Rc::new(tempfile::tempdir().unwrap()),
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
ucmd.arg("/C");
|
|
||||||
ucmd.arg(cmd);
|
|
||||||
ucmd.run()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -3200,4 +3283,52 @@ mod tests {
|
||||||
let ts = TestScenario::new("sleep");
|
let ts = TestScenario::new("sleep");
|
||||||
ts.ucmd().timeout(Duration::from_secs(60)).arg("1.0").run();
|
ts.ucmd().timeout(Duration::from_secs(60)).arg("1.0").run();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "echo")]
|
||||||
|
#[test]
|
||||||
|
fn test_ucommand_when_default() {
|
||||||
|
let shell_cmd = format!("{TESTS_BINARY} echo -n hello");
|
||||||
|
|
||||||
|
let mut command = UCommand::new();
|
||||||
|
command.arg(&shell_cmd).succeeds().stdout_is("hello");
|
||||||
|
|
||||||
|
#[cfg(target_os = "android")]
|
||||||
|
let (expected_bin, expected_arg) = (PathBuf::from("/system/bin/sh"), OsString::from("-c"));
|
||||||
|
#[cfg(all(unix, not(target_os = "android")))]
|
||||||
|
let (expected_bin, expected_arg) = (PathBuf::from("/bin/sh"), OsString::from("-c"));
|
||||||
|
#[cfg(windows)]
|
||||||
|
let (expected_bin, expected_arg) = (PathBuf::from("cmd"), OsString::from("/C"));
|
||||||
|
|
||||||
|
std::assert_eq!(&expected_bin, command.bin_path.as_ref().unwrap());
|
||||||
|
assert!(command.util_name.is_none());
|
||||||
|
std::assert_eq!(command.args, &[expected_arg, OsString::from(&shell_cmd)]);
|
||||||
|
assert!(command.tmpd.is_some());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "echo")]
|
||||||
|
#[test]
|
||||||
|
fn test_ucommand_with_util() {
|
||||||
|
let tmpd = tempfile::tempdir().unwrap();
|
||||||
|
let mut command = UCommand::with_util("echo", Rc::new(tmpd));
|
||||||
|
|
||||||
|
command
|
||||||
|
.args(&["-n", "hello"])
|
||||||
|
.succeeds()
|
||||||
|
.stdout_only("hello");
|
||||||
|
|
||||||
|
std::assert_eq!(
|
||||||
|
&PathBuf::from(TESTS_BINARY),
|
||||||
|
command.bin_path.as_ref().unwrap()
|
||||||
|
);
|
||||||
|
std::assert_eq!("echo", &command.util_name.unwrap());
|
||||||
|
std::assert_eq!(
|
||||||
|
&[
|
||||||
|
OsString::from("echo"),
|
||||||
|
OsString::from("-n"),
|
||||||
|
OsString::from("hello")
|
||||||
|
],
|
||||||
|
command.args.make_contiguous()
|
||||||
|
);
|
||||||
|
assert!(command.tmpd.is_some());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue