mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-27 11:07:44 +00:00
Merge branch 'main' into cp-pathbuf
This commit is contained in:
commit
49eb9a2e91
169 changed files with 4290 additions and 2163 deletions
18
.github/workflows/CICD.yml
vendored
18
.github/workflows/CICD.yml
vendored
|
@ -291,7 +291,13 @@ jobs:
|
|||
shell: bash
|
||||
run: |
|
||||
RUSTDOCFLAGS="-Dwarnings" cargo doc ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }} --no-deps --workspace --document-private-items
|
||||
|
||||
- uses: DavidAnson/markdownlint-cli2-action@v9
|
||||
with:
|
||||
command: fix
|
||||
globs: |
|
||||
*.md
|
||||
docs/src/*.md
|
||||
src/uu/*/*.md
|
||||
|
||||
min_version:
|
||||
name: MinRustV # Minimum supported rust version (aka, MinSRV or MSRV)
|
||||
|
@ -408,6 +414,16 @@ jobs:
|
|||
make test
|
||||
env:
|
||||
RUST_BACKTRACE: "1"
|
||||
- name: "`make install`"
|
||||
shell: bash
|
||||
run: |
|
||||
DESTDIR=/tmp/ make PROFILE=release install
|
||||
# Check that the manpage is present
|
||||
test -f /tmp/usr/local/share/man/man1/whoami.1
|
||||
# Check that the completion is present
|
||||
test -f /tmp/usr/local/share/zsh/site-functions/_install
|
||||
env:
|
||||
RUST_BACKTRACE: "1"
|
||||
|
||||
|
||||
build_rust_stable:
|
||||
|
|
6
.github/workflows/GnuTests.yml
vendored
6
.github/workflows/GnuTests.yml
vendored
|
@ -86,7 +86,7 @@ jobs:
|
|||
run: |
|
||||
## Install dependencies
|
||||
sudo apt-get update
|
||||
sudo apt-get install autoconf autopoint bison texinfo gperf gcc g++ gdb python3-pyinotify jq valgrind libexpect-perl
|
||||
sudo apt-get install -y autoconf autopoint bison texinfo gperf gcc g++ gdb python3-pyinotify jq valgrind libexpect-perl libacl1-dev libattr1-dev libcap-dev
|
||||
- name: Add various locales
|
||||
shell: bash
|
||||
run: |
|
||||
|
@ -316,8 +316,8 @@ jobs:
|
|||
- name: Install dependencies
|
||||
run: |
|
||||
## Install dependencies
|
||||
sudo apt update
|
||||
sudo apt install autoconf autopoint bison texinfo gperf gcc g++ gdb python3-pyinotify jq valgrind libexpect-perl -y
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y autoconf autopoint bison texinfo gperf gcc g++ gdb python3-pyinotify jq valgrind libexpect-perl libacl1-dev libattr1-dev libcap-dev
|
||||
- name: Add various locales
|
||||
run: |
|
||||
## Add various locales
|
||||
|
|
6
.markdownlint.yaml
Normal file
6
.markdownlint.yaml
Normal file
|
@ -0,0 +1,6 @@
|
|||
# Disable 'Line length'. Doesn't provide much values
|
||||
MD013: false
|
||||
# Disable 'Fenced code blocks should have a language specified'
|
||||
# Doesn't provide much in src/ to enforce it
|
||||
MD040: false
|
||||
|
|
@ -116,7 +116,7 @@ the community.
|
|||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||
version 2.0, available at
|
||||
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
|
||||
<https://www.contributor-covenant.org/version/2/0/code_of_conduct.html>.
|
||||
|
||||
Community Impact Guidelines were inspired by [Mozilla's code of conduct
|
||||
enforcement ladder](https://github.com/mozilla/diversity).
|
||||
|
@ -124,5 +124,5 @@ enforcement ladder](https://github.com/mozilla/diversity).
|
|||
[homepage]: https://www.contributor-covenant.org
|
||||
|
||||
For answers to common questions about this code of conduct, see the FAQ at
|
||||
https://www.contributor-covenant.org/faq. Translations are available at
|
||||
https://www.contributor-covenant.org/translations.
|
||||
<https://www.contributor-covenant.org/faq>. Translations are available at
|
||||
<https://www.contributor-covenant.org/translations>.
|
||||
|
|
|
@ -43,15 +43,14 @@ We take pride in supporting many operating systems and architectures.
|
|||
**Tip:**
|
||||
For Windows, Microsoft provides some images (VMWare, Hyper-V, VirtualBox and Parallels)
|
||||
for development:
|
||||
https://developer.microsoft.com/windows/downloads/virtual-machines/
|
||||
|
||||
<https://developer.microsoft.com/windows/downloads/virtual-machines/>
|
||||
|
||||
## Commit messages
|
||||
|
||||
To help the project maintainers review pull requests from contributors across
|
||||
numerous utilities, the team has settled on conventions for commit messages.
|
||||
|
||||
From http://git-scm.com/book/ch5-2.html:
|
||||
From <http://git-scm.com/book/ch5-2.html>:
|
||||
|
||||
```
|
||||
Short (50 chars or less) summary of changes
|
||||
|
|
218
Cargo.lock
generated
218
Cargo.lock
generated
|
@ -67,7 +67,7 @@ version = "0.2.14"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"hermit-abi 0.1.19",
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
@ -267,6 +267,16 @@ dependencies = [
|
|||
"os_str_bytes",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_mangen"
|
||||
version = "0.2.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bb0f09a0ca8f0dd8ac92c546b426f466ef19828185c6d504c80c48c9c2768ed9"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"roff",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "codespan-reporting"
|
||||
version = "0.11.1"
|
||||
|
@ -293,7 +303,7 @@ dependencies = [
|
|||
"lazy_static",
|
||||
"libc",
|
||||
"unicode-width",
|
||||
"windows-sys",
|
||||
"windows-sys 0.42.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -321,14 +331,15 @@ checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc"
|
|||
name = "coreutils"
|
||||
version = "0.0.17"
|
||||
dependencies = [
|
||||
"atty",
|
||||
"chrono",
|
||||
"clap",
|
||||
"clap_complete",
|
||||
"clap_mangen",
|
||||
"conv",
|
||||
"filetime",
|
||||
"glob",
|
||||
"hex-literal",
|
||||
"is-terminal",
|
||||
"libc",
|
||||
"nix",
|
||||
"once_cell",
|
||||
|
@ -635,7 +646,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "1631ca6e3c59112501a9d87fd86f21591ff77acd31331e8a73f8d80a65bbdd71"
|
||||
dependencies = [
|
||||
"nix",
|
||||
"windows-sys",
|
||||
"windows-sys 0.42.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -834,7 +845,7 @@ dependencies = [
|
|||
"cfg-if",
|
||||
"libc",
|
||||
"redox_syscall",
|
||||
"windows-sys",
|
||||
"windows-sys 0.42.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1045,6 +1056,12 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286"
|
||||
|
||||
[[package]]
|
||||
name = "hex"
|
||||
version = "0.4.3"
|
||||
|
@ -1139,6 +1156,28 @@ version = "0.7.5"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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]]
|
||||
name = "itertools"
|
||||
version = "0.10.5"
|
||||
|
@ -1235,6 +1274,12 @@ version = "0.0.46"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d4d2456c373231a208ad294c33dc5bff30051eafd954cd4caae83a712b12854d"
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4"
|
||||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
version = "0.4.9"
|
||||
|
@ -1326,7 +1371,7 @@ dependencies = [
|
|||
"libc",
|
||||
"log",
|
||||
"wasi",
|
||||
"windows-sys",
|
||||
"windows-sys 0.42.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1415,7 +1460,7 @@ version = "1.14.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f6058e64324c71e02bc2b150e4f3bc8286db6c83092132ffa3f6b1eab0f9def5"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"hermit-abi 0.1.19",
|
||||
"libc",
|
||||
]
|
||||
|
||||
|
@ -1436,9 +1481,9 @@ checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3"
|
|||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.16.0"
|
||||
version = "1.17.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860"
|
||||
checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3"
|
||||
|
||||
[[package]]
|
||||
name = "onig"
|
||||
|
@ -1545,7 +1590,7 @@ dependencies = [
|
|||
"libc",
|
||||
"redox_syscall",
|
||||
"smallvec",
|
||||
"windows-sys",
|
||||
"windows-sys 0.42.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1693,7 +1738,7 @@ dependencies = [
|
|||
"byteorder",
|
||||
"hex",
|
||||
"lazy_static",
|
||||
"rustix",
|
||||
"rustix 0.35.13",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1833,13 +1878,19 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "rlimit"
|
||||
version = "0.8.3"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f7278a1ec8bfd4a4e07515c589f5ff7b309a373f987393aef44813d9dcf87aa3"
|
||||
checksum = "f8a29d87a652dc4d43c586328706bb5cdff211f3f39a530f240b53f7221dab8e"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "roff"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b833d8d034ea094b1ea68aa6d5c740e0d04bad9d16568d08ba6f76823a114316"
|
||||
|
||||
[[package]]
|
||||
name = "rstest"
|
||||
version = "0.16.0"
|
||||
|
@ -1899,10 +1950,24 @@ checksum = "727a1a6d65f786ec22df8a81ca3121107f235970dc1705ed681d3e6e8b9cd5f9"
|
|||
dependencies = [
|
||||
"bitflags",
|
||||
"errno",
|
||||
"io-lifetimes",
|
||||
"io-lifetimes 0.7.5",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"windows-sys",
|
||||
"linux-raw-sys 0.0.46",
|
||||
"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]]
|
||||
|
@ -1934,9 +1999,9 @@ checksum = "9c8132065adcfd6e02db789d9285a0deb2f3fcb04002865ab67d5fb103533898"
|
|||
|
||||
[[package]]
|
||||
name = "selinux"
|
||||
version = "0.3.1"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "966a861c0b329c3078d82b404f7086009487123fd0cc905a9caac55d8b13bee1"
|
||||
checksum = "a00576725d21b588213fbd4af84cd7e4cc4304e8e9bd6c0f5a1498a3e2ca6a51"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"libc",
|
||||
|
@ -2053,6 +2118,15 @@ dependencies = [
|
|||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sm3"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f943a7c5e3089f2bd046221d1e9f4fa59396bf0fe966360983649683086215da"
|
||||
dependencies = [
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.10.0"
|
||||
|
@ -2161,8 +2235,8 @@ version = "0.2.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "40ca90c434fd12083d1a6bdcbe9f92a14f96c8a1ba600ba451734ac334521f7a"
|
||||
dependencies = [
|
||||
"rustix",
|
||||
"windows-sys",
|
||||
"rustix 0.35.13",
|
||||
"windows-sys 0.42.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -2334,8 +2408,8 @@ dependencies = [
|
|||
name = "uu_cat"
|
||||
version = "0.0.17"
|
||||
dependencies = [
|
||||
"atty",
|
||||
"clap",
|
||||
"is-terminal",
|
||||
"nix",
|
||||
"thiserror",
|
||||
"uucore",
|
||||
|
@ -2391,6 +2465,7 @@ name = "uu_cksum"
|
|||
version = "0.0.17"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"hex",
|
||||
"uucore",
|
||||
]
|
||||
|
||||
|
@ -2432,9 +2507,9 @@ dependencies = [
|
|||
name = "uu_cut"
|
||||
version = "0.0.17"
|
||||
dependencies = [
|
||||
"atty",
|
||||
"bstr",
|
||||
"clap",
|
||||
"is-terminal",
|
||||
"memchr",
|
||||
"uucore",
|
||||
]
|
||||
|
@ -2447,7 +2522,7 @@ dependencies = [
|
|||
"clap",
|
||||
"libc",
|
||||
"uucore",
|
||||
"windows-sys",
|
||||
"windows-sys 0.42.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -2503,7 +2578,7 @@ dependencies = [
|
|||
"clap",
|
||||
"glob",
|
||||
"uucore",
|
||||
"windows-sys",
|
||||
"windows-sys 0.42.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -2595,17 +2670,10 @@ dependencies = [
|
|||
name = "uu_hashsum"
|
||||
version = "0.0.17"
|
||||
dependencies = [
|
||||
"blake2b_simd",
|
||||
"blake3",
|
||||
"clap",
|
||||
"digest",
|
||||
"hex",
|
||||
"md-5",
|
||||
"memchr",
|
||||
"regex",
|
||||
"sha1",
|
||||
"sha2",
|
||||
"sha3",
|
||||
"uucore",
|
||||
]
|
||||
|
||||
|
@ -2634,7 +2702,7 @@ dependencies = [
|
|||
"clap",
|
||||
"hostname",
|
||||
"uucore",
|
||||
"windows-sys",
|
||||
"windows-sys 0.42.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -2705,10 +2773,10 @@ dependencies = [
|
|||
name = "uu_ls"
|
||||
version = "0.0.17"
|
||||
dependencies = [
|
||||
"atty",
|
||||
"chrono",
|
||||
"clap",
|
||||
"glob",
|
||||
"is-terminal",
|
||||
"lscolors",
|
||||
"number_prefix",
|
||||
"once_cell",
|
||||
|
@ -2759,9 +2827,9 @@ dependencies = [
|
|||
name = "uu_more"
|
||||
version = "0.0.17"
|
||||
dependencies = [
|
||||
"atty",
|
||||
"clap",
|
||||
"crossterm",
|
||||
"is-terminal",
|
||||
"nix",
|
||||
"unicode-segmentation",
|
||||
"unicode-width",
|
||||
|
@ -2801,8 +2869,8 @@ dependencies = [
|
|||
name = "uu_nohup"
|
||||
version = "0.0.17"
|
||||
dependencies = [
|
||||
"atty",
|
||||
"clap",
|
||||
"is-terminal",
|
||||
"libc",
|
||||
"uucore",
|
||||
]
|
||||
|
@ -2936,7 +3004,7 @@ dependencies = [
|
|||
"libc",
|
||||
"uucore",
|
||||
"walkdir",
|
||||
"windows-sys",
|
||||
"windows-sys 0.42.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -3079,7 +3147,7 @@ dependencies = [
|
|||
"libc",
|
||||
"nix",
|
||||
"uucore",
|
||||
"windows-sys",
|
||||
"windows-sys 0.42.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -3097,16 +3165,16 @@ dependencies = [
|
|||
name = "uu_tail"
|
||||
version = "0.0.17"
|
||||
dependencies = [
|
||||
"atty",
|
||||
"clap",
|
||||
"fundu",
|
||||
"is-terminal",
|
||||
"libc",
|
||||
"memchr",
|
||||
"notify",
|
||||
"same-file",
|
||||
"uucore",
|
||||
"winapi-util",
|
||||
"windows-sys",
|
||||
"windows-sys 0.42.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -3146,7 +3214,7 @@ dependencies = [
|
|||
"filetime",
|
||||
"time",
|
||||
"uucore",
|
||||
"windows-sys",
|
||||
"windows-sys 0.42.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -3186,8 +3254,8 @@ dependencies = [
|
|||
name = "uu_tty"
|
||||
version = "0.0.17"
|
||||
dependencies = [
|
||||
"atty",
|
||||
"clap",
|
||||
"is-terminal",
|
||||
"nix",
|
||||
"uucore",
|
||||
]
|
||||
|
@ -3282,7 +3350,7 @@ dependencies = [
|
|||
"clap",
|
||||
"libc",
|
||||
"uucore",
|
||||
"windows-sys",
|
||||
"windows-sys 0.42.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -3299,24 +3367,34 @@ dependencies = [
|
|||
name = "uucore"
|
||||
version = "0.0.17"
|
||||
dependencies = [
|
||||
"blake2b_simd",
|
||||
"blake3",
|
||||
"clap",
|
||||
"data-encoding",
|
||||
"data-encoding-macro",
|
||||
"digest",
|
||||
"dns-lookup",
|
||||
"dunce",
|
||||
"glob",
|
||||
"hex",
|
||||
"itertools",
|
||||
"libc",
|
||||
"md-5",
|
||||
"memchr",
|
||||
"nix",
|
||||
"once_cell",
|
||||
"os_display",
|
||||
"sha1",
|
||||
"sha2",
|
||||
"sha3",
|
||||
"sm3",
|
||||
"thiserror",
|
||||
"time",
|
||||
"uucore_procs",
|
||||
"walkdir",
|
||||
"wild",
|
||||
"winapi-util",
|
||||
"windows-sys",
|
||||
"windows-sys 0.42.0",
|
||||
"z85",
|
||||
]
|
||||
|
||||
|
@ -3478,46 +3556,70 @@ dependencies = [
|
|||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.42.0"
|
||||
name = "windows-sys"
|
||||
version = "0.45.0"
|
||||
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]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.42.0"
|
||||
version = "0.42.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4"
|
||||
checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.42.0"
|
||||
version = "0.42.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7"
|
||||
checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.42.0"
|
||||
version = "0.42.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246"
|
||||
checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.42.0"
|
||||
version = "0.42.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed"
|
||||
checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.42.0"
|
||||
version = "0.42.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028"
|
||||
checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.42.0"
|
||||
version = "0.42.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5"
|
||||
checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd"
|
||||
|
||||
[[package]]
|
||||
name = "xattr"
|
||||
|
|
22
Cargo.toml
22
Cargo.toml
|
@ -1,7 +1,7 @@
|
|||
# coreutils (uutils)
|
||||
# * see the repository LICENSE, README, and CONTRIBUTING files for more information
|
||||
|
||||
# spell-checker:ignore (libs) libselinux gethostid procfs bigdecimal kqueue fundu
|
||||
# spell-checker:ignore (libs) libselinux gethostid procfs bigdecimal kqueue fundu mangen
|
||||
|
||||
[package]
|
||||
name = "coreutils"
|
||||
|
@ -263,7 +263,6 @@ feat_os_windows_legacy = [
|
|||
test = [ "uu_test" ]
|
||||
|
||||
[workspace.dependencies]
|
||||
atty = "0.2"
|
||||
bigdecimal = "0.3"
|
||||
binary-heap-plus = "0.5.0"
|
||||
bstr = "1.0"
|
||||
|
@ -272,6 +271,7 @@ byteorder = "1.3.2"
|
|||
chrono = { version="^0.4.23", default-features=false, features=["std", "alloc", "clock"]}
|
||||
clap = { version = "4.0", features = ["wrap_help", "cargo"] }
|
||||
clap_complete = "4.0"
|
||||
clap_mangen = "0.2"
|
||||
compare = "0.1.0"
|
||||
coz = { version = "0.1.3" }
|
||||
crossterm = ">=0.19"
|
||||
|
@ -287,6 +287,7 @@ gcd = "2.2"
|
|||
glob = "0.3.0"
|
||||
half = "2.1"
|
||||
indicatif = "0.17"
|
||||
is-terminal = "0.4.3"
|
||||
itertools = "0.10.0"
|
||||
libc = "0.2.139"
|
||||
lscolors = { version = "0.13.0", default-features=false, features = ["nu-ansi-term"] }
|
||||
|
@ -312,7 +313,7 @@ redox_syscall = "0.2"
|
|||
regex = "1.7.1"
|
||||
rust-ini = "0.18.0"
|
||||
same-file = "1.0.6"
|
||||
selinux = "0.3"
|
||||
selinux = "0.4"
|
||||
signal-hook = "0.3.14"
|
||||
smallvec = { version = "1.10", features = ["union"] }
|
||||
strum = "0.24.1"
|
||||
|
@ -332,6 +333,16 @@ windows-sys = { version="0.42.0", default-features=false }
|
|||
xattr = "0.2.3"
|
||||
zip = { version = "0.6.3", default_features=false, features=["deflate"] }
|
||||
|
||||
hex = "0.4.3"
|
||||
md-5 = "0.10.5"
|
||||
sha1 = "0.10.1"
|
||||
sha2 = "0.10.2"
|
||||
sha3 = "0.10.6"
|
||||
blake2b_simd = "1.0.1"
|
||||
blake3 = "1.3.2"
|
||||
sm3 = "0.4.1"
|
||||
digest = "0.10.6"
|
||||
|
||||
uucore = { version=">=0.0.17", package="uucore", path="src/uucore" }
|
||||
uucore_procs = { version=">=0.0.17", package="uucore_procs", path="src/uucore_procs" }
|
||||
uu_ls = { version=">=0.0.17", path="src/uu/ls" }
|
||||
|
@ -342,6 +353,7 @@ clap = { workspace=true }
|
|||
once_cell = { workspace=true }
|
||||
uucore = { workspace=true }
|
||||
clap_complete = { workspace=true }
|
||||
clap_mangen = { workspace=true }
|
||||
phf = { workspace=true }
|
||||
selinux = { workspace=true, optional = true }
|
||||
textwrap = { workspace=true }
|
||||
|
@ -476,13 +488,13 @@ time = { workspace=true, features=["local-offset"] }
|
|||
unindent = "0.1"
|
||||
uucore = { workspace=true, features=["entries", "process", "signals"] }
|
||||
walkdir = { workspace=true }
|
||||
atty = { workspace=true }
|
||||
is-terminal = { workspace=true }
|
||||
hex-literal = "0.3.1"
|
||||
rstest = "0.16.0"
|
||||
|
||||
[target.'cfg(any(target_os = "linux", target_os = "android"))'.dev-dependencies]
|
||||
procfs = { version = "0.14", default-features = false }
|
||||
rlimit = "0.8.3"
|
||||
rlimit = "0.9.1"
|
||||
|
||||
[target.'cfg(unix)'.dev-dependencies]
|
||||
nix = { workspace=true, features=["process", "signal", "user"] }
|
||||
|
|
|
@ -1,21 +1,19 @@
|
|||
Documentation
|
||||
-------------
|
||||
# Documentation
|
||||
|
||||
The source of the documentation is available on:
|
||||
|
||||
https://uutils.github.io/dev/coreutils/
|
||||
<https://uutils.github.io/dev/coreutils/>
|
||||
|
||||
The documentation is updated everyday on this repository:
|
||||
|
||||
https://github.com/uutils/uutils.github.io/
|
||||
<https://github.com/uutils/uutils.github.io/>
|
||||
|
||||
Running GNU tests
|
||||
-----------------
|
||||
## Running GNU tests
|
||||
|
||||
<!-- spell-checker:ignore gnulib -->
|
||||
|
||||
- Check out https://github.com/coreutils/coreutils next to your fork as gnu
|
||||
- Check out https://github.com/coreutils/gnulib next to your fork as gnulib
|
||||
- Check out <https://github.com/coreutils/coreutils> next to your fork as gnu
|
||||
- Check out <https://github.com/coreutils/gnulib> next to your fork as gnulib
|
||||
- Rename the checkout of your fork to uutils
|
||||
|
||||
At the end you should have uutils, gnu and gnulib checked out next to each other.
|
||||
|
@ -23,9 +21,7 @@ At the end you should have uutils, gnu and gnulib checked out next to each other
|
|||
- Run `cd uutils && ./util/build-gnu.sh && cd ..` to get everything ready (this may take a while)
|
||||
- Finally, you can run tests with `bash uutils/util/run-gnu-test.sh <tests>`. Instead of `<tests>` insert the tests you want to run, e.g. `tests/misc/wc-proc.sh`.
|
||||
|
||||
|
||||
Code Coverage Report Generation
|
||||
---------------------------------
|
||||
## Code Coverage Report Generation
|
||||
|
||||
<!-- spell-checker:ignore (flags) Ccodegen Coverflow Cpanic Zinstrument Zpanic -->
|
||||
|
||||
|
@ -35,14 +31,14 @@ Code coverage report can be generated using [grcov](https://github.com/mozilla/g
|
|||
|
||||
To generate [gcov-based](https://github.com/mozilla/grcov#example-how-to-generate-gcda-files-for-a-rust-project) coverage report
|
||||
|
||||
```bash
|
||||
$ export CARGO_INCREMENTAL=0
|
||||
$ export RUSTFLAGS="-Zprofile -Ccodegen-units=1 -Copt-level=0 -Clink-dead-code -Coverflow-checks=off -Zpanic_abort_tests -Cpanic=abort"
|
||||
$ export RUSTDOCFLAGS="-Cpanic=abort"
|
||||
$ cargo build <options...> # e.g., --features feat_os_unix
|
||||
$ cargo test <options...> # e.g., --features feat_os_unix test_pathchk
|
||||
$ grcov . -s . --binary-path ./target/debug/ -t html --branch --ignore-not-existing --ignore build.rs --excl-br-line "^\s*((debug_)?assert(_eq|_ne)?\#\[derive\()" -o ./target/debug/coverage/
|
||||
$ # open target/debug/coverage/index.html in browser
|
||||
```shell
|
||||
export CARGO_INCREMENTAL=0
|
||||
export RUSTFLAGS="-Zprofile -Ccodegen-units=1 -Copt-level=0 -Clink-dead-code -Coverflow-checks=off -Zpanic_abort_tests -Cpanic=abort"
|
||||
export RUSTDOCFLAGS="-Cpanic=abort"
|
||||
cargo build <options...> # e.g., --features feat_os_unix
|
||||
cargo test <options...> # e.g., --features feat_os_unix test_pathchk
|
||||
grcov . -s . --binary-path ./target/debug/ -t html --branch --ignore-not-existing --ignore build.rs --excl-br-line "^\s*((debug_)?assert(_eq|_ne)?\#\[derive\()" -o ./target/debug/coverage/
|
||||
# open target/debug/coverage/index.html in browser
|
||||
```
|
||||
|
||||
if changes are not reflected in the report then run `cargo clean` and run the above commands.
|
||||
|
@ -52,19 +48,21 @@ if changes are not reflected in the report then run `cargo clean` and run the ab
|
|||
If you are using stable version of Rust that doesn't enable code coverage instrumentation by default
|
||||
then add `-Z-Zinstrument-coverage` flag to `RUSTFLAGS` env variable specified above.
|
||||
|
||||
|
||||
pre-commit hooks
|
||||
----------------
|
||||
## pre-commit hooks
|
||||
|
||||
A configuration for `pre-commit` is provided in the repository. It allows automatically checking every git commit you make to ensure it compiles, and passes `clippy` and `rustfmt` without warnings.
|
||||
|
||||
To use the provided hook:
|
||||
|
||||
1. [Install `pre-commit`](https://pre-commit.com/#install)
|
||||
2. Run `pre-commit install` while in the repository directory
|
||||
1. Run `pre-commit install` while in the repository directory
|
||||
|
||||
Your git commits will then automatically be checked. If a check fails, an error message will explain why, and your commit will be canceled. You can then make the suggested changes, and run `git commit ...` again.
|
||||
|
||||
### Using Clippy
|
||||
## Using Clippy
|
||||
|
||||
The `msrv` key in the clippy configuration file `clippy.toml` is used to disable lints pertaining to newer features by specifying the minimum supported Rust version (MSRV). However, this key is only supported on `nightly`. To invoke clippy without errors, use `cargo +nightly clippy`. In order to also check tests and non-default crate features, use `cargo +nightly clippy --all-targets --all-features`.
|
||||
|
||||
## Markdown linter
|
||||
|
||||
We use <https://github.com/DavidAnson/markdownlint> to lint the Markdown files.
|
||||
|
|
|
@ -349,10 +349,12 @@ endif
|
|||
mkdir -p $(DESTDIR)$(DATAROOTDIR)/zsh/site-functions
|
||||
mkdir -p $(DESTDIR)$(DATAROOTDIR)/bash-completion/completions
|
||||
mkdir -p $(DESTDIR)$(DATAROOTDIR)/fish/vendor_completions.d
|
||||
mkdir -p $(DESTDIR)$(DATAROOTDIR)/man/man1
|
||||
$(foreach prog, $(INSTALLEES), \
|
||||
$(BUILDDIR)/coreutils completion $(prog) zsh > $(DESTDIR)$(DATAROOTDIR)/zsh/site-functions/_$(PROG_PREFIX)$(prog); \
|
||||
$(BUILDDIR)/coreutils completion $(prog) bash > $(DESTDIR)$(DATAROOTDIR)/bash-completion/completions/$(PROG_PREFIX)$(prog); \
|
||||
$(BUILDDIR)/coreutils completion $(prog) fish > $(DESTDIR)$(DATAROOTDIR)/fish/vendor_completions.d/$(PROG_PREFIX)$(prog).fish; \
|
||||
$(BUILDDIR)/coreutils manpage $(prog) > $(DESTDIR)$(DATAROOTDIR)/man/man1/$(PROG_PREFIX)$(prog).1; \
|
||||
)
|
||||
|
||||
uninstall:
|
||||
|
|
210
README.md
210
README.md
|
@ -12,7 +12,7 @@
|
|||
-----------------------------------------------
|
||||
|
||||
<!-- markdownlint-disable commands-show-output no-duplicate-heading -->
|
||||
<!-- spell-checker:ignore markdownlint ; (options) DESTDIR RUNTEST UTILNAME -->
|
||||
<!-- spell-checker:ignore markdownlint ; (options) DESTDIR RUNTEST UTILNAME manpages -->
|
||||
|
||||
uutils is an attempt at writing universal (as in cross-platform) CLI
|
||||
utilities in [Rust](http://www.rust-lang.org).
|
||||
|
@ -21,11 +21,12 @@ or different behavior might be experienced.
|
|||
|
||||
To install it:
|
||||
|
||||
```
|
||||
$ cargo install coreutils
|
||||
$ ~/.cargo/bin/coreutils
|
||||
```shell
|
||||
cargo install coreutils
|
||||
~/.cargo/bin/coreutils
|
||||
```
|
||||
|
||||
<!-- markdownlint-disable-next-line MD026 -->
|
||||
## Why?
|
||||
|
||||
uutils aims to work on as many platforms as possible, to be able to use the
|
||||
|
@ -35,6 +36,7 @@ chosen not only because it is fast and safe, but is also excellent for
|
|||
writing cross-platform code.
|
||||
|
||||
## Documentation
|
||||
|
||||
uutils has both user and developer documentation available:
|
||||
|
||||
- [User Manual](https://uutils.github.io/user/)
|
||||
|
@ -46,8 +48,8 @@ Both can also be generated locally, the instructions for that can be found in th
|
|||
<!-- ANCHOR: build (this mark is needed for mdbook) -->
|
||||
## Requirements
|
||||
|
||||
* Rust (`cargo`, `rustc`)
|
||||
* GNU Make (optional)
|
||||
- Rust (`cargo`, `rustc`)
|
||||
- GNU Make (optional)
|
||||
|
||||
### Rust Version
|
||||
|
||||
|
@ -64,9 +66,9 @@ or GNU Make.
|
|||
|
||||
For either method, we first need to fetch the repository:
|
||||
|
||||
```bash
|
||||
$ git clone https://github.com/uutils/coreutils
|
||||
$ cd coreutils
|
||||
```shell
|
||||
git clone https://github.com/uutils/coreutils
|
||||
cd coreutils
|
||||
```
|
||||
|
||||
### Cargo
|
||||
|
@ -74,8 +76,8 @@ $ cd coreutils
|
|||
Building uutils using Cargo is easy because the process is the same as for
|
||||
every other Rust program:
|
||||
|
||||
```bash
|
||||
$ cargo build --release
|
||||
```shell
|
||||
cargo build --release
|
||||
```
|
||||
|
||||
This command builds the most portable common core set of uutils into a multicall
|
||||
|
@ -85,20 +87,20 @@ Additional platform-specific uutils are often available. Building these
|
|||
expanded sets of uutils for a platform (on that platform) is as simple as
|
||||
specifying it as a feature:
|
||||
|
||||
```bash
|
||||
$ cargo build --release --features macos
|
||||
```shell
|
||||
cargo build --release --features macos
|
||||
# or ...
|
||||
$ cargo build --release --features windows
|
||||
cargo build --release --features windows
|
||||
# or ...
|
||||
$ cargo build --release --features unix
|
||||
cargo build --release --features unix
|
||||
```
|
||||
|
||||
If you don't want to build every utility available on your platform into the
|
||||
final binary, you can also specify which ones you want to build manually.
|
||||
For example:
|
||||
|
||||
```bash
|
||||
$ cargo build --features "base32 cat echo rm" --no-default-features
|
||||
```shell
|
||||
cargo build --features "base32 cat echo rm" --no-default-features
|
||||
```
|
||||
|
||||
If you don't want to build the multicall binary and would prefer to build
|
||||
|
@ -107,8 +109,8 @@ is contained in its own package within the main repository, named
|
|||
"uu_UTILNAME". To build individual utilities, use cargo to build just the
|
||||
specific packages (using the `--package` [aka `-p`] option). For example:
|
||||
|
||||
```bash
|
||||
$ cargo build -p uu_base32 -p uu_cat -p uu_echo -p uu_rm
|
||||
```shell
|
||||
cargo build -p uu_base32 -p uu_cat -p uu_echo -p uu_rm
|
||||
```
|
||||
|
||||
### GNU Make
|
||||
|
@ -117,80 +119,80 @@ Building using `make` is a simple process as well.
|
|||
|
||||
To simply build all available utilities:
|
||||
|
||||
```bash
|
||||
$ make
|
||||
```shell
|
||||
make
|
||||
```
|
||||
|
||||
To build all but a few of the available utilities:
|
||||
|
||||
```bash
|
||||
$ make SKIP_UTILS='UTILITY_1 UTILITY_2'
|
||||
```shell
|
||||
make SKIP_UTILS='UTILITY_1 UTILITY_2'
|
||||
```
|
||||
|
||||
To build only a few of the available utilities:
|
||||
|
||||
```bash
|
||||
$ make UTILS='UTILITY_1 UTILITY_2'
|
||||
```shell
|
||||
make UTILS='UTILITY_1 UTILITY_2'
|
||||
```
|
||||
|
||||
## Installation
|
||||
|
||||
### Cargo
|
||||
### Install with Cargo
|
||||
|
||||
Likewise, installing can simply be done using:
|
||||
|
||||
```bash
|
||||
$ cargo install --path .
|
||||
```shell
|
||||
cargo install --path .
|
||||
```
|
||||
|
||||
This command will install uutils into Cargo's *bin* folder (*e.g.* `$HOME/.cargo/bin`).
|
||||
|
||||
This does not install files necessary for shell completion. For shell completion to work,
|
||||
use `GNU Make` or see `Manually install shell completions`.
|
||||
This does not install files necessary for shell completion or manpages.
|
||||
For manpages or shell completion to work, use `GNU Make` or see `Manually install shell completions`/`Manually install manpages`.
|
||||
|
||||
### GNU Make
|
||||
### Install with GNU Make
|
||||
|
||||
To install all available utilities:
|
||||
|
||||
```bash
|
||||
$ make install
|
||||
```shell
|
||||
make install
|
||||
```
|
||||
|
||||
To install using `sudo` switch `-E` must be used:
|
||||
|
||||
```bash
|
||||
$ sudo -E make install
|
||||
```shell
|
||||
sudo -E make install
|
||||
```
|
||||
|
||||
To install all but a few of the available utilities:
|
||||
|
||||
```bash
|
||||
$ make SKIP_UTILS='UTILITY_1 UTILITY_2' install
|
||||
```shell
|
||||
make SKIP_UTILS='UTILITY_1 UTILITY_2' install
|
||||
```
|
||||
|
||||
To install only a few of the available utilities:
|
||||
|
||||
```bash
|
||||
$ make UTILS='UTILITY_1 UTILITY_2' install
|
||||
```shell
|
||||
make UTILS='UTILITY_1 UTILITY_2' install
|
||||
```
|
||||
|
||||
To install every program with a prefix (e.g. uu-echo uu-cat):
|
||||
|
||||
```bash
|
||||
$ make PROG_PREFIX=PREFIX_GOES_HERE install
|
||||
```shell
|
||||
make PROG_PREFIX=PREFIX_GOES_HERE install
|
||||
```
|
||||
|
||||
To install the multicall binary:
|
||||
|
||||
```bash
|
||||
$ make MULTICALL=y install
|
||||
```shell
|
||||
make MULTICALL=y install
|
||||
```
|
||||
|
||||
Set install parent directory (default value is /usr/local):
|
||||
|
||||
```bash
|
||||
```shell
|
||||
# DESTDIR is also supported
|
||||
$ make PREFIX=/my/path install
|
||||
make PREFIX=/my/path install
|
||||
```
|
||||
|
||||
Installing with `make` installs shell completions for all installed utilities
|
||||
|
@ -203,123 +205,139 @@ The `coreutils` binary can generate completions for the `bash`, `elvish`, `fish`
|
|||
and `zsh` shells. It prints the result to stdout.
|
||||
|
||||
The syntax is:
|
||||
```bash
|
||||
|
||||
```shell
|
||||
cargo run completion <utility> <shell>
|
||||
```
|
||||
|
||||
So, to install completions for `ls` on `bash` to `/usr/local/share/bash-completion/completions/ls`,
|
||||
run:
|
||||
|
||||
```bash
|
||||
```shell
|
||||
cargo run completion ls bash > /usr/local/share/bash-completion/completions/ls
|
||||
```
|
||||
|
||||
### Manually install manpages
|
||||
|
||||
To generate manpages, the syntax is:
|
||||
```bash
|
||||
cargo run manpage <utility>
|
||||
```
|
||||
|
||||
So, to install the manpage for `ls` to `/usr/local/share/man/man1/ls.1`
|
||||
run:
|
||||
|
||||
```bash
|
||||
cargo run manpage ls > /usr/local/share/man/man1/ls.1
|
||||
```
|
||||
|
||||
## Un-installation
|
||||
|
||||
Un-installation differs depending on how you have installed uutils. If you used
|
||||
Cargo to install, use Cargo to uninstall. If you used GNU Make to install, use
|
||||
Make to uninstall.
|
||||
|
||||
### Cargo
|
||||
### Uninstall with Cargo
|
||||
|
||||
To uninstall uutils:
|
||||
|
||||
```bash
|
||||
$ cargo uninstall uutils
|
||||
```shell
|
||||
cargo uninstall uutils
|
||||
```
|
||||
|
||||
### GNU Make
|
||||
### Uninstall with GNU Make
|
||||
|
||||
To uninstall all utilities:
|
||||
|
||||
```bash
|
||||
$ make uninstall
|
||||
```shell
|
||||
make uninstall
|
||||
```
|
||||
|
||||
To uninstall every program with a set prefix:
|
||||
|
||||
```bash
|
||||
$ make PROG_PREFIX=PREFIX_GOES_HERE uninstall
|
||||
```shell
|
||||
make PROG_PREFIX=PREFIX_GOES_HERE uninstall
|
||||
```
|
||||
|
||||
To uninstall the multicall binary:
|
||||
|
||||
```bash
|
||||
$ make MULTICALL=y uninstall
|
||||
```shell
|
||||
make MULTICALL=y uninstall
|
||||
```
|
||||
|
||||
To uninstall from a custom parent directory:
|
||||
|
||||
```bash
|
||||
```shell
|
||||
# DESTDIR is also supported
|
||||
$ make PREFIX=/my/path uninstall
|
||||
make PREFIX=/my/path uninstall
|
||||
```
|
||||
|
||||
<!-- ANCHOR_END: build (this mark is needed for mdbook) -->
|
||||
|
||||
## Testing
|
||||
|
||||
Testing can be done using either Cargo or `make`.
|
||||
|
||||
### Cargo
|
||||
### Testing with Cargo
|
||||
|
||||
Just like with building, we follow the standard procedure for testing using
|
||||
Cargo:
|
||||
|
||||
```bash
|
||||
$ cargo test
|
||||
```shell
|
||||
cargo test
|
||||
```
|
||||
|
||||
By default, `cargo test` only runs the common programs. To run also platform
|
||||
specific tests, run:
|
||||
|
||||
```bash
|
||||
$ cargo test --features unix
|
||||
```shell
|
||||
cargo test --features unix
|
||||
```
|
||||
|
||||
If you would prefer to test a select few utilities:
|
||||
|
||||
```bash
|
||||
$ cargo test --features "chmod mv tail" --no-default-features
|
||||
```shell
|
||||
cargo test --features "chmod mv tail" --no-default-features
|
||||
```
|
||||
|
||||
If you also want to test the core utilities:
|
||||
|
||||
```bash
|
||||
$ cargo test -p uucore -p coreutils
|
||||
```shell
|
||||
cargo test -p uucore -p coreutils
|
||||
```
|
||||
|
||||
To debug:
|
||||
|
||||
```bash
|
||||
$ gdb --args target/debug/coreutils ls
|
||||
```shell
|
||||
gdb --args target/debug/coreutils ls
|
||||
(gdb) b ls.rs:79
|
||||
(gdb) run
|
||||
```
|
||||
|
||||
### GNU Make
|
||||
### Testing with GNU Make
|
||||
|
||||
To simply test all available utilities:
|
||||
|
||||
```bash
|
||||
$ make test
|
||||
```shell
|
||||
make test
|
||||
```
|
||||
|
||||
To test all but a few of the available utilities:
|
||||
|
||||
```bash
|
||||
$ make SKIP_UTILS='UTILITY_1 UTILITY_2' test
|
||||
```shell
|
||||
make SKIP_UTILS='UTILITY_1 UTILITY_2' test
|
||||
```
|
||||
|
||||
To test only a few of the available utilities:
|
||||
|
||||
```bash
|
||||
$ make UTILS='UTILITY_1 UTILITY_2' test
|
||||
```shell
|
||||
make UTILS='UTILITY_1 UTILITY_2' test
|
||||
```
|
||||
|
||||
To include tests for unimplemented behavior:
|
||||
|
||||
```bash
|
||||
$ make UTILS='UTILITY_1 UTILITY_2' SPEC=y test
|
||||
```shell
|
||||
make UTILS='UTILITY_1 UTILITY_2' SPEC=y test
|
||||
```
|
||||
|
||||
### Run Busybox Tests
|
||||
|
@ -329,20 +347,20 @@ requires `make`.
|
|||
|
||||
To run busybox tests for all utilities for which busybox has tests
|
||||
|
||||
```bash
|
||||
$ make busytest
|
||||
```shell
|
||||
make busytest
|
||||
```
|
||||
|
||||
To run busybox tests for a few of the available utilities
|
||||
|
||||
```bash
|
||||
$ make UTILS='UTILITY_1 UTILITY_2' busytest
|
||||
```shell
|
||||
make UTILS='UTILITY_1 UTILITY_2' busytest
|
||||
```
|
||||
|
||||
To pass an argument like "-v" to the busybox test runtime
|
||||
|
||||
```bash
|
||||
$ make UTILS='UTILITY_1 UTILITY_2' RUNTEST_ARGS='-v' busytest
|
||||
```shell
|
||||
make UTILS='UTILITY_1 UTILITY_2' RUNTEST_ARGS='-v' busytest
|
||||
```
|
||||
|
||||
### Comparing with GNU
|
||||
|
@ -355,15 +373,15 @@ breakdown of the GNU test results of the main branch can be found
|
|||
|
||||
To run locally:
|
||||
|
||||
```bash
|
||||
$ bash util/build-gnu.sh
|
||||
$ bash util/run-gnu-test.sh
|
||||
```shell
|
||||
bash util/build-gnu.sh
|
||||
bash util/run-gnu-test.sh
|
||||
# To run a single test:
|
||||
$ bash util/run-gnu-test.sh tests/touch/not-owner.sh # for example
|
||||
bash util/run-gnu-test.sh tests/touch/not-owner.sh # for example
|
||||
# To run several tests:
|
||||
$ bash util/run-gnu-test.sh tests/touch/not-owner.sh tests/rm/no-give-up.sh # for example
|
||||
bash util/run-gnu-test.sh tests/touch/not-owner.sh tests/rm/no-give-up.sh # for example
|
||||
# If this is a perl (.pl) test, to run in debug:
|
||||
$ DEBUG=1 bash util/run-gnu-test.sh tests/misc/sm3sum.pl
|
||||
DEBUG=1 bash util/run-gnu-test.sh tests/misc/sm3sum.pl
|
||||
```
|
||||
|
||||
Note that it relies on individual utilities (not the multicall binary).
|
||||
|
@ -387,7 +405,6 @@ To improve the GNU compatibility, the following process is recommended:
|
|||
1. Start to modify the Rust implementation to match the expected behavior
|
||||
1. Add a test to make sure that we don't regress (our test suite is super quick)
|
||||
|
||||
|
||||
## Contributing
|
||||
|
||||
To contribute to uutils, please see [CONTRIBUTING](CONTRIBUTING.md).
|
||||
|
@ -395,11 +412,12 @@ To contribute to uutils, please see [CONTRIBUTING](CONTRIBUTING.md).
|
|||
## Utilities
|
||||
|
||||
Please note that this is not fully accurate:
|
||||
* Some new options can be added / removed in the GNU implementation;
|
||||
* Some error management might be missing;
|
||||
* Some behaviors might be different.
|
||||
|
||||
See https://github.com/uutils/coreutils/issues/3336 for the main meta bugs
|
||||
- Some new options can be added / removed in the GNU implementation;
|
||||
- Some error management might be missing;
|
||||
- Some behaviors might be different.
|
||||
|
||||
See <https://github.com/uutils/coreutils/issues/3336> for the main meta bugs
|
||||
(many are missing).
|
||||
|
||||
| Done | WIP |
|
||||
|
|
14
deny.toml
14
deny.toml
|
@ -58,12 +58,24 @@ highlight = "all"
|
|||
# For each duplicate dependency, indicate the name of the dependency which
|
||||
# introduces it.
|
||||
# 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
|
||||
|
||||
# This section is considered when running `cargo deny check sources`.
|
||||
# More documentation about the 'sources' section can be found here:
|
||||
# https://embarkstudios.github.io/cargo-deny/checks/sources/cfg.html
|
||||
|
||||
[sources]
|
||||
unknown-registry = "warn"
|
||||
unknown-git = "warn"
|
||||
|
|
|
@ -1 +1,3 @@
|
|||
<!-- markdownlint-disable MD041 -->
|
||||
|
||||
{{ #include ../../CONTRIBUTING.md }}
|
|
@ -1,5 +1,9 @@
|
|||
<!-- markdownlint-disable MD041 -->
|
||||
|
||||
{{#include logo.svg}}
|
||||
|
||||
<!-- markdownlint-disable MD033 -->
|
||||
|
||||
<style>
|
||||
/* Make the logo a bit bigger and center */
|
||||
#logo {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<!-- spell-checker:ignore pacman pamac nixpkgs -->
|
||||
<!-- spell-checker:ignore pacman pamac nixpkgs openmandriva -->
|
||||
|
||||
# Installation
|
||||
|
||||
|
@ -11,9 +11,10 @@ You can also [build uutils from source](/build.md).
|
|||
<!-- toc -->
|
||||
|
||||
## Cargo
|
||||
|
||||
[](https://repology.org/project/uutils-coreutils/versions)
|
||||
|
||||
```bash
|
||||
```shell
|
||||
# Linux
|
||||
cargo install coreutils --features unix
|
||||
# MacOs
|
||||
|
@ -23,11 +24,12 @@ cargo install coreutils --features windows
|
|||
```
|
||||
|
||||
## Linux
|
||||
|
||||
### Alpine
|
||||
|
||||
[](https://pkgs.alpinelinux.org/packages?name=uutils-coreutils)
|
||||
|
||||
```bash
|
||||
```shell
|
||||
apk update uutils-coreutils
|
||||
```
|
||||
|
||||
|
@ -37,7 +39,7 @@ apk update uutils-coreutils
|
|||
|
||||
[](https://archlinux.org/packages/community/x86_64/uutils-coreutils/)
|
||||
|
||||
```bash
|
||||
```shell
|
||||
pacman -S uutils-coreutils
|
||||
```
|
||||
|
||||
|
@ -45,7 +47,7 @@ pacman -S uutils-coreutils
|
|||
|
||||
[](https://packages.debian.org/sid/source/rust-coreutils)
|
||||
|
||||
```bash
|
||||
```shell
|
||||
apt install rust-coreutils
|
||||
# To use it:
|
||||
export PATH=/usr/lib/cargo/bin/coreutils:$PATH
|
||||
|
@ -55,35 +57,45 @@ export PATH=/usr/lib/cargo/bin/coreutils:$PATH
|
|||
|
||||
### Gentoo
|
||||
|
||||
[](https://packages.gentoo.org/packages/sys-apps/uutils)
|
||||
[](https://packages.gentoo.org/packages/sys-apps/uutils-coreutils)
|
||||
|
||||
```bash
|
||||
emerge -pv sys-apps/uutils
|
||||
```shell
|
||||
emerge -pv sys-apps/uutils-coreutils
|
||||
```
|
||||
|
||||
### Manjaro
|
||||
|
||||

|
||||
[](https://repology.org/project/uutils-coreutils/versions)
|
||||
[](https://repology.org/project/uutils-coreutils/versions)
|
||||
|
||||
```bash
|
||||
```shell
|
||||
pacman -S uutils-coreutils
|
||||
# or
|
||||
pamac install uutils-coreutils
|
||||
```
|
||||
|
||||
### NixOS
|
||||
|
||||
[](https://repology.org/project/uutils-coreutils/versions)
|
||||
|
||||
```bash
|
||||
```shell
|
||||
nix-env -iA nixos.uutils-coreutils
|
||||
```
|
||||
|
||||
### OpenMandriva Lx
|
||||
|
||||
[](https://repology.org/project/uutils-coreutils/versions)
|
||||
|
||||
```shell
|
||||
dnf install uutils-coreutils
|
||||
```
|
||||
|
||||
### Ubuntu
|
||||
|
||||
[](https://packages.ubuntu.com/source/lunar/rust-coreutils)
|
||||
|
||||
```bash
|
||||
```shell
|
||||
apt install rust-coreutils
|
||||
# To use it:
|
||||
export PATH=/usr/lib/cargo/bin/coreutils:$PATH
|
||||
|
@ -94,25 +106,36 @@ export PATH=/usr/lib/cargo/bin/coreutils:$PATH
|
|||
## MacOS
|
||||
|
||||
### Homebrew
|
||||
|
||||
[](https://formulae.brew.sh/formula/uutils-coreutils)
|
||||
|
||||
```bash
|
||||
```shell
|
||||
brew install uutils-coreutils
|
||||
```
|
||||
|
||||
### MacPorts
|
||||
|
||||
[](https://ports.macports.org/port/coreutils-uutils/)
|
||||
|
||||
```
|
||||
port install coreutils-uutils
|
||||
```
|
||||
|
||||
## FreeBSD
|
||||
|
||||
[](https://repology.org/project/uutils-coreutils/versions)
|
||||
|
||||
```sh
|
||||
pkg install uutils
|
||||
```
|
||||
|
||||
## Windows
|
||||
|
||||
### Scoop
|
||||
|
||||
[](https://scoop.sh/#/apps?q=uutils-coreutils&s=0&d=1&o=true)
|
||||
|
||||
```bash
|
||||
```shell
|
||||
scoop install uutils-coreutils
|
||||
```
|
||||
|
||||
|
@ -122,4 +145,6 @@ scoop install uutils-coreutils
|
|||
|
||||
[](https://aur.archlinux.org/packages/coreutils-hybrid)
|
||||
|
||||
A GNU coreutils / uutils coreutils hybrid package. Uses stable uutils programs mixed with GNU counterparts if uutils counterpart is unfinished or buggy.
|
||||
A GNU coreutils / uutils coreutils hybrid package. Uses stable uutils
|
||||
programs mixed with GNU counterparts if uutils counterpart is
|
||||
unfinished or buggy.
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
# Multi-call binary
|
||||
|
||||
uutils includes a multi-call binary from which the utils can be invoked. This
|
||||
reduces the binary size of the binary and can be useful for portability.
|
||||
|
||||
|
@ -12,6 +13,7 @@ coreutils [util] [util options]
|
|||
The `--help` flag will print a list of available utils.
|
||||
|
||||
## Example
|
||||
```
|
||||
|
||||
```shell
|
||||
coreutils ls -l
|
||||
```
|
|
@ -1,5 +1,7 @@
|
|||
# GNU Test Coverage
|
||||
|
||||
<!-- markdownlint-disable MD033 -->
|
||||
|
||||
uutils is actively tested against the GNU coreutils test suite. The results
|
||||
below are automatically updated every day.
|
||||
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
// For the full copyright and license information, please view the LICENSE
|
||||
// file that was distributed with this source code.
|
||||
|
||||
// spell-checker:ignore manpages mangen
|
||||
|
||||
use clap::{Arg, Command};
|
||||
use clap_complete::Shell;
|
||||
use std::cmp;
|
||||
|
@ -90,6 +92,10 @@ fn main() {
|
|||
gen_completions(args, &utils);
|
||||
}
|
||||
|
||||
if util == "manpage" {
|
||||
gen_manpage(args, &utils);
|
||||
}
|
||||
|
||||
match utils.get(util) {
|
||||
Some(&(uumain, _)) => {
|
||||
process::exit(uumain((vec![util_os].into_iter()).chain(args)));
|
||||
|
@ -167,6 +173,39 @@ fn gen_completions<T: uucore::Args>(
|
|||
process::exit(0);
|
||||
}
|
||||
|
||||
/// Generate the manpage for the utility in the first parameter
|
||||
fn gen_manpage<T: uucore::Args>(
|
||||
args: impl Iterator<Item = OsString>,
|
||||
util_map: &UtilityMap<T>,
|
||||
) -> ! {
|
||||
let all_utilities: Vec<_> = std::iter::once("coreutils")
|
||||
.chain(util_map.keys().copied())
|
||||
.collect();
|
||||
|
||||
let matches = Command::new("manpage")
|
||||
.about("Prints manpage to stdout")
|
||||
.arg(
|
||||
Arg::new("utility")
|
||||
.value_parser(clap::builder::PossibleValuesParser::new(all_utilities))
|
||||
.required(true),
|
||||
)
|
||||
.get_matches_from(std::iter::once(OsString::from("manpage")).chain(args));
|
||||
|
||||
let utility = matches.get_one::<String>("utility").unwrap();
|
||||
|
||||
let command = if utility == "coreutils" {
|
||||
gen_coreutils_app(util_map)
|
||||
} else {
|
||||
util_map.get(utility).unwrap().1()
|
||||
};
|
||||
|
||||
let man = clap_mangen::Man::new(command);
|
||||
man.render(&mut io::stdout())
|
||||
.expect("Man page generation failed");
|
||||
io::stdout().flush().unwrap();
|
||||
process::exit(0);
|
||||
}
|
||||
|
||||
fn gen_coreutils_app<T: uucore::Args>(util_map: &UtilityMap<T>) -> Command {
|
||||
let mut command = Command::new("coreutils");
|
||||
for (_, (_, sub_app)) in util_map {
|
||||
|
|
11
src/uu/arch/arch.md
Normal file
11
src/uu/arch/arch.md
Normal file
|
@ -0,0 +1,11 @@
|
|||
# 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 uucore::error::{FromIo, UResult};
|
||||
use uucore::{help_about, help_section};
|
||||
|
||||
static ABOUT: &str = "Display machine architecture";
|
||||
static SUMMARY: &str = "Determine architecture name for current machine.";
|
||||
static ABOUT: &str = help_about!("arch.md");
|
||||
static SUMMARY: &str = help_section!("after help", "arch.md");
|
||||
|
||||
#[uucore::main]
|
||||
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||
|
|
|
@ -7,8 +7,8 @@ base32 [OPTION]... [FILE]
|
|||
encode/decode data and print to standard output
|
||||
With no FILE, or when FILE is -, read standard input.
|
||||
|
||||
The data are encoded as described for the base32 alphabet in RFC
|
||||
4648. When decoding, the input may contain newlines in addition
|
||||
The data are encoded as described for the base32 alphabet in RFC 4648.
|
||||
When decoding, the input may contain newlines in addition
|
||||
to the bytes of the formal base32 alphabet. Use --ignore-garbage
|
||||
to attempt to recover from any other non-alphabet bytes in the
|
||||
encoded stream.
|
||||
|
|
|
@ -7,8 +7,8 @@ base64 [OPTION]... [FILE]
|
|||
encode/decode data and print to standard output
|
||||
With no FILE, or when FILE is -, read standard input.
|
||||
|
||||
The data are encoded as described for the base64 alphabet in RFC
|
||||
3548. When decoding, the input may contain newlines in addition
|
||||
The data are encoded as described for the base64 alphabet in RFC 3548.
|
||||
When decoding, the input may contain newlines in addition
|
||||
to the bytes of the formal base64 alphabet. Use --ignore-garbage
|
||||
to attempt to recover from any other non-alphabet bytes in the
|
||||
encoded stream.
|
||||
|
|
9
src/uu/basename/basename.md
Normal file
9
src/uu/basename/basename.md
Normal file
|
@ -0,0 +1,9 @@
|
|||
# basename
|
||||
|
||||
```
|
||||
basename NAME [SUFFIX]
|
||||
basename OPTION... NAME...
|
||||
```
|
||||
|
||||
Print NAME with any leading directory components removed
|
||||
If specified, also remove a trailing SUFFIX
|
|
@ -11,13 +11,11 @@ use clap::{crate_version, Arg, ArgAction, Command};
|
|||
use std::path::{is_separator, PathBuf};
|
||||
use uucore::display::Quotable;
|
||||
use uucore::error::{UResult, UUsageError};
|
||||
use uucore::format_usage;
|
||||
use uucore::{format_usage, help_about, help_usage};
|
||||
|
||||
static ABOUT: &str = r#"Print NAME with any leading directory components removed
|
||||
If specified, also remove a trailing SUFFIX"#;
|
||||
static ABOUT: &str = help_about!("basename.md");
|
||||
|
||||
const USAGE: &str = "{} NAME [SUFFIX]
|
||||
{} OPTION... NAME...";
|
||||
const USAGE: &str = help_usage!("basename.md");
|
||||
|
||||
pub mod options {
|
||||
pub static MULTIPLE: &str = "multiple";
|
||||
|
|
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 uucore::error::UClapError;
|
||||
|
||||
static ABOUT: &str = "\
|
||||
Encode/decode data and print to standard output
|
||||
With no FILE, or when FILE is -, read standard input.
|
||||
use uucore::{help_about, help_usage};
|
||||
|
||||
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.
|
||||
";
|
||||
const ABOUT: &str = help_about!("basenc.md");
|
||||
const USAGE: &str = help_usage!("basenc.md");
|
||||
|
||||
const ENCODINGS: &[(&str, Format)] = &[
|
||||
("base64", Format::Base64),
|
||||
|
@ -39,8 +35,6 @@ const ENCODINGS: &[(&str, Format)] = &[
|
|||
("z85", Format::Z85),
|
||||
];
|
||||
|
||||
const USAGE: &str = "{} [OPTION]... [FILE]";
|
||||
|
||||
pub fn uu_app() -> Command {
|
||||
let mut command = base_common::base_app(ABOUT, USAGE);
|
||||
for encoding in ENCODINGS {
|
||||
|
|
|
@ -17,7 +17,7 @@ path = "src/cat.rs"
|
|||
[dependencies]
|
||||
clap = { workspace=true }
|
||||
thiserror = { workspace = true }
|
||||
atty = { workspace=true }
|
||||
is-terminal = { workspace = true }
|
||||
uucore = { workspace=true, features=["fs", "pipes"] }
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
|
||||
// last synced with: cat (GNU coreutils) 8.13
|
||||
use clap::{crate_version, Arg, ArgAction, Command};
|
||||
use is_terminal::IsTerminal;
|
||||
use std::fs::{metadata, File};
|
||||
use std::io::{self, Read, Write};
|
||||
use thiserror::Error;
|
||||
|
@ -332,7 +333,7 @@ fn cat_path(
|
|||
let stdin = io::stdin();
|
||||
let mut handle = InputHandle {
|
||||
reader: stdin,
|
||||
is_interactive: atty::is(atty::Stream::Stdin),
|
||||
is_interactive: std::io::stdin().is_terminal(),
|
||||
};
|
||||
cat_handle(&mut handle, options, state)
|
||||
}
|
||||
|
|
11
src/uu/chcon/chcon.md
Normal file
11
src/uu/chcon/chcon.md
Normal file
|
@ -0,0 +1,11 @@
|
|||
<!-- spell-checker:ignore (vars) RFILE -->
|
||||
# chcon
|
||||
|
||||
```
|
||||
chcon [OPTION]... CONTEXT FILE...
|
||||
chcon [OPTION]... [-u USER] [-r ROLE] [-l RANGE] [-t TYPE] FILE...
|
||||
chcon [OPTION]... --reference=RFILE FILE...
|
||||
```
|
||||
|
||||
Change the SELinux security context of each FILE to CONTEXT.
|
||||
With --reference, change the security context of each FILE to that of RFILE.
|
|
@ -1,13 +1,11 @@
|
|||
// spell-checker:ignore (vars) RFILE
|
||||
|
||||
#![allow(clippy::upper_case_acronyms)]
|
||||
|
||||
use clap::builder::ValueParser;
|
||||
use uucore::error::{UResult, USimpleError, UUsageError};
|
||||
use uucore::format_usage;
|
||||
use uucore::{display::Quotable, show_error, show_warning};
|
||||
use uucore::{display::Quotable, format_usage, help_about, help_usage, show_error, show_warning};
|
||||
|
||||
use clap::{Arg, ArgAction, Command};
|
||||
use clap::{crate_version, Arg, ArgAction, Command};
|
||||
use selinux::{OpaqueSecurityContext, SecurityContext};
|
||||
|
||||
use std::borrow::Cow;
|
||||
|
@ -21,13 +19,8 @@ mod fts;
|
|||
|
||||
use errors::*;
|
||||
|
||||
static VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
static ABOUT: &str = "Change the SELinux security context of each FILE to CONTEXT. \n\
|
||||
With --reference, change the security context of each FILE to that of RFILE.";
|
||||
const USAGE: &str = "\
|
||||
{} [OPTION]... CONTEXT FILE... \n \
|
||||
{} [OPTION]... [-u USER] [-r ROLE] [-l RANGE] [-t TYPE] FILE... \n \
|
||||
{} [OPTION]... --reference=RFILE FILE...";
|
||||
const ABOUT: &str = help_about!("chcon.md");
|
||||
const USAGE: &str = help_usage!("chcon.md");
|
||||
|
||||
pub mod options {
|
||||
pub static HELP: &str = "help";
|
||||
|
@ -152,7 +145,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
|||
|
||||
pub fn uu_app() -> Command {
|
||||
Command::new(uucore::util_name())
|
||||
.version(VERSION)
|
||||
.version(crate_version!())
|
||||
.about(ABOUT)
|
||||
.override_usage(format_usage(USAGE))
|
||||
.infer_long_args(true)
|
||||
|
|
10
src/uu/chgrp/chgrp.md
Normal file
10
src/uu/chgrp/chgrp.md
Normal file
|
@ -0,0 +1,10 @@
|
|||
<!-- spell-checker:ignore (vars) RFILE -->
|
||||
|
||||
# chgrp
|
||||
|
||||
```
|
||||
chgrp [OPTION]... GROUP FILE...
|
||||
[OPTION]... --reference=RFILE FILE...
|
||||
```
|
||||
|
||||
Change the group of each FILE to GROUP.
|
|
@ -10,20 +10,16 @@
|
|||
use uucore::display::Quotable;
|
||||
pub use uucore::entries;
|
||||
use uucore::error::{FromIo, UResult, USimpleError};
|
||||
use uucore::format_usage;
|
||||
use uucore::perms::{chown_base, options, IfFrom};
|
||||
use uucore::{format_usage, help_about, help_usage};
|
||||
|
||||
use clap::{Arg, ArgAction, ArgMatches, Command};
|
||||
use clap::{crate_version, Arg, ArgAction, ArgMatches, Command};
|
||||
|
||||
use std::fs;
|
||||
use std::os::unix::fs::MetadataExt;
|
||||
|
||||
static ABOUT: &str = "Change the group of each FILE to GROUP.";
|
||||
static VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
|
||||
const USAGE: &str = "\
|
||||
{} [OPTION]... GROUP FILE...\n \
|
||||
{} [OPTION]... --reference=RFILE FILE...";
|
||||
static ABOUT: &str = help_about!("chgrp.md");
|
||||
const USAGE: &str = help_usage!("chgrp.md");
|
||||
|
||||
fn parse_gid_and_uid(matches: &ArgMatches) -> UResult<(Option<u32>, Option<u32>, IfFrom)> {
|
||||
let dest_gid = if let Some(file) = matches.get_one::<String>(options::REFERENCE) {
|
||||
|
@ -59,7 +55,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
|||
|
||||
pub fn uu_app() -> Command {
|
||||
Command::new(uucore::util_name())
|
||||
.version(VERSION)
|
||||
.version(crate_version!())
|
||||
.about(ABOUT)
|
||||
.override_usage(format_usage(USAGE))
|
||||
.infer_long_args(true)
|
||||
|
|
16
src/uu/chmod/chmod.md
Normal file
16
src/uu/chmod/chmod.md
Normal file
|
@ -0,0 +1,16 @@
|
|||
<!-- spell-checker:ignore RFILE ugoa -->
|
||||
|
||||
# chmod
|
||||
|
||||
```
|
||||
chmod [OPTION]... MODE[,MODE]... FILE...
|
||||
chmod [OPTION]... OCTAL-MODE FILE...
|
||||
chmod [OPTION]... --reference=RFILE FILE...
|
||||
```
|
||||
|
||||
Change the mode of each FILE to MODE.
|
||||
With --reference, change the mode of each FILE to that of RFILE.
|
||||
|
||||
## After Help
|
||||
|
||||
Each MODE is of the form '[ugoa]*([-+=]([rwxXst]*|[ugo]))+|[-+=]?[0-7]+'.
|
|
@ -17,16 +17,11 @@ use uucore::fs::display_permissions_unix;
|
|||
use uucore::libc::mode_t;
|
||||
#[cfg(not(windows))]
|
||||
use uucore::mode;
|
||||
use uucore::{format_usage, show, show_error};
|
||||
use uucore::{format_usage, help_about, help_section, help_usage, show, show_error};
|
||||
|
||||
const ABOUT: &str = "Change the mode of each FILE to MODE.\n\
|
||||
With --reference, change the mode of each FILE to that of RFILE.";
|
||||
const USAGE: &str = "\
|
||||
{} [OPTION]... MODE[,MODE]... FILE...
|
||||
{} [OPTION]... OCTAL-MODE FILE...
|
||||
{} [OPTION]... --reference=RFILE FILE...";
|
||||
const LONG_USAGE: &str =
|
||||
"Each MODE is of the form '[ugoa]*([-+=]([rwxXst]*|[ugo]))+|[-+=]?[0-7]+'.";
|
||||
const ABOUT: &str = help_about!("chmod.md");
|
||||
const USAGE: &str = help_usage!("chmod.md");
|
||||
const LONG_USAGE: &str = help_section!("after help", "chmod.md");
|
||||
|
||||
mod options {
|
||||
pub const CHANGES: &str = "changes";
|
||||
|
@ -110,6 +105,7 @@ pub fn uu_app() -> Command {
|
|||
.version(crate_version!())
|
||||
.about(ABOUT)
|
||||
.override_usage(format_usage(USAGE))
|
||||
.args_override_self(true)
|
||||
.infer_long_args(true)
|
||||
.arg(
|
||||
Arg::new(options::CHANGES)
|
||||
|
@ -190,16 +186,18 @@ impl Chmoder {
|
|||
let file = Path::new(filename);
|
||||
if !file.exists() {
|
||||
if file.is_symlink() {
|
||||
println!(
|
||||
"failed to change mode of {} from 0000 (---------) to 0000 (---------)",
|
||||
filename.quote()
|
||||
);
|
||||
if !self.quiet {
|
||||
show!(USimpleError::new(
|
||||
1,
|
||||
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 {
|
||||
show!(USimpleError::new(
|
||||
1,
|
||||
|
|
9
src/uu/chown/chown.md
Normal file
9
src/uu/chown/chown.md
Normal file
|
@ -0,0 +1,9 @@
|
|||
<!-- spell-checker:ignore RFILE -->
|
||||
# chown
|
||||
|
||||
```
|
||||
chown [OPTION]... [OWNER][:[GROUP]] FILE...
|
||||
chown [OPTION]... --reference=RFILE FILE...
|
||||
```
|
||||
|
||||
Change file owner and group
|
|
@ -9,8 +9,8 @@
|
|||
|
||||
use uucore::display::Quotable;
|
||||
pub use uucore::entries::{self, Group, Locate, Passwd};
|
||||
use uucore::format_usage;
|
||||
use uucore::perms::{chown_base, options, IfFrom};
|
||||
use uucore::{format_usage, help_about, help_usage};
|
||||
|
||||
use uucore::error::{FromIo, UResult, USimpleError};
|
||||
|
||||
|
@ -19,11 +19,9 @@ use clap::{crate_version, Arg, ArgAction, ArgMatches, Command};
|
|||
use std::fs;
|
||||
use std::os::unix::fs::MetadataExt;
|
||||
|
||||
static ABOUT: &str = "Change file owner and group";
|
||||
static ABOUT: &str = help_about!("chown.md");
|
||||
|
||||
const USAGE: &str = "\
|
||||
{} [OPTION]... [OWNER][:[GROUP]] FILE...
|
||||
{} [OPTION]... --reference=RFILE FILE...";
|
||||
const USAGE: &str = help_usage!("chown.md");
|
||||
|
||||
fn parse_gid_uid_and_filter(matches: &ArgMatches) -> UResult<(Option<u32>, Option<u32>, IfFrom)> {
|
||||
let filter = if let Some(spec) = matches.get_one::<String>(options::FROM) {
|
||||
|
|
|
@ -16,7 +16,8 @@ path = "src/cksum.rs"
|
|||
|
||||
[dependencies]
|
||||
clap = { workspace=true }
|
||||
uucore = { workspace=true }
|
||||
uucore = { workspace=true, features=["sum"] }
|
||||
hex = { workspace=true }
|
||||
|
||||
[[bin]]
|
||||
name = "cksum"
|
||||
|
|
|
@ -5,134 +5,244 @@
|
|||
// For the full copyright and license information, please view the LICENSE
|
||||
// file that was distributed with this source code.
|
||||
|
||||
// spell-checker:ignore (ToDO) fname
|
||||
// spell-checker:ignore (ToDO) fname, algo
|
||||
use clap::{crate_version, Arg, Command};
|
||||
use hex::encode;
|
||||
use std::ffi::OsStr;
|
||||
use std::fs::File;
|
||||
use std::io::{self, stdin, BufReader, Read};
|
||||
use std::iter;
|
||||
use std::path::Path;
|
||||
use uucore::display::Quotable;
|
||||
use uucore::error::{FromIo, UResult};
|
||||
use uucore::{format_usage, show};
|
||||
|
||||
// NOTE: CRC_TABLE_LEN *must* be <= 256 as we cast 0..CRC_TABLE_LEN to u8
|
||||
const CRC_TABLE_LEN: usize = 256;
|
||||
const CRC_TABLE: [u32; CRC_TABLE_LEN] = generate_crc_table();
|
||||
use uucore::{
|
||||
error::{FromIo, UResult},
|
||||
format_usage,
|
||||
sum::{
|
||||
div_ceil, Blake2b, Digest, DigestWriter, Md5, Sha1, Sha224, Sha256, Sha384, Sha512, Sm3,
|
||||
BSD, CRC, SYSV,
|
||||
},
|
||||
};
|
||||
|
||||
const USAGE: &str = "{} [OPTIONS] [FILE]...";
|
||||
const ABOUT: &str = "Print CRC and size for each file";
|
||||
|
||||
const fn generate_crc_table() -> [u32; CRC_TABLE_LEN] {
|
||||
let mut table = [0; CRC_TABLE_LEN];
|
||||
const ALGORITHM_OPTIONS_SYSV: &str = "sysv";
|
||||
const ALGORITHM_OPTIONS_BSD: &str = "bsd";
|
||||
const ALGORITHM_OPTIONS_CRC: &str = "crc";
|
||||
const ALGORITHM_OPTIONS_MD5: &str = "md5";
|
||||
const ALGORITHM_OPTIONS_SHA1: &str = "sha1";
|
||||
const ALGORITHM_OPTIONS_SHA224: &str = "sha224";
|
||||
const ALGORITHM_OPTIONS_SHA256: &str = "sha256";
|
||||
const ALGORITHM_OPTIONS_SHA384: &str = "sha384";
|
||||
const ALGORITHM_OPTIONS_SHA512: &str = "sha512";
|
||||
const ALGORITHM_OPTIONS_BLAKE2B: &str = "blake2b";
|
||||
const ALGORITHM_OPTIONS_SM3: &str = "sm3";
|
||||
|
||||
let mut i = 0;
|
||||
while i < CRC_TABLE_LEN {
|
||||
table[i] = crc_entry(i as u8);
|
||||
|
||||
i += 1;
|
||||
fn detect_algo(program: &str) -> (&'static str, Box<dyn Digest + 'static>, usize) {
|
||||
match program {
|
||||
ALGORITHM_OPTIONS_SYSV => (
|
||||
ALGORITHM_OPTIONS_SYSV,
|
||||
Box::new(SYSV::new()) as Box<dyn Digest>,
|
||||
512,
|
||||
),
|
||||
ALGORITHM_OPTIONS_BSD => (
|
||||
ALGORITHM_OPTIONS_BSD,
|
||||
Box::new(BSD::new()) as Box<dyn Digest>,
|
||||
1024,
|
||||
),
|
||||
ALGORITHM_OPTIONS_CRC => (
|
||||
ALGORITHM_OPTIONS_CRC,
|
||||
Box::new(CRC::new()) as Box<dyn Digest>,
|
||||
256,
|
||||
),
|
||||
ALGORITHM_OPTIONS_MD5 => (
|
||||
ALGORITHM_OPTIONS_MD5,
|
||||
Box::new(Md5::new()) as Box<dyn Digest>,
|
||||
128,
|
||||
),
|
||||
ALGORITHM_OPTIONS_SHA1 => (
|
||||
ALGORITHM_OPTIONS_SHA1,
|
||||
Box::new(Sha1::new()) as Box<dyn Digest>,
|
||||
160,
|
||||
),
|
||||
ALGORITHM_OPTIONS_SHA224 => (
|
||||
ALGORITHM_OPTIONS_SHA224,
|
||||
Box::new(Sha224::new()) as Box<dyn Digest>,
|
||||
224,
|
||||
),
|
||||
ALGORITHM_OPTIONS_SHA256 => (
|
||||
ALGORITHM_OPTIONS_SHA256,
|
||||
Box::new(Sha256::new()) as Box<dyn Digest>,
|
||||
256,
|
||||
),
|
||||
ALGORITHM_OPTIONS_SHA384 => (
|
||||
ALGORITHM_OPTIONS_SHA384,
|
||||
Box::new(Sha384::new()) as Box<dyn Digest>,
|
||||
384,
|
||||
),
|
||||
ALGORITHM_OPTIONS_SHA512 => (
|
||||
ALGORITHM_OPTIONS_SHA512,
|
||||
Box::new(Sha512::new()) as Box<dyn Digest>,
|
||||
512,
|
||||
),
|
||||
ALGORITHM_OPTIONS_BLAKE2B => (
|
||||
ALGORITHM_OPTIONS_BLAKE2B,
|
||||
Box::new(Blake2b::new()) as Box<dyn Digest>,
|
||||
512,
|
||||
),
|
||||
ALGORITHM_OPTIONS_SM3 => (
|
||||
ALGORITHM_OPTIONS_SM3,
|
||||
Box::new(Sm3::new()) as Box<dyn Digest>,
|
||||
512,
|
||||
),
|
||||
_ => unreachable!("unknown algorithm: clap should have prevented this case"),
|
||||
}
|
||||
}
|
||||
|
||||
table
|
||||
struct Options {
|
||||
algo_name: &'static str,
|
||||
digest: Box<dyn Digest + 'static>,
|
||||
output_bits: usize,
|
||||
}
|
||||
|
||||
const fn crc_entry(input: u8) -> u32 {
|
||||
let mut crc = (input as u32) << 24;
|
||||
|
||||
let mut i = 0;
|
||||
while i < 8 {
|
||||
let if_condition = crc & 0x8000_0000;
|
||||
let if_body = (crc << 1) ^ 0x04c1_1db7;
|
||||
let else_body = crc << 1;
|
||||
|
||||
// NOTE: i feel like this is easier to understand than emulating an if statement in bitwise
|
||||
// ops
|
||||
let condition_table = [else_body, if_body];
|
||||
|
||||
crc = condition_table[(if_condition != 0) as usize];
|
||||
i += 1;
|
||||
}
|
||||
|
||||
crc
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn crc_update(crc: u32, input: u8) -> u32 {
|
||||
(crc << 8) ^ CRC_TABLE[((crc >> 24) as usize ^ input as usize) & 0xFF]
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn crc_final(mut crc: u32, mut length: usize) -> u32 {
|
||||
while length != 0 {
|
||||
crc = crc_update(crc, length as u8);
|
||||
length >>= 8;
|
||||
}
|
||||
|
||||
!crc
|
||||
}
|
||||
|
||||
fn init_byte_array() -> Vec<u8> {
|
||||
vec![0; 1024 * 1024]
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn cksum(fname: &str) -> io::Result<(u32, usize)> {
|
||||
let mut crc = 0u32;
|
||||
let mut size = 0usize;
|
||||
|
||||
let mut rd: Box<dyn Read> = match fname {
|
||||
"-" => Box::new(stdin()),
|
||||
_ => {
|
||||
let p = Path::new(fname);
|
||||
|
||||
// Directories should not give an error, but should be interpreted
|
||||
// as empty files to match GNU semantics.
|
||||
if p.is_dir() {
|
||||
/// Calculate checksum
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `options` - CLI options for the assigning checksum algorithm
|
||||
/// * `files` - A iterator of OsStr which is a bunch of files that are using for calculating checksum
|
||||
#[allow(clippy::cognitive_complexity)]
|
||||
fn cksum<'a, I>(mut options: Options, files: I) -> UResult<()>
|
||||
where
|
||||
I: Iterator<Item = &'a OsStr>,
|
||||
{
|
||||
for filename in files {
|
||||
let filename = Path::new(filename);
|
||||
let stdin_buf;
|
||||
let file_buf;
|
||||
let not_file = filename == OsStr::new("-");
|
||||
let mut file = BufReader::new(if not_file {
|
||||
stdin_buf = stdin();
|
||||
Box::new(stdin_buf) as Box<dyn Read>
|
||||
} else if filename.is_dir() {
|
||||
Box::new(BufReader::new(io::empty())) as Box<dyn Read>
|
||||
} else {
|
||||
Box::new(BufReader::new(File::open(p)?)) as Box<dyn Read>
|
||||
}
|
||||
}
|
||||
};
|
||||
file_buf =
|
||||
File::open(filename).map_err_context(|| filename.to_str().unwrap().to_string())?;
|
||||
Box::new(file_buf) as Box<dyn Read>
|
||||
});
|
||||
let (sum, sz) = digest_read(&mut options.digest, &mut file, options.output_bits)
|
||||
.map_err_context(|| "failed to read input".to_string())?;
|
||||
|
||||
let mut bytes = init_byte_array();
|
||||
loop {
|
||||
let num_bytes = rd.read(&mut bytes)?;
|
||||
if num_bytes == 0 {
|
||||
return Ok((crc_final(crc, size), size));
|
||||
// The BSD checksum output is 5 digit integer
|
||||
let bsd_width = 5;
|
||||
match (options.algo_name, not_file) {
|
||||
(ALGORITHM_OPTIONS_SYSV, true) => println!(
|
||||
"{} {}",
|
||||
sum.parse::<u16>().unwrap(),
|
||||
div_ceil(sz, options.output_bits)
|
||||
),
|
||||
(ALGORITHM_OPTIONS_SYSV, false) => println!(
|
||||
"{} {} {}",
|
||||
sum.parse::<u16>().unwrap(),
|
||||
div_ceil(sz, options.output_bits),
|
||||
filename.display()
|
||||
),
|
||||
(ALGORITHM_OPTIONS_BSD, true) => println!(
|
||||
"{:0bsd_width$} {:bsd_width$}",
|
||||
sum.parse::<u16>().unwrap(),
|
||||
div_ceil(sz, options.output_bits)
|
||||
),
|
||||
(ALGORITHM_OPTIONS_BSD, false) => println!(
|
||||
"{:0bsd_width$} {:bsd_width$} {}",
|
||||
sum.parse::<u16>().unwrap(),
|
||||
div_ceil(sz, options.output_bits),
|
||||
filename.display()
|
||||
),
|
||||
(_, true) => println!("{sum} {sz}"),
|
||||
(_, false) => println!("{sum} {sz} {}", filename.display()),
|
||||
}
|
||||
for &b in bytes[..num_bytes].iter() {
|
||||
crc = crc_update(crc, b);
|
||||
}
|
||||
size += num_bytes;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn digest_read<T: Read>(
|
||||
digest: &mut Box<dyn Digest>,
|
||||
reader: &mut BufReader<T>,
|
||||
output_bits: usize,
|
||||
) -> io::Result<(String, usize)> {
|
||||
digest.reset();
|
||||
|
||||
// Read bytes from `reader` and write those bytes to `digest`.
|
||||
//
|
||||
// If `binary` is `false` and the operating system is Windows, then
|
||||
// `DigestWriter` replaces "\r\n" with "\n" before it writes the
|
||||
// bytes into `digest`. Otherwise, it just inserts the bytes as-is.
|
||||
//
|
||||
// In order to support replacing "\r\n", we must call `finalize()`
|
||||
// in order to support the possibility that the last character read
|
||||
// from the reader was "\r". (This character gets buffered by
|
||||
// `DigestWriter` and only written if the following character is
|
||||
// "\n". But when "\r" is the last character read, we need to force
|
||||
// it to be written.)
|
||||
let mut digest_writer = DigestWriter::new(digest, true);
|
||||
let output_size = std::io::copy(reader, &mut digest_writer)? as usize;
|
||||
digest_writer.finalize();
|
||||
|
||||
if digest.output_bits() > 0 {
|
||||
Ok((digest.result_str(), output_size))
|
||||
} else {
|
||||
// Assume it's SHAKE. result_str() doesn't work with shake (as of 8/30/2016)
|
||||
let mut bytes = Vec::new();
|
||||
bytes.resize((output_bits + 7) / 8, 0);
|
||||
digest.hash_finalize(&mut bytes);
|
||||
Ok((encode(bytes), output_size))
|
||||
}
|
||||
}
|
||||
|
||||
mod options {
|
||||
pub static FILE: &str = "file";
|
||||
pub static ALGORITHM: &str = "algorithm";
|
||||
}
|
||||
|
||||
const ALGORITHM_HELP_DESC: &str =
|
||||
"DIGEST determines the digest algorithm and default output format:\n\
|
||||
\n\
|
||||
-a=sysv: (equivalent to sum -s)\n\
|
||||
-a=bsd: (equivalent to sum -r)\n\
|
||||
-a=crc: (equivalent to cksum)\n\
|
||||
-a=md5: (equivalent to md5sum)\n\
|
||||
-a=sha1: (equivalent to sha1sum)\n\
|
||||
-a=sha224: (equivalent to sha224sum)\n\
|
||||
-a=sha256: (equivalent to sha256sum)\n\
|
||||
-a=sha384: (equivalent to sha384sum)\n\
|
||||
-a=sha512: (equivalent to sha512sum)\n\
|
||||
-a=blake2b: (equivalent to b2sum)\n\
|
||||
-a=sm3: (only available through cksum)\n";
|
||||
|
||||
#[uucore::main]
|
||||
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||
let args = args.collect_ignore();
|
||||
|
||||
let matches = uu_app().try_get_matches_from(args)?;
|
||||
|
||||
let files: Vec<String> = match matches.get_many::<String>(options::FILE) {
|
||||
Some(v) => v.clone().map(|v| v.to_owned()).collect(),
|
||||
None => vec![],
|
||||
let algo_name: &str = match matches.get_one::<String>(options::ALGORITHM) {
|
||||
Some(v) => v,
|
||||
None => ALGORITHM_OPTIONS_CRC,
|
||||
};
|
||||
|
||||
if files.is_empty() {
|
||||
let (crc, size) = cksum("-")?;
|
||||
println!("{crc} {size}");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
for fname in &files {
|
||||
match cksum(fname.as_ref()).map_err_context(|| format!("{}", fname.maybe_quote())) {
|
||||
Ok((crc, size)) => println!("{crc} {size} {fname}"),
|
||||
Err(err) => show!(err),
|
||||
let (name, algo, bits) = detect_algo(algo_name);
|
||||
let opts = Options {
|
||||
algo_name: name,
|
||||
digest: algo,
|
||||
output_bits: bits,
|
||||
};
|
||||
}
|
||||
|
||||
match matches.get_many::<String>(options::FILE) {
|
||||
Some(files) => cksum(opts, files.map(OsStr::new))?,
|
||||
None => cksum(opts, iter::once(OsStr::new("-")))?,
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -148,4 +258,25 @@ pub fn uu_app() -> Command {
|
|||
.action(clap::ArgAction::Append)
|
||||
.value_hint(clap::ValueHint::FilePath),
|
||||
)
|
||||
.arg(
|
||||
Arg::new(options::ALGORITHM)
|
||||
.long(options::ALGORITHM)
|
||||
.short('a')
|
||||
.help("select the digest type to use. See DIGEST below")
|
||||
.value_name("ALGORITHM")
|
||||
.value_parser([
|
||||
ALGORITHM_OPTIONS_SYSV,
|
||||
ALGORITHM_OPTIONS_BSD,
|
||||
ALGORITHM_OPTIONS_CRC,
|
||||
ALGORITHM_OPTIONS_MD5,
|
||||
ALGORITHM_OPTIONS_SHA1,
|
||||
ALGORITHM_OPTIONS_SHA224,
|
||||
ALGORITHM_OPTIONS_SHA256,
|
||||
ALGORITHM_OPTIONS_SHA384,
|
||||
ALGORITHM_OPTIONS_SHA512,
|
||||
ALGORITHM_OPTIONS_BLAKE2B,
|
||||
ALGORITHM_OPTIONS_SM3,
|
||||
]),
|
||||
)
|
||||
.after_help(ALGORITHM_HELP_DESC)
|
||||
}
|
||||
|
|
13
src/uu/comm/comm.md
Normal file
13
src/uu/comm/comm.md
Normal file
|
@ -0,0 +1,13 @@
|
|||
# comm
|
||||
|
||||
```
|
||||
comm [OPTION]... FILE1 FILE2
|
||||
```
|
||||
|
||||
Compare two sorted files line by line.
|
||||
|
||||
When FILE1 or FILE2 (not both) is -, read standard input.
|
||||
|
||||
With no options, produce three-column output. Column one contains
|
||||
lines unique to FILE1, column two contains lines unique to FILE2,
|
||||
and column three contains lines common to both files.
|
|
@ -8,18 +8,17 @@
|
|||
// spell-checker:ignore (ToDO) delim mkdelim
|
||||
|
||||
use std::cmp::Ordering;
|
||||
use std::fmt::Display;
|
||||
use std::fs::File;
|
||||
use std::io::{self, stdin, BufRead, BufReader, Stdin};
|
||||
use std::path::Path;
|
||||
use uucore::error::FromIo;
|
||||
use uucore::error::UResult;
|
||||
use uucore::format_usage;
|
||||
use uucore::error::{FromIo, UResult};
|
||||
use uucore::{format_usage, help_about, help_usage};
|
||||
|
||||
use clap::{crate_version, Arg, ArgAction, ArgMatches, Command};
|
||||
|
||||
static ABOUT: &str = "Compare two sorted files line by line";
|
||||
static LONG_HELP: &str = "";
|
||||
const USAGE: &str = "{} [OPTION]... FILE1 FILE2";
|
||||
const ABOUT: &str = help_about!("comm.md");
|
||||
const USAGE: &str = help_usage!("comm.md");
|
||||
|
||||
mod options {
|
||||
pub const COLUMN_1: &str = "1";
|
||||
|
@ -30,6 +29,7 @@ mod options {
|
|||
pub const FILE_1: &str = "FILE1";
|
||||
pub const FILE_2: &str = "FILE2";
|
||||
pub const TOTAL: &str = "total";
|
||||
pub const ZERO_TERMINATED: &str = "zero-terminated";
|
||||
}
|
||||
|
||||
fn column_width(col: &str, opts: &ArgMatches) -> usize {
|
||||
|
@ -40,23 +40,66 @@ fn column_width(col: &str, opts: &ArgMatches) -> usize {
|
|||
}
|
||||
}
|
||||
|
||||
fn ensure_nl(line: &mut String) {
|
||||
if !line.ends_with('\n') {
|
||||
line.push('\n');
|
||||
#[repr(u8)]
|
||||
#[derive(Clone, Copy)]
|
||||
enum LineEnding {
|
||||
Newline = b'\n',
|
||||
Nul = 0,
|
||||
}
|
||||
|
||||
impl From<LineEnding> for u8 {
|
||||
fn from(line_ending: LineEnding) -> Self {
|
||||
line_ending as Self
|
||||
}
|
||||
}
|
||||
|
||||
enum LineReader {
|
||||
impl From<bool> for LineEnding {
|
||||
fn from(is_zero_terminated: bool) -> Self {
|
||||
if is_zero_terminated {
|
||||
Self::Nul
|
||||
} else {
|
||||
Self::Newline
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for LineEnding {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Newline => writeln!(f),
|
||||
Self::Nul => write!(f, "\0"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum Input {
|
||||
Stdin(Stdin),
|
||||
FileIn(BufReader<File>),
|
||||
}
|
||||
|
||||
impl LineReader {
|
||||
fn read_line(&mut self, buf: &mut String) -> io::Result<usize> {
|
||||
match *self {
|
||||
Self::Stdin(ref mut r) => r.read_line(buf),
|
||||
Self::FileIn(ref mut r) => r.read_line(buf),
|
||||
struct LineReader {
|
||||
line_ending: LineEnding,
|
||||
input: Input,
|
||||
}
|
||||
|
||||
impl LineReader {
|
||||
fn new(input: Input, line_ending: LineEnding) -> Self {
|
||||
Self { input, line_ending }
|
||||
}
|
||||
|
||||
fn read_line(&mut self, buf: &mut Vec<u8>) -> io::Result<usize> {
|
||||
let line_ending = self.line_ending.into();
|
||||
|
||||
let result = match &mut self.input {
|
||||
Input::Stdin(r) => r.lock().read_until(line_ending, buf),
|
||||
Input::FileIn(r) => r.read_until(line_ending, buf),
|
||||
};
|
||||
|
||||
if !buf.ends_with(&[line_ending]) {
|
||||
buf.push(line_ending);
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -72,9 +115,9 @@ fn comm(a: &mut LineReader, b: &mut LineReader, opts: &ArgMatches) {
|
|||
let delim_col_2 = delim.repeat(width_col_1);
|
||||
let delim_col_3 = delim.repeat(width_col_1 + width_col_2);
|
||||
|
||||
let ra = &mut String::new();
|
||||
let ra = &mut Vec::new();
|
||||
let mut na = a.read_line(ra);
|
||||
let rb = &mut String::new();
|
||||
let rb = &mut Vec::new();
|
||||
let mut nb = b.read_line(rb);
|
||||
|
||||
let mut total_col_1 = 0;
|
||||
|
@ -97,8 +140,7 @@ fn comm(a: &mut LineReader, b: &mut LineReader, opts: &ArgMatches) {
|
|||
match ord {
|
||||
Ordering::Less => {
|
||||
if !opts.get_flag(options::COLUMN_1) {
|
||||
ensure_nl(ra);
|
||||
print!("{ra}");
|
||||
print!("{}", String::from_utf8_lossy(ra));
|
||||
}
|
||||
ra.clear();
|
||||
na = a.read_line(ra);
|
||||
|
@ -106,8 +148,7 @@ fn comm(a: &mut LineReader, b: &mut LineReader, opts: &ArgMatches) {
|
|||
}
|
||||
Ordering::Greater => {
|
||||
if !opts.get_flag(options::COLUMN_2) {
|
||||
ensure_nl(rb);
|
||||
print!("{delim_col_2}{rb}");
|
||||
print!("{delim_col_2}{}", String::from_utf8_lossy(rb));
|
||||
}
|
||||
rb.clear();
|
||||
nb = b.read_line(rb);
|
||||
|
@ -115,8 +156,7 @@ fn comm(a: &mut LineReader, b: &mut LineReader, opts: &ArgMatches) {
|
|||
}
|
||||
Ordering::Equal => {
|
||||
if !opts.get_flag(options::COLUMN_3) {
|
||||
ensure_nl(ra);
|
||||
print!("{delim_col_3}{ra}");
|
||||
print!("{delim_col_3}{}", String::from_utf8_lossy(ra));
|
||||
}
|
||||
ra.clear();
|
||||
rb.clear();
|
||||
|
@ -128,17 +168,20 @@ fn comm(a: &mut LineReader, b: &mut LineReader, opts: &ArgMatches) {
|
|||
}
|
||||
|
||||
if opts.get_flag(options::TOTAL) {
|
||||
println!("{total_col_1}{delim}{total_col_2}{delim}{total_col_3}{delim}total");
|
||||
let line_ending = LineEnding::from(opts.get_flag(options::ZERO_TERMINATED));
|
||||
print!("{total_col_1}{delim}{total_col_2}{delim}{total_col_3}{delim}total{line_ending}");
|
||||
}
|
||||
}
|
||||
|
||||
fn open_file(name: &str) -> io::Result<LineReader> {
|
||||
match name {
|
||||
"-" => Ok(LineReader::Stdin(stdin())),
|
||||
_ => {
|
||||
fn open_file(name: &str, line_ending: LineEnding) -> io::Result<LineReader> {
|
||||
if name == "-" {
|
||||
Ok(LineReader::new(Input::Stdin(stdin()), line_ending))
|
||||
} else {
|
||||
let f = File::open(Path::new(name))?;
|
||||
Ok(LineReader::FileIn(BufReader::new(f)))
|
||||
}
|
||||
Ok(LineReader::new(
|
||||
Input::FileIn(BufReader::new(f)),
|
||||
line_ending,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -147,10 +190,11 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
|||
let args = args.collect_lossy();
|
||||
|
||||
let matches = uu_app().try_get_matches_from(args)?;
|
||||
let line_ending = LineEnding::from(matches.get_flag(options::ZERO_TERMINATED));
|
||||
let filename1 = matches.get_one::<String>(options::FILE_1).unwrap();
|
||||
let filename2 = matches.get_one::<String>(options::FILE_2).unwrap();
|
||||
let mut f1 = open_file(filename1).map_err_context(|| filename1.to_string())?;
|
||||
let mut f2 = open_file(filename2).map_err_context(|| filename2.to_string())?;
|
||||
let mut f1 = open_file(filename1, line_ending).map_err_context(|| filename1.to_string())?;
|
||||
let mut f2 = open_file(filename2, line_ending).map_err_context(|| filename2.to_string())?;
|
||||
|
||||
comm(&mut f1, &mut f2, &matches);
|
||||
Ok(())
|
||||
|
@ -160,7 +204,6 @@ pub fn uu_app() -> Command {
|
|||
Command::new(uucore::util_name())
|
||||
.version(crate_version!())
|
||||
.about(ABOUT)
|
||||
.after_help(LONG_HELP)
|
||||
.override_usage(format_usage(USAGE))
|
||||
.infer_long_args(true)
|
||||
.arg(
|
||||
|
@ -189,6 +232,14 @@ pub fn uu_app() -> Command {
|
|||
.default_value(options::DELIMITER_DEFAULT)
|
||||
.hide_default_value(true),
|
||||
)
|
||||
.arg(
|
||||
Arg::new(options::ZERO_TERMINATED)
|
||||
.long(options::ZERO_TERMINATED)
|
||||
.short('z')
|
||||
.overrides_with(options::ZERO_TERMINATED)
|
||||
.help("line delimiter is NUL, not newline")
|
||||
.action(ArgAction::SetTrue),
|
||||
)
|
||||
.arg(
|
||||
Arg::new(options::FILE_1)
|
||||
.required(true)
|
||||
|
|
|
@ -1,18 +1,18 @@
|
|||
<!-- markdownlint-disable first-line-heading -->
|
||||
<!-- spell-checker:ignore (markdown) markdownlint -->
|
||||
|
||||
## Feature list
|
||||
# Feature list
|
||||
|
||||
<!-- spell-checker:ignore (options) linkgs reflink -->
|
||||
|
||||
### To Do
|
||||
## To Do
|
||||
|
||||
- [ ] cli-symbolic-links
|
||||
- [ ] context
|
||||
- [ ] copy-contents
|
||||
- [ ] sparse
|
||||
|
||||
### Completed
|
||||
## Completed
|
||||
|
||||
- [x] archive
|
||||
- [x] attributes-only
|
||||
|
|
|
@ -93,7 +93,7 @@ impl<'a> Context<'a> {
|
|||
fn new(root: &'a Path, target: &'a Path) -> std::io::Result<Self> {
|
||||
let current_dir = env::current_dir()?;
|
||||
let root_path = current_dir.join(root);
|
||||
let root_parent = if target.exists() {
|
||||
let root_parent = if target.exists() && !root.to_str().unwrap().ends_with("/.") {
|
||||
root_path.parent().map(|p| p.to_path_buf())
|
||||
} else {
|
||||
Some(root_path)
|
||||
|
|
|
@ -1,14 +1,13 @@
|
|||
## Benchmarking cut
|
||||
# Benchmarking cut
|
||||
|
||||
### Performance profile
|
||||
## Performance profile
|
||||
|
||||
In normal use cases a significant amount of the total execution time of `cut`
|
||||
is spent performing I/O. When invoked with the `-f` option (cut fields) some
|
||||
CPU time is spent on detecting fields (in `Searcher::next`). Other than that
|
||||
some small amount of CPU time is spent on breaking the input stream into lines.
|
||||
|
||||
|
||||
### How to
|
||||
## How to
|
||||
|
||||
When fixing bugs or adding features you might want to compare
|
||||
performance before and after your code changes.
|
||||
|
@ -16,11 +15,12 @@ performance before and after your code changes.
|
|||
- `hyperfine` can be used to accurately measure and compare the total
|
||||
execution time of one or more commands.
|
||||
|
||||
```
|
||||
$ cargo build --release --package uu_cut
|
||||
```shell
|
||||
cargo build --release --package uu_cut
|
||||
|
||||
$ hyperfine -w3 "./target/release/cut -f2-4,8 -d' ' input.txt" "cut -f2-4,8 -d' ' input.txt"
|
||||
hyperfine -w3 "./target/release/cut -f2-4,8 -d' ' input.txt" "cut -f2-4,8 -d' ' input.txt"
|
||||
```
|
||||
|
||||
You can put those two commands in a shell script to be sure that you don't
|
||||
forget to build after making any changes.
|
||||
|
||||
|
@ -29,12 +29,11 @@ function is called, and the amount of time it takes can be useful.
|
|||
|
||||
- `cargo flamegraph` generates flame graphs from function level metrics it records using `perf` or `dtrace`
|
||||
|
||||
```
|
||||
$ cargo flamegraph --bin cut --package uu_cut -- -f1,3-4 input.txt > /dev/null
|
||||
```shell
|
||||
cargo flamegraph --bin cut --package uu_cut -- -f1,3-4 input.txt > /dev/null
|
||||
```
|
||||
|
||||
|
||||
### What to benchmark
|
||||
## What to benchmark
|
||||
|
||||
There are four different performance paths in `cut` to benchmark.
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@ clap = { workspace=true }
|
|||
uucore = { workspace=true }
|
||||
memchr = { workspace=true }
|
||||
bstr = { workspace=true }
|
||||
atty = { workspace=true }
|
||||
is-terminal = { workspace=true }
|
||||
|
||||
[[bin]]
|
||||
name = "cut"
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
|
||||
use bstr::io::BufReadExt;
|
||||
use clap::{crate_version, Arg, ArgAction, Command};
|
||||
use is_terminal::IsTerminal;
|
||||
use std::fs::File;
|
||||
use std::io::{stdin, stdout, BufReader, BufWriter, Read, Write};
|
||||
use std::path::Path;
|
||||
|
@ -136,7 +137,7 @@ enum Mode {
|
|||
}
|
||||
|
||||
fn stdout_writer() -> Box<dyn Write> {
|
||||
if atty::is(atty::Stream::Stdout) {
|
||||
if std::io::stdout().is_terminal() {
|
||||
Box::new(stdout())
|
||||
} else {
|
||||
Box::new(BufWriter::new(stdout())) as Box<dyn Write>
|
||||
|
|
10
src/uu/date/date.md
Normal file
10
src/uu/date/date.md
Normal file
|
@ -0,0 +1,10 @@
|
|||
<!-- spell-checker:ignore Dhhmm -->
|
||||
|
||||
# date
|
||||
|
||||
```
|
||||
date [OPTION]... [+FORMAT]...
|
||||
date [OPTION]... [MMDDhhmm[[CC]YY][.ss]]
|
||||
```
|
||||
|
||||
Print or set the system date and time
|
|
@ -22,7 +22,7 @@ use uucore::display::Quotable;
|
|||
#[cfg(not(any(target_os = "macos", target_os = "redox")))]
|
||||
use uucore::error::FromIo;
|
||||
use uucore::error::{UResult, USimpleError};
|
||||
use uucore::{format_usage, show_error};
|
||||
use uucore::{format_usage, help_about, help_usage, show_error};
|
||||
#[cfg(windows)]
|
||||
use windows_sys::Win32::{Foundation::SYSTEMTIME, System::SystemInformation::SetSystemTime};
|
||||
|
||||
|
@ -36,10 +36,8 @@ const MINUTE: &str = "minute";
|
|||
const SECOND: &str = "second";
|
||||
const NS: &str = "ns";
|
||||
|
||||
const ABOUT: &str = "Print or set the system date and time";
|
||||
const USAGE: &str = "\
|
||||
{} [OPTION]... [+FORMAT]...
|
||||
{} [OPTION]... [MMDDhhmm[[CC]YY][.ss]]";
|
||||
const ABOUT: &str = help_about!("date.md");
|
||||
const USAGE: &str = help_usage!("date.md");
|
||||
|
||||
const OPT_DATE: &str = "date";
|
||||
const OPT_FORMAT: &str = "format";
|
||||
|
|
|
@ -45,7 +45,7 @@ be roughly equivalent to the total bytes copied (`blocksize` x `count`).
|
|||
|
||||
Some useful invocations for testing would be the following:
|
||||
|
||||
```
|
||||
```shell
|
||||
hyperfine "./target/release/dd bs=4k count=1000000 < /dev/zero > /dev/null"
|
||||
hyperfine "./target/release/dd bs=1M count=20000 < /dev/zero > /dev/null"
|
||||
hyperfine "./target/release/dd bs=1G count=10 < /dev/zero > /dev/null"
|
||||
|
@ -57,7 +57,7 @@ Typically you would choose a small blocksize for measuring the performance of
|
|||
typically does some set amount of work per block which only depends on the size
|
||||
of the block if conversions are used.
|
||||
|
||||
As an example, https://github.com/uutils/coreutils/pull/3600 made a change to
|
||||
As an example, <https://github.com/uutils/coreutils/pull/3600> made a change to
|
||||
reuse the same buffer between block copies, avoiding the need to reallocate a
|
||||
new block of memory for each copy. The impact of that change mostly had an
|
||||
impact on large block size copies because those are the circumstances where the
|
||||
|
|
176
src/uu/dd/dd.md
176
src/uu/dd/dd.md
|
@ -1,6 +1,7 @@
|
|||
<!-- spell-checker:ignore convs iseek oseek -->
|
||||
# dd
|
||||
|
||||
<!-- spell-checker:ignore convs iseek oseek -->
|
||||
|
||||
```
|
||||
dd [OPERAND]...
|
||||
dd OPTION
|
||||
|
@ -10,117 +11,116 @@ Copy, and optionally convert, a file system resource
|
|||
|
||||
## After Help
|
||||
|
||||
OPERANDS:
|
||||
### Operands
|
||||
|
||||
bs=BYTES read and write up to BYTES bytes at a time (default: 512);
|
||||
overwrites ibs and obs.
|
||||
cbs=BYTES the 'conversion block size' in bytes. Applies to
|
||||
the conv=block, and conv=unblock operations.
|
||||
conv=CONVS a comma-separated list of conversion options or
|
||||
(for legacy reasons) file flags.
|
||||
count=N stop reading input after N ibs-sized read operations rather
|
||||
than proceeding until EOF. See iflag=count_bytes if stopping
|
||||
after N bytes is preferred
|
||||
ibs=N the size of buffer used for reads (default: 512)
|
||||
if=FILE the file used for input. When not specified, stdin is used instead
|
||||
iflag=FLAGS a comma-separated list of input flags which specify how the input
|
||||
source is treated. FLAGS may be any of the input-flags or
|
||||
general-flags specified below.
|
||||
skip=N (or iseek=N) skip N ibs-sized records into input before beginning
|
||||
copy/convert operations. See iflag=seek_bytes if seeking N bytes
|
||||
is preferred.
|
||||
obs=N the size of buffer used for writes (default: 512)
|
||||
of=FILE the file used for output. When not specified, stdout is used
|
||||
- `Bs=BYTES` : read and write up to BYTES bytes at a time (default: 512);
|
||||
overwrites `ibs` and `obs`.
|
||||
- `cbs=BYTES` : the 'conversion block size' in bytes. Applies to the
|
||||
`conv=block`, and `conv=unblock` operations.
|
||||
- `conv=CONVS` : a comma-separated list of conversion options or (for legacy
|
||||
reasons) file flags.
|
||||
- `count=N` : stop reading input after N ibs-sized read operations rather
|
||||
than proceeding until EOF. See `iflag=count_bytes` if stopping after N bytes
|
||||
is preferred
|
||||
- `ibs=N` : the size of buffer used for reads (default: 512)
|
||||
- `if=FILE` : the file used for input. When not specified, stdin is used instead
|
||||
- `iflag=FLAGS` : a comma-separated list of input flags which specify how the
|
||||
input source is treated. FLAGS may be any of the input-flags or general-flags
|
||||
specified below.
|
||||
- `skip=N` (or `iseek=N`) : skip N ibs-sized records into input before beginning
|
||||
copy/convert operations. See iflag=seek_bytes if seeking N bytes is preferred.
|
||||
- `obs=N` : the size of buffer used for writes (default: 512)
|
||||
- `of=FILE` : the file used for output. When not specified, stdout is used
|
||||
instead
|
||||
oflag=FLAGS comma separated list of output flags which specify how the output
|
||||
source is treated. FLAGS may be any of the output flags or
|
||||
general flags specified below
|
||||
seek=N (or oseek=N) seeks N obs-sized records into output before
|
||||
beginning copy/convert operations. See oflag=seek_bytes if
|
||||
seeking N bytes is preferred
|
||||
status=LEVEL controls whether volume and performance stats are written to
|
||||
- `oflag=FLAGS` : comma separated list of output flags which specify how the
|
||||
output source is treated. FLAGS may be any of the output flags or general
|
||||
flags specified below
|
||||
- `seek=N` (or `oseek=N`) : seeks N obs-sized records into output before
|
||||
beginning copy/convert operations. See oflag=seek_bytes if seeking N bytes is
|
||||
preferred
|
||||
- `status=LEVEL` : controls whether volume and performance stats are written to
|
||||
stderr.
|
||||
|
||||
When unspecified, dd will print stats upon completion. An example is below.
|
||||
|
||||
```plain
|
||||
6+0 records in
|
||||
16+0 records out
|
||||
8192 bytes (8.2 kB, 8.0 KiB) copied, 0.00057009 s, 14.4 MB/s
|
||||
The first two lines are the 'volume' stats and the final line is
|
||||
the 'performance' stats.
|
||||
The volume stats indicate the number of complete and partial
|
||||
ibs-sized reads, or obs-sized writes that took place during the
|
||||
copy. The format of the volume stats is
|
||||
<complete>+<partial>. If records have been truncated (see
|
||||
conv=block), the volume stats will contain the number of
|
||||
truncated records.
|
||||
8192 bytes (8.2 kB, 8.0 KiB) copied, 0.00057009 s,
|
||||
14.4 MB/s
|
||||
```
|
||||
|
||||
The first two lines are the 'volume' stats and the final line is the
|
||||
'performance' stats.
|
||||
The volume stats indicate the number of complete and partial ibs-sized reads,
|
||||
or obs-sized writes that took place during the copy. The format of the volume
|
||||
stats is `<complete>+<partial>`. If records have been truncated (see
|
||||
`conv=block`), the volume stats will contain the number of truncated records.
|
||||
|
||||
Possible LEVEL values are:
|
||||
progress: Print periodic performance stats as the copy
|
||||
proceeds.
|
||||
noxfer: Print final volume stats, but not performance stats.
|
||||
none: Do not print any stats.
|
||||
- `progress` : Print periodic performance stats as the copy proceeds.
|
||||
- `noxfer` : Print final volume stats, but not performance stats.
|
||||
- `none` : Do not print any stats.
|
||||
|
||||
Printing performance stats is also triggered by the INFO signal
|
||||
(where supported), or the USR1 signal. Setting the
|
||||
POSIXLY_CORRECT environment variable to any value (including an
|
||||
empty value) will cause the USR1 signal to be ignored.
|
||||
Printing performance stats is also triggered by the INFO signal (where supported),
|
||||
or the USR1 signal. Setting the POSIXLY_CORRECT environment variable to any value
|
||||
(including an empty value) will cause the USR1 signal to be ignored.
|
||||
|
||||
CONVERSION OPTIONS:
|
||||
### Conversion Options
|
||||
|
||||
ascii convert from EBCDIC to ASCII. This is the inverse of the 'ebcdic'
|
||||
option. Implies conv=unblock.
|
||||
ebcdic convert from ASCII to EBCDIC. This is the inverse of the 'ascii'
|
||||
option. Implies conv=block.
|
||||
ibm convert from ASCII to EBCDIC, applying the conventions for '[', ']'
|
||||
and '~' specified in POSIX. Implies conv=block.
|
||||
- `ascii` : convert from EBCDIC to ASCII. This is the inverse of the `ebcdic`
|
||||
option. Implies `conv=unblock`.
|
||||
- `ebcdic` : convert from ASCII to EBCDIC. This is the inverse of the `ascii`
|
||||
option. Implies `conv=block`.
|
||||
- `ibm` : convert from ASCII to EBCDIC, applying the conventions for `[`, `]`
|
||||
and `~` specified in POSIX. Implies `conv=block`.
|
||||
|
||||
ucase convert from lower-case to upper-case
|
||||
lcase converts from upper-case to lower-case.
|
||||
- `ucase` : convert from lower-case to upper-case.
|
||||
- `lcase` : converts from upper-case to lower-case.
|
||||
|
||||
block for each newline less than the size indicated by cbs=BYTES, remove
|
||||
the newline and pad with spaces up to cbs. Lines longer than cbs are
|
||||
truncated.
|
||||
unblock for each block of input of the size indicated by cbs=BYTES, remove
|
||||
- `block` : for each newline less than the size indicated by cbs=BYTES, remove
|
||||
the newline and pad with spaces up to cbs. Lines longer than cbs are truncated.
|
||||
- `unblock` : for each block of input of the size indicated by cbs=BYTES, remove
|
||||
right-trailing spaces and replace with a newline character.
|
||||
|
||||
sparse attempts to seek the output when an obs-sized block consists of only
|
||||
zeros.
|
||||
swab swaps each adjacent pair of bytes. If an odd number of bytes is
|
||||
- `sparse` : attempts to seek the output when an obs-sized block consists of
|
||||
only zeros.
|
||||
- `swab` : swaps each adjacent pair of bytes. If an odd number of bytes is
|
||||
present, the final byte is omitted.
|
||||
sync pad each ibs-sided block with zeros. If 'block' or 'unblock' is
|
||||
- `sync` : pad each ibs-sided block with zeros. If `block` or `unblock` is
|
||||
specified, pad with spaces instead.
|
||||
excl the output file must be created. Fail if the output file is already
|
||||
- `excl` : the output file must be created. Fail if the output file is already
|
||||
present.
|
||||
nocreat the output file will not be created. Fail if the output file in not
|
||||
already present.
|
||||
notrunc the output file will not be truncated. If this option is not
|
||||
- `nocreat` : the output file will not be created. Fail if the output file in
|
||||
not already present.
|
||||
- `notrunc` : the output file will not be truncated. If this option is not
|
||||
present, output will be truncated when opened.
|
||||
noerror all read errors will be ignored. If this option is not present, dd
|
||||
will only ignore Error::Interrupted.
|
||||
fdatasync data will be written before finishing.
|
||||
fsync data and metadata will be written before finishing.
|
||||
- `noerror` : all read errors will be ignored. If this option is not present,
|
||||
dd will only ignore Error::Interrupted.
|
||||
- `fdatasync` : data will be written before finishing.
|
||||
- `fsync` : data and metadata will be written before finishing.
|
||||
|
||||
INPUT FLAGS:
|
||||
### Input flags
|
||||
|
||||
count_bytes a value to count=N will be interpreted as bytes.
|
||||
skip_bytes a value to skip=N will be interpreted as bytes.
|
||||
fullblock wait for ibs bytes from each read. zero-length reads are still
|
||||
- `count_bytes` : a value to `count=N` will be interpreted as bytes.
|
||||
- `skip_bytes` : a value to `skip=N` will be interpreted as bytes.
|
||||
- `fullblock` : wait for ibs bytes from each read. zero-length reads are still
|
||||
considered EOF.
|
||||
|
||||
OUTPUT FLAGS:
|
||||
### Output flags
|
||||
|
||||
append open file in append mode. Consider setting conv=notrunc as well.
|
||||
seek_bytes a value to seek=N will be interpreted as bytes.
|
||||
- `append` : open file in append mode. Consider setting conv=notrunc as well.
|
||||
- `seek_bytes` : a value to seek=N will be interpreted as bytes.
|
||||
|
||||
GENERAL FLAGS:
|
||||
### General Flags
|
||||
|
||||
direct use direct I/O for data.
|
||||
directory fail unless the given input (if used as an iflag) or output (if used
|
||||
as an oflag) is a directory.
|
||||
dsync use synchronized I/O for data.
|
||||
sync use synchronized I/O for data and metadata.
|
||||
nonblock use non-blocking I/O.
|
||||
noatime do not update access time.
|
||||
nocache request that OS drop cache.
|
||||
noctty do not assign a controlling tty.
|
||||
nofollow do not follow system links.
|
||||
- `Direct` : use direct I/O for data.
|
||||
- `directory` : fail unless the given input (if used as an iflag) or
|
||||
output (if used as an oflag) is a directory.
|
||||
- `dsync` : use synchronized I/O for data.
|
||||
- `sync` : use synchronized I/O for data and metadata.
|
||||
- `nonblock` : use non-blocking I/O.
|
||||
- `noatime` : do not update access time.
|
||||
- `nocache` : request that OS drop cache.
|
||||
- `noctty` : do not assign a controlling tty.
|
||||
- `nofollow` : do not follow system links.
|
||||
|
|
|
@ -27,7 +27,9 @@ use std::cmp;
|
|||
use std::env;
|
||||
use std::ffi::OsString;
|
||||
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"))]
|
||||
use std::os::unix::fs::OpenOptionsExt;
|
||||
use std::path::Path;
|
||||
|
@ -90,30 +92,106 @@ impl Num {
|
|||
}
|
||||
}
|
||||
|
||||
struct Input<'a, R: Read> {
|
||||
src: R,
|
||||
/// Data sources.
|
||||
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 [`dd_copy`] 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,
|
||||
}
|
||||
|
||||
impl<'a> Input<'a, io::Stdin> {
|
||||
fn new(settings: &'a Settings) -> UResult<Self> {
|
||||
let mut input = Self {
|
||||
src: io::stdin(),
|
||||
settings,
|
||||
impl<'a> Input<'a> {
|
||||
/// Instantiate this struct with stdin as a source.
|
||||
fn new_stdin(settings: &'a Settings) -> UResult<Self> {
|
||||
let mut src = Source::Stdin(io::stdin());
|
||||
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 let Err(e) = input.read_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());
|
||||
}
|
||||
src.skip(settings.skip)?;
|
||||
}
|
||||
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> {
|
||||
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> {
|
||||
impl<'a> Read for Input<'a> {
|
||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||
let mut base_idx = 0;
|
||||
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.
|
||||
/// Reads in increments of 'self.ibs'.
|
||||
/// 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,
|
||||
})
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
|
@ -297,6 +337,14 @@ enum Dest {
|
|||
/// The [`Density`] component indicates whether to attempt to
|
||||
/// write a sparse file when all-zero blocks are encountered.
|
||||
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 {
|
||||
|
@ -307,6 +355,13 @@ impl Dest {
|
|||
f.flush()?;
|
||||
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.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 {
|
||||
Self::Stdout(stdout) => io::copy(&mut io::repeat(0).take(n), stdout),
|
||||
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.
|
||||
fn truncate(&mut self) -> io::Result<()> {
|
||||
match self {
|
||||
Self::Stdout(_) => Ok(()),
|
||||
Self::File(f, _) => {
|
||||
let pos = f.stream_position()?;
|
||||
f.set_len(pos)
|
||||
}
|
||||
_ => Ok(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -357,6 +426,10 @@ impl Write for Dest {
|
|||
}
|
||||
Self::File(f, _) => f.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 {
|
||||
Self::Stdout(stdout) => stdout.flush(),
|
||||
Self::File(f, _) => f.flush(),
|
||||
#[cfg(unix)]
|
||||
Self::Fifo(f) => f.flush(),
|
||||
#[cfg(unix)]
|
||||
Self::Sink => Ok(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -372,7 +449,7 @@ impl Write for Dest {
|
|||
///
|
||||
/// Use the [`Output::new_stdout`] or [`Output::new_file`] functions
|
||||
/// to construct a new instance of this struct. Then use the
|
||||
/// [`Output::dd_out`] function to execute the main copy operation for
|
||||
/// [`dd_copy`] function to execute the main copy operation for
|
||||
/// `dd`.
|
||||
struct Output<'a> {
|
||||
/// The destination to which bytes will be written.
|
||||
|
@ -433,6 +510,35 @@ impl<'a> Output<'a> {
|
|||
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.
|
||||
///
|
||||
/// This may write partial blocks (for example, if the underlying
|
||||
|
@ -473,6 +579,7 @@ impl<'a> Output<'a> {
|
|||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Copy the given input data to this output, consuming both.
|
||||
///
|
||||
|
@ -485,7 +592,7 @@ impl<'a> Output<'a> {
|
|||
///
|
||||
/// If there is a problem reading from the input or writing to
|
||||
/// this output.
|
||||
fn dd_out<R: Read>(mut self, mut i: Input<R>) -> std::io::Result<()> {
|
||||
fn dd_copy(mut i: Input, mut o: Output) -> std::io::Result<()> {
|
||||
// The read and write statistics.
|
||||
//
|
||||
// These objects are counters, initialized to zero. After each
|
||||
|
@ -507,7 +614,7 @@ impl<'a> Output<'a> {
|
|||
//
|
||||
// This is an educated guess about a good buffer size based on
|
||||
// the input and output block sizes.
|
||||
let bsize = calc_bsize(i.settings.ibs, self.settings.obs);
|
||||
let bsize = calc_bsize(i.settings.ibs, o.settings.obs);
|
||||
|
||||
// Start a thread that reports transfer progress.
|
||||
//
|
||||
|
@ -526,7 +633,7 @@ impl<'a> Output<'a> {
|
|||
// Optimization: if no blocks are to be written, then don't
|
||||
// bother allocating any buffers.
|
||||
if let Some(Num::Blocks(0) | Num::Bytes(0)) = i.settings.count {
|
||||
return self.finalize(rstat, wstat, start, &prog_tx, output_thread);
|
||||
return finalize(&mut o, rstat, wstat, start, &prog_tx, output_thread);
|
||||
};
|
||||
|
||||
// Create a common buffer with a capacity of the block size.
|
||||
|
@ -545,13 +652,12 @@ impl<'a> Output<'a> {
|
|||
// As an optimization, make an educated guess about the
|
||||
// best buffer size for reading based on the number of
|
||||
// blocks already read and the number of blocks remaining.
|
||||
let loop_bsize =
|
||||
calc_loop_bsize(&i.settings.count, &rstat, &wstat, i.settings.ibs, bsize);
|
||||
let loop_bsize = calc_loop_bsize(&i.settings.count, &rstat, &wstat, i.settings.ibs, bsize);
|
||||
let rstat_update = read_helper(&mut i, &mut buf, loop_bsize)?;
|
||||
if rstat_update.is_empty() {
|
||||
break;
|
||||
}
|
||||
let wstat_update = self.write_blocks(&buf)?;
|
||||
let wstat_update = o.write_blocks(&buf)?;
|
||||
|
||||
// Update the read/write stats and inform the progress thread once per second.
|
||||
//
|
||||
|
@ -567,12 +673,12 @@ impl<'a> Output<'a> {
|
|||
prog_tx.send(prog_update).unwrap_or(());
|
||||
}
|
||||
}
|
||||
self.finalize(rstat, wstat, start, &prog_tx, output_thread)
|
||||
finalize(&mut o, rstat, wstat, start, &prog_tx, output_thread)
|
||||
}
|
||||
|
||||
/// Flush output, print final stats, and join with the progress thread.
|
||||
fn finalize<T>(
|
||||
&mut self,
|
||||
output: &mut Output,
|
||||
rstat: ReadStat,
|
||||
wstat: WriteStat,
|
||||
start: time::Instant,
|
||||
|
@ -580,7 +686,7 @@ impl<'a> Output<'a> {
|
|||
output_thread: thread::JoinHandle<T>,
|
||||
) -> std::io::Result<()> {
|
||||
// Flush the output, if configured to do so.
|
||||
self.sync()?;
|
||||
output.sync()?;
|
||||
|
||||
// Truncate the file to the final cursor location.
|
||||
//
|
||||
|
@ -590,8 +696,8 @@ impl<'a> Output<'a> {
|
|||
// suppress the error by calling `Result::ok()`. This matches
|
||||
// the behavior of GNU `dd` when given the command-line
|
||||
// argument `of=/dev/null`.
|
||||
if !self.settings.oconv.notrunc {
|
||||
self.dst.truncate().ok();
|
||||
if !output.settings.oconv.notrunc {
|
||||
output.dst.truncate().ok();
|
||||
}
|
||||
|
||||
// Print the final read/write statistics.
|
||||
|
@ -603,7 +709,6 @@ impl<'a> Output<'a> {
|
|||
.expect("Failed to join with the output thread.");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
fn make_linux_oflags(oflags: &OFlags) -> Option<libc::c_int> {
|
||||
|
@ -645,12 +750,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.
|
||||
fn read_helper<R: Read>(
|
||||
i: &mut Input<R>,
|
||||
buf: &mut Vec<u8>,
|
||||
bsize: usize,
|
||||
) -> std::io::Result<ReadStat> {
|
||||
/// Read from an input (that is, a source of bytes) into the given buffer.
|
||||
///
|
||||
/// This function also performs any conversions as specified by
|
||||
/// `conv=swab` or `conv=block` command-line arguments. This function
|
||||
/// mutates the `buf` argument in-place. The returned [`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 -------------------------------------------------
|
||||
fn perform_swab(buf: &mut [u8]) {
|
||||
for base in (1..buf.len()).step_by(2) {
|
||||
|
@ -778,6 +884,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]
|
||||
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||
let args = args.collect_ignore();
|
||||
|
@ -792,40 +909,22 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
|||
.collect::<Vec<_>>()[..],
|
||||
)?;
|
||||
|
||||
match (&settings.infile, &settings.outfile) {
|
||||
(Some(infile), Some(outfile)) => {
|
||||
let i = Input::<File>::new(Path::new(&infile), &settings)?;
|
||||
let o = Output::new_file(Path::new(&outfile), &settings)?;
|
||||
o.dd_out(i).map_err_context(|| "IO error".to_string())
|
||||
}
|
||||
(None, Some(outfile)) => {
|
||||
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())
|
||||
}
|
||||
(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())
|
||||
}
|
||||
}
|
||||
let i = match settings.infile {
|
||||
#[cfg(unix)]
|
||||
Some(ref infile) if is_fifo(infile) => Input::new_fifo(Path::new(&infile), &settings)?,
|
||||
Some(ref infile) => Input::new_file(Path::new(&infile), &settings)?,
|
||||
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 => Output::new_stdout(&settings)?,
|
||||
};
|
||||
dd_copy(i, o).map_err_context(|| "IO error".to_string())
|
||||
}
|
||||
|
||||
pub fn uu_app() -> Command {
|
||||
|
|
18
src/uu/df/df.md
Normal file
18
src/uu/df/df.md
Normal file
|
@ -0,0 +1,18 @@
|
|||
# df
|
||||
|
||||
```
|
||||
df [OPTION]... [FILE]...
|
||||
```
|
||||
|
||||
Show information about the file system on which each FILE resides,
|
||||
or all file systems by default.
|
||||
|
||||
## After Help
|
||||
|
||||
Display values are in units of the first available SIZE from --block-size,
|
||||
and the DF_BLOCK_SIZE, BLOCK_SIZE and BLOCKSIZE environment variables.
|
||||
Otherwise, units default to 1024 bytes (or 512 if POSIXLY_CORRECT is set).
|
||||
|
||||
SIZE is an integer and optional unit (example: 10M is 10*1024*1024).
|
||||
Units are K, M, G, T, P, E, Z, Y (powers of 1024) or KB, MB,... (powers
|
||||
of 1000).
|
|
@ -19,7 +19,7 @@ use uucore::error::FromIo;
|
|||
use uucore::error::{UError, UResult, USimpleError};
|
||||
use uucore::fsext::{read_fs_list, MountInfo};
|
||||
use uucore::parse_size::ParseSizeError;
|
||||
use uucore::{format_usage, show};
|
||||
use uucore::{format_usage, help_about, help_section, help_usage, show};
|
||||
|
||||
use clap::{crate_version, parser::ValueSource, Arg, ArgAction, ArgMatches, Command};
|
||||
|
||||
|
@ -33,16 +33,9 @@ use crate::columns::{Column, ColumnError};
|
|||
use crate::filesystem::Filesystem;
|
||||
use crate::table::Table;
|
||||
|
||||
static ABOUT: &str = "Show information about the file system on which each FILE resides,\n\
|
||||
or all file systems by default.";
|
||||
const USAGE: &str = "{} [OPTION]... [FILE]...";
|
||||
const LONG_HELP: &str = "Display values are in units of the first available SIZE from --block-size,
|
||||
and the DF_BLOCK_SIZE, BLOCK_SIZE and BLOCKSIZE environment variables.
|
||||
Otherwise, units default to 1024 bytes (or 512 if POSIXLY_CORRECT is set).
|
||||
|
||||
SIZE is an integer and optional unit (example: 10M is 10*1024*1024).
|
||||
Units are K, M, G, T, P, E, Z, Y (powers of 1024) or KB, MB,... (powers
|
||||
of 1000).";
|
||||
const ABOUT: &str = help_about!("df.md");
|
||||
const USAGE: &str = help_usage!("df.md");
|
||||
const AFTER_HELP: &str = help_section!("after help", "df.md");
|
||||
|
||||
static OPT_HELP: &str = "help";
|
||||
static OPT_ALL: &str = "all";
|
||||
|
@ -487,7 +480,7 @@ pub fn uu_app() -> Command {
|
|||
.version(crate_version!())
|
||||
.about(ABOUT)
|
||||
.override_usage(format_usage(USAGE))
|
||||
.after_help(LONG_HELP)
|
||||
.after_help(AFTER_HELP)
|
||||
.infer_long_args(true)
|
||||
.disable_help_flag(true)
|
||||
.arg(
|
||||
|
|
|
@ -1,18 +1,18 @@
|
|||
## How to update the internal database
|
||||
# How to update the internal database
|
||||
|
||||
Create the test fixtures by writing the output of the GNU dircolors commands to the fixtures folder:
|
||||
|
||||
```
|
||||
$ dircolors --print-database > /PATH_TO_COREUTILS/tests/fixtures/dircolors/internal.expected
|
||||
$ dircolors --print-ls-colors > /PATH_TO_COREUTILS/tests/fixtures/dircolors/ls_colors.expected
|
||||
$ dircolors -b > /PATH_TO_COREUTILS/tests/fixtures/dircolors/bash_def.expected
|
||||
$ dircolors -c > /PATH_TO_COREUTILS/tests/fixtures/dircolors/csh_def.expected
|
||||
```shell
|
||||
dircolors --print-database > /PATH_TO_COREUTILS/tests/fixtures/dircolors/internal.expected
|
||||
dircolors --print-ls-colors > /PATH_TO_COREUTILS/tests/fixtures/dircolors/ls_colors.expected
|
||||
dircolors -b > /PATH_TO_COREUTILS/tests/fixtures/dircolors/bash_def.expected
|
||||
dircolors -c > /PATH_TO_COREUTILS/tests/fixtures/dircolors/csh_def.expected
|
||||
```
|
||||
|
||||
Run the tests:
|
||||
|
||||
```
|
||||
$ cargo test --features "dircolors" --no-default-features
|
||||
```shell
|
||||
cargo test --features "dircolors" --no-default-features
|
||||
```
|
||||
|
||||
Edit `/PATH_TO_COREUTILS/src/uu/dircolors/src/colors.rs` until the tests pass.
|
||||
|
|
13
src/uu/dircolors/dircolors.md
Normal file
13
src/uu/dircolors/dircolors.md
Normal file
|
@ -0,0 +1,13 @@
|
|||
# dircolors
|
||||
|
||||
```
|
||||
dircolors [OPTION]... [FILE]
|
||||
```
|
||||
|
||||
Output commands to set the LS_COLORS environment variable.
|
||||
|
||||
## After Help
|
||||
|
||||
If FILE is specified, read it to determine which colors to use for which
|
||||
file types and extensions. Otherwise, a precompiled database is used.
|
||||
For details on the format of these files, run 'dircolors --print-database'
|
|
@ -16,6 +16,7 @@ use std::io::{BufRead, BufReader};
|
|||
use clap::{crate_version, Arg, ArgAction, Command};
|
||||
use uucore::display::Quotable;
|
||||
use uucore::error::{UResult, USimpleError, UUsageError};
|
||||
use uucore::{help_about, help_section, help_usage};
|
||||
|
||||
mod options {
|
||||
pub const BOURNE_SHELL: &str = "bourne-shell";
|
||||
|
@ -25,13 +26,9 @@ mod options {
|
|||
pub const FILE: &str = "FILE";
|
||||
}
|
||||
|
||||
static USAGE: &str = "{} [OPTION]... [FILE]";
|
||||
static ABOUT: &str = "Output commands to set the LS_COLORS environment variable.";
|
||||
static LONG_HELP: &str = "
|
||||
If FILE is specified, read it to determine which colors to use for which
|
||||
file types and extensions. Otherwise, a precompiled database is used.
|
||||
For details on the format of these files, run 'dircolors --print-database'
|
||||
";
|
||||
const USAGE: &str = help_usage!("dircolors.md");
|
||||
const ABOUT: &str = help_about!("dircolors.md");
|
||||
const AFTER_HELP: &str = help_section!("after help", "dircolors.md");
|
||||
|
||||
mod colors;
|
||||
use self::colors::INTERNAL_DB;
|
||||
|
@ -170,7 +167,7 @@ pub fn uu_app() -> Command {
|
|||
Command::new(uucore::util_name())
|
||||
.version(crate_version!())
|
||||
.about(ABOUT)
|
||||
.after_help(LONG_HELP)
|
||||
.after_help(AFTER_HELP)
|
||||
.override_usage(format_usage(USAGE))
|
||||
.infer_long_args(true)
|
||||
.arg(
|
||||
|
|
12
src/uu/dirname/dirname.md
Normal file
12
src/uu/dirname/dirname.md
Normal file
|
@ -0,0 +1,12 @@
|
|||
# dirname
|
||||
|
||||
```
|
||||
dirname [OPTION] NAME...
|
||||
```
|
||||
|
||||
Strip last component from file name
|
||||
|
||||
## After Help
|
||||
|
||||
Output each NAME with its last non-slash component and trailing slashes
|
||||
removed; if NAME contains no /'s, output '.' (meaning the current directory).
|
|
@ -9,13 +9,11 @@ use clap::{crate_version, Arg, ArgAction, Command};
|
|||
use std::path::Path;
|
||||
use uucore::display::print_verbatim;
|
||||
use uucore::error::{UResult, UUsageError};
|
||||
use uucore::format_usage;
|
||||
use uucore::{format_usage, help_about, help_section, help_usage};
|
||||
|
||||
const ABOUT: &str = "Strip last component from file name";
|
||||
const USAGE: &str = "{} [OPTION] NAME...";
|
||||
const LONG_USAGE: &str = "\
|
||||
Output each NAME with its last non-slash component and trailing slashes \n\
|
||||
removed; if NAME contains no /'s, output '.' (meaning the current directory).";
|
||||
const ABOUT: &str = help_about!("dirname.md");
|
||||
const USAGE: &str = help_usage!("dirname.md");
|
||||
const AFTER_HELP: &str = help_section!("after help", "dirname.md");
|
||||
|
||||
mod options {
|
||||
pub const ZERO: &str = "zero";
|
||||
|
@ -26,7 +24,7 @@ mod options {
|
|||
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||
let args = args.collect_lossy();
|
||||
|
||||
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 separator = if matches.get_flag(options::ZERO) {
|
||||
"\0"
|
||||
|
|
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::parse_glob;
|
||||
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)]
|
||||
use windows_sys::Win32::Foundation::HANDLE;
|
||||
#[cfg(windows)]
|
||||
|
@ -73,25 +75,9 @@ mod options {
|
|||
pub const FILE: &str = "FILE";
|
||||
}
|
||||
|
||||
const ABOUT: &str = "Estimate file space usage";
|
||||
const LONG_HELP: &str = "
|
||||
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
|
||||
";
|
||||
const USAGE: &str = "\
|
||||
{} [OPTION]... [FILE]...
|
||||
{} [OPTION]... --files0-from=F";
|
||||
const ABOUT: &str = help_about!("du.md");
|
||||
const AFTER_HELP: &str = help_section!("after help", "du.md");
|
||||
const USAGE: &str = help_usage!("du.md");
|
||||
|
||||
// TODO: Support Z & Y (currently limited by size of u64)
|
||||
const UNITS: [(char, u32); 6] = [('E', 6), ('P', 5), ('T', 4), ('G', 3), ('M', 2), ('K', 1)];
|
||||
|
@ -705,7 +691,7 @@ pub fn uu_app() -> Command {
|
|||
Command::new(uucore::util_name())
|
||||
.version(crate_version!())
|
||||
.about(ABOUT)
|
||||
.after_help(LONG_HELP)
|
||||
.after_help(AFTER_HELP)
|
||||
.override_usage(format_usage(USAGE))
|
||||
.infer_long_args(true)
|
||||
.disable_help_flag(true)
|
||||
|
|
|
@ -14,44 +14,39 @@ separates increasing precedence groups.
|
|||
|
||||
`EXPRESSION` may be:
|
||||
|
||||
ARG1 | ARG2 ARG1 if it is neither null nor 0, otherwise ARG2
|
||||
|
||||
ARG1 & ARG2 ARG1 if neither argument is null or 0, otherwise 0
|
||||
|
||||
ARG1 < ARG2 ARG1 is less than ARG2
|
||||
ARG1 <= ARG2 ARG1 is less than or equal to ARG2
|
||||
ARG1 = ARG2 ARG1 is equal to ARG2
|
||||
ARG1 != ARG2 ARG1 is unequal to ARG2
|
||||
ARG1 >= ARG2 ARG1 is greater than or equal to ARG2
|
||||
ARG1 > ARG2 ARG1 is greater than ARG2
|
||||
|
||||
ARG1 + ARG2 arithmetic sum of ARG1 and ARG2
|
||||
ARG1 - ARG2 arithmetic difference of ARG1 and ARG2
|
||||
|
||||
ARG1 * ARG2 arithmetic product of ARG1 and ARG2
|
||||
ARG1 / ARG2 arithmetic quotient of ARG1 divided by ARG2
|
||||
ARG1 % ARG2 arithmetic remainder of ARG1 divided by ARG2
|
||||
|
||||
STRING : REGEXP anchored pattern match of REGEXP in STRING
|
||||
|
||||
match STRING REGEXP same as STRING : REGEXP
|
||||
substr STRING POS LENGTH substring of STRING, POS counted from 1
|
||||
index STRING CHARS index in STRING where any CHARS is found, or 0
|
||||
length STRING length of STRING
|
||||
+ TOKEN interpret TOKEN as a string, even if it is a
|
||||
keyword like 'match' or an operator like '/'
|
||||
|
||||
( EXPRESSION ) value of EXPRESSION
|
||||
- `ARG1 | ARG2`: `ARG1` if it is neither null nor 0, otherwise `ARG2`
|
||||
- `ARG1 & ARG2`: `ARG1` if neither argument is null or 0, otherwise 0
|
||||
- `ARG1 < ARG2`: `ARG1` is less than `ARG2`
|
||||
- `ARG1 <= ARG2`: `ARG1` is less than or equal to `ARG2`
|
||||
- `ARG1 = ARG2`: `ARG1` is equal to `ARG2`
|
||||
- `ARG1 != ARG2`: `ARG1` is unequal to `ARG2`
|
||||
- `ARG1 >= ARG2`: `ARG1` is greater than or equal to `ARG2`
|
||||
- `ARG1 > ARG2`: `ARG1` is greater than `ARG2`
|
||||
- `ARG1 + ARG2`: arithmetic sum of `ARG1` and `ARG2`
|
||||
- `ARG1 - ARG2`: arithmetic difference of `ARG1` and `ARG2`
|
||||
- `ARG1 * ARG2`: arithmetic product of `ARG1` and `ARG2`
|
||||
- `ARG1 / ARG2`: arithmetic quotient of `ARG1` divided by `ARG2`
|
||||
- `ARG1 % ARG2`: arithmetic remainder of `ARG1` divided by `ARG2`
|
||||
- `STRING : REGEXP`: anchored pattern match of `REGEXP` in `STRING`
|
||||
- `match STRING REGEXP`: same as `STRING : REGEXP`
|
||||
- `substr STRING POS LENGTH`: substring of `STRING`, `POS` counted from 1
|
||||
- `index STRING CHARS`: index in `STRING` where any `CHARS` is found, or 0
|
||||
- `length STRING`: length of `STRING`
|
||||
- `+ TOKEN`: interpret `TOKEN` as a string, even if it is a keyword like `match`
|
||||
or an operator like `/`
|
||||
- `( EXPRESSION )`: value of `EXPRESSION`
|
||||
|
||||
Beware that many operators need to be escaped or quoted for shells.
|
||||
Comparisons are arithmetic if both ARGs are numbers, else lexicographical.
|
||||
Pattern matches return the string matched between \( and \) or null; if
|
||||
\( and \) are not used, they return the number of characters matched or 0.
|
||||
|
||||
Exit status is `0` if `EXPRESSION` is neither null nor `0`, `1` if `EXPRESSION` is null
|
||||
or `0`, `2` if `EXPRESSION` is syntactically invalid, and `3` if an error occurred.
|
||||
Exit status is `0` if `EXPRESSION` is neither null nor `0`, `1` if `EXPRESSION`
|
||||
is null or `0`, `2` if `EXPRESSION` is syntactically invalid, and `3` if an
|
||||
error occurred.
|
||||
|
||||
Environment variables:
|
||||
|
||||
- `EXPR_DEBUG_TOKENS=1`: dump expression's tokens
|
||||
- `EXPR_DEBUG_RPN=1`: dump expression represented in reverse polish notation
|
||||
- `EXPR_DEBUG_SYA_STEP=1`: dump each parser step
|
||||
|
|
|
@ -53,19 +53,19 @@ which I recommend reading if you want to add benchmarks to `factor`.
|
|||
so each sample takes a very short time, minimizing variability and
|
||||
maximizing the numbers of samples we can take in a given time.
|
||||
|
||||
2. Benchmarks are immutable (once merged in `uutils`)
|
||||
1. Benchmarks are immutable (once merged in `uutils`)
|
||||
|
||||
Modifying a benchmark means previously-collected values cannot meaningfully
|
||||
be compared, silently giving nonsensical results. If you must modify an
|
||||
existing benchmark, rename it.
|
||||
|
||||
3. Test common cases
|
||||
1. Test common cases
|
||||
|
||||
We are interested in overall performance, rather than specific edge-cases;
|
||||
use **reproducibly-randomized inputs**, sampling from either all possible
|
||||
input values or some subset of interest.
|
||||
|
||||
4. Use [`criterion`], `criterion::black_box`, ...
|
||||
1. Use [`criterion`], `criterion::black_box`, ...
|
||||
|
||||
`criterion` isn't perfect, but it is also much better than ad-hoc
|
||||
solutions in each benchmark.
|
||||
|
@ -103,7 +103,7 @@ characteristics:
|
|||
1. integer factoring algorithms are randomized, with large variance in
|
||||
execution time ;
|
||||
|
||||
2. various inputs also have large differences in factoring time, that
|
||||
1. various inputs also have large differences in factoring time, that
|
||||
corresponds to no natural, linear ordering of the inputs.
|
||||
|
||||
If (1) was untrue (i.e. if execution time wasn't random), we could faithfully
|
||||
|
|
7
src/uu/fmt/fmt.md
Normal file
7
src/uu/fmt/fmt.md
Normal file
|
@ -0,0 +1,7 @@
|
|||
# fmt
|
||||
|
||||
```
|
||||
fmt [OPTION]... [FILE]...
|
||||
```
|
||||
|
||||
Reformat paragraphs from input files (or stdin) to stdout.
|
|
@ -14,7 +14,7 @@ use std::io::{stdin, stdout, Write};
|
|||
use std::io::{BufReader, BufWriter, Read};
|
||||
use uucore::display::Quotable;
|
||||
use uucore::error::{FromIo, UResult, USimpleError};
|
||||
use uucore::{format_usage, show_warning};
|
||||
use uucore::{format_usage, help_about, help_usage, show_warning};
|
||||
|
||||
use self::linebreak::break_lines;
|
||||
use self::parasplit::ParagraphStream;
|
||||
|
@ -22,8 +22,8 @@ use self::parasplit::ParagraphStream;
|
|||
mod linebreak;
|
||||
mod parasplit;
|
||||
|
||||
static ABOUT: &str = "Reformat paragraphs from input files (or stdin) to stdout.";
|
||||
const USAGE: &str = "{} [OPTION]... [FILE]...";
|
||||
static ABOUT: &str = help_about!("fmt.md");
|
||||
const USAGE: &str = help_usage!("fmt.md");
|
||||
static MAX_WIDTH: usize = 2500;
|
||||
|
||||
static OPT_CROWN_MARGIN: &str = "crown-margin";
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
## Benchmarking hashsum
|
||||
# Benchmarking hashsum
|
||||
|
||||
### To bench blake2
|
||||
## To bench blake2
|
||||
|
||||
Taken from: https://github.com/uutils/coreutils/pull/2296
|
||||
Taken from: <https://github.com/uutils/coreutils/pull/2296>
|
||||
|
||||
With a large file:
|
||||
$ hyperfine "./target/release/coreutils hashsum --b2sum large-file" "b2sum large-file"
|
||||
|
||||
```shell
|
||||
hyperfine "./target/release/coreutils hashsum --b2sum large-file" "b2sum large-file"
|
||||
```
|
||||
|
|
|
@ -15,18 +15,11 @@ edition = "2021"
|
|||
path = "src/hashsum.rs"
|
||||
|
||||
[dependencies]
|
||||
digest = "0.10.6"
|
||||
clap = { workspace=true }
|
||||
hex = "0.4.3"
|
||||
memchr = { workspace=true }
|
||||
md-5 = "0.10.5"
|
||||
regex = { workspace=true }
|
||||
sha1 = "0.10.1"
|
||||
sha2 = "0.10.2"
|
||||
sha3 = "0.10.6"
|
||||
blake2b_simd = "1.0.1"
|
||||
blake3 = "1.3.2"
|
||||
uucore = { workspace=true }
|
||||
memchr = { workspace=true }
|
||||
regex = { workspace=true }
|
||||
hex = { workspace=true }
|
||||
|
||||
[[bin]]
|
||||
name = "hashsum"
|
||||
|
|
|
@ -1,287 +0,0 @@
|
|||
// spell-checker:ignore memmem
|
||||
//! Implementations of digest functions, like md5 and sha1.
|
||||
//!
|
||||
//! The [`Digest`] trait represents the interface for providing inputs
|
||||
//! to these digest functions and accessing the resulting hash. The
|
||||
//! [`DigestWriter`] struct provides a wrapper around [`Digest`] that
|
||||
//! implements the [`Write`] trait, for use in situations where calling
|
||||
//! [`write`] would be useful.
|
||||
use std::io::Write;
|
||||
|
||||
use hex::encode;
|
||||
#[cfg(windows)]
|
||||
use memchr::memmem;
|
||||
|
||||
pub trait Digest {
|
||||
fn new() -> Self
|
||||
where
|
||||
Self: Sized;
|
||||
fn input(&mut self, input: &[u8]);
|
||||
fn result(&mut self, out: &mut [u8]);
|
||||
fn reset(&mut self);
|
||||
fn output_bits(&self) -> usize;
|
||||
fn output_bytes(&self) -> usize {
|
||||
(self.output_bits() + 7) / 8
|
||||
}
|
||||
fn result_str(&mut self) -> String {
|
||||
let mut buf: Vec<u8> = vec![0; self.output_bytes()];
|
||||
self.result(&mut buf);
|
||||
encode(buf)
|
||||
}
|
||||
}
|
||||
|
||||
impl Digest for blake2b_simd::State {
|
||||
fn new() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
|
||||
fn input(&mut self, input: &[u8]) {
|
||||
self.update(input);
|
||||
}
|
||||
|
||||
fn result(&mut self, out: &mut [u8]) {
|
||||
let hash_result = &self.finalize();
|
||||
out.copy_from_slice(hash_result.as_bytes());
|
||||
}
|
||||
|
||||
fn reset(&mut self) {
|
||||
*self = Self::new();
|
||||
}
|
||||
|
||||
fn output_bits(&self) -> usize {
|
||||
512
|
||||
}
|
||||
}
|
||||
|
||||
impl Digest for blake3::Hasher {
|
||||
fn new() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
|
||||
fn input(&mut self, input: &[u8]) {
|
||||
self.update(input);
|
||||
}
|
||||
|
||||
fn result(&mut self, out: &mut [u8]) {
|
||||
let hash_result = &self.finalize();
|
||||
out.copy_from_slice(hash_result.as_bytes());
|
||||
}
|
||||
|
||||
fn reset(&mut self) {
|
||||
*self = Self::new();
|
||||
}
|
||||
|
||||
fn output_bits(&self) -> usize {
|
||||
256
|
||||
}
|
||||
}
|
||||
|
||||
// Implements the Digest trait for sha2 / sha3 algorithms with fixed output
|
||||
macro_rules! impl_digest_common {
|
||||
($type: ty, $size: expr) => {
|
||||
impl Digest for $type {
|
||||
fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
fn input(&mut self, input: &[u8]) {
|
||||
digest::Digest::update(self, input);
|
||||
}
|
||||
|
||||
fn result(&mut self, out: &mut [u8]) {
|
||||
digest::Digest::finalize_into_reset(self, out.into());
|
||||
}
|
||||
|
||||
fn reset(&mut self) {
|
||||
*self = Self::new();
|
||||
}
|
||||
|
||||
fn output_bits(&self) -> usize {
|
||||
$size
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Implements the Digest trait for sha2 / sha3 algorithms with variable output
|
||||
macro_rules! impl_digest_shake {
|
||||
($type: ty) => {
|
||||
impl Digest for $type {
|
||||
fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
fn input(&mut self, input: &[u8]) {
|
||||
digest::Update::update(self, input);
|
||||
}
|
||||
|
||||
fn result(&mut self, out: &mut [u8]) {
|
||||
digest::ExtendableOutputReset::finalize_xof_reset_into(self, out);
|
||||
}
|
||||
|
||||
fn reset(&mut self) {
|
||||
*self = Self::new();
|
||||
}
|
||||
|
||||
fn output_bits(&self) -> usize {
|
||||
0
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl_digest_common!(md5::Md5, 128);
|
||||
impl_digest_common!(sha1::Sha1, 160);
|
||||
impl_digest_common!(sha2::Sha224, 224);
|
||||
impl_digest_common!(sha2::Sha256, 256);
|
||||
impl_digest_common!(sha2::Sha384, 384);
|
||||
impl_digest_common!(sha2::Sha512, 512);
|
||||
|
||||
impl_digest_common!(sha3::Sha3_224, 224);
|
||||
impl_digest_common!(sha3::Sha3_256, 256);
|
||||
impl_digest_common!(sha3::Sha3_384, 384);
|
||||
impl_digest_common!(sha3::Sha3_512, 512);
|
||||
impl_digest_shake!(sha3::Shake128);
|
||||
impl_digest_shake!(sha3::Shake256);
|
||||
|
||||
/// A struct that writes to a digest.
|
||||
///
|
||||
/// This struct wraps a [`Digest`] and provides a [`Write`]
|
||||
/// implementation that passes input bytes directly to the
|
||||
/// [`Digest::input`].
|
||||
///
|
||||
/// On Windows, if `binary` is `false`, then the [`write`]
|
||||
/// implementation replaces instances of "\r\n" with "\n" before passing
|
||||
/// the input bytes to the [`digest`].
|
||||
pub struct DigestWriter<'a> {
|
||||
digest: &'a mut Box<dyn Digest>,
|
||||
|
||||
/// Whether to write to the digest in binary mode or text mode on Windows.
|
||||
///
|
||||
/// If this is `false`, then instances of "\r\n" are replaced with
|
||||
/// "\n" before passing input bytes to the [`digest`].
|
||||
#[allow(dead_code)]
|
||||
binary: bool,
|
||||
|
||||
/// Whether the previous
|
||||
#[allow(dead_code)]
|
||||
was_last_character_carriage_return: bool,
|
||||
// TODO These are dead code only on non-Windows operating systems.
|
||||
// It might be better to use a `#[cfg(windows)]` guard here.
|
||||
}
|
||||
|
||||
impl<'a> DigestWriter<'a> {
|
||||
pub fn new(digest: &'a mut Box<dyn Digest>, binary: bool) -> DigestWriter {
|
||||
let was_last_character_carriage_return = false;
|
||||
DigestWriter {
|
||||
digest,
|
||||
binary,
|
||||
was_last_character_carriage_return,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn finalize(&mut self) -> bool {
|
||||
if self.was_last_character_carriage_return {
|
||||
self.digest.input(&[b'\r']);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Write for DigestWriter<'a> {
|
||||
#[cfg(not(windows))]
|
||||
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
|
||||
self.digest.input(buf);
|
||||
Ok(buf.len())
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
|
||||
if self.binary {
|
||||
self.digest.input(buf);
|
||||
return Ok(buf.len());
|
||||
}
|
||||
|
||||
// The remaining code handles Windows text mode, where we must
|
||||
// replace each occurrence of "\r\n" with "\n".
|
||||
//
|
||||
// First, if the last character written was "\r" and the first
|
||||
// character in the current buffer to write is not "\n", then we
|
||||
// need to write the "\r" that we buffered from the previous
|
||||
// call to `write()`.
|
||||
let n = buf.len();
|
||||
if self.was_last_character_carriage_return && n > 0 && buf[0] != b'\n' {
|
||||
self.digest.input(&[b'\r']);
|
||||
}
|
||||
|
||||
// Next, find all occurrences of "\r\n", inputting the slice
|
||||
// just before the "\n" in the previous instance of "\r\n" and
|
||||
// the beginning of this "\r\n".
|
||||
let mut i_prev = 0;
|
||||
for i in memmem::find_iter(buf, b"\r\n") {
|
||||
self.digest.input(&buf[i_prev..i]);
|
||||
i_prev = i + 1;
|
||||
}
|
||||
|
||||
// Finally, check whether the last character is "\r". If so,
|
||||
// buffer it until we know that the next character is not "\n",
|
||||
// which can only be known on the next call to `write()`.
|
||||
//
|
||||
// This all assumes that `write()` will be called on adjacent
|
||||
// blocks of the input.
|
||||
if n > 0 && buf[n - 1] == b'\r' {
|
||||
self.was_last_character_carriage_return = true;
|
||||
self.digest.input(&buf[i_prev..n - 1]);
|
||||
} else {
|
||||
self.was_last_character_carriage_return = false;
|
||||
self.digest.input(&buf[i_prev..n]);
|
||||
}
|
||||
|
||||
// Even though we dropped a "\r" for each "\r\n" we found, we
|
||||
// still report the number of bytes written as `n`. This is
|
||||
// because the meaning of the returned number is supposed to be
|
||||
// the number of bytes consumed by the writer, so that if the
|
||||
// calling code were calling `write()` in a loop, it would know
|
||||
// where the next contiguous slice of the buffer starts.
|
||||
Ok(n)
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> std::io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
/// Test for replacing a "\r\n" sequence with "\n" when the "\r" is
|
||||
/// at the end of one block and the "\n" is at the beginning of the
|
||||
/// next block, when reading in blocks.
|
||||
#[cfg(windows)]
|
||||
#[test]
|
||||
fn test_crlf_across_blocks() {
|
||||
use std::io::Write;
|
||||
|
||||
use crate::digest::Digest;
|
||||
use crate::digest::DigestWriter;
|
||||
|
||||
// Writing "\r" in one call to `write()`, and then "\n" in another.
|
||||
let mut digest = Box::new(md5::Md5::new()) as Box<dyn Digest>;
|
||||
let mut writer_crlf = DigestWriter::new(&mut digest, false);
|
||||
writer_crlf.write_all(&[b'\r']).unwrap();
|
||||
writer_crlf.write_all(&[b'\n']).unwrap();
|
||||
writer_crlf.finalize();
|
||||
let result_crlf = digest.result_str();
|
||||
|
||||
// We expect "\r\n" to be replaced with "\n" in text mode on Windows.
|
||||
let mut digest = Box::new(md5::Md5::new()) as Box<dyn Digest>;
|
||||
let mut writer_lf = DigestWriter::new(&mut digest, false);
|
||||
writer_lf.write_all(&[b'\n']).unwrap();
|
||||
writer_lf.finalize();
|
||||
let result_lf = digest.result_str();
|
||||
|
||||
assert_eq!(result_crlf, result_lf);
|
||||
}
|
||||
}
|
|
@ -9,21 +9,12 @@
|
|||
|
||||
// spell-checker:ignore (ToDO) algo, algoname, regexes, nread, nonames
|
||||
|
||||
mod digest;
|
||||
|
||||
use self::digest::Digest;
|
||||
use self::digest::DigestWriter;
|
||||
|
||||
use clap::builder::ValueParser;
|
||||
use clap::crate_version;
|
||||
use clap::ArgAction;
|
||||
use clap::{Arg, ArgMatches, Command};
|
||||
use hex::encode;
|
||||
use md5::Md5;
|
||||
use regex::Regex;
|
||||
use sha1::Sha1;
|
||||
use sha2::{Sha224, Sha256, Sha384, Sha512};
|
||||
use sha3::{Sha3_224, Sha3_256, Sha3_384, Sha3_512, Shake128, Shake256};
|
||||
use std::cmp::Ordering;
|
||||
use std::error::Error;
|
||||
use std::ffi::{OsStr, OsString};
|
||||
|
@ -32,10 +23,12 @@ use std::io::{self, stdin, BufRead, BufReader, Read};
|
|||
use std::iter;
|
||||
use std::num::ParseIntError;
|
||||
use std::path::Path;
|
||||
use uucore::crash;
|
||||
use uucore::display::Quotable;
|
||||
use uucore::error::{FromIo, UError, UResult};
|
||||
use uucore::show_warning;
|
||||
use uucore::sum::{
|
||||
Blake2b, Blake3, Digest, DigestWriter, Md5, Sha1, Sha224, Sha256, Sha384, Sha3_224, Sha3_256,
|
||||
Sha3_384, Sha3_512, Sha512, Shake128, Shake256,
|
||||
};
|
||||
use uucore::{crash, display::Quotable, show_warning};
|
||||
|
||||
const NAME: &str = "hashsum";
|
||||
|
||||
|
@ -68,16 +61,8 @@ fn detect_algo(
|
|||
"sha256sum" => ("SHA256", Box::new(Sha256::new()) as Box<dyn Digest>, 256),
|
||||
"sha384sum" => ("SHA384", Box::new(Sha384::new()) as Box<dyn Digest>, 384),
|
||||
"sha512sum" => ("SHA512", Box::new(Sha512::new()) as Box<dyn Digest>, 512),
|
||||
"b2sum" => (
|
||||
"BLAKE2",
|
||||
Box::new(blake2b_simd::State::new()) as Box<dyn Digest>,
|
||||
512,
|
||||
),
|
||||
"b3sum" => (
|
||||
"BLAKE3",
|
||||
Box::new(blake3::Hasher::new()) as Box<dyn Digest>,
|
||||
256,
|
||||
),
|
||||
"b2sum" => ("BLAKE2", Box::new(Blake2b::new()) as Box<dyn Digest>, 512),
|
||||
"b3sum" => ("BLAKE3", Box::new(Blake3::new()) as Box<dyn Digest>, 256),
|
||||
"sha3sum" => match matches.get_one::<usize>("bits") {
|
||||
Some(224) => (
|
||||
"SHA3-224",
|
||||
|
@ -170,10 +155,10 @@ fn detect_algo(
|
|||
set_or_crash("SHA512", Box::new(Sha512::new()), 512);
|
||||
}
|
||||
if matches.get_flag("b2sum") {
|
||||
set_or_crash("BLAKE2", Box::new(blake2b_simd::State::new()), 512);
|
||||
set_or_crash("BLAKE2", Box::new(Blake2b::new()), 512);
|
||||
}
|
||||
if matches.get_flag("b3sum") {
|
||||
set_or_crash("BLAKE3", Box::new(blake3::Hasher::new()), 256);
|
||||
set_or_crash("BLAKE3", Box::new(Blake3::new()), 256);
|
||||
}
|
||||
if matches.get_flag("sha3") {
|
||||
match matches.get_one::<usize>("bits") {
|
||||
|
@ -680,7 +665,7 @@ fn digest_reader<T: Read>(
|
|||
// Assume it's SHAKE. result_str() doesn't work with shake (as of 8/30/2016)
|
||||
let mut bytes = Vec::new();
|
||||
bytes.resize((output_bits + 7) / 8, 0);
|
||||
digest.result(&mut bytes);
|
||||
digest.hash_finalize(&mut bytes);
|
||||
Ok(encode(bytes))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,23 +5,31 @@ GNU version of `head`, you can use a benchmarking tool like
|
|||
[hyperfine][0]. On Ubuntu 18.04 or later, you can install `hyperfine` by
|
||||
running
|
||||
|
||||
```shell
|
||||
sudo apt-get install hyperfine
|
||||
```
|
||||
|
||||
Next, build the `head` binary under the release profile:
|
||||
|
||||
```shell
|
||||
cargo build --release -p uu_head
|
||||
```
|
||||
|
||||
Now, get a text file to test `head` on. I used the *Complete Works of
|
||||
William Shakespeare*, which is in the public domain in the United States
|
||||
and most other parts of the world.
|
||||
|
||||
```shell
|
||||
wget -O shakespeare.txt https://www.gutenberg.org/files/100/100-0.txt
|
||||
```
|
||||
|
||||
This particular file has about 170,000 lines, each of which is no longer
|
||||
than 96 characters:
|
||||
|
||||
```shell
|
||||
$ wc -lL shakespeare.txt
|
||||
170592 96 shakespeare.txt
|
||||
```
|
||||
|
||||
You could use files of different shapes and sizes to test the
|
||||
performance of `head` in different situations. For a larger file, you
|
||||
|
@ -32,9 +40,11 @@ contains about 130 million lines.
|
|||
Finally, you can compare the performance of the two versions of `head`
|
||||
by running, for example,
|
||||
|
||||
```shell
|
||||
hyperfine \
|
||||
"head -n 100000 shakespeare.txt" \
|
||||
"target/release/head -n 100000 shakespeare.txt"
|
||||
```
|
||||
|
||||
[0]: https://github.com/sharkdp/hyperfine
|
||||
[1]: https://www.wikidata.org/wiki/Wikidata:Database_download
|
||||
|
|
|
@ -41,8 +41,8 @@ pub struct Behavior {
|
|||
specified_mode: Option<u32>,
|
||||
backup_mode: BackupMode,
|
||||
suffix: String,
|
||||
owner: String,
|
||||
group: String,
|
||||
owner_id: Option<u32>,
|
||||
group_id: Option<u32>,
|
||||
verbose: bool,
|
||||
preserve_timestamps: bool,
|
||||
compare: bool,
|
||||
|
@ -58,14 +58,15 @@ enum InstallError {
|
|||
DirNeedsArg(),
|
||||
CreateDirFailed(PathBuf, std::io::Error),
|
||||
ChmodFailed(PathBuf),
|
||||
ChownFailed(PathBuf, String),
|
||||
InvalidTarget(PathBuf),
|
||||
TargetDirIsntDir(PathBuf),
|
||||
BackupFailed(PathBuf, PathBuf, std::io::Error),
|
||||
InstallFailed(PathBuf, PathBuf, std::io::Error),
|
||||
StripProgramFailed(String),
|
||||
MetadataFailed(std::io::Error),
|
||||
NoSuchUser(String),
|
||||
NoSuchGroup(String),
|
||||
InvalidUser(String),
|
||||
InvalidGroup(String),
|
||||
OmittingDirectory(PathBuf),
|
||||
}
|
||||
|
||||
|
@ -99,6 +100,7 @@ impl Display for InstallError {
|
|||
Display::fmt(&uio_error!(e, "failed to create {}", dir.quote()), f)
|
||||
}
|
||||
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!(
|
||||
f,
|
||||
"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::MetadataFailed(e) => Display::fmt(&uio_error!(e, ""), f),
|
||||
Self::NoSuchUser(user) => write!(f, "no such user: {}", user.maybe_quote()),
|
||||
Self::NoSuchGroup(group) => write!(f, "no such group: {}", group.maybe_quote()),
|
||||
Self::InvalidUser(user) => write!(f, "invalid user: {}", user.quote()),
|
||||
Self::InvalidGroup(group) => write!(f, "invalid group: {}", group.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");
|
||||
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 {
|
||||
main_function,
|
||||
specified_mode,
|
||||
backup_mode,
|
||||
suffix: backup_control::determine_backup_suffix(matches),
|
||||
owner: matches
|
||||
.get_one::<String>(OPT_OWNER)
|
||||
.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(),
|
||||
owner_id,
|
||||
group_id,
|
||||
verbose: matches.get_flag(OPT_VERBOSE),
|
||||
preserve_timestamps,
|
||||
compare,
|
||||
|
@ -466,6 +491,8 @@ fn directory(paths: &[String], b: &Behavior) -> UResult<()> {
|
|||
uucore::error::set_exit_code(1);
|
||||
continue;
|
||||
}
|
||||
|
||||
show_if_err!(chown_optional_user_group(path, b));
|
||||
}
|
||||
// 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
|
||||
|
@ -620,6 +647,42 @@ fn copy_files_into_dir(files: &[PathBuf], target_dir: &Path, b: &Behavior) -> UR
|
|||
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.
|
||||
///
|
||||
/// Returns a Result type with the Err variant containing the error message.
|
||||
|
@ -702,66 +765,7 @@ fn copy(from: &Path, to: &Path, b: &Behavior) -> UResult<()> {
|
|||
return Err(InstallError::ChmodFailed(to.to_path_buf()).into());
|
||||
}
|
||||
|
||||
if !b.owner.is_empty() {
|
||||
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),
|
||||
}
|
||||
}
|
||||
chown_optional_user_group(to, b)?;
|
||||
|
||||
if b.preserve_timestamps {
|
||||
let meta = match fs::metadata(from) {
|
||||
|
@ -841,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.
|
||||
|
||||
if !b.owner.is_empty() {
|
||||
let owner_id = match usr2uid(&b.owner) {
|
||||
Ok(id) => id,
|
||||
_ => return Err(InstallError::NoSuchUser(b.owner.clone()).into()),
|
||||
};
|
||||
if let Some(owner_id) = b.owner_id {
|
||||
if owner_id != to_meta.uid() {
|
||||
return Ok(true);
|
||||
}
|
||||
} else if !b.group.is_empty() {
|
||||
let group_id = match grp2gid(&b.group) {
|
||||
Ok(id) => id,
|
||||
_ => return Err(InstallError::NoSuchGroup(b.group.clone()).into()),
|
||||
};
|
||||
} else if let Some(group_id) = b.group_id {
|
||||
if group_id != to_meta.gid() {
|
||||
return Ok(true);
|
||||
}
|
||||
|
|
|
@ -17,11 +17,14 @@ A benchmark with `-j` and `-i` shows the following time:
|
|||
| libc | 25% | I/O and memory allocation. |
|
||||
|
||||
More detailed profiles can be obtained via [flame graphs](https://github.com/flamegraph-rs/flamegraph):
|
||||
```
|
||||
|
||||
```shell
|
||||
cargo flamegraph --bin join --package uu_join -- file1 file2 > /dev/null
|
||||
```
|
||||
|
||||
You may need to add the following lines to the top-level `Cargo.toml` to get full stack traces:
|
||||
```
|
||||
|
||||
```toml
|
||||
[profile.release]
|
||||
debug = true
|
||||
```
|
||||
|
@ -34,18 +37,22 @@ in practice many CSV datasets will function well after being sorted.
|
|||
|
||||
Like most of the utils, the recommended tool for benchmarking is [hyperfine](https://github.com/sharkdp/hyperfine).
|
||||
To benchmark your changes:
|
||||
|
||||
- checkout the main branch (without your changes), do a `--release` build, and back up the executable produced at `target/release/join`
|
||||
- checkout your working branch (with your changes), do a `--release` build
|
||||
- run
|
||||
```
|
||||
|
||||
```shell
|
||||
hyperfine -w 5 "/path/to/main/branch/build/join file1 file2" "/path/to/working/branch/build/join file1 file2"
|
||||
```
|
||||
|
||||
- you'll likely need to add additional options to both commands, such as a field separator, or if you're benchmarking some particular behavior
|
||||
- you can also optionally benchmark against GNU's join
|
||||
|
||||
## What to benchmark
|
||||
|
||||
The following options can have a non-trivial impact on performance:
|
||||
|
||||
- `-a`/`-v` if one of the two files has significantly more lines than the other
|
||||
- `-j`/`-1`/`-2` cause work to be done to grab the appropriate field
|
||||
- `-i` adds a call to `to_ascii_lowercase()` that adds some time for allocating and dropping memory for the lowercase key
|
||||
|
|
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::error::{FromIo, UError, UResult, USimpleError};
|
||||
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.";
|
||||
const USAGE: &str = "{} [OPTIONS]... PID...";
|
||||
static ABOUT: &str = help_about!("kill.md");
|
||||
const USAGE: &str = help_usage!("kill.md");
|
||||
|
||||
pub mod options {
|
||||
pub static PIDS_OR_SIGNALS: &str = "pids_or_signals";
|
||||
|
|
7
src/uu/link/link.md
Normal file
7
src/uu/link/link.md
Normal file
|
@ -0,0 +1,7 @@
|
|||
# link
|
||||
|
||||
```
|
||||
link FILE1 FILE2
|
||||
```
|
||||
|
||||
Call the link function to create a link named FILE2 to an existing FILE1.
|
|
@ -11,10 +11,10 @@ use std::fs::hard_link;
|
|||
use std::path::Path;
|
||||
use uucore::display::Quotable;
|
||||
use uucore::error::{FromIo, UResult};
|
||||
use uucore::format_usage;
|
||||
use uucore::{format_usage, help_about, help_usage};
|
||||
|
||||
static ABOUT: &str = "Call the link function to create a link named FILE2 to an existing FILE1.";
|
||||
const USAGE: &str = "{} FILE1 FILE2";
|
||||
static ABOUT: &str = help_about!("link.md");
|
||||
const USAGE: &str = help_usage!("link.md");
|
||||
|
||||
pub mod options {
|
||||
pub static FILES: &str = "FILES";
|
||||
|
|
21
src/uu/ln/ln.md
Normal file
21
src/uu/ln/ln.md
Normal file
|
@ -0,0 +1,21 @@
|
|||
# ln
|
||||
|
||||
```
|
||||
ln [OPTION]... [-T] TARGET LINK_NAME
|
||||
ln [OPTION]... TARGET
|
||||
ln [OPTION]... TARGET... DIRECTORY
|
||||
ln [OPTION]... -t DIRECTORY TARGET...
|
||||
```
|
||||
|
||||
Change file owner and group
|
||||
|
||||
## After Help
|
||||
|
||||
In the 1st form, create a link to TARGET with the name LINK_NAME.
|
||||
In the 2nd form, create a link to TARGET in the current directory.
|
||||
In the 3rd and 4th forms, create links to each TARGET in DIRECTORY.
|
||||
Create hard links by default, symbolic links with --symbolic.
|
||||
By default, each destination (name of new link) should not already exist.
|
||||
When creating hard links, each TARGET must exist. Symbolic links
|
||||
can hold arbitrary text; if later resolved, a relative link is
|
||||
interpreted in relation to its parent directory.
|
|
@ -11,7 +11,7 @@ use clap::{crate_version, Arg, ArgAction, Command};
|
|||
use uucore::display::Quotable;
|
||||
use uucore::error::{FromIo, UError, UResult};
|
||||
use uucore::fs::{make_path_relative_to, paths_refer_to_same_file};
|
||||
use uucore::{format_usage, prompt_yes, show_error};
|
||||
use uucore::{format_usage, help_about, help_section, help_usage, prompt_yes, show_error};
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::error::Error;
|
||||
|
@ -85,21 +85,9 @@ impl UError for LnError {
|
|||
}
|
||||
}
|
||||
|
||||
const ABOUT: &str = "Change file owner and group";
|
||||
const USAGE: &str = "\
|
||||
{} [OPTION]... [-T] TARGET LINK_NAME
|
||||
{} [OPTION]... TARGET
|
||||
{} [OPTION]... TARGET... DIRECTORY
|
||||
{} [OPTION]... -t DIRECTORY TARGET...";
|
||||
const LONG_USAGE: &str = "\
|
||||
In the 1st form, create a link to TARGET with the name LINK_NAME.\n\
|
||||
In the 2nd form, create a link to TARGET in the current directory.\n\
|
||||
In the 3rd and 4th forms, create links to each TARGET in DIRECTORY.\n\
|
||||
Create hard links by default, symbolic links with --symbolic.\n\
|
||||
By default, each destination (name of new link) should not already exist.\n\
|
||||
When creating hard links, each TARGET must exist. Symbolic links\n\
|
||||
can hold arbitrary text; if later resolved, a relative link is\n\
|
||||
interpreted in relation to its parent directory.";
|
||||
const ABOUT: &str = help_about!("ln.md");
|
||||
const USAGE: &str = help_usage!("ln.md");
|
||||
const AFTER_HELP: &str = help_section!("after help", "ln.md");
|
||||
|
||||
mod options {
|
||||
pub const FORCE: &str = "force";
|
||||
|
@ -119,13 +107,13 @@ static ARG_FILES: &str = "files";
|
|||
|
||||
#[uucore::main]
|
||||
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||
let long_usage = format!(
|
||||
let after_help = format!(
|
||||
"{}\n\n{}",
|
||||
LONG_USAGE,
|
||||
AFTER_HELP,
|
||||
backup_control::BACKUP_CONTROL_LONG_HELP
|
||||
);
|
||||
|
||||
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)?;
|
||||
|
||||
/* the list of files */
|
||||
|
||||
|
|
|
@ -29,7 +29,8 @@ Example: `hyperfine --warmup 2 "target/release/coreutils ls -al -R tree > /dev/n
|
|||
This can also be used to compare with version of ls built before your changes to ensure your change does not regress this.
|
||||
|
||||
Here is a `bash` script for doing this comparison:
|
||||
```bash
|
||||
|
||||
```shell
|
||||
#!/bin/bash
|
||||
cargo build --no-default-features --features ls --release
|
||||
args="$@"
|
||||
|
@ -46,12 +47,14 @@ hyperfine "ls $args" "target/release/coreutils ls $args"
|
|||
## Cargo Flamegraph
|
||||
|
||||
With Cargo Flamegraph you can easily make a flamegraph of `ls`:
|
||||
```bash
|
||||
|
||||
```shell
|
||||
cargo flamegraph --cmd coreutils -- ls [additional parameters]
|
||||
```
|
||||
|
||||
However, if the `-R` option is given, the output becomes pretty much useless due to recursion. We can fix this by merging all the direct recursive calls with `uniq`, below is a `bash` script that does this.
|
||||
```bash
|
||||
|
||||
```shell
|
||||
#!/bin/bash
|
||||
cargo build --release --no-default-features --features ls
|
||||
perf record target/release/coreutils ls "$@"
|
||||
|
|
|
@ -25,7 +25,7 @@ glob = { workspace=true }
|
|||
lscolors = { workspace=true }
|
||||
uucore = { workspace=true, features = ["entries", "fs"] }
|
||||
once_cell = { workspace=true }
|
||||
atty = { workspace=true }
|
||||
is-terminal = { workspace=true }
|
||||
selinux = { workspace=true, optional = true }
|
||||
|
||||
[[bin]]
|
||||
|
|
12
src/uu/ls/ls.md
Normal file
12
src/uu/ls/ls.md
Normal file
|
@ -0,0 +1,12 @@
|
|||
# ls
|
||||
|
||||
```
|
||||
ls [OPTION]... [FILE]...
|
||||
```
|
||||
|
||||
List directory contents.
|
||||
Ignore files and directories starting with a '.' by default
|
||||
|
||||
## After help
|
||||
|
||||
The TIME_STYLE argument can be full-iso, long-iso, iso, locale or +FORMAT. FORMAT is interpreted like in date. Also the TIME_STYLE environment variable sets the default style to use.
|
|
@ -12,6 +12,7 @@ use clap::{
|
|||
crate_version, Arg, ArgAction, Command,
|
||||
};
|
||||
use glob::{MatchOptions, Pattern};
|
||||
use is_terminal::IsTerminal;
|
||||
use lscolors::LsColors;
|
||||
use number_prefix::NumberPrefix;
|
||||
use once_cell::unsync::OnceCell;
|
||||
|
@ -54,17 +55,16 @@ use uucore::{
|
|||
parse_size::parse_size,
|
||||
version_cmp::version_cmp,
|
||||
};
|
||||
use uucore::{parse_glob, show, show_error, show_warning};
|
||||
use uucore::{help_about, help_section, help_usage, parse_glob, show, show_error, show_warning};
|
||||
|
||||
#[cfg(not(feature = "selinux"))]
|
||||
static CONTEXT_HELP_TEXT: &str = "print any security context of each file (not enabled)";
|
||||
#[cfg(feature = "selinux")]
|
||||
static CONTEXT_HELP_TEXT: &str = "print any security context of each file";
|
||||
|
||||
const ABOUT: &str = r#"List directory contents.
|
||||
Ignore files and directories starting with a '.' by default"#;
|
||||
|
||||
const USAGE: &str = "{} [OPTION]... [FILE]...";
|
||||
const ABOUT: &str = help_about!("ls.md");
|
||||
const AFTER_HELP: &str = help_section!("after help", "ls.md");
|
||||
const USAGE: &str = help_usage!("ls.md");
|
||||
|
||||
pub mod options {
|
||||
pub mod format {
|
||||
|
@ -451,7 +451,7 @@ impl Config {
|
|||
(Format::Commas, Some(options::format::COMMAS))
|
||||
} else if options.get_flag(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)
|
||||
} else {
|
||||
(Format::OneLine, None)
|
||||
|
@ -557,7 +557,7 @@ impl Config {
|
|||
None => options.contains_id(options::COLOR),
|
||||
Some(val) => match val.as_str() {
|
||||
"" | "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,
|
||||
},
|
||||
};
|
||||
|
@ -678,7 +678,7 @@ impl Config {
|
|||
} else if options.get_flag(options::SHOW_CONTROL_CHARS) {
|
||||
true
|
||||
} else {
|
||||
!atty::is(atty::Stream::Stdout)
|
||||
!std::io::stdout().is_terminal()
|
||||
};
|
||||
|
||||
let opt_quoting_style = options
|
||||
|
@ -750,7 +750,7 @@ impl Config {
|
|||
"never" | "no" | "none" => IndicatorStyle::None,
|
||||
"always" | "yes" | "force" => IndicatorStyle::Classify,
|
||||
"auto" | "tty" | "if-tty" => {
|
||||
if atty::is(atty::Stream::Stdout) {
|
||||
if std::io::stdout().is_terminal() {
|
||||
IndicatorStyle::Classify
|
||||
} else {
|
||||
IndicatorStyle::None
|
||||
|
@ -1620,10 +1620,7 @@ pub fn uu_app() -> Command {
|
|||
.value_hint(clap::ValueHint::AnyPath)
|
||||
.value_parser(ValueParser::os_string()),
|
||||
)
|
||||
.after_help(
|
||||
"The TIME_STYLE argument can be full-iso, long-iso, iso, locale or +FORMAT. FORMAT is interpreted like in date. \
|
||||
Also the TIME_STYLE environment variable sets the default style to use.",
|
||||
)
|
||||
.after_help(AFTER_HELP)
|
||||
}
|
||||
|
||||
/// Represents a Path along with it's associated data.
|
||||
|
|
13
src/uu/mkdir/mkdir.md
Normal file
13
src/uu/mkdir/mkdir.md
Normal file
|
@ -0,0 +1,13 @@
|
|||
# mkdir
|
||||
|
||||
<!-- spell-checker:ignore ugoa -->
|
||||
|
||||
```
|
||||
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))]
|
||||
use uucore::mode;
|
||||
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;
|
||||
|
||||
const ABOUT: &str = "Create the given DIRECTORY(ies) if they do not exist";
|
||||
const USAGE: &str = "{} [OPTION]... [USER]";
|
||||
const LONG_USAGE: &str =
|
||||
"Each MODE is of the form '[ugoa]*([-+=]([rwxXst]*|[ugo]))+|[-+=]?[0-7]+'.";
|
||||
const ABOUT: &str = help_about!("mkdir.md");
|
||||
const USAGE: &str = help_usage!("mkdir.md");
|
||||
const AFTER_HELP: &str = help_section!("after help", "mkdir.md");
|
||||
|
||||
mod options {
|
||||
pub const MODE: &str = "mode";
|
||||
|
@ -90,7 +89,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
|||
// Linux-specific options, not implemented
|
||||
// opts.optflag("Z", "context", "set SELinux security context" +
|
||||
// " 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
|
||||
.get_many::<OsString>(options::DIRS)
|
||||
|
|
|
@ -18,7 +18,7 @@ path = "src/more.rs"
|
|||
clap = { workspace=true }
|
||||
uucore = { workspace=true }
|
||||
crossterm = { workspace=true }
|
||||
atty = { workspace=true }
|
||||
is-terminal = { workspace=true }
|
||||
unicode-width = { workspace=true }
|
||||
unicode-segmentation = { workspace=true }
|
||||
|
||||
|
|
|
@ -23,6 +23,7 @@ use crossterm::{
|
|||
terminal,
|
||||
};
|
||||
|
||||
use is_terminal::IsTerminal;
|
||||
use unicode_segmentation::UnicodeSegmentation;
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
use uucore::display::Quotable;
|
||||
|
@ -83,7 +84,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
|||
buff.clear();
|
||||
}
|
||||
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();
|
||||
let mut stdout = setup_term();
|
||||
more(&buff, &mut stdout, None, silent)?;
|
||||
|
|
|
@ -17,7 +17,7 @@ path = "src/nohup.rs"
|
|||
[dependencies]
|
||||
clap = { workspace=true }
|
||||
libc = { workspace=true }
|
||||
atty = { workspace=true }
|
||||
is-terminal = { workspace=true }
|
||||
uucore = { workspace=true, features=["fs"] }
|
||||
|
||||
[[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
|
||||
|
||||
use clap::{crate_version, Arg, ArgAction, Command};
|
||||
use is_terminal::IsTerminal;
|
||||
use libc::{c_char, dup2, execvp, signal};
|
||||
use libc::{SIGHUP, SIG_IGN};
|
||||
use std::env;
|
||||
|
@ -19,18 +20,11 @@ use std::os::unix::prelude::*;
|
|||
use std::path::{Path, PathBuf};
|
||||
use uucore::display::Quotable;
|
||||
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.";
|
||||
static LONG_HELP: &str = "
|
||||
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.
|
||||
";
|
||||
const USAGE: &str = "\
|
||||
{} COMMAND [ARG]...
|
||||
{} FLAG";
|
||||
const ABOUT: &str = help_about!("nohup.md");
|
||||
const AFTER_HELP: &str = help_section!("after help", "nohup.md");
|
||||
const USAGE: &str = help_usage!("nohup.md");
|
||||
static NOHUP_OUT: &str = "nohup.out";
|
||||
// exit codes that match the GNU implementation
|
||||
static EXIT_CANCELED: i32 = 125;
|
||||
|
@ -115,7 +109,7 @@ pub fn uu_app() -> Command {
|
|||
Command::new(uucore::util_name())
|
||||
.version(crate_version!())
|
||||
.about(ABOUT)
|
||||
.after_help(LONG_HELP)
|
||||
.after_help(AFTER_HELP)
|
||||
.override_usage(format_usage(USAGE))
|
||||
.arg(
|
||||
Arg::new(options::CMD)
|
||||
|
@ -129,7 +123,7 @@ pub fn uu_app() -> Command {
|
|||
}
|
||||
|
||||
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"))
|
||||
.map_err(|e| NohupError::CannotReplace("STDIN", e))?;
|
||||
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 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());
|
||||
}
|
||||
Ok(())
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<!-- spell-checker:ignore N'th M'th -->
|
||||
# numfmt
|
||||
|
||||
<!-- spell-checker:ignore N'th M'th -->
|
||||
|
||||
```
|
||||
numfmt [OPTION]... [NUMBER]...
|
||||
```
|
||||
|
@ -10,6 +11,7 @@ Convert numbers from/to human-readable strings
|
|||
## After Help
|
||||
|
||||
`UNIT` options:
|
||||
|
||||
- `none`: no auto-scaling is done; suffixes will trigger an error
|
||||
- `auto`: accept optional single/two letter suffix:
|
||||
|
||||
|
@ -27,7 +29,7 @@ Convert numbers from/to human-readable strings
|
|||
|
||||
1Ki = 1024, 1Mi = 1048576, ...
|
||||
|
||||
`FIELDS` supports `cut(1)` style field ranges:
|
||||
- `FIELDS` supports `cut(1)` style field ranges:
|
||||
|
||||
N N'th field, counted from 1
|
||||
N- from N'th field, to end of line
|
||||
|
|
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 uucore::display::Quotable;
|
||||
use uucore::error::{UResult, USimpleError};
|
||||
use uucore::format_usage;
|
||||
use uucore::parse_size::ParseSizeError;
|
||||
use uucore::show_error;
|
||||
use uucore::show_warning;
|
||||
use uucore::{format_usage, help_about, help_section, help_usage, show_error, show_warning};
|
||||
|
||||
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 = "\
|
||||
{} [OPTION]... [--] [FILENAME]...
|
||||
{} [-abcdDefFhHiIlLoOsxX] [FILENAME] [[+][0x]OFFSET[.][b]]
|
||||
{} --traditional [OPTION]... [FILENAME] [[+][0x]OFFSET[.][b] [[+][0x]LABEL[.][b]]]";
|
||||
const USAGE: &str = help_usage!("od.md");
|
||||
|
||||
static LONG_HELP: &str = r#"
|
||||
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."#;
|
||||
const AFTER_HELP: &str = help_section!("after help", "od.md");
|
||||
|
||||
pub(crate) mod options {
|
||||
pub const HELP: &str = "help";
|
||||
|
@ -295,7 +253,7 @@ pub fn uu_app() -> Command {
|
|||
.version(crate_version!())
|
||||
.about(ABOUT)
|
||||
.override_usage(format_usage(USAGE))
|
||||
.after_help(LONG_HELP)
|
||||
.after_help(AFTER_HELP)
|
||||
.trailing_var_arg(true)
|
||||
.dont_delimit_trailing_values(true)
|
||||
.infer_long_args(true)
|
||||
|
|
7
src/uu/pwd/pwd.md
Normal file
7
src/uu/pwd/pwd.md
Normal file
|
@ -0,0 +1,7 @@
|
|||
# pwd
|
||||
|
||||
```
|
||||
pwd [OPTION]... [FILE]...
|
||||
```
|
||||
|
||||
Display the full filename of the current working directory.
|
|
@ -10,13 +10,13 @@ use clap::{crate_version, Arg, Command};
|
|||
use std::env;
|
||||
use std::io;
|
||||
use std::path::PathBuf;
|
||||
use uucore::format_usage;
|
||||
use uucore::{format_usage, help_about, help_usage};
|
||||
|
||||
use uucore::display::println_verbatim;
|
||||
use uucore::error::{FromIo, UResult};
|
||||
|
||||
static ABOUT: &str = "Display the full filename of the current working directory.";
|
||||
const USAGE: &str = "{} [OPTION]... FILE...";
|
||||
static ABOUT: &str = help_about!("pwd.md");
|
||||
const USAGE: &str = help_usage!("pwd.md");
|
||||
static OPT_LOGICAL: &str = "logical";
|
||||
static OPT_PHYSICAL: &str = "physical";
|
||||
|
||||
|
|
7
src/uu/realpath/realpath.md
Normal file
7
src/uu/realpath/realpath.md
Normal file
|
@ -0,0 +1,7 @@
|
|||
# realpath
|
||||
|
||||
```
|
||||
realpath [OPTION]... FILE...
|
||||
```
|
||||
|
||||
Print the resolved path
|
|
@ -20,11 +20,12 @@ use uucore::{
|
|||
error::{FromIo, UResult},
|
||||
format_usage,
|
||||
fs::{canonicalize, MissingHandling, ResolveMode},
|
||||
help_about, help_usage,
|
||||
};
|
||||
use uucore::{error::UClapError, show, show_if_err};
|
||||
|
||||
static ABOUT: &str = "Print the resolved path";
|
||||
const USAGE: &str = "{} [OPTION]... FILE...";
|
||||
static ABOUT: &str = help_about!("realpath.md");
|
||||
const USAGE: &str = help_usage!("realpath.md");
|
||||
|
||||
static OPT_QUIET: &str = "quiet";
|
||||
static OPT_STRIP: &str = "strip";
|
||||
|
|
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 uucore::display::Quotable;
|
||||
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};
|
||||
|
||||
#[derive(Eq, PartialEq, Clone, Copy)]
|
||||
|
@ -37,21 +37,9 @@ struct Options {
|
|||
verbose: bool,
|
||||
}
|
||||
|
||||
const ABOUT: &str = "Remove (unlink) the FILE(s)";
|
||||
const USAGE: &str = "{} [OPTION]... FILE...";
|
||||
const LONG_USAGE: &str = "\
|
||||
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.";
|
||||
const ABOUT: &str = help_about!("rm.md");
|
||||
const USAGE: &str = help_usage!("rm.md");
|
||||
const AFTER_HELP: &str = help_section!("after help", "rm.md");
|
||||
|
||||
static OPT_DIR: &str = "dir";
|
||||
static OPT_INTERACTIVE: &str = "interactive";
|
||||
|
@ -69,7 +57,7 @@ static ARG_FILES: &str = "files";
|
|||
|
||||
#[uucore::main]
|
||||
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
|
||||
.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::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.";
|
||||
const USAGE: &str = "{} [OPTION]... DIRECTORY...";
|
||||
static ABOUT: &str = help_about!("rmdir.md");
|
||||
const USAGE: &str = help_usage!("rmdir.md");
|
||||
static OPT_IGNORE_FAIL_NON_EMPTY: &str = "ignore-fail-on-non-empty";
|
||||
static OPT_PARENTS: &str = "parents";
|
||||
static OPT_VERBOSE: &str = "verbose";
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
use clap::builder::ValueParser;
|
||||
use uucore::error::{UResult, UUsageError};
|
||||
|
||||
use clap::{Arg, ArgAction, Command};
|
||||
use clap::{crate_version, Arg, ArgAction, Command};
|
||||
use selinux::{OpaqueSecurityContext, SecurityClass, SecurityContext};
|
||||
use uucore::format_usage;
|
||||
|
||||
|
@ -18,7 +18,6 @@ mod errors;
|
|||
use errors::error_exit_status;
|
||||
use errors::{Error, Result, RunconError};
|
||||
|
||||
const VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
const ABOUT: &str = "Run command with specified security context.";
|
||||
const USAGE: &str = "\
|
||||
{} [CONTEXT COMMAND [ARG...]]
|
||||
|
@ -107,7 +106,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
|||
|
||||
pub fn uu_app() -> Command {
|
||||
Command::new(uucore::util_name())
|
||||
.version(VERSION)
|
||||
.version(crate_version!())
|
||||
.about(ABOUT)
|
||||
.after_help(DESCRIPTION)
|
||||
.override_usage(format_usage(USAGE))
|
||||
|
|
|
@ -5,15 +5,21 @@ GNU version of `seq`, you can use a benchmarking tool like
|
|||
[hyperfine][0]. On Ubuntu 18.04 or later, you can install `hyperfine` by
|
||||
running
|
||||
|
||||
```shell
|
||||
sudo apt-get install hyperfine
|
||||
```
|
||||
|
||||
Next, build the `seq` binary under the release profile:
|
||||
|
||||
```shell
|
||||
cargo build --release -p uu_seq
|
||||
```
|
||||
|
||||
Finally, you can compare the performance of the two versions of `head`
|
||||
by running, for example,
|
||||
|
||||
```shell
|
||||
hyperfine "seq 1000000" "target/release/seq 1000000"
|
||||
```
|
||||
|
||||
[0]: https://github.com/sharkdp/hyperfine
|
||||
|
|
11
src/uu/shuf/shuf.md
Normal file
11
src/uu/shuf/shuf.md
Normal file
|
@ -0,0 +1,11 @@
|
|||
# shuf
|
||||
|
||||
```
|
||||
shuf [OPTION]... [FILE]
|
||||
shuf -e [OPTION]... [ARG]...
|
||||
shuf -i LO-HI [OPTION]...;
|
||||
```
|
||||
|
||||
Shuffle the input by outputting a random permutation of input lines.
|
||||
Each output permutation is equally likely.
|
||||
With no FILE, or when FILE is -, read standard input.
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue