1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2025-07-28 11:37:44 +00:00

Merge branch 'master' into pr

This commit is contained in:
Terts Diepraam 2021-05-29 19:14:27 +02:00
commit bc1870c0a7
352 changed files with 58309 additions and 7742 deletions

View file

@ -1,7 +1,14 @@
env:
# Temporary workaround for error `error: sysinfo not supported on
# this platform` seen on FreeBSD platforms, affecting Rustup
#
# References: https://github.com/rust-lang/rustup/issues/2774
RUSTUP_IO_THREADS: 1
task: task:
name: stable x86_64-unknown-freebsd-12 name: stable x86_64-unknown-freebsd-12
freebsd_instance: freebsd_instance:
image: freebsd-12-1-release-amd64 image: freebsd-12-2-release-amd64
setup_script: setup_script:
- pkg install -y curl gmake - pkg install -y curl gmake
- curl https://sh.rustup.rs -sSf --output rustup.sh - curl https://sh.rustup.rs -sSf --output rustup.sh

View file

@ -11,7 +11,7 @@ env:
PROJECT_NAME: coreutils PROJECT_NAME: coreutils
PROJECT_DESC: "Core universal (cross-platform) utilities" PROJECT_DESC: "Core universal (cross-platform) utilities"
PROJECT_AUTH: "uutils" PROJECT_AUTH: "uutils"
RUST_MIN_SRV: "1.40.0" ## v1.40.0 RUST_MIN_SRV: "1.43.1" ## v1.43.0
RUST_COV_SRV: "2020-08-01" ## (~v1.47.0) supported rust version for code coverage; (date required/used by 'coverage') ## !maint: refactor when code coverage support is included in the stable channel RUST_COV_SRV: "2020-08-01" ## (~v1.47.0) supported rust version for code coverage; (date required/used by 'coverage') ## !maint: refactor when code coverage support is included in the stable channel
on: [push, pull_request] on: [push, pull_request]
@ -235,6 +235,9 @@ jobs:
arm-unknown-linux-gnueabihf) sudo apt-get -y update ; sudo apt-get -y install gcc-arm-linux-gnueabihf ;; arm-unknown-linux-gnueabihf) sudo apt-get -y update ; sudo apt-get -y install gcc-arm-linux-gnueabihf ;;
aarch64-unknown-linux-gnu) sudo apt-get -y update ; sudo apt-get -y install gcc-aarch64-linux-gnu ;; aarch64-unknown-linux-gnu) sudo apt-get -y update ; sudo apt-get -y install gcc-aarch64-linux-gnu ;;
esac esac
case '${{ matrix.job.os }}' in
macos-latest) brew install coreutils ;; # needed for testing
esac
- name: Initialize workflow variables - name: Initialize workflow variables
id: vars id: vars
shell: bash shell: bash
@ -360,6 +363,10 @@ jobs:
mkdir -p '${{ steps.vars.outputs.STAGING }}/dpkg' mkdir -p '${{ steps.vars.outputs.STAGING }}/dpkg'
- name: rust toolchain ~ install - name: rust toolchain ~ install
uses: actions-rs/toolchain@v1 uses: actions-rs/toolchain@v1
env:
# Override auto-detection of RAM for Rustc install.
# https://github.com/rust-lang/rustup/issues/2229#issuecomment-585855925
RUSTUP_UNPACK_RAM: "21474836480"
with: with:
toolchain: ${{ steps.vars.outputs.TOOLCHAIN }} toolchain: ${{ steps.vars.outputs.TOOLCHAIN }}
target: ${{ matrix.job.target }} target: ${{ matrix.job.target }}
@ -486,6 +493,13 @@ jobs:
- { os: windows-latest , features: windows } - { os: windows-latest , features: windows }
steps: steps:
- uses: actions/checkout@v1 - uses: actions/checkout@v1
- name: Install/setup prerequisites
shell: bash
run: |
## install/setup prerequisites
case '${{ matrix.job.os }}' in
macos-latest) brew install coreutils ;; # needed for testing
esac
# - name: Reattach HEAD ## may be needed for accurate code coverage info # - name: Reattach HEAD ## may be needed for accurate code coverage info
# run: git checkout ${{ github.head_ref }} # run: git checkout ${{ github.head_ref }}
- name: Initialize workflow variables - name: Initialize workflow variables

View file

@ -12,16 +12,18 @@ jobs:
uses: actions/checkout@v2 uses: actions/checkout@v2
with: with:
path: 'uutils' path: 'uutils'
- name: Chechout GNU coreutils - name: Checkout GNU coreutils
uses: actions/checkout@v2 uses: actions/checkout@v2
with: with:
repository: 'coreutils/coreutils' repository: 'coreutils/coreutils'
path: 'gnu' path: 'gnu'
- name: Chechout GNU corelib ref: v8.32
- name: Checkout GNU corelib
uses: actions/checkout@v2 uses: actions/checkout@v2
with: with:
repository: 'coreutils/gnulib' repository: 'coreutils/gnulib'
path: 'gnulib' path: 'gnulib'
ref: 8e99f24c0931a38880c6ee9b8287c7da80b0036b
fetch-depth: 0 # gnu gets upset if gnulib is a shallow checkout fetch-depth: 0 # gnu gets upset if gnulib is a shallow checkout
- name: Install `rust` toolchain - name: Install `rust` toolchain
uses: actions-rs/toolchain@v1 uses: actions-rs/toolchain@v1
@ -30,100 +32,43 @@ jobs:
default: true default: true
profile: minimal # minimal component installation (ie, no documentation) profile: minimal # minimal component installation (ie, no documentation)
components: rustfmt components: rustfmt
- name: Build binaries - name: Install deps
shell: bash shell: bash
run: | run: |
sudo apt-get update sudo apt-get update
sudo apt-get install autoconf autopoint bison texinfo gperf gcc g++ gdb python-pyinotify python3-sphinx sudo apt-get install autoconf autopoint bison texinfo gperf gcc g++ gdb python-pyinotify python3-sphinx jq
pushd uutils - name: Build binaries
make PROFILE=release shell: bash
BUILDDIR="$PWD/target/release/" run: |
cp "${BUILDDIR}/install" "${BUILDDIR}/ginstall" # The GNU tests rename this script before running, to avoid confusion with the make target cd uutils
# Create *sum binaries bash util/build-gnu.sh
for sum in b2sum md5sum sha1sum sha224sum sha256sum sha384sum sha512sum
do
sum_path="${BUILDDIR}/${sum}"
test -f "${sum_path}" || cp "${BUILDDIR}/hashsum" "${sum_path}"
done
test -f "${BUILDDIR}/[" || cp "${BUILDDIR}/test" "${BUILDDIR}/["
popd
GNULIB_SRCDIR="$PWD/gnulib"
pushd gnu/
# Any binaries that aren't built become `false` so their tests fail
for binary in $(./build-aux/gen-lists-of-programs.sh --list-progs)
do
bin_path="${BUILDDIR}/${binary}"
test -f "${bin_path}" || { echo "'${binary}' was not built with uutils, using the 'false' program"; cp "${BUILDDIR}/false" "${bin_path}"; }
done
./bootstrap --gnulib-srcdir="$GNULIB_SRCDIR"
./configure --quiet --disable-gcc-warnings
#Add timeout to to protect against hangs
sed -i 's|"\$@|/usr/bin/timeout 600 "\$@|' build-aux/test-driver
# Change the PATH in the Makefile to test the uutils coreutils instead of the GNU coreutils
sed -i "s/^[[:blank:]]*PATH=.*/ PATH='${BUILDDIR//\//\\/}\$(PATH_SEPARATOR)'\"\$\$PATH\" \\\/" Makefile
sed -i 's| tr | /usr/bin/tr |' tests/init.sh
make
# Generate the factor tests, so they can be fixed
for i in {00..36}
do
make tests/factor/t${i}.sh
done
grep -rl 'path_prepend_' tests/* | xargs sed -i 's|path_prepend_ ./src||'
sed -i -e 's|^seq |/usr/bin/seq |' -e 's|sha1sum |/usr/bin/sha1sum |' tests/factor/t*sh
# Remove tests checking for --version & --help
# Not really interesting for us and logs are too big
sed -i -e '/tests\/misc\/invalid-opt.pl/ D' \
-e '/tests\/misc\/help-version.sh/ D' \
-e '/tests\/misc\/help-version-getopt.sh/ D' \
Makefile
# Use the system coreutils where the test fails due to error in a util that is not the one being tested
sed -i 's|stat|/usr/bin/stat|' tests/chgrp/basic.sh tests/cp/existing-perm-dir.sh tests/touch/60-seconds.sh tests/misc/sort-compress-proc.sh
sed -i 's|ls -|/usr/bin/ls -|' tests/chgrp/posix-H.sh tests/chown/deref.sh tests/cp/same-file.sh tests/misc/mknod.sh tests/mv/part-symlink.sh tests/du/8gb.sh
sed -i 's|mkdir |/usr/bin/mkdir |' tests/cp/existing-perm-dir.sh tests/rm/empty-inacc.sh
sed -i 's|timeout \([[:digit:]]\)| /usr/bin/timeout \1|' tests/tail-2/inotify-rotate.sh tests/tail-2/inotify-dir-recreate.sh tests/tail-2/inotify-rotate-resources.sh tests/cp/parent-perm-race.sh tests/ls/infloop.sh tests/misc/sort-exit-early.sh tests/misc/sort-NaN-infloop.sh tests/misc/uniq-perf.sh tests/tail-2/inotify-only-regular.sh tests/tail-2/pipe-f2.sh tests/tail-2/retry.sh tests/tail-2/symlink.sh tests/tail-2/wait.sh tests/tail-2/pid.sh tests/dd/stats.sh tests/tail-2/follow-name.sh tests/misc/shuf.sh # Don't break the function called 'grep_timeout'
sed -i 's|chmod |/usr/bin/chmod |' tests/du/inacc-dir.sh tests/mkdir/p-3.sh tests/tail-2/tail-n0f.sh tests/cp/fail-perm.sh tests/du/inaccessible-cwd.sh tests/mv/i-2.sh tests/chgrp/basic.sh tests/misc/shuf.sh
sed -i 's|sort |/usr/bin/sort |' tests/ls/hyperlink.sh tests/misc/test-N.sh
sed -i 's|split |/usr/bin/split |' tests/misc/factor-parallel.sh
sed -i 's|truncate |/usr/bin/truncate |' tests/split/fail.sh
sed -i 's|dd |/usr/bin/dd |' tests/du/8gb.sh tests/tail-2/big-4gb.sh tests/cp/fiemap-2.sh init.cfg
sed -i 's|id -|/usr/bin/id -|' tests/misc/runcon-no-reorder.sh
sed -i 's|touch |/usr/bin/touch |' tests/cp/preserve-link.sh tests/cp/reflink-perm.sh tests/ls/block-size.sh tests/ls/abmon-align.sh tests/ls/rt-1.sh tests/mv/update.sh tests/misc/ls-time.sh tests/misc/stat-nanoseconds.sh tests/misc/time-style.sh tests/misc/test-N.sh
sed -i 's|ln -|/usr/bin/ln -|' tests/cp/link-deref.sh
sed -i 's|printf |/usr/bin/printf |' tests/dd/ascii.sh
sed -i 's|cp |/usr/bin/cp |' tests/mv/hard-2.sh
sed -i 's|paste |/usr/bin/paste |' tests/misc/od-endian.sh
sed -i 's|seq |/usr/bin/seq |' tests/misc/sort-discrim.sh
#Add specific timeout to tests that currently hang to limit time spent waiting
sed -i 's|seq \$|/usr/bin/timeout 0.1 seq \$|' tests/misc/seq-precision.sh tests/misc/seq-long-double.sh
sed -i 's|cat |/usr/bin/timeout 0.1 cat |' tests/misc/cat-self.sh
test -f "${BUILDDIR}/getlimits" || cp src/getlimits "${BUILDDIR}"
- name: Run GNU tests - name: Run GNU tests
shell: bash shell: bash
run: | run: |
BUILDDIR="${PWD}/uutils/target/release" bash uutils/util/run-gnu-test.sh
GNULIB_DIR="${PWD}/gnulib"
pushd gnu
timeout -sKILL 2h make -j "$(nproc)" check SUBDIRS=. RUN_EXPENSIVE_TESTS=yes RUN_VERY_EXPENSIVE_TESTS=yes VERBOSE=no || : # Kill after 4 hours in case something gets stuck in make
- name: Extract tests info - name: Extract tests info
shell: bash shell: bash
run: | run: |
if test -f gnu/tests/test-suite.log LOG_FILE=gnu/tests/test-suite.log
if test -f "$LOG_FILE"
then then
TOTAL=$( grep "# TOTAL:" gnu/tests/test-suite.log|cut -d' ' -f2-) TOTAL=$(sed -n "s/.*# TOTAL: \(.*\)/\1/p" "$LOG_FILE"|tr -d '\r'|head -n1)
PASS=$( grep "# PASS:" gnu/tests/test-suite.log|cut -d' ' -f2-) PASS=$(sed -n "s/.*# PASS: \(.*\)/\1/p" "$LOG_FILE"|tr -d '\r'|head -n1)
SKIP=$( grep "# SKIP:" gnu/tests/test-suite.log|cut -d' ' -f2-) SKIP=$(sed -n "s/.*# SKIP: \(.*\)/\1/p" "$LOG_FILE"|tr -d '\r'|head -n1)
FAIL=$( grep "# FAIL:" gnu/tests/test-suite.log|cut -d' ' -f2-) FAIL=$(sed -n "s/.*# FAIL: \(.*\)/\1/p" "$LOG_FILE"|tr -d '\r'|head -n1)
XPASS=$( grep "# XPASS:" gnu/tests/test-suite.log|cut -d' ' -f2-) XPASS=$(sed -n "s/.*# XPASS: \(.*\)/\1/p" "$LOG_FILE"|tr -d '\r'|head -n1)
ERROR=$( grep "# ERROR:" gnu/tests/test-suite.log|cut -d' ' -f2-) ERROR=$(sed -n "s/.*# ERROR: \(.*\)/\1/p" "$LOG_FILE"|tr -d '\r'|head -n1)
echo "::warning ::GNU testsuite = $TOTAL / $PASS / $FAIL / $ERROR" echo "::warning ::GNU testsuite = TOTAL: $TOTAL / PASS: $PASS / FAIL: $FAIL / ERROR: $ERROR"
jq -n \
--arg date "$(date --rfc-email)" \
--arg sha "$GITHUB_SHA" \
--arg total "$TOTAL" \
--arg pass "$PASS" \
--arg skip "$SKIP" \
--arg fail "$FAIL" \
--arg xpass "$XPASS" \
--arg error "$ERROR" \
'{($date): { sha: $sha, total: $total, pass: $pass, skip: $skip, fail: $fail, xpass: $xpass, error: $error, }}' > gnu-result.json
else else
echo "::error ::Failed to get summary of test results" echo "::error ::Failed to get summary of test results"
fi fi
@ -132,3 +77,8 @@ jobs:
with: with:
name: test-report name: test-report
path: gnu/tests/**/*.log path: gnu/tests/**/*.log
- uses: actions/upload-artifact@v2
with:
name: gnu-result
path: gnu-result.json

3
.gitignore vendored
View file

@ -12,3 +12,6 @@ target/
Cargo.lock Cargo.lock
lib*.a lib*.a
/docs/_build /docs/_build
*.iml
### macOS ###
.DS_Store

View file

@ -1,72 +0,0 @@
language: rust
rust:
- stable
- beta
os:
- linux
# - osx
env:
# sphinx v1.8.0 is bugged & fails for linux builds; so, force specific `sphinx` version
global: FEATURES='' TEST_INSTALL='' SPHINX_VERSIONED='sphinx==1.7.8'
matrix:
allow_failures:
- rust: beta
- rust: nightly
fast_finish: true
include:
- rust: 1.40.0
env: FEATURES=unix
# - rust: stable
# os: linux
# env: FEATURES=unix TEST_INSTALL=true
# - rust: stable
# os: osx
# env: FEATURES=macos TEST_INSTALL=true
- rust: nightly
os: linux
env: FEATURES=nightly,unix TEST_INSTALL=true
- rust: nightly
os: osx
env: FEATURES=nightly,macos TEST_INSTALL=true
- rust: nightly
os: linux
env: FEATURES=nightly,feat_os_unix_redox CC=x86_64-unknown-redox-gcc CARGO_ARGS='--no-default-features --target=x86_64-unknown-redox' REDOX=1
cache:
directories:
- $HOME/.cargo
sudo: true
before_install:
- if [ $REDOX ]; then ./.travis/redox-toolchain.sh; fi
install:
- if [ $TRAVIS_OS_NAME = linux ]; then sudo apt-get install python-pip && sudo pip install $SPHINX_VERSIONED; fi
- |
if [ $TRAVIS_OS_NAME = osx ]; then
brew update
brew upgrade python
pip3 install $SPHINX_VERSIONED
fi
script:
- cargo build $CARGO_ARGS --features "$FEATURES"
- if [ ! $REDOX ]; then cargo test $CARGO_ARGS -p uucore -p coreutils --features "$FEATURES" --no-fail-fast; fi
- if [ -n "$TEST_INSTALL" ]; then mkdir installdir_test; DESTDIR=installdir_test make install; [ `ls installdir_test/usr/local/bin | wc -l` -gt 0 ]; fi
addons:
apt:
packages:
- libssl-dev
after_success: |
if [ "$TRAVIS_OS_NAME" = linux -a "$TRAVIS_RUST_VERSION" = stable ]; then
bash <(curl https://raw.githubusercontent.com/xd009642/tarpaulin/master/travis-install.sh)
cargo tarpaulin --out Xml
bash <(curl -s https://codecov.io/bash)
fi

View file

@ -22,6 +22,7 @@ search the issues to make sure no one else is working on it.
1. Make sure that the code coverage is covering all of the cases, including errors. 1. Make sure that the code coverage is covering all of the cases, including errors.
1. The code must be clippy-warning-free and rustfmt-compliant. 1. The code must be clippy-warning-free and rustfmt-compliant.
1. Don't hesitate to move common functions into uucore if they can be reused by other binaries. 1. Don't hesitate to move common functions into uucore if they can be reused by other binaries.
1. Unsafe code should be documented with Safety comments.
## Commit messages ## Commit messages
@ -69,10 +70,6 @@ lines for non-utility modules include:
README: add help README: add help
``` ```
```
travis: fix build
```
``` ```
uucore: add new modules uucore: add new modules
``` ```

542
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -326,6 +326,9 @@ wc = { optional=true, version="0.0.6", package="uu_wc", path="src/uu/wc" }
who = { optional=true, version="0.0.6", package="uu_who", path="src/uu/who" } who = { optional=true, version="0.0.6", package="uu_who", path="src/uu/who" }
whoami = { optional=true, version="0.0.6", package="uu_whoami", path="src/uu/whoami" } whoami = { optional=true, version="0.0.6", package="uu_whoami", path="src/uu/whoami" }
yes = { optional=true, version="0.0.6", package="uu_yes", path="src/uu/yes" } yes = { optional=true, version="0.0.6", package="uu_yes", path="src/uu/yes" }
factor_benches = { optional = true, version = "0.0.0", package = "uu_factor_benches", path = "tests/benches/factor" }
# #
# * pinned transitive dependencies # * pinned transitive dependencies
# Not needed for now. Keep as examples: # Not needed for now. Keep as examples:
@ -338,17 +341,15 @@ filetime = "0.2"
glob = "0.3.0" glob = "0.3.0"
libc = "0.2" libc = "0.2"
nix = "0.20.0" nix = "0.20.0"
pretty_assertions = "0.7.2"
rand = "0.7" rand = "0.7"
regex = "1.0" regex = "1.0"
sha1 = { version="0.6", features=["std"] } sha1 = { version="0.6", features=["std"] }
## tempfile 3.2 depends on recent version of rand which depends on getrandom v0.2 which has compiler errors for MinRustV v1.32.0 tempfile = "3.2.0"
## min dep for tempfile = Rustc 1.40
tempfile = "= 3.1.0"
time = "0.1" time = "0.1"
unindent = "0.1" unindent = "0.1"
uucore = { version=">=0.0.8", package="uucore", path="src/uucore", features=["entries"] } uucore = { version=">=0.0.8", package="uucore", path="src/uucore", features=["entries"] }
walkdir = "2.2" walkdir = "2.2"
tempdir = "0.3"
[target.'cfg(unix)'.dev-dependencies] [target.'cfg(unix)'.dev-dependencies]
rust-users = { version="0.10", package="users" } rust-users = { version="0.10", package="users" }

View file

@ -6,7 +6,6 @@
[![LOC](https://tokei.rs/b1/github/uutils/coreutils?category=code)](https://github.com/Aaronepower/tokei) [![LOC](https://tokei.rs/b1/github/uutils/coreutils?category=code)](https://github.com/Aaronepower/tokei)
[![dependency status](https://deps.rs/repo/github/uutils/coreutils/status.svg)](https://deps.rs/repo/github/uutils/coreutils) [![dependency status](https://deps.rs/repo/github/uutils/coreutils/status.svg)](https://deps.rs/repo/github/uutils/coreutils)
[![Build Status](https://api.travis-ci.org/uutils/coreutils.svg?branch=master)](https://travis-ci.org/uutils/coreutils)
[![Build Status (FreeBSD)](https://api.cirrus-ci.com/github/uutils/coreutils.svg)](https://cirrus-ci.com/github/uutils/coreutils/master) [![Build Status (FreeBSD)](https://api.cirrus-ci.com/github/uutils/coreutils.svg)](https://cirrus-ci.com/github/uutils/coreutils/master)
[![CodeCov](https://codecov.io/gh/uutils/coreutils/branch/master/graph/badge.svg)](https://codecov.io/gh/uutils/coreutils) [![CodeCov](https://codecov.io/gh/uutils/coreutils/branch/master/graph/badge.svg)](https://codecov.io/gh/uutils/coreutils)
@ -40,7 +39,7 @@ to compile anywhere, and this is as good a way as any to try and learn it.
### Rust Version ### Rust Version
uutils follows Rust's release channels and is tested against stable, beta and nightly. uutils follows Rust's release channels and is tested against stable, beta and nightly.
The current oldest supported version of the Rust compiler is `1.40.0`. The current oldest supported version of the Rust compiler is `1.43.1`.
On both Windows and Redox, only the nightly version is tested currently. On both Windows and Redox, only the nightly version is tested currently.
@ -93,7 +92,7 @@ $ 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 If you don't want to build the multicall binary and would prefer to build
the utilities as individual binaries, that is also possible. Each utility the utilities as individual binaries, that is also possible. Each utility
is contained in it's own package within the main repository, named is contained in its own package within the main repository, named
"uu_UTILNAME". To build individual utilities, use cargo to build just the "uu_UTILNAME". To build individual utilities, use cargo to build just the
specific packages (using the `--package` [aka `-p`] option). For example: specific packages (using the `--package` [aka `-p`] option). For example:
@ -319,6 +318,16 @@ To pass an argument like "-v" to the busybox test runtime
$ make UTILS='UTILITY_1 UTILITY_2' RUNTEST_ARGS='-v' busytest $ make UTILS='UTILITY_1 UTILITY_2' RUNTEST_ARGS='-v' busytest
``` ```
## Comparing with GNU
![Evolution over time](https://github.com/uutils/coreutils-tracking/blob/main/gnu-results.png?raw=true)
To run locally:
```bash
$ bash util/build-gnu.sh
$ bash util/run-gnu-test.sh
```
## Contribute ## Contribute
To contribute to uutils, please see [CONTRIBUTING](CONTRIBUTING.md). To contribute to uutils, please see [CONTRIBUTING](CONTRIBUTING.md).
@ -429,6 +438,7 @@ This is an auto-generated table showing which binaries compile for each target-t
|windows-msvc|i686|y|y|y|y|y| | | | |y|y|y|y|y|y|y|y|y| |y|y|y| |y|y|y|y| |y|y|y|y| | |y| |y|y|y|y|y| | |y|y|y| |y| |y|y|y|y| | |y|y|y|y|y|y|y|y|y|y|y|y|y|y|y| | |y|y|y|y|y|y| |y|y|y|y|y| |y|y|y| |y| |y| |y|y| |windows-msvc|i686|y|y|y|y|y| | | | |y|y|y|y|y|y|y|y|y| |y|y|y| |y|y|y|y| |y|y|y|y| | |y| |y|y|y|y|y| | |y|y|y| |y| |y|y|y|y| | |y|y|y|y|y|y|y|y|y|y|y|y|y|y|y| | |y|y|y|y|y|y| |y|y|y|y|y| |y|y|y| |y| |y| |y|y|
|windows-gnu|x86_64|y|y|y|y|y| | | | |y|y|y|y|y|y|y|y|y| |y|y|y| |y|y|y|y| |y|y|y|y| | |y| |y|y|y|y|y| | |y|y|y| |y| |y|y|y|y| | |y|y|y|y|y|y|y|y|y|y|y|y|y|y|y| | |y|y|y|y|y|y| |y|y|y|y|y|y|y|y|y| |y| |y| |y|y| |windows-gnu|x86_64|y|y|y|y|y| | | | |y|y|y|y|y|y|y|y|y| |y|y|y| |y|y|y|y| |y|y|y|y| | |y| |y|y|y|y|y| | |y|y|y| |y| |y|y|y|y| | |y|y|y|y|y|y|y|y|y|y|y|y|y|y|y| | |y|y|y|y|y|y| |y|y|y|y|y|y|y|y|y| |y| |y| |y|y|
|windows-msvc|x86_64|y|y|y|y|y| | | | |y|y|y|y|y|y|y|y|y| |y|y|y| |y|y|y|y| |y|y|y|y| | |y| |y|y|y|y|y| | |y|y|y| |y| |y|y|y|y| | |y|y|y|y|y|y|y|y|y|y|y|y|y|y|y| | |y|y|y|y|y|y| |y|y|y|y|y| |y|y|y| |y| |y| |y|y| |windows-msvc|x86_64|y|y|y|y|y| | | | |y|y|y|y|y|y|y|y|y| |y|y|y| |y|y|y|y| |y|y|y|y| | |y| |y|y|y|y|y| | |y|y|y| |y| |y|y|y|y| | |y|y|y|y|y|y|y|y|y|y|y|y|y|y|y| | |y|y|y|y|y|y| |y|y|y|y|y| |y|y|y| |y| |y| |y|y|
|apple MacOS|aarch64|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y| |y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|
|apple MacOS|x86_64|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y| |y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y| |apple MacOS|x86_64|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y| |y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|
|freebsd|x86_64|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y| |freebsd|x86_64|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|
|netbsd|x86_64|y|y|y|y|y|y|y|y| |y|y|y|y|y|y| |y|y|y|y|y|y| |y|y|y|y|y|y|y|y|y| |y|y| |y|y|y|y|y|y|y|y|y|y|y|y| |y|y|y|y|y| |y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y| |y|y|y|y|y|y| |y|y|y| | |y| |y|y| |netbsd|x86_64|y|y|y|y|y|y|y|y| |y|y|y|y|y|y| |y|y|y|y|y|y| |y|y|y|y|y|y|y|y|y| |y|y| |y|y|y|y|y|y|y|y|y|y|y|y| |y|y|y|y|y| |y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y|y| |y|y|y|y|y|y| |y|y|y| | |y| |y|y|

View file

@ -26,6 +26,7 @@ TARGETS = [
"x86_64-pc-windows-gnu", "x86_64-pc-windows-gnu",
"x86_64-pc-windows-msvc", "x86_64-pc-windows-msvc",
# Apple # Apple
"aarch64-apple-darwin",
"x86_64-apple-darwin", "x86_64-apple-darwin",
"aarch64-apple-ios", "aarch64-apple-ios",
"x86_64-apple-ios", "x86_64-apple-ios",
@ -231,4 +232,4 @@ if __name__ == "__main__":
prev_table, _, _ = load_csv(CACHE_PATH) prev_table, _, _ = load_csv(CACHE_PATH)
new_table = merge_tables(prev_table, table) new_table = merge_tables(prev_table, table)
save_csv(CACHE_PATH, new_table) save_csv(CACHE_PATH, new_table)

View file

@ -16,6 +16,7 @@ path = "src/arch.rs"
[dependencies] [dependencies]
platform-info = "0.1" platform-info = "0.1"
clap = "2.33"
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore = { version=">=0.0.8", package="uucore", path="../../uucore" }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }

View file

@ -11,12 +11,19 @@ extern crate uucore;
use platform_info::*; use platform_info::*;
static SYNTAX: &str = "Display machine architecture"; use clap::App;
static VERSION: &str = env!("CARGO_PKG_VERSION");
static ABOUT: &str = "Display machine architecture";
static SUMMARY: &str = "Determine architecture name for current machine."; static SUMMARY: &str = "Determine architecture name for current machine.";
static LONG_HELP: &str = "";
pub fn uumain(args: impl uucore::Args) -> i32 { pub fn uumain(args: impl uucore::Args) -> i32 {
app!(SYNTAX, SUMMARY, LONG_HELP).parse(args.collect_str()); App::new(executable!())
.version(VERSION)
.about(ABOUT)
.after_help(SUMMARY)
.get_matches_from(args);
let uts = return_if_err!(1, PlatformInfo::new()); let uts = return_if_err!(1, PlatformInfo::new());
println!("{}", uts.machine().trim()); println!("{}", uts.machine().trim());
0 0

View file

@ -15,6 +15,7 @@ edition = "2018"
path = "src/base32.rs" path = "src/base32.rs"
[dependencies] [dependencies]
clap = "2.33"
uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features = ["encoding"] } uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features = ["encoding"] }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }

View file

@ -7,13 +7,14 @@
#[macro_use] #[macro_use]
extern crate uucore; extern crate uucore;
use std::io::{stdin, Read};
use uucore::encoding::Format; use uucore::encoding::Format;
mod base_common; pub mod base_common;
static SYNTAX: &str = "[OPTION]... [FILE]"; static ABOUT: &str = "
static SUMMARY: &str = "Base32 encode or decode FILE, or standard input, to standard output.";
static LONG_HELP: &str = "
With no FILE, or when FILE is -, read standard input. With no FILE, or when FILE is -, read standard input.
The data are encoded as described for the base32 alphabet in RFC The data are encoded as described for the base32 alphabet in RFC
@ -22,13 +23,43 @@ static LONG_HELP: &str = "
to attempt to recover from any other non-alphabet bytes in the to attempt to recover from any other non-alphabet bytes in the
encoded stream. encoded stream.
"; ";
static VERSION: &str = env!("CARGO_PKG_VERSION");
static BASE_CMD_PARSE_ERROR: i32 = 1;
fn get_usage() -> String {
format!("{0} [OPTION]... [FILE]", executable!())
}
pub fn uumain(args: impl uucore::Args) -> i32 { pub fn uumain(args: impl uucore::Args) -> i32 {
base_common::execute( let format = Format::Base32;
args.collect_str(), let usage = get_usage();
SYNTAX, let name = executable!();
SUMMARY,
LONG_HELP, let config_result: Result<base_common::Config, String> =
Format::Base32, base_common::parse_base_cmd_args(args, name, VERSION, ABOUT, &usage);
)
if config_result.is_err() {
match config_result {
Ok(_) => panic!(),
Err(s) => crash!(BASE_CMD_PARSE_ERROR, "{}", s),
}
}
// Create a reference to stdin so we can return a locked stdin from
// parse_base_cmd_args
let stdin_raw = stdin();
let config = config_result.unwrap();
let mut input: Box<dyn Read> = base_common::get_input(&config, &stdin_raw);
base_common::handle_input(
&mut input,
format,
config.wrap_cols,
config.ignore_garbage,
config.decode,
name,
);
0
} }

View file

@ -7,73 +7,133 @@
// For the full copyright and license information, please view the LICENSE file // For the full copyright and license information, please view the LICENSE file
// that was distributed with this source code. // that was distributed with this source code.
use std::fs::File; use std::io::{stdout, Read, Write};
use std::io::{stdin, stdout, BufReader, Read, Write};
use std::path::Path;
use uucore::encoding::{wrap_print, Data, Format}; use uucore::encoding::{wrap_print, Data, Format};
use uucore::InvalidEncodingHandling;
pub fn execute( use std::fs::File;
args: Vec<String>, use std::io::{BufReader, Stdin};
syntax: &str, use std::path::Path;
summary: &str,
long_help: &str,
format: Format,
) -> i32 {
let matches = app!(syntax, summary, long_help)
.optflag("d", "decode", "decode data")
.optflag(
"i",
"ignore-garbage",
"when decoding, ignore non-alphabetic characters",
)
.optopt(
"w",
"wrap",
"wrap encoded lines after COLS character (default 76, 0 to disable wrapping)",
"COLS",
)
.parse(args);
let line_wrap = matches.opt_str("wrap").map(|s| match s.parse() { use clap::{App, Arg};
Ok(n) => n,
Err(e) => {
crash!(1, "invalid wrap size: {}: {}", s, e);
}
});
let ignore_garbage = matches.opt_present("ignore-garbage");
let decode = matches.opt_present("decode");
if matches.free.len() > 1 { // Config.
show_usage_error!("extra operand {}", matches.free[0]); pub struct Config {
return 1; pub decode: bool,
} pub ignore_garbage: bool,
pub wrap_cols: Option<usize>,
if matches.free.is_empty() || &matches.free[0][..] == "-" { pub to_read: Option<String>,
let stdin_raw = stdin();
handle_input(
&mut stdin_raw.lock(),
format,
line_wrap,
ignore_garbage,
decode,
);
} else {
let path = Path::new(matches.free[0].as_str());
let file_buf = safe_unwrap!(File::open(&path));
let mut input = BufReader::new(file_buf);
handle_input(&mut input, format, line_wrap, ignore_garbage, decode);
};
0
} }
fn handle_input<R: Read>( pub mod options {
pub static DECODE: &str = "decode";
pub static WRAP: &str = "wrap";
pub static IGNORE_GARBAGE: &str = "ignore-garbage";
pub static FILE: &str = "file";
}
impl Config {
fn from(options: clap::ArgMatches) -> Result<Config, String> {
let file: Option<String> = match options.values_of(options::FILE) {
Some(mut values) => {
let name = values.next().unwrap();
if values.len() != 0 {
return Err(format!("extra operand {}", name));
}
if name == "-" {
None
} else {
if !Path::exists(Path::new(name)) {
return Err(format!("{}: No such file or directory", name));
}
Some(name.to_owned())
}
}
None => None,
};
let cols = match options.value_of(options::WRAP) {
Some(num) => match num.parse::<usize>() {
Ok(n) => Some(n),
Err(e) => {
return Err(format!("Invalid wrap size: {}: {}", num, e));
}
},
None => None,
};
Ok(Config {
decode: options.is_present(options::DECODE),
ignore_garbage: options.is_present(options::IGNORE_GARBAGE),
wrap_cols: cols,
to_read: file,
})
}
}
pub fn parse_base_cmd_args(
args: impl uucore::Args,
name: &str,
version: &str,
about: &str,
usage: &str,
) -> Result<Config, String> {
let app = App::new(name)
.version(version)
.about(about)
.usage(usage)
// Format arguments.
.arg(
Arg::with_name(options::DECODE)
.short("d")
.long(options::DECODE)
.help("decode data"),
)
.arg(
Arg::with_name(options::IGNORE_GARBAGE)
.short("i")
.long(options::IGNORE_GARBAGE)
.help("when decoding, ignore non-alphabetic characters"),
)
.arg(
Arg::with_name(options::WRAP)
.short("w")
.long(options::WRAP)
.takes_value(true)
.help(
"wrap encoded lines after COLS character (default 76, 0 to disable wrapping)",
),
)
// "multiple" arguments are used to check whether there is more than one
// file passed in.
.arg(Arg::with_name(options::FILE).index(1).multiple(true));
let arg_list = args
.collect_str(InvalidEncodingHandling::ConvertLossy)
.accept_any();
Config::from(app.get_matches_from(arg_list))
}
pub fn get_input<'a>(config: &Config, stdin_ref: &'a Stdin) -> Box<dyn Read + 'a> {
match &config.to_read {
Some(name) => {
let file_buf = safe_unwrap!(File::open(Path::new(name)));
Box::new(BufReader::new(file_buf)) // as Box<dyn Read>
}
None => {
Box::new(stdin_ref.lock()) // as Box<dyn Read>
}
}
}
pub fn handle_input<R: Read>(
input: &mut R, input: &mut R,
format: Format, format: Format,
line_wrap: Option<usize>, line_wrap: Option<usize>,
ignore_garbage: bool, ignore_garbage: bool,
decode: bool, decode: bool,
name: &str,
) { ) {
let mut data = Data::new(input, format).ignore_garbage(ignore_garbage); let mut data = Data::new(input, format).ignore_garbage(ignore_garbage);
if let Some(wrap) = line_wrap { if let Some(wrap) = line_wrap {
@ -88,10 +148,14 @@ fn handle_input<R: Read>(
Ok(s) => { Ok(s) => {
if stdout().write_all(&s).is_err() { if stdout().write_all(&s).is_err() {
// on windows console, writing invalid utf8 returns an error // on windows console, writing invalid utf8 returns an error
crash!(1, "Cannot write non-utf8 data"); eprintln!("{}: error: Cannot write non-utf8 data", name);
exit!(1)
} }
} }
Err(_) => crash!(1, "invalid input"), Err(_) => {
eprintln!("{}: error: invalid input", name);
exit!(1)
}
} }
} }
} }

View file

@ -15,8 +15,10 @@ edition = "2018"
path = "src/base64.rs" path = "src/base64.rs"
[dependencies] [dependencies]
clap = "2.33"
uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features = ["encoding"] } uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features = ["encoding"] }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
uu_base32 = { version=">=0.0.6", package="uu_base32", path="../base32"}
[[bin]] [[bin]]
name = "base64" name = "base64"

View file

@ -8,13 +8,14 @@
#[macro_use] #[macro_use]
extern crate uucore; extern crate uucore;
use uu_base32::base_common;
use uucore::encoding::Format; use uucore::encoding::Format;
mod base_common; use std::io::{stdin, Read};
static SYNTAX: &str = "[OPTION]... [FILE]"; static ABOUT: &str = "
static SUMMARY: &str = "Base64 encode or decode FILE, or standard input, to standard output.";
static LONG_HELP: &str = "
With no FILE, or when FILE is -, read standard input. With no FILE, or when FILE is -, read standard input.
The data are encoded as described for the base64 alphabet in RFC The data are encoded as described for the base64 alphabet in RFC
@ -23,13 +24,42 @@ static LONG_HELP: &str = "
to attempt to recover from any other non-alphabet bytes in the to attempt to recover from any other non-alphabet bytes in the
encoded stream. encoded stream.
"; ";
static VERSION: &str = env!("CARGO_PKG_VERSION");
static BASE_CMD_PARSE_ERROR: i32 = 1;
fn get_usage() -> String {
format!("{0} [OPTION]... [FILE]", executable!())
}
pub fn uumain(args: impl uucore::Args) -> i32 { pub fn uumain(args: impl uucore::Args) -> i32 {
base_common::execute( let format = Format::Base64;
args.collect_str(), let usage = get_usage();
SYNTAX, let name = executable!();
SUMMARY, let config_result: Result<base_common::Config, String> =
LONG_HELP, base_common::parse_base_cmd_args(args, name, VERSION, ABOUT, &usage);
Format::Base64,
) if config_result.is_err() {
match config_result {
Ok(_) => panic!(),
Err(s) => crash!(BASE_CMD_PARSE_ERROR, "{}", s),
}
}
// Create a reference to stdin so we can return a locked stdin from
// parse_base_cmd_args
let stdin_raw = stdin();
let config = config_result.unwrap();
let mut input: Box<dyn Read> = base_common::get_input(&config, &stdin_raw);
base_common::handle_input(
&mut input,
format,
config.wrap_cols,
config.ignore_garbage,
config.decode,
name,
);
0
} }

View file

@ -1,97 +0,0 @@
// This file is part of the uutils coreutils package.
//
// (c) Jordy Dickinson <jordy.dickinson@gmail.com>
// (c) Jian Zeng <anonymousknight96@gmail.com>
// (c) Alex Lyon <arcterus@mail.com>
//
// For the full copyright and license information, please view the LICENSE file
// that was distributed with this source code.
use std::fs::File;
use std::io::{stdin, stdout, BufReader, Read, Write};
use std::path::Path;
use uucore::encoding::{wrap_print, Data, Format};
pub fn execute(
args: Vec<String>,
syntax: &str,
summary: &str,
long_help: &str,
format: Format,
) -> i32 {
let matches = app!(syntax, summary, long_help)
.optflag("d", "decode", "decode data")
.optflag(
"i",
"ignore-garbage",
"when decoding, ignore non-alphabetic characters",
)
.optopt(
"w",
"wrap",
"wrap encoded lines after COLS character (default 76, 0 to disable wrapping)",
"COLS",
)
.parse(args);
let line_wrap = matches.opt_str("wrap").map(|s| match s.parse() {
Ok(n) => n,
Err(e) => {
crash!(1, "invalid wrap size: {}: {}", s, e);
}
});
let ignore_garbage = matches.opt_present("ignore-garbage");
let decode = matches.opt_present("decode");
if matches.free.len() > 1 {
show_usage_error!("extra operand {}", matches.free[0]);
return 1;
}
if matches.free.is_empty() || &matches.free[0][..] == "-" {
let stdin_raw = stdin();
handle_input(
&mut stdin_raw.lock(),
format,
line_wrap,
ignore_garbage,
decode,
);
} else {
let path = Path::new(matches.free[0].as_str());
let file_buf = safe_unwrap!(File::open(&path));
let mut input = BufReader::new(file_buf);
handle_input(&mut input, format, line_wrap, ignore_garbage, decode);
};
0
}
fn handle_input<R: Read>(
input: &mut R,
format: Format,
line_wrap: Option<usize>,
ignore_garbage: bool,
decode: bool,
) {
let mut data = Data::new(input, format).ignore_garbage(ignore_garbage);
if let Some(wrap) = line_wrap {
data = data.line_wrap(wrap);
}
if !decode {
let encoded = data.encode();
wrap_print(&data, encoded);
} else {
match data.decode() {
Ok(s) => {
if stdout().write_all(&s).is_err() {
// on windows console, writing invalid utf8 returns an error
crash!(1, "Cannot write non-utf8 data");
}
}
Err(_) => crash!(1, "invalid input"),
}
}
}

View file

@ -15,6 +15,7 @@ edition = "2018"
path = "src/basename.rs" path = "src/basename.rs"
[dependencies] [dependencies]
clap = "2.33.2"
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore = { version=">=0.0.8", package="uucore", path="../../uucore" }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }

View file

@ -10,81 +10,106 @@
#[macro_use] #[macro_use]
extern crate uucore; extern crate uucore;
use clap::{App, Arg};
use std::path::{is_separator, PathBuf}; use std::path::{is_separator, PathBuf};
use uucore::InvalidEncodingHandling;
static NAME: &str = "basename"; static VERSION: &str = env!("CARGO_PKG_VERSION");
static SYNTAX: &str = "NAME [SUFFIX]";
static SUMMARY: &str = "Print NAME with any leading directory components removed static SUMMARY: &str = "Print NAME with any leading directory components removed
If specified, also remove a trailing SUFFIX"; If specified, also remove a trailing SUFFIX";
static LONG_HELP: &str = "";
fn get_usage() -> String {
format!(
"{0} NAME [SUFFIX]
{0} OPTION... NAME...",
executable!()
)
}
pub mod options {
pub static MULTIPLE: &str = "multiple";
pub static NAME: &str = "name";
pub static SUFFIX: &str = "suffix";
pub static ZERO: &str = "zero";
}
pub fn uumain(args: impl uucore::Args) -> i32 { pub fn uumain(args: impl uucore::Args) -> i32 {
let args = args.collect_str(); let args = args
.collect_str(InvalidEncodingHandling::ConvertLossy)
.accept_any();
let usage = get_usage();
// //
// Argument parsing // Argument parsing
// //
let matches = app!(SYNTAX, SUMMARY, LONG_HELP) let matches = App::new(executable!())
.optflag( .version(VERSION)
"a", .about(SUMMARY)
"multiple", .usage(&usage[..])
"Support more than one argument. Treat every argument as a name.", .arg(
Arg::with_name(options::MULTIPLE)
.short("a")
.long(options::MULTIPLE)
.help("support multiple arguments and treat each as a NAME"),
) )
.optopt( .arg(Arg::with_name(options::NAME).multiple(true).hidden(true))
"s", .arg(
"suffix", Arg::with_name(options::SUFFIX)
"Remove a trailing suffix. This option implies the -a option.", .short("s")
"SUFFIX", .long(options::SUFFIX)
.value_name("SUFFIX")
.help("remove a trailing SUFFIX; implies -a"),
) )
.optflag( .arg(
"z", Arg::with_name(options::ZERO)
"zero", .short("z")
"Output a zero byte (ASCII NUL) at the end of each line, rather than a newline.", .long(options::ZERO)
.help("end each output line with NUL, not newline"),
) )
.parse(args); .get_matches_from(args);
// too few arguments // too few arguments
if matches.free.is_empty() { if !matches.is_present(options::NAME) {
crash!( crash!(
1, 1,
"{0}: {1}\nTry '{0} --help' for more information.", "{1}\nTry '{0} --help' for more information.",
NAME, executable!(),
"missing operand" "missing operand"
); );
} }
let opt_s = matches.opt_present("s");
let opt_a = matches.opt_present("a"); let opt_suffix = matches.is_present(options::SUFFIX);
let opt_z = matches.opt_present("z"); let opt_multiple = matches.is_present(options::MULTIPLE);
let multiple_paths = opt_s || opt_a; let opt_zero = matches.is_present(options::ZERO);
let multiple_paths = opt_suffix || opt_multiple;
// too many arguments // too many arguments
if !multiple_paths && matches.free.len() > 2 { if !multiple_paths && matches.occurrences_of(options::NAME) > 2 {
crash!( crash!(
1, 1,
"{0}: extra operand '{1}'\nTry '{0} --help' for more information.", "extra operand '{1}'\nTry '{0} --help' for more information.",
NAME, executable!(),
matches.free[2] matches.values_of(options::NAME).unwrap().nth(2).unwrap()
); );
} }
let suffix = if opt_s { let suffix = if opt_suffix {
matches.opt_str("s").unwrap() matches.value_of(options::SUFFIX).unwrap()
} else if !opt_a && matches.free.len() > 1 { } else if !opt_multiple && matches.occurrences_of(options::NAME) > 1 {
matches.free[1].clone() matches.values_of(options::NAME).unwrap().nth(1).unwrap()
} else { } else {
"".to_owned() ""
}; };
// //
// Main Program Processing // Main Program Processing
// //
let paths = if multiple_paths { let paths: Vec<_> = if multiple_paths {
&matches.free[..] matches.values_of(options::NAME).unwrap().collect()
} else { } else {
&matches.free[0..1] matches.values_of(options::NAME).unwrap().take(1).collect()
}; };
let line_ending = if opt_z { "\0" } else { "\n" }; let line_ending = if opt_zero { "\0" } else { "\n" };
for path in paths { for path in paths {
print!("{}{}", basename(&path, &suffix), line_ending); print!("{}{}", basename(&path, &suffix), line_ending);
} }
@ -111,6 +136,7 @@ fn basename(fullname: &str, suffix: &str) -> String {
} }
} }
#[allow(clippy::manual_strip)] // can be replaced with strip_suffix once the minimum rust version is 1.45
fn strip_suffix(name: &str, suffix: &str) -> String { fn strip_suffix(name: &str, suffix: &str) -> String {
if name == suffix { if name == suffix {
return name.to_owned(); return name.to_owned();

View file

@ -16,13 +16,16 @@ path = "src/cat.rs"
[dependencies] [dependencies]
clap = "2.33" clap = "2.33"
quick-error = "1.2.3" thiserror = "1.0"
uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["fs"] } uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["fs"] }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
[target.'cfg(unix)'.dependencies] [target.'cfg(unix)'.dependencies]
unix_socket = "0.5.0" unix_socket = "0.5.0"
[target.'cfg(any(target_os = "linux", target_os = "android"))'.dependencies]
nix = "0.20"
[[bin]] [[bin]]
name = "cat" name = "cat"
path = "src/main.rs" path = "src/main.rs"

View file

@ -3,14 +3,13 @@
// (c) Jordi Boggiano <j.boggiano@seld.be> // (c) Jordi Boggiano <j.boggiano@seld.be>
// (c) Evgeniy Klyuchikov <evgeniy.klyuchikov@gmail.com> // (c) Evgeniy Klyuchikov <evgeniy.klyuchikov@gmail.com>
// (c) Joshua S. Miller <jsmiller@uchicago.edu> // (c) Joshua S. Miller <jsmiller@uchicago.edu>
// (c) Árni Dagur <arni@dagur.eu>
// //
// For the full copyright and license information, please view the LICENSE // For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code. // file that was distributed with this source code.
// spell-checker:ignore (ToDO) nonprint nonblank nonprinting // spell-checker:ignore (ToDO) nonprint nonblank nonprinting
#[macro_use]
extern crate quick_error;
#[cfg(unix)] #[cfg(unix)]
extern crate unix_socket; extern crate unix_socket;
#[macro_use] #[macro_use]
@ -18,11 +17,17 @@ extern crate uucore;
// last synced with: cat (GNU coreutils) 8.13 // last synced with: cat (GNU coreutils) 8.13
use clap::{App, Arg}; use clap::{App, Arg};
use quick_error::ResultExt;
use std::fs::{metadata, File}; use std::fs::{metadata, File};
use std::io::{self, stderr, stdin, stdout, BufWriter, Read, Write}; use std::io::{self, Read, Write};
use thiserror::Error;
use uucore::fs::is_stdin_interactive; use uucore::fs::is_stdin_interactive;
/// Linux splice support
#[cfg(any(target_os = "linux", target_os = "android"))]
mod splice;
#[cfg(any(target_os = "linux", target_os = "android"))]
use std::os::unix::io::{AsRawFd, RawFd};
/// Unix domain socket support /// Unix domain socket support
#[cfg(unix)] #[cfg(unix)]
use std::net::Shutdown; use std::net::Shutdown;
@ -30,6 +35,7 @@ use std::net::Shutdown;
use std::os::unix::fs::FileTypeExt; use std::os::unix::fs::FileTypeExt;
#[cfg(unix)] #[cfg(unix)]
use unix_socket::UnixStream; use unix_socket::UnixStream;
use uucore::InvalidEncodingHandling;
static NAME: &str = "cat"; static NAME: &str = "cat";
static VERSION: &str = env!("CARGO_PKG_VERSION"); static VERSION: &str = env!("CARGO_PKG_VERSION");
@ -37,6 +43,27 @@ static SYNTAX: &str = "[OPTION]... [FILE]...";
static SUMMARY: &str = "Concatenate FILE(s), or standard input, to standard output static SUMMARY: &str = "Concatenate FILE(s), or standard input, to standard output
With no FILE, or when FILE is -, read standard input."; With no FILE, or when FILE is -, read standard input.";
#[derive(Error, Debug)]
enum CatError {
/// Wrapper around `io::Error`
#[error("{0}")]
Io(#[from] io::Error),
/// Wrapper around `nix::Error`
#[cfg(any(target_os = "linux", target_os = "android"))]
#[error("{0}")]
Nix(#[from] nix::Error),
/// Unknown file type; it's not a regular file, socket, etc.
#[error("unknown filetype: {}", ft_debug)]
UnknownFiletype {
/// A debug print of the file type
ft_debug: String,
},
#[error("Is a directory")]
IsDirectory,
}
type CatResult<T> = Result<T, CatError>;
#[derive(PartialEq)] #[derive(PartialEq)]
enum NumberingMode { enum NumberingMode {
None, None,
@ -44,39 +71,6 @@ enum NumberingMode {
All, All,
} }
quick_error! {
#[derive(Debug)]
enum CatError {
/// Wrapper for io::Error with path context
Input(err: io::Error, path: String) {
display("cat: {0}: {1}", path, err)
context(path: &'a str, err: io::Error) -> (err, path.to_owned())
cause(err)
}
/// Wrapper for io::Error with no context
Output(err: io::Error) {
display("cat: {0}", err) from()
cause(err)
}
/// Unknown Filetype classification
UnknownFiletype(path: String) {
display("cat: {0}: unknown filetype", path)
}
/// At least one error was encountered in reading or writing
EncounteredErrors(count: usize) {
display("cat: encountered {0} errors", count)
}
/// Denotes an error caused by trying to `cat` a directory
IsDirectory(path: String) {
display("cat: {0}: Is a directory", path)
}
}
}
struct OutputOptions { struct OutputOptions {
/// Line numbering mode /// Line numbering mode
number: NumberingMode, number: NumberingMode,
@ -87,21 +81,56 @@ struct OutputOptions {
/// display TAB characters as `tab` /// display TAB characters as `tab`
show_tabs: bool, show_tabs: bool,
/// If `show_tabs == true`, this string will be printed in the /// Show end of lines
/// place of tabs show_ends: bool,
tab: String,
/// Can be set to show characters other than '\n' a the end of
/// each line, e.g. $
end_of_line: String,
/// use ^ and M- notation, except for LF (\\n) and TAB (\\t) /// use ^ and M- notation, except for LF (\\n) and TAB (\\t)
show_nonprint: bool, show_nonprint: bool,
} }
impl OutputOptions {
fn tab(&self) -> &'static str {
if self.show_tabs {
"^I"
} else {
"\t"
}
}
fn end_of_line(&self) -> &'static str {
if self.show_ends {
"$\n"
} else {
"\n"
}
}
/// We can write fast if we can simply copy the contents of the file to
/// stdout, without augmenting the output with e.g. line numbers.
fn can_write_fast(&self) -> bool {
!(self.show_tabs
|| self.show_nonprint
|| self.show_ends
|| self.squeeze_blank
|| self.number != NumberingMode::None)
}
}
/// State that persists between output of each file. This struct is only used
/// when we can't write fast.
struct OutputState {
/// The current line number
line_number: usize,
/// Whether the output cursor is at the beginning of a new line
at_line_start: bool,
}
/// Represents an open file handle, stream, or other device /// Represents an open file handle, stream, or other device
struct InputHandle { struct InputHandle<R: Read> {
reader: Box<dyn Read>, #[cfg(any(target_os = "linux", target_os = "android"))]
file_descriptor: RawFd,
reader: R,
is_interactive: bool, is_interactive: bool,
} }
@ -124,8 +153,6 @@ enum InputType {
Socket, Socket,
} }
type CatResult<T> = Result<T, CatError>;
mod options { mod options {
pub static FILE: &str = "file"; pub static FILE: &str = "file";
pub static SHOW_ALL: &str = "show-all"; pub static SHOW_ALL: &str = "show-all";
@ -140,7 +167,9 @@ mod options {
} }
pub fn uumain(args: impl uucore::Args) -> i32 { pub fn uumain(args: impl uucore::Args) -> i32 {
let args = args.collect_str(); let args = args
.collect_str(InvalidEncodingHandling::Ignore)
.accept_any();
let matches = App::new(executable!()) let matches = App::new(executable!())
.name(NAME) .name(NAME)
@ -243,30 +272,14 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
None => vec!["-".to_owned()], None => vec!["-".to_owned()],
}; };
let can_write_fast = !(show_tabs let options = OutputOptions {
|| show_nonprint show_ends,
|| show_ends number: number_mode,
|| squeeze_blank show_nonprint,
|| number_mode != NumberingMode::None); show_tabs,
squeeze_blank,
let success = if can_write_fast {
write_fast(files).is_ok()
} else {
let tab = if show_tabs { "^I" } else { "\t" }.to_owned();
let end_of_line = if show_ends { "$\n" } else { "\n" }.to_owned();
let options = OutputOptions {
end_of_line,
number: number_mode,
show_nonprint,
show_tabs,
squeeze_blank,
tab,
};
write_lines(files, &options).is_ok()
}; };
let success = cat_files(files, &options).is_ok();
if success { if success {
0 0
@ -275,6 +288,76 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
} }
} }
fn cat_handle<R: Read>(
handle: &mut InputHandle<R>,
options: &OutputOptions,
state: &mut OutputState,
) -> CatResult<()> {
if options.can_write_fast() {
write_fast(handle)
} else {
write_lines(handle, &options, state)
}
}
fn cat_path(path: &str, options: &OutputOptions, state: &mut OutputState) -> CatResult<()> {
if path == "-" {
let stdin = io::stdin();
let mut handle = InputHandle {
#[cfg(any(target_os = "linux", target_os = "android"))]
file_descriptor: stdin.as_raw_fd(),
reader: stdin,
is_interactive: is_stdin_interactive(),
};
return cat_handle(&mut handle, &options, state);
}
match get_input_type(path)? {
InputType::Directory => Err(CatError::IsDirectory),
#[cfg(unix)]
InputType::Socket => {
let socket = UnixStream::connect(path)?;
socket.shutdown(Shutdown::Write)?;
let mut handle = InputHandle {
#[cfg(any(target_os = "linux", target_os = "android"))]
file_descriptor: socket.as_raw_fd(),
reader: socket,
is_interactive: false,
};
cat_handle(&mut handle, &options, state)
}
_ => {
let file = File::open(path)?;
let mut handle = InputHandle {
#[cfg(any(target_os = "linux", target_os = "android"))]
file_descriptor: file.as_raw_fd(),
reader: file,
is_interactive: false,
};
cat_handle(&mut handle, &options, state)
}
}
}
fn cat_files(files: Vec<String>, options: &OutputOptions) -> Result<(), u32> {
let mut error_count = 0;
let mut state = OutputState {
line_number: 1,
at_line_start: true,
};
for path in &files {
if let Err(err) = cat_path(path, &options, &mut state) {
show_error!("{}: {}", path, err);
error_count += 1;
}
}
if error_count == 0 {
Ok(())
} else {
Err(error_count)
}
}
/// Classifies the `InputType` of file at `path` if possible /// Classifies the `InputType` of file at `path` if possible
/// ///
/// # Arguments /// # Arguments
@ -285,7 +368,8 @@ fn get_input_type(path: &str) -> CatResult<InputType> {
return Ok(InputType::StdIn); return Ok(InputType::StdIn);
} }
match metadata(path).context(path)?.file_type() { let ft = metadata(path)?.file_type();
match ft {
#[cfg(unix)] #[cfg(unix)]
ft if ft.is_block_device() => Ok(InputType::BlockDevice), ft if ft.is_block_device() => Ok(InputType::BlockDevice),
#[cfg(unix)] #[cfg(unix)]
@ -297,125 +381,47 @@ fn get_input_type(path: &str) -> CatResult<InputType> {
ft if ft.is_dir() => Ok(InputType::Directory), ft if ft.is_dir() => Ok(InputType::Directory),
ft if ft.is_file() => Ok(InputType::File), ft if ft.is_file() => Ok(InputType::File),
ft if ft.is_symlink() => Ok(InputType::SymLink), ft if ft.is_symlink() => Ok(InputType::SymLink),
_ => Err(CatError::UnknownFiletype(path.to_owned())), _ => Err(CatError::UnknownFiletype {
ft_debug: format!("{:?}", ft),
}),
} }
} }
/// Returns an InputHandle from which a Reader can be accessed or an /// Writes handle to stdout with no configuration. This allows a
/// error /// simple memory copy.
/// fn write_fast<R: Read>(handle: &mut InputHandle<R>) -> CatResult<()> {
/// # Arguments let stdout = io::stdout();
/// let mut stdout_lock = stdout.lock();
/// * `path` - `InputHandler` will wrap a reader from this file path #[cfg(any(target_os = "linux", target_os = "android"))]
fn open(path: &str) -> CatResult<InputHandle> { {
if path == "-" { // If we're on Linux or Android, try to use the splice() system call
let stdin = stdin(); // for faster writing. If it works, we're done.
return Ok(InputHandle { if !splice::write_fast_using_splice(handle, stdout_lock.as_raw_fd())? {
reader: Box::new(stdin) as Box<dyn Read>, return Ok(());
is_interactive: is_stdin_interactive(),
});
}
match get_input_type(path)? {
InputType::Directory => Err(CatError::IsDirectory(path.to_owned())),
#[cfg(unix)]
InputType::Socket => {
let socket = UnixStream::connect(path).context(path)?;
socket.shutdown(Shutdown::Write).context(path)?;
Ok(InputHandle {
reader: Box::new(socket) as Box<dyn Read>,
is_interactive: false,
})
}
_ => {
let file = File::open(path).context(path)?;
Ok(InputHandle {
reader: Box::new(file) as Box<dyn Read>,
is_interactive: false,
})
} }
} }
} // If we're not on Linux or Android, or the splice() call failed,
// fall back on slower writing.
/// Writes files to stdout with no configuration. This allows a let mut buf = [0; 1024 * 64];
/// simple memory copy. Returns `Ok(())` if no errors were while let Ok(n) = handle.reader.read(&mut buf) {
/// encountered, or an error with the number of errors encountered. if n == 0 {
/// break;
/// # Arguments
///
/// * `files` - There is no short circuit when encountering an error
/// reading a file in this vector
fn write_fast(files: Vec<String>) -> CatResult<()> {
let mut writer = stdout();
let mut in_buf = [0; 1024 * 64];
let mut error_count = 0;
for file in files {
match open(&file[..]) {
Ok(mut handle) => {
while let Ok(n) = handle.reader.read(&mut in_buf) {
if n == 0 {
break;
}
writer.write_all(&in_buf[..n]).context(&file[..])?;
}
}
Err(error) => {
writeln!(&mut stderr(), "{}", error)?;
error_count += 1;
}
} }
stdout_lock.write_all(&buf[..n])?;
} }
Ok(())
match error_count {
0 => Ok(()),
_ => Err(CatError::EncounteredErrors(error_count)),
}
}
/// State that persists between output of each file
struct OutputState {
/// The current line number
line_number: usize,
/// Whether the output cursor is at the beginning of a new line
at_line_start: bool,
}
/// Writes files to stdout with `options` as configuration. Returns
/// `Ok(())` if no errors were encountered, or an error with the
/// number of errors encountered.
///
/// # Arguments
///
/// * `files` - There is no short circuit when encountering an error
/// reading a file in this vector
fn write_lines(files: Vec<String>, options: &OutputOptions) -> CatResult<()> {
let mut error_count = 0;
let mut state = OutputState {
line_number: 1,
at_line_start: true,
};
for file in files {
if let Err(error) = write_file_lines(&file, options, &mut state) {
writeln!(&mut stderr(), "{}", error).context(&file[..])?;
error_count += 1;
}
}
match error_count {
0 => Ok(()),
_ => Err(CatError::EncounteredErrors(error_count)),
}
} }
/// Outputs file contents to stdout in a line-by-line fashion, /// Outputs file contents to stdout in a line-by-line fashion,
/// propagating any errors that might occur. /// propagating any errors that might occur.
fn write_file_lines(file: &str, options: &OutputOptions, state: &mut OutputState) -> CatResult<()> { fn write_lines<R: Read>(
let mut handle = open(file)?; handle: &mut InputHandle<R>,
options: &OutputOptions,
state: &mut OutputState,
) -> CatResult<()> {
let mut in_buf = [0; 1024 * 31]; let mut in_buf = [0; 1024 * 31];
let mut writer = BufWriter::with_capacity(1024 * 64, stdout()); let stdout = io::stdout();
let mut writer = stdout.lock();
let mut one_blank_kept = false; let mut one_blank_kept = false;
while let Ok(n) = handle.reader.read(&mut in_buf) { while let Ok(n) = handle.reader.read(&mut in_buf) {
@ -433,9 +439,9 @@ fn write_file_lines(file: &str, options: &OutputOptions, state: &mut OutputState
write!(&mut writer, "{0:6}\t", state.line_number)?; write!(&mut writer, "{0:6}\t", state.line_number)?;
state.line_number += 1; state.line_number += 1;
} }
writer.write_all(options.end_of_line.as_bytes())?; writer.write_all(options.end_of_line().as_bytes())?;
if handle.is_interactive { if handle.is_interactive {
writer.flush().context(file)?; writer.flush()?;
} }
} }
state.at_line_start = true; state.at_line_start = true;
@ -450,7 +456,7 @@ fn write_file_lines(file: &str, options: &OutputOptions, state: &mut OutputState
// print to end of line or end of buffer // print to end of line or end of buffer
let offset = if options.show_nonprint { let offset = if options.show_nonprint {
write_nonprint_to_end(&in_buf[pos..], &mut writer, options.tab.as_bytes()) write_nonprint_to_end(&in_buf[pos..], &mut writer, options.tab().as_bytes())
} else if options.show_tabs { } else if options.show_tabs {
write_tab_to_end(&in_buf[pos..], &mut writer) write_tab_to_end(&in_buf[pos..], &mut writer)
} else { } else {
@ -462,7 +468,7 @@ fn write_file_lines(file: &str, options: &OutputOptions, state: &mut OutputState
break; break;
} }
// print suitable end of line // print suitable end of line
writer.write_all(options.end_of_line.as_bytes())?; writer.write_all(options.end_of_line().as_bytes())?;
if handle.is_interactive { if handle.is_interactive {
writer.flush()?; writer.flush()?;
} }

91
src/uu/cat/src/splice.rs Normal file
View file

@ -0,0 +1,91 @@
use super::{CatResult, InputHandle};
use nix::fcntl::{splice, SpliceFFlags};
use nix::unistd::{self, pipe};
use std::io::Read;
use std::os::unix::io::RawFd;
const BUF_SIZE: usize = 1024 * 16;
/// This function is called from `write_fast()` on Linux and Android. The
/// function `splice()` is used to move data between two file descriptors
/// without copying between kernel- and userspace. This results in a large
/// speedup.
///
/// The `bool` in the result value indicates if we need to fall back to normal
/// copying or not. False means we don't have to.
#[inline]
pub(super) fn write_fast_using_splice<R: Read>(
handle: &mut InputHandle<R>,
write_fd: RawFd,
) -> CatResult<bool> {
let (pipe_rd, pipe_wr) = match pipe() {
Ok(r) => r,
Err(_) => {
// It is very rare that creating a pipe fails, but it can happen.
return Ok(true);
}
};
loop {
match splice(
handle.file_descriptor,
None,
pipe_wr,
None,
BUF_SIZE,
SpliceFFlags::empty(),
) {
Ok(n) => {
if n == 0 {
return Ok(false);
}
if splice_exact(pipe_rd, write_fd, n).is_err() {
// If the first splice manages to copy to the intermediate
// pipe, but the second splice to stdout fails for some reason
// we can recover by copying the data that we have from the
// intermediate pipe to stdout using normal read/write. Then
// we tell the caller to fall back.
copy_exact(pipe_rd, write_fd, n)?;
return Ok(true);
}
}
Err(_) => {
return Ok(true);
}
}
}
}
/// Splice wrapper which handles short writes.
#[inline]
fn splice_exact(read_fd: RawFd, write_fd: RawFd, num_bytes: usize) -> nix::Result<()> {
let mut left = num_bytes;
loop {
let written = splice(read_fd, None, write_fd, None, left, SpliceFFlags::empty())?;
left -= written;
if left == 0 {
break;
}
}
Ok(())
}
/// Caller must ensure that `num_bytes <= BUF_SIZE`, otherwise this function
/// will panic. The way we use this function in `write_fast_using_splice`
/// above is safe because `splice` is set to write at most `BUF_SIZE` to the
/// pipe.
#[inline]
fn copy_exact(read_fd: RawFd, write_fd: RawFd, num_bytes: usize) -> nix::Result<()> {
let mut left = num_bytes;
let mut buf = [0; BUF_SIZE];
loop {
let read = unistd::read(read_fd, &mut buf[..left])?;
let written = unistd::write(write_fd, &buf[..read])?;
left -= written;
if left == 0 {
break;
}
}
Ok(())
}

View file

@ -22,6 +22,7 @@ use std::fs::Metadata;
use std::os::unix::fs::MetadataExt; use std::os::unix::fs::MetadataExt;
use std::path::Path; use std::path::Path;
use uucore::InvalidEncodingHandling;
static SYNTAX: &str = static SYNTAX: &str =
"chgrp [OPTION]... GROUP FILE...\n or : chgrp [OPTION]... --reference=RFILE FILE..."; "chgrp [OPTION]... GROUP FILE...\n or : chgrp [OPTION]... --reference=RFILE FILE...";
@ -32,7 +33,9 @@ const FTS_PHYSICAL: u8 = 1 << 1;
const FTS_LOGICAL: u8 = 1 << 2; const FTS_LOGICAL: u8 = 1 << 2;
pub fn uumain(args: impl uucore::Args) -> i32 { pub fn uumain(args: impl uucore::Args) -> i32 {
let args = args.collect_str(); let args = args
.collect_str(InvalidEncodingHandling::ConvertLossy)
.accept_any();
let mut opts = app!(SYNTAX, SUMMARY, ""); let mut opts = app!(SYNTAX, SUMMARY, "");
opts.optflag("c", opts.optflag("c",
@ -94,7 +97,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
if recursive { if recursive {
if bit_flag == FTS_PHYSICAL { if bit_flag == FTS_PHYSICAL {
if derefer == 1 { if derefer == 1 {
show_info!("-R --dereference requires -H or -L"); show_error!("-R --dereference requires -H or -L");
return 1; return 1;
} }
derefer = 0; derefer = 0;
@ -129,7 +132,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
dest_gid = meta.gid(); dest_gid = meta.gid();
} }
Err(e) => { Err(e) => {
show_info!("failed to get attributes of '{}': {}", file, e); show_error!("failed to get attributes of '{}': {}", file, e);
return 1; return 1;
} }
} }
@ -140,7 +143,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
dest_gid = g; dest_gid = g;
} }
_ => { _ => {
show_info!("invalid group: {}", matches.free[0].as_str()); show_error!("invalid group: {}", matches.free[0].as_str());
return 1; return 1;
} }
} }
@ -232,8 +235,8 @@ impl Chgrper {
if let Some(p) = may_exist { if let Some(p) = may_exist {
if p.parent().is_none() || self.is_bind_root(p) { if p.parent().is_none() || self.is_bind_root(p) {
show_info!("it is dangerous to operate recursively on '/'"); show_error!("it is dangerous to operate recursively on '/'");
show_info!("use --no-preserve-root to override this failsafe"); show_error!("use --no-preserve-root to override this failsafe");
return 1; return 1;
} }
} }
@ -247,12 +250,12 @@ impl Chgrper {
self.verbosity.clone(), self.verbosity.clone(),
) { ) {
Ok(n) => { Ok(n) => {
show_info!("{}", n); show_error!("{}", n);
0 0
} }
Err(e) => { Err(e) => {
if self.verbosity != Verbosity::Silent { if self.verbosity != Verbosity::Silent {
show_info!("{}", e); show_error!("{}", e);
} }
1 1
} }
@ -272,7 +275,7 @@ impl Chgrper {
for entry in WalkDir::new(root).follow_links(follow).min_depth(1) { for entry in WalkDir::new(root).follow_links(follow).min_depth(1) {
let entry = unwrap!(entry, e, { let entry = unwrap!(entry, e, {
ret = 1; ret = 1;
show_info!("{}", e); show_error!("{}", e);
continue; continue;
}); });
let path = entry.path(); let path = entry.path();
@ -286,14 +289,14 @@ impl Chgrper {
ret = match wrap_chgrp(path, &meta, self.dest_gid, follow, self.verbosity.clone()) { ret = match wrap_chgrp(path, &meta, self.dest_gid, follow, self.verbosity.clone()) {
Ok(n) => { Ok(n) => {
if n != "" { if !n.is_empty() {
show_info!("{}", n); show_error!("{}", n);
} }
0 0
} }
Err(e) => { Err(e) => {
if self.verbosity != Verbosity::Silent { if self.verbosity != Verbosity::Silent {
show_info!("{}", e); show_error!("{}", e);
} }
1 1
} }
@ -310,7 +313,7 @@ impl Chgrper {
unwrap!(path.metadata(), e, { unwrap!(path.metadata(), e, {
match self.verbosity { match self.verbosity {
Silent => (), Silent => (),
_ => show_info!("cannot access '{}': {}", path.display(), e), _ => show_error!("cannot access '{}': {}", path.display(), e),
} }
return None; return None;
}) })
@ -318,7 +321,7 @@ impl Chgrper {
unwrap!(path.symlink_metadata(), e, { unwrap!(path.symlink_metadata(), e, {
match self.verbosity { match self.verbosity {
Silent => (), Silent => (),
_ => show_info!("cannot dereference '{}': {}", path.display(), e), _ => show_error!("cannot dereference '{}': {}", path.display(), e),
} }
return None; return None;
}) })

View file

@ -15,8 +15,10 @@ use std::fs;
use std::os::unix::fs::{MetadataExt, PermissionsExt}; use std::os::unix::fs::{MetadataExt, PermissionsExt};
use std::path::Path; use std::path::Path;
use uucore::fs::display_permissions_unix; use uucore::fs::display_permissions_unix;
use uucore::libc::mode_t;
#[cfg(not(windows))] #[cfg(not(windows))]
use uucore::mode; use uucore::mode;
use uucore::InvalidEncodingHandling;
use walkdir::WalkDir; use walkdir::WalkDir;
static VERSION: &str = env!("CARGO_PKG_VERSION"); static VERSION: &str = env!("CARGO_PKG_VERSION");
@ -49,7 +51,9 @@ fn get_long_usage() -> String {
} }
pub fn uumain(args: impl uucore::Args) -> i32 { pub fn uumain(args: impl uucore::Args) -> i32 {
let mut args = args.collect_str(); let mut args = args
.collect_str(InvalidEncodingHandling::ConvertLossy)
.accept_any();
// Before we can parse 'args' with clap (and previously getopts), // Before we can parse 'args' with clap (and previously getopts),
// a possible MODE prefix '-' needs to be removed (e.g. "chmod -x FILE"). // a possible MODE prefix '-' needs to be removed (e.g. "chmod -x FILE").
@ -171,13 +175,13 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
// of a prefix '-' if it's associated with MODE // of a prefix '-' if it's associated with MODE
// e.g. "chmod -v -xw -R FILE" -> "chmod -v xw -R FILE" // e.g. "chmod -v -xw -R FILE" -> "chmod -v xw -R FILE"
pub fn strip_minus_from_mode(args: &mut Vec<String>) -> bool { pub fn strip_minus_from_mode(args: &mut Vec<String>) -> bool {
for i in 0..args.len() { for arg in args {
if args[i].starts_with("-") { if arg.starts_with('-') {
if let Some(second) = args[i].chars().nth(1) { if let Some(second) = arg.chars().nth(1) {
match second { match second {
'r' | 'w' | 'x' | 'X' | 's' | 't' | 'u' | 'g' | 'o' | '0'..='7' => { 'r' | 'w' | 'x' | 'X' | 's' | 't' | 'u' | 'g' | 'o' | '0'..='7' => {
// TODO: use strip_prefix() once minimum rust version reaches 1.45.0 // TODO: use strip_prefix() once minimum rust version reaches 1.45.0
args[i] = args[i][1..args[i].len()].to_string(); *arg = arg[1..arg.len()].to_string();
return true; return true;
} }
_ => {} _ => {}
@ -258,8 +262,10 @@ impl Chmoder {
); );
} }
return Ok(()); return Ok(());
} else if err.kind() == std::io::ErrorKind::PermissionDenied {
show_error!("'{}': Permission denied", file.display());
} else { } else {
show_error!("{}: '{}'", err, file.display()); show_error!("'{}': {}", file.display(), err);
} }
return Err(1); return Err(1);
} }
@ -303,7 +309,7 @@ impl Chmoder {
"mode of '{}' retained as {:04o} ({})", "mode of '{}' retained as {:04o} ({})",
file.display(), file.display(),
fperm, fperm,
display_permissions_unix(fperm), display_permissions_unix(fperm as mode_t, false),
); );
} }
Ok(()) Ok(())
@ -312,25 +318,25 @@ impl Chmoder {
show_error!("{}", err); show_error!("{}", err);
} }
if self.verbose { if self.verbose {
show_info!( show_error!(
"failed to change mode of file '{}' from {:o} ({}) to {:o} ({})", "failed to change mode of file '{}' from {:o} ({}) to {:o} ({})",
file.display(), file.display(),
fperm, fperm,
display_permissions_unix(fperm), display_permissions_unix(fperm as mode_t, false),
mode, mode,
display_permissions_unix(mode) display_permissions_unix(mode as mode_t, false)
); );
} }
Err(1) Err(1)
} else { } else {
if self.verbose || self.changes { if self.verbose || self.changes {
show_info!( show_error!(
"mode of '{}' changed from {:o} ({}) to {:o} ({})", "mode of '{}' changed from {:o} ({}) to {:o} ({})",
file.display(), file.display(),
fperm, fperm,
display_permissions_unix(fperm), display_permissions_unix(fperm as mode_t, false),
mode, mode,
display_permissions_unix(mode) display_permissions_unix(mode as mode_t, false)
); );
} }
Ok(()) Ok(())

View file

@ -23,6 +23,7 @@ use std::os::unix::fs::MetadataExt;
use std::convert::AsRef; use std::convert::AsRef;
use std::path::Path; use std::path::Path;
use uucore::InvalidEncodingHandling;
static ABOUT: &str = "change file owner and group"; static ABOUT: &str = "change file owner and group";
static VERSION: &str = env!("CARGO_PKG_VERSION"); static VERSION: &str = env!("CARGO_PKG_VERSION");
@ -67,7 +68,9 @@ fn get_usage() -> String {
} }
pub fn uumain(args: impl uucore::Args) -> i32 { pub fn uumain(args: impl uucore::Args) -> i32 {
let args = args.collect_str(); let args = args
.collect_str(InvalidEncodingHandling::Ignore)
.accept_any();
let usage = get_usage(); let usage = get_usage();
@ -196,7 +199,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
if recursive { if recursive {
if bit_flag == FTS_PHYSICAL { if bit_flag == FTS_PHYSICAL {
if derefer == 1 { if derefer == 1 {
show_info!("-R --dereference requires -H or -L"); show_error!("-R --dereference requires -H or -L");
return 1; return 1;
} }
derefer = 0; derefer = 0;
@ -224,7 +227,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
Ok((Some(uid), Some(gid))) => IfFrom::UserGroup(uid, gid), Ok((Some(uid), Some(gid))) => IfFrom::UserGroup(uid, gid),
Ok((None, None)) => IfFrom::All, Ok((None, None)) => IfFrom::All,
Err(e) => { Err(e) => {
show_info!("{}", e); show_error!("{}", e);
return 1; return 1;
} }
} }
@ -241,7 +244,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
dest_uid = Some(meta.uid()); dest_uid = Some(meta.uid());
} }
Err(e) => { Err(e) => {
show_info!("failed to get attributes of '{}': {}", file, e); show_error!("failed to get attributes of '{}': {}", file, e);
return 1; return 1;
} }
} }
@ -252,7 +255,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
dest_gid = g; dest_gid = g;
} }
Err(e) => { Err(e) => {
show_info!("{}", e); show_error!("{}", e);
return 1; return 1;
} }
} }
@ -272,16 +275,16 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
} }
fn parse_spec(spec: &str) -> Result<(Option<u32>, Option<u32>), String> { fn parse_spec(spec: &str) -> Result<(Option<u32>, Option<u32>), String> {
let args = spec.split(':').collect::<Vec<_>>(); let args = spec.split_terminator(':').collect::<Vec<_>>();
let usr_only = args.len() == 1; let usr_only = args.len() == 1 && !args[0].is_empty();
let grp_only = args.len() == 2 && args[0].is_empty() && !args[1].is_empty(); let grp_only = args.len() == 2 && args[0].is_empty();
let usr_grp = args.len() == 2 && !args[0].is_empty() && !args[1].is_empty(); let usr_grp = args.len() == 2 && !args[0].is_empty() && !args[1].is_empty();
if usr_only { if usr_only {
Ok(( Ok((
Some(match Passwd::locate(args[0]) { Some(match Passwd::locate(args[0]) {
Ok(v) => v.uid(), Ok(v) => v.uid(),
_ => return Err(format!("invalid user: '{}'", spec)), _ => return Err(format!("invalid user: {}", spec)),
}), }),
None, None,
)) ))
@ -290,18 +293,18 @@ fn parse_spec(spec: &str) -> Result<(Option<u32>, Option<u32>), String> {
None, None,
Some(match Group::locate(args[1]) { Some(match Group::locate(args[1]) {
Ok(v) => v.gid(), Ok(v) => v.gid(),
_ => return Err(format!("invalid group: '{}'", spec)), _ => return Err(format!("invalid group: {}", spec)),
}), }),
)) ))
} else if usr_grp { } else if usr_grp {
Ok(( Ok((
Some(match Passwd::locate(args[0]) { Some(match Passwd::locate(args[0]) {
Ok(v) => v.uid(), Ok(v) => v.uid(),
_ => return Err(format!("invalid user: '{}'", spec)), _ => return Err(format!("invalid user: {}", spec)),
}), }),
Some(match Group::locate(args[1]) { Some(match Group::locate(args[1]) {
Ok(v) => v.gid(), Ok(v) => v.gid(),
_ => return Err(format!("invalid group: '{}'", spec)), _ => return Err(format!("invalid group: {}", spec)),
}), }),
)) ))
} else { } else {
@ -374,8 +377,8 @@ impl Chowner {
if let Some(p) = may_exist { if let Some(p) = may_exist {
if p.parent().is_none() { if p.parent().is_none() {
show_info!("it is dangerous to operate recursively on '/'"); show_error!("it is dangerous to operate recursively on '/'");
show_info!("use --no-preserve-root to override this failsafe"); show_error!("use --no-preserve-root to override this failsafe");
return 1; return 1;
} }
} }
@ -391,14 +394,14 @@ impl Chowner {
self.verbosity.clone(), self.verbosity.clone(),
) { ) {
Ok(n) => { Ok(n) => {
if n != "" { if !n.is_empty() {
show_info!("{}", n); show_error!("{}", n);
} }
0 0
} }
Err(e) => { Err(e) => {
if self.verbosity != Verbosity::Silent { if self.verbosity != Verbosity::Silent {
show_info!("{}", e); show_error!("{}", e);
} }
1 1
} }
@ -421,7 +424,7 @@ impl Chowner {
for entry in WalkDir::new(root).follow_links(follow).min_depth(1) { for entry in WalkDir::new(root).follow_links(follow).min_depth(1) {
let entry = unwrap!(entry, e, { let entry = unwrap!(entry, e, {
ret = 1; ret = 1;
show_info!("{}", e); show_error!("{}", e);
continue; continue;
}); });
let path = entry.path(); let path = entry.path();
@ -446,14 +449,14 @@ impl Chowner {
self.verbosity.clone(), self.verbosity.clone(),
) { ) {
Ok(n) => { Ok(n) => {
if n != "" { if !n.is_empty() {
show_info!("{}", n); show_error!("{}", n);
} }
0 0
} }
Err(e) => { Err(e) => {
if self.verbosity != Verbosity::Silent { if self.verbosity != Verbosity::Silent {
show_info!("{}", e); show_error!("{}", e);
} }
1 1
} }
@ -469,7 +472,7 @@ impl Chowner {
unwrap!(path.metadata(), e, { unwrap!(path.metadata(), e, {
match self.verbosity { match self.verbosity {
Silent => (), Silent => (),
_ => show_info!("cannot access '{}': {}", path.display(), e), _ => show_error!("cannot access '{}': {}", path.display(), e),
} }
return None; return None;
}) })
@ -477,7 +480,7 @@ impl Chowner {
unwrap!(path.symlink_metadata(), e, { unwrap!(path.symlink_metadata(), e, {
match self.verbosity { match self.verbosity {
Silent => (), Silent => (),
_ => show_info!("cannot dereference '{}': {}", path.display(), e), _ => show_error!("cannot dereference '{}': {}", path.display(), e),
} }
return None; return None;
}) })

View file

@ -15,8 +15,8 @@ use std::ffi::CString;
use std::io::Error; use std::io::Error;
use std::path::Path; use std::path::Path;
use std::process::Command; use std::process::Command;
use uucore::entries;
use uucore::libc::{self, chroot, setgid, setgroups, setuid}; use uucore::libc::{self, chroot, setgid, setgroups, setuid};
use uucore::{entries, InvalidEncodingHandling};
static VERSION: &str = env!("CARGO_PKG_VERSION"); static VERSION: &str = env!("CARGO_PKG_VERSION");
static NAME: &str = "chroot"; static NAME: &str = "chroot";
@ -32,7 +32,9 @@ mod options {
} }
pub fn uumain(args: impl uucore::Args) -> i32 { pub fn uumain(args: impl uucore::Args) -> i32 {
let args = args.collect_str(); let args = args
.collect_str(InvalidEncodingHandling::ConvertLossy)
.accept_any();
let matches = App::new(executable!()) let matches = App::new(executable!())
.version(VERSION) .version(VERSION)
@ -104,7 +106,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
_ => { _ => {
let mut vector: Vec<&str> = Vec::new(); let mut vector: Vec<&str> = Vec::new();
for (&k, v) in matches.args.iter() { for (&k, v) in matches.args.iter() {
vector.push(k.clone()); vector.push(k);
vector.push(&v.vals[0].to_str().unwrap()); vector.push(&v.vals[0].to_str().unwrap());
} }
vector vector
@ -133,7 +135,7 @@ fn set_context(root: &Path, options: &clap::ArgMatches) {
let userspec = match userspec_str { let userspec = match userspec_str {
Some(ref u) => { Some(ref u) => {
let s: Vec<&str> = u.split(':').collect(); let s: Vec<&str> = u.split(':').collect();
if s.len() != 2 || s.iter().any(|&spec| spec == "") { if s.len() != 2 || s.iter().any(|&spec| spec.is_empty()) {
crash!(1, "invalid userspec: `{}`", u) crash!(1, "invalid userspec: `{}`", u)
}; };
s s
@ -142,16 +144,16 @@ fn set_context(root: &Path, options: &clap::ArgMatches) {
}; };
let (user, group) = if userspec.is_empty() { let (user, group) = if userspec.is_empty() {
(&user_str[..], &group_str[..]) (user_str, group_str)
} else { } else {
(&userspec[0][..], &userspec[1][..]) (userspec[0], userspec[1])
}; };
enter_chroot(root); enter_chroot(root);
set_groups_from_str(&groups_str[..]); set_groups_from_str(groups_str);
set_main_group(&group[..]); set_main_group(group);
set_user(&user[..]); set_user(user);
} }
fn enter_chroot(root: &Path) { fn enter_chroot(root: &Path) {

View file

@ -14,6 +14,7 @@ use clap::{App, Arg};
use std::fs::File; use std::fs::File;
use std::io::{self, stdin, BufReader, Read}; use std::io::{self, stdin, BufReader, Read};
use std::path::Path; use std::path::Path;
use uucore::InvalidEncodingHandling;
// NOTE: CRC_TABLE_LEN *must* be <= 256 as we cast 0..CRC_TABLE_LEN to u8 // 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_LEN: usize = 256;
@ -180,7 +181,9 @@ mod options {
} }
pub fn uumain(args: impl uucore::Args) -> i32 { pub fn uumain(args: impl uucore::Args) -> i32 {
let args = args.collect_str(); let args = args
.collect_str(InvalidEncodingHandling::Ignore)
.accept_any();
let matches = App::new(executable!()) let matches = App::new(executable!())
.name(NAME) .name(NAME)

View file

@ -14,6 +14,7 @@ use std::cmp::Ordering;
use std::fs::File; use std::fs::File;
use std::io::{self, stdin, BufRead, BufReader, Stdin}; use std::io::{self, stdin, BufRead, BufReader, Stdin};
use std::path::Path; use std::path::Path;
use uucore::InvalidEncodingHandling;
use clap::{App, Arg, ArgMatches}; use clap::{App, Arg, ArgMatches};
@ -134,6 +135,9 @@ fn open_file(name: &str) -> io::Result<LineReader> {
pub fn uumain(args: impl uucore::Args) -> i32 { pub fn uumain(args: impl uucore::Args) -> i32 {
let usage = get_usage(); let usage = get_usage();
let args = args
.collect_str(InvalidEncodingHandling::ConvertLossy)
.accept_any();
let matches = App::new(executable!()) let matches = App::new(executable!())
.version(VERSION) .version(VERSION)

View file

@ -47,6 +47,7 @@ use std::os::windows::ffi::OsStrExt;
use std::path::{Path, PathBuf, StripPrefixError}; use std::path::{Path, PathBuf, StripPrefixError};
use std::str::FromStr; use std::str::FromStr;
use std::string::ToString; use std::string::ToString;
use uucore::backup_control::{self, BackupMode};
use uucore::fs::resolve_relative_path; use uucore::fs::resolve_relative_path;
use uucore::fs::{canonicalize, CanonicalizeMode}; use uucore::fs::{canonicalize, CanonicalizeMode};
use walkdir::WalkDir; use walkdir::WalkDir;
@ -132,7 +133,9 @@ macro_rules! prompt_yes(
pub type CopyResult<T> = Result<T, Error>; pub type CopyResult<T> = Result<T, Error>;
pub type Source = PathBuf; pub type Source = PathBuf;
pub type SourceSlice = Path;
pub type Target = PathBuf; pub type Target = PathBuf;
pub type TargetSlice = Path;
/// Specifies whether when overwrite files /// Specifies whether when overwrite files
#[derive(Clone, Eq, PartialEq)] #[derive(Clone, Eq, PartialEq)]
@ -153,7 +156,8 @@ pub enum OverwriteMode {
NoClobber, NoClobber,
} }
#[derive(Clone, Eq, PartialEq)] /// Possible arguments for `--reflink`.
#[derive(Copy, Clone, Eq, PartialEq)]
pub enum ReflinkMode { pub enum ReflinkMode {
Always, Always,
Auto, Auto,
@ -166,14 +170,6 @@ pub enum TargetType {
File, File,
} }
#[derive(Clone, Eq, PartialEq)]
pub enum BackupMode {
ExistingBackup,
NoBackup,
NumberedBackup,
SimpleBackup,
}
pub enum CopyMode { pub enum CopyMode {
Link, Link,
SymLink, SymLink,
@ -198,7 +194,7 @@ pub enum Attribute {
#[allow(dead_code)] #[allow(dead_code)]
pub struct Options { pub struct Options {
attributes_only: bool, attributes_only: bool,
backup: bool, backup: BackupMode,
copy_contents: bool, copy_contents: bool,
copy_mode: CopyMode, copy_mode: CopyMode,
dereference: bool, dereference: bool,
@ -208,7 +204,6 @@ pub struct Options {
overwrite: OverwriteMode, overwrite: OverwriteMode,
parents: bool, parents: bool,
strip_trailing_slashes: bool, strip_trailing_slashes: bool,
reflink: bool,
reflink_mode: ReflinkMode, reflink_mode: ReflinkMode,
preserve_attributes: Vec<Attribute>, preserve_attributes: Vec<Attribute>,
recursive: bool, recursive: bool,
@ -220,6 +215,7 @@ pub struct Options {
static VERSION: &str = env!("CARGO_PKG_VERSION"); static VERSION: &str = env!("CARGO_PKG_VERSION");
static ABOUT: &str = "Copy SOURCE to DEST, or multiple SOURCE(s) to DIRECTORY."; static ABOUT: &str = "Copy SOURCE to DEST, or multiple SOURCE(s) to DIRECTORY.";
static LONG_HELP: &str = "";
static EXIT_OK: i32 = 0; static EXIT_OK: i32 = 0;
static EXIT_ERR: i32 = 1; static EXIT_ERR: i32 = 1;
@ -236,6 +232,7 @@ fn get_usage() -> String {
static OPT_ARCHIVE: &str = "archive"; static OPT_ARCHIVE: &str = "archive";
static OPT_ATTRIBUTES_ONLY: &str = "attributes-only"; static OPT_ATTRIBUTES_ONLY: &str = "attributes-only";
static OPT_BACKUP: &str = "backup"; static OPT_BACKUP: &str = "backup";
static OPT_BACKUP_NO_ARG: &str = "b";
static OPT_CLI_SYMBOLIC_LINKS: &str = "cli-symbolic-links"; static OPT_CLI_SYMBOLIC_LINKS: &str = "cli-symbolic-links";
static OPT_CONTEXT: &str = "context"; static OPT_CONTEXT: &str = "context";
static OPT_COPY_CONTENTS: &str = "copy-contents"; static OPT_COPY_CONTENTS: &str = "copy-contents";
@ -299,6 +296,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
let matches = App::new(executable!()) let matches = App::new(executable!())
.version(VERSION) .version(VERSION)
.about(ABOUT) .about(ABOUT)
.after_help(&*format!("{}\n{}", LONG_HELP, backup_control::BACKUP_CONTROL_LONG_HELP))
.usage(&usage[..]) .usage(&usage[..])
.arg(Arg::with_name(OPT_TARGET_DIRECTORY) .arg(Arg::with_name(OPT_TARGET_DIRECTORY)
.short("t") .short("t")
@ -360,14 +358,22 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.help("remove each existing destination file before attempting to open it \ .help("remove each existing destination file before attempting to open it \
(contrast with --force). On Windows, current only works for writeable files.")) (contrast with --force). On Windows, current only works for writeable files."))
.arg(Arg::with_name(OPT_BACKUP) .arg(Arg::with_name(OPT_BACKUP)
.short("b")
.long(OPT_BACKUP) .long(OPT_BACKUP)
.help("make a backup of each existing destination file")) .help("make a backup of each existing destination file")
.takes_value(true)
.require_equals(true)
.min_values(0)
.possible_values(backup_control::BACKUP_CONTROL_VALUES)
.value_name("CONTROL")
)
.arg(Arg::with_name(OPT_BACKUP_NO_ARG)
.short(OPT_BACKUP_NO_ARG)
.help("like --backup but does not accept an argument")
)
.arg(Arg::with_name(OPT_SUFFIX) .arg(Arg::with_name(OPT_SUFFIX)
.short("S") .short("S")
.long(OPT_SUFFIX) .long(OPT_SUFFIX)
.takes_value(true) .takes_value(true)
.default_value("~")
.value_name("SUFFIX") .value_name("SUFFIX")
.help("override the usual backup suffix")) .help("override the usual backup suffix"))
.arg(Arg::with_name(OPT_UPDATE) .arg(Arg::with_name(OPT_UPDATE)
@ -461,6 +467,12 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.get_matches_from(args); .get_matches_from(args);
let options = crash_if_err!(EXIT_ERR, Options::from_matches(&matches)); let options = crash_if_err!(EXIT_ERR, Options::from_matches(&matches));
if options.overwrite == OverwriteMode::NoClobber && options.backup != BackupMode::NoBackup {
show_usage_error!("options --backup and --no-clobber are mutually exclusive");
return 1;
}
let paths: Vec<String> = matches let paths: Vec<String> = matches
.values_of(OPT_PATHS) .values_of(OPT_PATHS)
.map(|v| v.map(ToString::to_string).collect()) .map(|v| v.map(ToString::to_string).collect())
@ -547,14 +559,17 @@ impl FromStr for Attribute {
} }
fn add_all_attributes() -> Vec<Attribute> { fn add_all_attributes() -> Vec<Attribute> {
let mut attr = Vec::new(); use Attribute::*;
#[cfg(target_os = "windows")]
let attr = vec![Ownership, Timestamps, Context, Xattr, Links];
#[cfg(not(target_os = "windows"))]
let mut attr = vec![Ownership, Timestamps, Context, Xattr, Links];
#[cfg(unix)] #[cfg(unix)]
attr.push(Attribute::Mode); attr.insert(0, Mode);
attr.push(Attribute::Ownership);
attr.push(Attribute::Timestamps);
attr.push(Attribute::Context);
attr.push(Attribute::Xattr);
attr.push(Attribute::Links);
attr attr
} }
@ -580,7 +595,13 @@ impl Options {
|| matches.is_present(OPT_RECURSIVE_ALIAS) || matches.is_present(OPT_RECURSIVE_ALIAS)
|| matches.is_present(OPT_ARCHIVE); || matches.is_present(OPT_ARCHIVE);
let backup = matches.is_present(OPT_BACKUP) || (matches.occurrences_of(OPT_SUFFIX) > 0); let backup_mode = backup_control::determine_backup_mode(
matches.is_present(OPT_BACKUP_NO_ARG) || matches.is_present(OPT_BACKUP),
matches.value_of(OPT_BACKUP),
);
let backup_suffix = backup_control::determine_backup_suffix(matches.value_of(OPT_SUFFIX));
let overwrite = OverwriteMode::from_matches(matches);
// Parse target directory options // Parse target directory options
let no_target_dir = matches.is_present(OPT_NO_TARGET_DIRECTORY); let no_target_dir = matches.is_present(OPT_NO_TARGET_DIRECTORY);
@ -626,18 +647,16 @@ impl Options {
|| matches.is_present(OPT_NO_DEREFERENCE_PRESERVE_LINKS) || matches.is_present(OPT_NO_DEREFERENCE_PRESERVE_LINKS)
|| matches.is_present(OPT_ARCHIVE), || matches.is_present(OPT_ARCHIVE),
one_file_system: matches.is_present(OPT_ONE_FILE_SYSTEM), one_file_system: matches.is_present(OPT_ONE_FILE_SYSTEM),
overwrite: OverwriteMode::from_matches(matches),
parents: matches.is_present(OPT_PARENTS), parents: matches.is_present(OPT_PARENTS),
backup_suffix: matches.value_of(OPT_SUFFIX).unwrap().to_string(),
update: matches.is_present(OPT_UPDATE), update: matches.is_present(OPT_UPDATE),
verbose: matches.is_present(OPT_VERBOSE), verbose: matches.is_present(OPT_VERBOSE),
strip_trailing_slashes: matches.is_present(OPT_STRIP_TRAILING_SLASHES), strip_trailing_slashes: matches.is_present(OPT_STRIP_TRAILING_SLASHES),
reflink: matches.is_present(OPT_REFLINK),
reflink_mode: { reflink_mode: {
if let Some(reflink) = matches.value_of(OPT_REFLINK) { if let Some(reflink) = matches.value_of(OPT_REFLINK) {
match reflink { match reflink {
"always" => ReflinkMode::Always, "always" => ReflinkMode::Always,
"auto" => ReflinkMode::Auto, "auto" => ReflinkMode::Auto,
"never" => ReflinkMode::Never,
value => { value => {
return Err(Error::InvalidArgument(format!( return Err(Error::InvalidArgument(format!(
"invalid argument '{}' for \'reflink\'", "invalid argument '{}' for \'reflink\'",
@ -649,7 +668,9 @@ impl Options {
ReflinkMode::Never ReflinkMode::Never
} }
}, },
backup, backup: backup_mode,
backup_suffix: backup_suffix,
overwrite: overwrite,
no_target_dir, no_target_dir,
preserve_attributes, preserve_attributes,
recursive, recursive,
@ -665,7 +686,7 @@ impl TargetType {
/// ///
/// Treat target as a dir if we have multiple sources or the target /// Treat target as a dir if we have multiple sources or the target
/// exists and already is a directory /// exists and already is a directory
fn determine(sources: &[Source], target: &Target) -> TargetType { fn determine(sources: &[Source], target: &TargetSlice) -> TargetType {
if sources.len() > 1 || target.is_dir() { if sources.len() > 1 || target.is_dir() {
TargetType::Directory TargetType::Directory
} else { } else {
@ -714,7 +735,7 @@ fn parse_path_args(path_args: &[String], options: &Options) -> CopyResult<(Vec<S
fn preserve_hardlinks( fn preserve_hardlinks(
hard_links: &mut Vec<(String, u64)>, hard_links: &mut Vec<(String, u64)>,
source: &std::path::PathBuf, source: &std::path::Path,
dest: std::path::PathBuf, dest: std::path::PathBuf,
found_hard_link: &mut bool, found_hard_link: &mut bool,
) -> CopyResult<()> { ) -> CopyResult<()> {
@ -788,7 +809,7 @@ fn preserve_hardlinks(
/// Behavior depends on `options`, see [`Options`] for details. /// Behavior depends on `options`, see [`Options`] for details.
/// ///
/// [`Options`]: ./struct.Options.html /// [`Options`]: ./struct.Options.html
fn copy(sources: &[Source], target: &Target, options: &Options) -> CopyResult<()> { fn copy(sources: &[Source], target: &TargetSlice, options: &Options) -> CopyResult<()> {
let target_type = TargetType::determine(sources, target); let target_type = TargetType::determine(sources, target);
verify_target_type(target, &target_type)?; verify_target_type(target, &target_type)?;
@ -840,7 +861,7 @@ fn copy(sources: &[Source], target: &Target, options: &Options) -> CopyResult<()
fn construct_dest_path( fn construct_dest_path(
source_path: &Path, source_path: &Path,
target: &Target, target: &TargetSlice,
target_type: &TargetType, target_type: &TargetType,
options: &Options, options: &Options,
) -> CopyResult<PathBuf> { ) -> CopyResult<PathBuf> {
@ -870,8 +891,8 @@ fn construct_dest_path(
} }
fn copy_source( fn copy_source(
source: &Source, source: &SourceSlice,
target: &Target, target: &TargetSlice,
target_type: &TargetType, target_type: &TargetType,
options: &Options, options: &Options,
) -> CopyResult<()> { ) -> CopyResult<()> {
@ -912,7 +933,7 @@ fn adjust_canonicalization(p: &Path) -> Cow<Path> {
/// ///
/// Any errors encountered copying files in the tree will be logged but /// Any errors encountered copying files in the tree will be logged but
/// will not cause a short-circuit. /// will not cause a short-circuit.
fn copy_directory(root: &Path, target: &Target, options: &Options) -> CopyResult<()> { fn copy_directory(root: &Path, target: &TargetSlice, options: &Options) -> CopyResult<()> {
if !options.recursive { if !options.recursive {
return Err(format!("omitting directory '{}'", root.display()).into()); return Err(format!("omitting directory '{}'", root.display()).into());
} }
@ -1068,6 +1089,7 @@ fn copy_attribute(source: &Path, dest: &Path, attribute: &Attribute) -> CopyResu
} }
#[cfg(not(windows))] #[cfg(not(windows))]
#[allow(clippy::unnecessary_wraps)] // needed for windows version
fn symlink_file(source: &Path, dest: &Path, context: &str) -> CopyResult<()> { fn symlink_file(source: &Path, dest: &Path, context: &str) -> CopyResult<()> {
match std::os::unix::fs::symlink(source, dest).context(context) { match std::os::unix::fs::symlink(source, dest).context(context) {
Ok(_) => Ok(()), Ok(_) => Ok(()),
@ -1084,14 +1106,10 @@ fn context_for(src: &Path, dest: &Path) -> String {
format!("'{}' -> '{}'", src.display(), dest.display()) format!("'{}' -> '{}'", src.display(), dest.display())
} }
/// Implements a relatively naive backup that is not as full featured /// Implements a simple backup copy for the destination file.
/// as GNU cp. No CONTROL version control method argument is taken /// TODO: for the backup, should this function be replaced by `copy_file(...)`?
/// for backups. fn backup_dest(dest: &Path, backup_path: &PathBuf) -> CopyResult<PathBuf> {
/// TODO: Add version control methods fs::copy(dest, &backup_path)?;
fn backup_file(path: &Path, suffix: &str) -> CopyResult<PathBuf> {
let mut backup_path = path.to_path_buf().into_os_string();
backup_path.push(suffix);
fs::copy(path, &backup_path)?;
Ok(backup_path.into()) Ok(backup_path.into())
} }
@ -1102,8 +1120,9 @@ fn handle_existing_dest(source: &Path, dest: &Path, options: &Options) -> CopyRe
options.overwrite.verify(dest)?; options.overwrite.verify(dest)?;
if options.backup { let backup_path = backup_control::get_backup_path(options.backup, dest, &options.backup_suffix);
backup_file(dest, &options.backup_suffix)?; if let Some(backup_path) = backup_path {
backup_dest(dest, &backup_path)?;
} }
match options.overwrite { match options.overwrite {
@ -1191,47 +1210,20 @@ fn copy_file(source: &Path, dest: &Path, options: &Options) -> CopyResult<()> {
Ok(()) Ok(())
} }
///Copy the file from `source` to `dest` either using the normal `fs::copy` or the /// Copy the file from `source` to `dest` either using the normal `fs::copy` or a
///`FICLONE` ioctl if --reflink is specified and the filesystem supports it. /// copy-on-write scheme if --reflink is specified and the filesystem supports it.
fn copy_helper(source: &Path, dest: &Path, options: &Options) -> CopyResult<()> { fn copy_helper(source: &Path, dest: &Path, options: &Options) -> CopyResult<()> {
if options.reflink { if options.reflink_mode != ReflinkMode::Never {
#[cfg(not(target_os = "linux"))] #[cfg(not(any(target_os = "linux", target_os = "macos")))]
return Err("--reflink is only supported on linux".to_string().into()); return Err("--reflink is only supported on linux and macOS"
.to_string()
.into());
#[cfg(target_os = "macos")]
copy_on_write_macos(source, dest, options.reflink_mode)?;
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
{ copy_on_write_linux(source, dest, options.reflink_mode)?;
let src_file = File::open(source).unwrap().into_raw_fd();
let dst_file = OpenOptions::new()
.write(true)
.truncate(false)
.create(true)
.open(dest)
.unwrap()
.into_raw_fd();
match options.reflink_mode {
ReflinkMode::Always => unsafe {
let result = ficlone(dst_file, src_file as *const i32);
if result != 0 {
return Err(format!(
"failed to clone {:?} from {:?}: {}",
source,
dest,
std::io::Error::last_os_error()
)
.into());
} else {
return Ok(());
}
},
ReflinkMode::Auto => unsafe {
let result = ficlone(dst_file, src_file as *const i32);
if result != 0 {
fs::copy(source, dest).context(&*context_for(source, dest))?;
}
},
ReflinkMode::Never => {}
}
}
} else if options.no_dereference && fs::symlink_metadata(&source)?.file_type().is_symlink() { } else if options.no_dereference && fs::symlink_metadata(&source)?.file_type().is_symlink() {
// Here, we will copy the symlink itself (actually, just recreate it) // Here, we will copy the symlink itself (actually, just recreate it)
let link = fs::read_link(&source)?; let link = fs::read_link(&source)?;
@ -1264,6 +1256,101 @@ fn copy_helper(source: &Path, dest: &Path, options: &Options) -> CopyResult<()>
Ok(()) Ok(())
} }
/// Copies `source` to `dest` using copy-on-write if possible.
#[cfg(target_os = "linux")]
fn copy_on_write_linux(source: &Path, dest: &Path, mode: ReflinkMode) -> CopyResult<()> {
debug_assert!(mode != ReflinkMode::Never);
let src_file = File::open(source).unwrap().into_raw_fd();
let dst_file = OpenOptions::new()
.write(true)
.truncate(false)
.create(true)
.open(dest)
.unwrap()
.into_raw_fd();
match mode {
ReflinkMode::Always => unsafe {
let result = ficlone(dst_file, src_file as *const i32);
if result != 0 {
return Err(format!(
"failed to clone {:?} from {:?}: {}",
source,
dest,
std::io::Error::last_os_error()
)
.into());
} else {
return Ok(());
}
},
ReflinkMode::Auto => unsafe {
let result = ficlone(dst_file, src_file as *const i32);
if result != 0 {
fs::copy(source, dest).context(&*context_for(source, dest))?;
}
},
ReflinkMode::Never => unreachable!(),
}
Ok(())
}
/// Copies `source` to `dest` using copy-on-write if possible.
#[cfg(target_os = "macos")]
fn copy_on_write_macos(source: &Path, dest: &Path, mode: ReflinkMode) -> CopyResult<()> {
debug_assert!(mode != ReflinkMode::Never);
// Extract paths in a form suitable to be passed to a syscall.
// The unwrap() is safe because they come from the command-line and so contain non nul
// character.
use std::os::unix::ffi::OsStrExt;
let src = CString::new(source.as_os_str().as_bytes()).unwrap();
let dst = CString::new(dest.as_os_str().as_bytes()).unwrap();
// clonefile(2) was introduced in macOS 10.12 so we cannot statically link against it
// for backward compatibility.
let clonefile = CString::new("clonefile").unwrap();
let raw_pfn = unsafe { libc::dlsym(libc::RTLD_NEXT, clonefile.as_ptr()) };
let mut error = 0;
if !raw_pfn.is_null() {
// Call clonefile(2).
// Safety: Casting a C function pointer to a rust function value is one of the few
// blessed uses of `transmute()`.
unsafe {
let pfn: extern "C" fn(
src: *const libc::c_char,
dst: *const libc::c_char,
flags: u32,
) -> libc::c_int = std::mem::transmute(raw_pfn);
error = pfn(src.as_ptr(), dst.as_ptr(), 0);
if std::io::Error::last_os_error().kind() == std::io::ErrorKind::AlreadyExists {
// clonefile(2) fails if the destination exists. Remove it and try again. Do not
// bother to check if removal worked because we're going to try to clone again.
let _ = fs::remove_file(dest);
error = pfn(src.as_ptr(), dst.as_ptr(), 0);
}
}
}
if raw_pfn.is_null() || error != 0 {
// clonefile(2) is not supported or it error'ed out (possibly because the FS does not
// support COW).
match mode {
ReflinkMode::Always => {
return Err(
format!("failed to clone {:?} from {:?}: {}", source, dest, error).into(),
)
}
ReflinkMode::Auto => fs::copy(source, dest).context(&*context_for(source, dest))?,
ReflinkMode::Never => unreachable!(),
};
}
Ok(())
}
/// Generate an error message if `target` is not the correct `target_type` /// Generate an error message if `target` is not the correct `target_type`
pub fn verify_target_type(target: &Path, target_type: &TargetType) -> CopyResult<()> { pub fn verify_target_type(target: &Path, target_type: &TargetType) -> CopyResult<()> {
match (target_type, target.is_dir()) { match (target_type, target.is_dir()) {

View file

@ -17,6 +17,7 @@ mod splitname;
use crate::csplit_error::CsplitError; use crate::csplit_error::CsplitError;
use crate::splitname::SplitName; use crate::splitname::SplitName;
use uucore::InvalidEncodingHandling;
static VERSION: &str = env!("CARGO_PKG_VERSION"); static VERSION: &str = env!("CARGO_PKG_VERSION");
static SUMMARY: &str = "split a file into sections determined by context lines"; static SUMMARY: &str = "split a file into sections determined by context lines";
@ -123,12 +124,7 @@ where
// split the file based on patterns // split the file based on patterns
for pattern in patterns.into_iter() { for pattern in patterns.into_iter() {
let pattern_as_str = pattern.to_string(); let pattern_as_str = pattern.to_string();
#[allow(clippy::match_like_matches_macro)] let is_skip = matches!(pattern, patterns::Pattern::SkipToMatch(_, _, _));
let is_skip = if let patterns::Pattern::SkipToMatch(_, _, _) = pattern {
true
} else {
false
};
match pattern { match pattern {
patterns::Pattern::UpToLine(n, ex) => { patterns::Pattern::UpToLine(n, ex) => {
let mut up_to_line = n; let mut up_to_line = n;
@ -711,7 +707,9 @@ mod tests {
pub fn uumain(args: impl uucore::Args) -> i32 { pub fn uumain(args: impl uucore::Args) -> i32 {
let usage = get_usage(); let usage = get_usage();
let args = args.collect_str(); let args = args
.collect_str(InvalidEncodingHandling::Ignore)
.accept_any();
let matches = App::new(executable!()) let matches = App::new(executable!())
.version(VERSION) .version(VERSION)

View file

@ -0,0 +1,46 @@
## Benchmarking cut
### 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
When fixing bugs or adding features you might want to compare
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
$ 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.
When optimizing or fixing performance regressions seeing the number of times a
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
```
### What to benchmark
There are four different performance paths in `cut` to benchmark.
- Byte ranges `-c`/`--characters` or `-b`/`--bytes` e.g. `cut -c 2,4,6-`
- Byte ranges with output delimiters e.g. `cut -c 4- --output-delimiter=/`
- Fields e.g. `cut -f -4`
- Fields with output delimiters e.g. `cut -f 7-10 --output-delimiter=:`
Choose a test input file with large number of lines so that program startup time does not significantly affect the benchmark.

View file

@ -18,6 +18,8 @@ path = "src/cut.rs"
clap = "2.33" clap = "2.33"
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore = { version=">=0.0.8", package="uucore", path="../../uucore" }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
memchr = "2"
bstr = "0.2"
[[bin]] [[bin]]
name = "cut" name = "cut"

View file

@ -1,152 +0,0 @@
// This file is part of the uutils coreutils package.
//
// (c) Rolf Morel <rolfmorel@gmail.com>
// (c) kwantam <kwantam@gmail.com>
// * substantial rewrite to use the `std::io::BufReader` trait
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
// spell-checker:ignore (ToDO) SRes Newl
use std::io::Result as IoResult;
use std::io::{BufRead, BufReader, Read, Write};
#[allow(non_snake_case)]
pub mod Bytes {
use std::io::Write;
pub trait Select {
fn select<W: Write>(&mut self, bytes: usize, out: Option<&mut W>) -> Selected;
}
#[derive(PartialEq, Eq, Debug)]
pub enum Selected {
NewlineFound,
Complete(usize),
Partial(usize),
EndOfFile,
}
}
#[derive(Debug)]
pub struct ByteReader<R>
where
R: Read,
{
inner: BufReader<R>,
newline_char: u8,
}
impl<R: Read> ByteReader<R> {
pub fn new(read: R, newline_char: u8) -> ByteReader<R> {
ByteReader {
inner: BufReader::with_capacity(4096, read),
newline_char,
}
}
}
impl<R: Read> Read for ByteReader<R> {
fn read(&mut self, buf: &mut [u8]) -> IoResult<usize> {
self.inner.read(buf)
}
}
impl<R: Read> BufRead for ByteReader<R> {
fn fill_buf(&mut self) -> IoResult<&[u8]> {
self.inner.fill_buf()
}
fn consume(&mut self, amt: usize) {
self.inner.consume(amt)
}
}
impl<R: Read> ByteReader<R> {
pub fn consume_line(&mut self) -> usize {
let mut bytes_consumed = 0;
let mut consume_val;
let newline_char = self.newline_char;
loop {
{
// need filled_buf to go out of scope
let filled_buf = match self.fill_buf() {
Ok(b) => {
if b.is_empty() {
return bytes_consumed;
} else {
b
}
}
Err(e) => crash!(1, "read error: {}", e),
};
if let Some(idx) = filled_buf.iter().position(|byte| *byte == newline_char) {
consume_val = idx + 1;
bytes_consumed += consume_val;
break;
}
consume_val = filled_buf.len();
}
bytes_consumed += consume_val;
self.consume(consume_val);
}
self.consume(consume_val);
bytes_consumed
}
}
impl<R: Read> self::Bytes::Select for ByteReader<R> {
fn select<W: Write>(&mut self, bytes: usize, out: Option<&mut W>) -> Bytes::Selected {
enum SRes {
Comp,
Part,
Newl,
}
use self::Bytes::Selected::*;
let newline_char = self.newline_char;
let (res, consume_val) = {
let buffer = match self.fill_buf() {
Err(e) => crash!(1, "read error: {}", e),
Ok(b) => b,
};
let (res, consume_val) = match buffer.len() {
0 => return EndOfFile,
buf_used if bytes < buf_used => {
// because the output delimiter should only be placed between
// segments check if the byte after bytes is a newline
let buf_slice = &buffer[0..=bytes];
match buf_slice.iter().position(|byte| *byte == newline_char) {
Some(idx) => (SRes::Newl, idx + 1),
None => (SRes::Comp, bytes),
}
}
_ => match buffer.iter().position(|byte| *byte == newline_char) {
Some(idx) => (SRes::Newl, idx + 1),
None => (SRes::Part, buffer.len()),
},
};
if let Some(out) = out {
crash_if_err!(1, out.write_all(&buffer[0..consume_val]));
}
(res, consume_val)
};
self.consume(consume_val);
match res {
SRes::Comp => Complete(consume_val),
SRes::Part => Partial(consume_val),
SRes::Newl => NewlineFound,
}
}
}

View file

@ -10,15 +10,17 @@
#[macro_use] #[macro_use]
extern crate uucore; extern crate uucore;
use bstr::io::BufReadExt;
use clap::{App, Arg}; use clap::{App, Arg};
use std::fs::File; use std::fs::File;
use std::io::{stdin, stdout, BufRead, BufReader, Read, Stdout, Write}; use std::io::{stdin, stdout, BufReader, BufWriter, Read, Write};
use std::path::Path; use std::path::Path;
use self::searcher::Searcher; use self::searcher::Searcher;
use uucore::fs::is_stdout_interactive;
use uucore::ranges::Range; use uucore::ranges::Range;
use uucore::InvalidEncodingHandling;
mod buffer;
mod searcher; mod searcher;
static NAME: &str = "cut"; static NAME: &str = "cut";
@ -125,6 +127,14 @@ enum Mode {
Fields(Vec<Range>, FieldOptions), Fields(Vec<Range>, FieldOptions),
} }
fn stdout_writer() -> Box<dyn Write> {
if is_stdout_interactive() {
Box::new(stdout())
} else {
Box::new(BufWriter::new(stdout())) as Box<dyn Write>
}
}
fn list_to_ranges(list: &str, complement: bool) -> Result<Vec<Range>, String> { fn list_to_ranges(list: &str, complement: bool) -> Result<Vec<Range>, String> {
if complement { if complement {
Range::from_list(list).map(|r| uucore::ranges::complement(&r)) Range::from_list(list).map(|r| uucore::ranges::complement(&r))
@ -134,72 +144,35 @@ fn list_to_ranges(list: &str, complement: bool) -> Result<Vec<Range>, String> {
} }
fn cut_bytes<R: Read>(reader: R, ranges: &[Range], opts: &Options) -> i32 { fn cut_bytes<R: Read>(reader: R, ranges: &[Range], opts: &Options) -> i32 {
use self::buffer::Bytes::Select;
use self::buffer::Bytes::Selected::*;
let newline_char = if opts.zero_terminated { b'\0' } else { b'\n' }; let newline_char = if opts.zero_terminated { b'\0' } else { b'\n' };
let mut buf_read = buffer::ByteReader::new(reader, newline_char); let buf_in = BufReader::new(reader);
let mut out = stdout(); let mut out = stdout_writer();
let delim = opts
.out_delim
.as_ref()
.map_or("", String::as_str)
.as_bytes();
'newline: loop { let res = buf_in.for_byte_record(newline_char, |line| {
let mut cur_pos = 1;
let mut print_delim = false; let mut print_delim = false;
for &Range { low, high } in ranges {
for &Range { low, high } in ranges.iter() { if low > line.len() {
// skip up to low break;
let orig_pos = cur_pos;
loop {
match buf_read.select(low - cur_pos, None::<&mut Stdout>) {
NewlineFound => {
crash_if_err!(1, out.write_all(&[newline_char]));
continue 'newline;
}
Complete(len) => {
cur_pos += len;
break;
}
Partial(len) => cur_pos += len,
EndOfFile => {
if orig_pos != cur_pos {
crash_if_err!(1, out.write_all(&[newline_char]));
}
break 'newline;
}
}
} }
if print_delim {
if let Some(ref delim) = opts.out_delim { out.write_all(delim)?;
if print_delim { } else if opts.out_delim.is_some() {
crash_if_err!(1, out.write_all(delim.as_bytes()));
}
print_delim = true; print_delim = true;
} }
// change `low` from 1-indexed value to 0-index value
// write out from low to high let low = low - 1;
loop { let high = high.min(line.len());
match buf_read.select(high - cur_pos + 1, Some(&mut out)) { out.write_all(&line[low..high])?;
NewlineFound => continue 'newline,
Partial(len) => cur_pos += len,
Complete(_) => {
cur_pos = high + 1;
break;
}
EndOfFile => {
if cur_pos != low || low == high {
crash_if_err!(1, out.write_all(&[newline_char]));
}
break 'newline;
}
}
}
} }
out.write_all(&[newline_char])?;
buf_read.consume_line(); Ok(true)
crash_if_err!(1, out.write_all(&[newline_char])); });
} crash_if_err!(1, res);
0 0
} }
@ -212,23 +185,11 @@ fn cut_fields_delimiter<R: Read>(
newline_char: u8, newline_char: u8,
out_delim: &str, out_delim: &str,
) -> i32 { ) -> i32 {
let mut buf_in = BufReader::new(reader); let buf_in = BufReader::new(reader);
let mut out = stdout(); let mut out = stdout_writer();
let mut buffer = Vec::new(); let input_delim_len = delim.len();
'newline: loop { let result = buf_in.for_byte_record_with_terminator(newline_char, |line| {
buffer.clear();
match buf_in.read_until(newline_char, &mut buffer) {
Ok(n) if n == 0 => break,
Err(e) => {
if buffer.is_empty() {
crash!(1, "read error: {}", e);
}
}
_ => (),
}
let line = &buffer[..];
let mut fields_pos = 1; let mut fields_pos = 1;
let mut low_idx = 0; let mut low_idx = 0;
let mut delim_search = Searcher::new(line, delim.as_bytes()).peekable(); let mut delim_search = Searcher::new(line, delim.as_bytes()).peekable();
@ -236,46 +197,46 @@ fn cut_fields_delimiter<R: Read>(
if delim_search.peek().is_none() { if delim_search.peek().is_none() {
if !only_delimited { if !only_delimited {
crash_if_err!(1, out.write_all(line)); out.write_all(line)?;
if line[line.len() - 1] != newline_char { if line[line.len() - 1] != newline_char {
crash_if_err!(1, out.write_all(&[newline_char])); out.write_all(&[newline_char])?;
} }
} }
continue; return Ok(true);
} }
for &Range { low, high } in ranges.iter() { for &Range { low, high } in ranges {
if low - fields_pos > 0 { if low - fields_pos > 0 {
low_idx = match delim_search.nth(low - fields_pos - 1) { low_idx = match delim_search.nth(low - fields_pos - 1) {
Some((_, beyond_delim)) => beyond_delim, Some(index) => index + input_delim_len,
None => break, None => break,
}; };
} }
for _ in 0..=high - low { for _ in 0..=high - low {
if print_delim { if print_delim {
crash_if_err!(1, out.write_all(out_delim.as_bytes())); out.write_all(out_delim.as_bytes())?;
} else {
print_delim = true;
} }
match delim_search.next() { match delim_search.next() {
Some((high_idx, next_low_idx)) => { Some(high_idx) => {
let segment = &line[low_idx..high_idx]; let segment = &line[low_idx..high_idx];
crash_if_err!(1, out.write_all(segment)); out.write_all(segment)?;
print_delim = true; low_idx = high_idx + input_delim_len;
low_idx = next_low_idx;
fields_pos = high + 1; fields_pos = high + 1;
} }
None => { None => {
let segment = &line[low_idx..]; let segment = &line[low_idx..];
crash_if_err!(1, out.write_all(segment)); out.write_all(segment)?;
if line[line.len() - 1] == newline_char { if line[line.len() - 1] == newline_char {
continue 'newline; return Ok(true);
} }
break; break;
} }
@ -283,9 +244,10 @@ fn cut_fields_delimiter<R: Read>(
} }
} }
crash_if_err!(1, out.write_all(&[newline_char])); out.write_all(&[newline_char])?;
} Ok(true)
});
crash_if_err!(1, result);
0 0
} }
@ -303,23 +265,11 @@ fn cut_fields<R: Read>(reader: R, ranges: &[Range], opts: &FieldOptions) -> i32
); );
} }
let mut buf_in = BufReader::new(reader); let buf_in = BufReader::new(reader);
let mut out = stdout(); let mut out = stdout_writer();
let mut buffer = Vec::new(); let delim_len = opts.delimiter.len();
'newline: loop { let result = buf_in.for_byte_record_with_terminator(newline_char, |line| {
buffer.clear();
match buf_in.read_until(newline_char, &mut buffer) {
Ok(n) if n == 0 => break,
Err(e) => {
if buffer.is_empty() {
crash!(1, "read error: {}", e);
}
}
_ => (),
}
let line = &buffer[..];
let mut fields_pos = 1; let mut fields_pos = 1;
let mut low_idx = 0; let mut low_idx = 0;
let mut delim_search = Searcher::new(line, opts.delimiter.as_bytes()).peekable(); let mut delim_search = Searcher::new(line, opts.delimiter.as_bytes()).peekable();
@ -327,53 +277,54 @@ fn cut_fields<R: Read>(reader: R, ranges: &[Range], opts: &FieldOptions) -> i32
if delim_search.peek().is_none() { if delim_search.peek().is_none() {
if !opts.only_delimited { if !opts.only_delimited {
crash_if_err!(1, out.write_all(line)); out.write_all(line)?;
if line[line.len() - 1] != newline_char { if line[line.len() - 1] != newline_char {
crash_if_err!(1, out.write_all(&[newline_char])); out.write_all(&[newline_char])?;
} }
} }
continue; return Ok(true);
} }
for &Range { low, high } in ranges.iter() { for &Range { low, high } in ranges {
if low - fields_pos > 0 { if low - fields_pos > 0 {
low_idx = match delim_search.nth(low - fields_pos - 1) { if let Some(delim_pos) = delim_search.nth(low - fields_pos - 1) {
Some((_, beyond_delim)) => beyond_delim, low_idx = if print_delim {
None => break, delim_pos
}; } else {
} delim_pos + delim_len
}
if print_delim && low_idx >= opts.delimiter.as_bytes().len() { } else {
low_idx -= opts.delimiter.as_bytes().len(); break;
}
} }
match delim_search.nth(high - low) { match delim_search.nth(high - low) {
Some((high_idx, next_low_idx)) => { Some(high_idx) => {
let segment = &line[low_idx..high_idx]; let segment = &line[low_idx..high_idx];
crash_if_err!(1, out.write_all(segment)); out.write_all(segment)?;
print_delim = true; print_delim = true;
low_idx = next_low_idx; low_idx = high_idx;
fields_pos = high + 1; fields_pos = high + 1;
} }
None => { None => {
let segment = &line[low_idx..line.len()]; let segment = &line[low_idx..line.len()];
crash_if_err!(1, out.write_all(segment)); out.write_all(segment)?;
if line[line.len() - 1] == newline_char { if line[line.len() - 1] == newline_char {
continue 'newline; return Ok(true);
} }
break; break;
} }
} }
} }
out.write_all(&[newline_char])?;
crash_if_err!(1, out.write_all(&[newline_char])); Ok(true)
} });
crash_if_err!(1, result);
0 0
} }
@ -406,7 +357,7 @@ fn cut_files(mut filenames: Vec<String>, mode: Mode) -> i32 {
continue; continue;
} }
if !path.metadata().is_ok() { if path.metadata().is_err() {
show_error!("{}: No such file or directory", filename); show_error!("{}: No such file or directory", filename);
continue; continue;
} }
@ -443,7 +394,9 @@ mod options {
} }
pub fn uumain(args: impl uucore::Args) -> i32 { pub fn uumain(args: impl uucore::Args) -> i32 {
let args = args.collect_str(); let args = args
.collect_str(InvalidEncodingHandling::Ignore)
.accept_any();
let matches = App::new(executable!()) let matches = App::new(executable!())
.name(NAME) .name(NAME)
@ -487,7 +440,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.help("filter field columns from the input source") .help("filter field columns from the input source")
.takes_value(true) .takes_value(true)
.allow_hyphen_values(true) .allow_hyphen_values(true)
.value_name("LIST") .value_name("LIST")
.display_order(4), .display_order(4),
) )
.arg( .arg(
@ -535,40 +488,36 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
matches.value_of(options::CHARACTERS), matches.value_of(options::CHARACTERS),
matches.value_of(options::FIELDS), matches.value_of(options::FIELDS),
) { ) {
(Some(byte_ranges), None, None) => { (Some(byte_ranges), None, None) => list_to_ranges(byte_ranges, complement).map(|ranges| {
list_to_ranges(&byte_ranges[..], complement).map(|ranges| { Mode::Bytes(
Mode::Bytes( ranges,
ranges, Options {
Options { out_delim: Some(
out_delim: Some( matches
matches .value_of(options::OUTPUT_DELIMITER)
.value_of(options::OUTPUT_DELIMITER) .unwrap_or_default()
.unwrap_or_default() .to_owned(),
.to_owned(), ),
), zero_terminated: matches.is_present(options::ZERO_TERMINATED),
zero_terminated: matches.is_present(options::ZERO_TERMINATED), },
}, )
) }),
}) (None, Some(char_ranges), None) => list_to_ranges(char_ranges, complement).map(|ranges| {
} Mode::Characters(
(None, Some(char_ranges), None) => { ranges,
list_to_ranges(&char_ranges[..], complement).map(|ranges| { Options {
Mode::Characters( out_delim: Some(
ranges, matches
Options { .value_of(options::OUTPUT_DELIMITER)
out_delim: Some( .unwrap_or_default()
matches .to_owned(),
.value_of(options::OUTPUT_DELIMITER) ),
.unwrap_or_default() zero_terminated: matches.is_present(options::ZERO_TERMINATED),
.to_owned(), },
), )
zero_terminated: matches.is_present(options::ZERO_TERMINATED), }),
},
)
})
}
(None, None, Some(field_ranges)) => { (None, None, Some(field_ranges)) => {
list_to_ranges(&field_ranges[..], complement).and_then(|ranges| { list_to_ranges(field_ranges, complement).and_then(|ranges| {
let out_delim = match matches.value_of(options::OUTPUT_DELIMITER) { let out_delim = match matches.value_of(options::OUTPUT_DELIMITER) {
Some(s) => { Some(s) => {
if s.is_empty() { if s.is_empty() {

View file

@ -5,7 +5,8 @@
// For the full copyright and license information, please view the LICENSE // For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code. // file that was distributed with this source code.
#[derive(Clone)] use memchr::memchr;
pub struct Searcher<'a> { pub struct Searcher<'a> {
haystack: &'a [u8], haystack: &'a [u8],
needle: &'a [u8], needle: &'a [u8],
@ -14,6 +15,7 @@ pub struct Searcher<'a> {
impl<'a> Searcher<'a> { impl<'a> Searcher<'a> {
pub fn new(haystack: &'a [u8], needle: &'a [u8]) -> Searcher<'a> { pub fn new(haystack: &'a [u8], needle: &'a [u8]) -> Searcher<'a> {
assert!(!needle.is_empty());
Searcher { Searcher {
haystack, haystack,
needle, needle,
@ -23,30 +25,81 @@ impl<'a> Searcher<'a> {
} }
impl<'a> Iterator for Searcher<'a> { impl<'a> Iterator for Searcher<'a> {
type Item = (usize, usize); type Item = usize;
fn next(&mut self) -> Option<(usize, usize)> { fn next(&mut self) -> Option<Self::Item> {
if self.needle.len() == 1 { loop {
for offset in self.position..self.haystack.len() { if let Some(match_idx) = memchr(self.needle[0], self.haystack) {
if self.haystack[offset] == self.needle[0] { if self.needle.len() == 1
self.position = offset + 1; || self.haystack[match_idx + 1..].starts_with(&self.needle[1..])
return Some((offset, offset + 1)); {
let match_pos = self.position + match_idx;
let skip = match_idx + self.needle.len();
self.haystack = &self.haystack[skip..];
self.position += skip;
return Some(match_pos);
} else {
let skip = match_idx + 1;
self.haystack = &self.haystack[skip..];
self.position += skip;
// continue
} }
}
self.position = self.haystack.len();
return None;
}
while self.position + self.needle.len() <= self.haystack.len() {
if &self.haystack[self.position..self.position + self.needle.len()] == self.needle {
let match_pos = self.position;
self.position += self.needle.len();
return Some((match_pos, match_pos + self.needle.len()));
} else { } else {
self.position += 1; return None;
} }
} }
None }
}
#[cfg(test)]
mod tests {
use super::*;
const NEEDLE: &[u8] = "ab".as_bytes();
#[test]
fn test_normal() {
let iter = Searcher::new("a.a.a".as_bytes(), "a".as_bytes());
let items: Vec<usize> = iter.collect();
assert_eq!(vec![0, 2, 4], items);
}
#[test]
fn test_empty() {
let iter = Searcher::new("".as_bytes(), "a".as_bytes());
let items: Vec<usize> = iter.collect();
assert_eq!(vec![] as Vec<usize>, items);
}
fn test_multibyte(line: &[u8], expected: Vec<usize>) {
let iter = Searcher::new(line, NEEDLE);
let items: Vec<usize> = iter.collect();
assert_eq!(expected, items);
}
#[test]
fn test_multibyte_normal() {
test_multibyte("...ab...ab...".as_bytes(), vec![3, 8]);
}
#[test]
fn test_multibyte_needle_head_at_end() {
test_multibyte("a".as_bytes(), vec![]);
}
#[test]
fn test_multibyte_starting_needle() {
test_multibyte("ab...ab...".as_bytes(), vec![0, 5]);
}
#[test]
fn test_multibyte_trailing_needle() {
test_multibyte("...ab...ab".as_bytes(), vec![3, 8]);
}
#[test]
fn test_multibyte_first_byte_false_match() {
test_multibyte("aA..aCaC..ab..aD".as_bytes(), vec![10]);
} }
} }

View file

@ -207,11 +207,15 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.alias(OPT_UNIVERSAL_2) .alias(OPT_UNIVERSAL_2)
.help("print or set Coordinated Universal Time (UTC)"), .help("print or set Coordinated Universal Time (UTC)"),
) )
.arg(Arg::with_name(OPT_FORMAT).multiple(true)) .arg(Arg::with_name(OPT_FORMAT).multiple(false))
.get_matches_from(args); .get_matches_from(args);
let format = if let Some(form) = matches.value_of(OPT_FORMAT) { let format = if let Some(form) = matches.value_of(OPT_FORMAT) {
let form = form[1..].into(); if !form.starts_with('+') {
eprintln!("date: invalid date {}", form);
return 1;
}
let form = form[1..].to_string();
Format::Custom(form) Format::Custom(form)
} else if let Some(fmt) = matches } else if let Some(fmt) = matches
.values_of(OPT_ISO_8601) .values_of(OPT_ISO_8601)
@ -237,7 +241,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
let set_to = match matches.value_of(OPT_SET).map(parse_date) { let set_to = match matches.value_of(OPT_SET).map(parse_date) {
None => None, None => None,
Some(Err((input, _err))) => { Some(Err((input, _err))) => {
eprintln!("date: invalid date '{}'", input); eprintln!("date: invalid date {}", input);
return 1; return 1;
} }
Some(Ok(date)) => Some(date), Some(Ok(date)) => Some(date),
@ -297,11 +301,13 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
for date in dates { for date in dates {
match date { match date {
Ok(date) => { Ok(date) => {
let formatted = date.format(format_string); // GNU `date` uses `%N` for nano seconds, however crate::chrono uses `%f`
let format_string = &format_string.replace("%N", "%f");
let formatted = date.format(format_string).to_string().replace("%f", "%N");
println!("{}", formatted); println!("{}", formatted);
} }
Err((input, _err)) => { Err((input, _err)) => {
println!("date: invalid date '{}'", input); println!("date: invalid date {}", input);
} }
} }
} }
@ -348,7 +354,7 @@ fn set_system_datetime(_date: DateTime<Utc>) -> i32 {
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
fn set_system_datetime(_date: DateTime<Utc>) -> i32 { fn set_system_datetime(_date: DateTime<Utc>) -> i32 {
eprintln!("date: setting the date is not supported by macOS"); eprintln!("date: setting the date is not supported by macOS");
return 1; 1
} }
#[cfg(all(unix, not(target_os = "macos")))] #[cfg(all(unix, not(target_os = "macos")))]

View file

@ -16,14 +16,10 @@ path = "src/df.rs"
[dependencies] [dependencies]
clap = "2.33" clap = "2.33"
libc = "0.2"
number_prefix = "0.4" number_prefix = "0.4"
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["libc", "fsext"] }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
[target.'cfg(target_os = "windows")'.dependencies]
winapi = { version = "0.3", features = ["errhandlingapi", "fileapi", "handleapi", "winerror"] }
[[bin]] [[bin]]
name = "df" name = "df"
path = "src/main.rs" path = "src/main.rs"

View file

@ -6,22 +6,17 @@
// For the full copyright and license information, please view the LICENSE file // For the full copyright and license information, please view the LICENSE file
// that was distributed with this source code. // that was distributed with this source code.
// spell-checker:ignore (ToDO) mountinfo mtab BLOCKSIZE getmntinfo fobj mptr noatime Iused overmounted // spell-checker:ignore (ToDO) mountinfo BLOCKSIZE fobj mptr noatime Iused overmounted
// spell-checker:ignore (libc/fs) asyncreads asyncwrites autofs bavail bfree bsize charspare cifs debugfs devfs devpts ffree frsize fsid fstypename fusectl inode inodes iosize kernfs mntbufp mntfromname mntonname mqueue namemax pipefs smbfs statfs statvfs subfs syncreads syncwrites sysfs wcslen // spell-checker:ignore (libc/fs) asyncreads asyncwrites autofs bavail bfree bsize charspare cifs debugfs devfs devpts ffree frsize fsid fstypename fusectl inode inodes iosize kernfs mntbufp mntfromname mntonname mqueue namemax pipefs smbfs statvfs subfs syncreads syncwrites sysfs wcslen
#[macro_use] #[macro_use]
extern crate uucore; extern crate uucore;
#[cfg(unix)]
use uucore::fsext::statfs_fn;
use uucore::fsext::{read_fs_list, FsUsage, MountInfo};
use clap::{App, Arg}; use clap::{App, Arg};
#[cfg(windows)]
use winapi::um::errhandlingapi::GetLastError;
#[cfg(windows)]
use winapi::um::fileapi::{
FindFirstVolumeW, FindNextVolumeW, FindVolumeClose, GetDriveTypeW, GetVolumeInformationW,
GetVolumePathNamesForVolumeNameW, QueryDosDeviceW,
};
use number_prefix::NumberPrefix; use number_prefix::NumberPrefix;
use std::cell::Cell; use std::cell::Cell;
use std::collections::HashMap; use std::collections::HashMap;
@ -32,41 +27,11 @@ use std::ffi::CString;
#[cfg(unix)] #[cfg(unix)]
use std::mem; use std::mem;
#[cfg(any(target_vendor = "apple", target_os = "freebsd"))]
use libc::c_int;
#[cfg(target_vendor = "apple")]
use libc::statfs;
#[cfg(any(target_vendor = "apple", target_os = "freebsd"))]
use std::ffi::CStr;
#[cfg(any(target_vendor = "apple", target_os = "freebsd", target_os = "windows"))]
use std::ptr;
#[cfg(any(target_vendor = "apple", target_os = "freebsd"))]
use std::slice;
#[cfg(target_os = "freebsd")] #[cfg(target_os = "freebsd")]
use libc::{c_char, fsid_t, uid_t}; use uucore::libc::{c_char, fsid_t, uid_t};
#[cfg(target_os = "linux")]
use std::fs::File;
#[cfg(target_os = "linux")]
use std::io::{BufRead, BufReader};
#[cfg(windows)]
use std::ffi::OsString;
#[cfg(windows)]
use std::os::windows::ffi::OsStrExt;
#[cfg(windows)]
use std::os::windows::ffi::OsStringExt;
#[cfg(windows)] #[cfg(windows)]
use std::path::Path; use std::path::Path;
#[cfg(windows)]
use winapi::shared::minwindef::DWORD;
#[cfg(windows)]
use winapi::um::fileapi::GetDiskFreeSpaceW;
#[cfg(windows)]
use winapi::um::handleapi::INVALID_HANDLE_VALUE;
#[cfg(windows)]
use winapi::um::winbase::DRIVE_REMOTE;
static VERSION: &str = env!("CARGO_PKG_VERSION"); static VERSION: &str = env!("CARGO_PKG_VERSION");
static ABOUT: &str = "Show information about the file system on which each FILE resides,\n\ static ABOUT: &str = "Show information about the file system on which each FILE resides,\n\
@ -75,14 +40,6 @@ static ABOUT: &str = "Show information about the file system on which each FILE
static EXIT_OK: i32 = 0; static EXIT_OK: i32 = 0;
static EXIT_ERR: i32 = 1; static EXIT_ERR: i32 = 1;
#[cfg(windows)]
const MAX_PATH: usize = 266;
#[cfg(target_os = "linux")]
static LINUX_MOUNTINFO: &str = "/proc/self/mountinfo";
#[cfg(target_os = "linux")]
static LINUX_MTAB: &str = "/etc/mtab";
static OPT_ALL: &str = "all"; static OPT_ALL: &str = "all";
static OPT_BLOCKSIZE: &str = "blocksize"; static OPT_BLOCKSIZE: &str = "blocksize";
static OPT_DIRECT: &str = "direct"; static OPT_DIRECT: &str = "direct";
@ -101,8 +58,6 @@ static OPT_TYPE: &str = "type";
static OPT_PRINT_TYPE: &str = "print-type"; static OPT_PRINT_TYPE: &str = "print-type";
static OPT_EXCLUDE_TYPE: &str = "exclude-type"; static OPT_EXCLUDE_TYPE: &str = "exclude-type";
static MOUNT_OPT_BIND: &str = "bind";
/// Store names of file systems as a selector. /// Store names of file systems as a selector.
/// Note: `exclude` takes priority over `include`. /// Note: `exclude` takes priority over `include`.
struct FsSelector { struct FsSelector {
@ -116,142 +71,21 @@ struct Options {
show_listed_fs: bool, show_listed_fs: bool,
show_fs_type: bool, show_fs_type: bool,
show_inode_instead: bool, show_inode_instead: bool,
print_grand_total: bool,
// block_size: usize, // block_size: usize,
human_readable_base: i64, human_readable_base: i64,
fs_selector: FsSelector, fs_selector: FsSelector,
} }
#[derive(Debug, Clone)]
struct MountInfo {
// it stores `volume_name` in windows platform and `dev_id` in unix platform
dev_id: String,
dev_name: String,
fs_type: String,
mount_dir: String,
mount_option: String, // we only care "bind" option
mount_root: String,
remote: bool,
dummy: bool,
}
#[cfg(all(
target_os = "freebsd",
not(all(target_vendor = "apple", target_arch = "x86_64"))
))]
#[repr(C)]
#[derive(Copy, Clone)]
#[allow(non_camel_case_types)]
struct statfs {
f_version: u32,
f_type: u32,
f_flags: u64,
f_bsize: u64,
f_iosize: u64,
f_blocks: u64,
f_bfree: u64,
f_bavail: i64,
f_files: u64,
f_ffree: i64,
f_syncwrites: u64,
f_asyncwrites: u64,
f_syncreads: u64,
f_asyncreads: u64,
f_spare: [u64; 10usize],
f_namemax: u32,
f_owner: uid_t,
f_fsid: fsid_t,
f_charspare: [c_char; 80usize],
f_fstypename: [c_char; 16usize],
f_mntfromname: [c_char; 88usize],
f_mntonname: [c_char; 88usize],
}
#[derive(Debug, Clone)]
struct FsUsage {
blocksize: u64,
blocks: u64,
bfree: u64,
bavail: u64,
bavail_top_bit_set: bool,
files: u64,
ffree: u64,
}
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
struct Filesystem { struct Filesystem {
mountinfo: MountInfo, mountinfo: MountInfo,
usage: FsUsage, usage: FsUsage,
} }
#[cfg(windows)]
macro_rules! String2LPWSTR {
($str: expr) => {
OsString::from($str.clone())
.as_os_str()
.encode_wide()
.chain(Some(0))
.collect::<Vec<u16>>()
.as_ptr()
};
}
#[cfg(windows)]
#[allow(non_snake_case)]
fn LPWSTR2String(buf: &[u16]) -> String {
let len = unsafe { libc::wcslen(buf.as_ptr()) };
OsString::from_wide(&buf[..len as usize])
.into_string()
.unwrap()
}
fn get_usage() -> String { fn get_usage() -> String {
format!("{0} [OPTION]... [FILE]...", executable!()) format!("{0} [OPTION]... [FILE]...", executable!())
} }
#[cfg(any(target_os = "freebsd", target_vendor = "apple"))]
extern "C" {
#[cfg(all(target_vendor = "apple", target_arch = "x86_64"))]
#[link_name = "getmntinfo$INODE64"]
fn getmntinfo(mntbufp: *mut *mut statfs, flags: c_int) -> c_int;
#[cfg(any(
all(target_os = "freebsd"),
all(target_vendor = "apple", target_arch = "aarch64")
))]
fn getmntinfo(mntbufp: *mut *mut statfs, flags: c_int) -> c_int;
}
#[cfg(any(target_os = "freebsd", target_vendor = "apple"))]
impl From<statfs> for MountInfo {
fn from(statfs: statfs) -> Self {
let mut info = MountInfo {
dev_id: "".to_string(),
dev_name: unsafe {
CStr::from_ptr(&statfs.f_mntfromname[0])
.to_string_lossy()
.into_owned()
},
fs_type: unsafe {
CStr::from_ptr(&statfs.f_fstypename[0])
.to_string_lossy()
.into_owned()
},
mount_dir: unsafe {
CStr::from_ptr(&statfs.f_mntonname[0])
.to_string_lossy()
.into_owned()
},
mount_root: "".to_string(),
mount_option: "".to_string(),
remote: false,
dummy: false,
};
info.set_missing_fields();
info
}
}
impl FsSelector { impl FsSelector {
fn new() -> FsSelector { fn new() -> FsSelector {
FsSelector { FsSelector {
@ -286,7 +120,6 @@ impl Options {
show_listed_fs: false, show_listed_fs: false,
show_fs_type: false, show_fs_type: false,
show_inode_instead: false, show_inode_instead: false,
print_grand_total: false,
// block_size: match env::var("BLOCKSIZE") { // block_size: match env::var("BLOCKSIZE") {
// Ok(size) => size.parse().unwrap(), // Ok(size) => size.parse().unwrap(),
// Err(_) => 512, // Err(_) => 512,
@ -297,239 +130,6 @@ impl Options {
} }
} }
impl MountInfo {
fn set_missing_fields(&mut self) {
#[cfg(unix)]
{
// We want to keep the dev_id on Windows
// but set dev_id
let path = CString::new(self.mount_dir.clone()).unwrap();
unsafe {
let mut stat = mem::zeroed();
if libc::stat(path.as_ptr(), &mut stat) == 0 {
self.dev_id = (stat.st_dev as i32).to_string();
} else {
self.dev_id = "".to_string();
}
}
}
// set MountInfo::dummy
match self.fs_type.as_ref() {
"autofs" | "proc" | "subfs"
/* for Linux 2.6/3.x */
| "debugfs" | "devpts" | "fusectl" | "mqueue" | "rpc_pipefs" | "sysfs"
/* FreeBSD, Linux 2.4 */
| "devfs"
/* for NetBSD 3.0 */
| "kernfs"
/* for Irix 6.5 */
| "ignore" => self.dummy = true,
_ => self.dummy = self.fs_type == "none"
&& self.mount_option.find(MOUNT_OPT_BIND).is_none(),
}
// set MountInfo::remote
#[cfg(windows)]
{
self.remote = DRIVE_REMOTE == unsafe { GetDriveTypeW(String2LPWSTR!(self.mount_root)) };
}
#[cfg(unix)]
{
if self.dev_name.find(':').is_some()
|| (self.dev_name.starts_with("//") && self.fs_type == "smbfs"
|| self.fs_type == "cifs")
|| self.dev_name == "-hosts"
{
self.remote = true;
} else {
self.remote = false;
}
}
}
#[cfg(target_os = "linux")]
fn new(file_name: &str, raw: Vec<&str>) -> Option<MountInfo> {
match file_name {
// Format: 36 35 98:0 /mnt1 /mnt2 rw,noatime master:1 - ext3 /dev/root rw,errors=continue
// "man proc" for more details
"/proc/self/mountinfo" => {
let mut m = MountInfo {
dev_id: "".to_string(),
dev_name: raw[9].to_string(),
fs_type: raw[8].to_string(),
mount_root: raw[3].to_string(),
mount_dir: raw[4].to_string(),
mount_option: raw[5].to_string(),
remote: false,
dummy: false,
};
m.set_missing_fields();
Some(m)
}
"/etc/mtab" => {
let mut m = MountInfo {
dev_id: "".to_string(),
dev_name: raw[0].to_string(),
fs_type: raw[2].to_string(),
mount_root: "".to_string(),
mount_dir: raw[1].to_string(),
mount_option: raw[3].to_string(),
remote: false,
dummy: false,
};
m.set_missing_fields();
Some(m)
}
_ => None,
}
}
#[cfg(windows)]
fn new(mut volume_name: String) -> Option<MountInfo> {
let mut dev_name_buf = [0u16; MAX_PATH];
volume_name.pop();
unsafe {
QueryDosDeviceW(
OsString::from(volume_name.clone())
.as_os_str()
.encode_wide()
.chain(Some(0))
.skip(4)
.collect::<Vec<u16>>()
.as_ptr(),
dev_name_buf.as_mut_ptr(),
dev_name_buf.len() as DWORD,
)
};
volume_name.push('\\');
let dev_name = LPWSTR2String(&dev_name_buf);
let mut mount_root_buf = [0u16; MAX_PATH];
let success = unsafe {
GetVolumePathNamesForVolumeNameW(
String2LPWSTR!(volume_name),
mount_root_buf.as_mut_ptr(),
mount_root_buf.len() as DWORD,
ptr::null_mut(),
)
};
if 0 == success {
// TODO: support the case when `GetLastError()` returns `ERROR_MORE_DATA`
return None;
}
let mount_root = LPWSTR2String(&mount_root_buf);
let mut fs_type_buf = [0u16; MAX_PATH];
let success = unsafe {
GetVolumeInformationW(
String2LPWSTR!(mount_root),
ptr::null_mut(),
0 as DWORD,
ptr::null_mut(),
ptr::null_mut(),
ptr::null_mut(),
fs_type_buf.as_mut_ptr(),
fs_type_buf.len() as DWORD,
)
};
let fs_type = if 0 != success {
Some(LPWSTR2String(&fs_type_buf))
} else {
None
};
let mut mn_info = MountInfo {
dev_id: volume_name,
dev_name,
fs_type: fs_type.unwrap_or_else(|| "".to_string()),
mount_root,
mount_dir: "".to_string(),
mount_option: "".to_string(),
remote: false,
dummy: false,
};
mn_info.set_missing_fields();
Some(mn_info)
}
}
impl FsUsage {
#[cfg(unix)]
fn new(statvfs: libc::statvfs) -> FsUsage {
{
FsUsage {
blocksize: if statvfs.f_frsize != 0 {
statvfs.f_frsize as u64
} else {
statvfs.f_bsize as u64
},
blocks: statvfs.f_blocks as u64,
bfree: statvfs.f_bfree as u64,
bavail: statvfs.f_bavail as u64,
bavail_top_bit_set: ((statvfs.f_bavail as u64) & (1u64.rotate_right(1))) != 0,
files: statvfs.f_files as u64,
ffree: statvfs.f_ffree as u64,
}
}
}
#[cfg(not(unix))]
fn new(path: &Path) -> FsUsage {
let mut root_path = [0u16; MAX_PATH];
let success = unsafe {
GetVolumePathNamesForVolumeNameW(
//path_utf8.as_ptr(),
String2LPWSTR!(path.as_os_str()),
root_path.as_mut_ptr(),
root_path.len() as DWORD,
ptr::null_mut(),
)
};
if 0 == success {
crash!(
EXIT_ERR,
"GetVolumePathNamesForVolumeNameW failed: {}",
unsafe { GetLastError() }
);
}
let mut sectors_per_cluster = 0;
let mut bytes_per_sector = 0;
let mut number_of_free_clusters = 0;
let mut total_number_of_clusters = 0;
let success = unsafe {
GetDiskFreeSpaceW(
String2LPWSTR!(path.as_os_str()),
&mut sectors_per_cluster,
&mut bytes_per_sector,
&mut number_of_free_clusters,
&mut total_number_of_clusters,
)
};
if 0 == success {
// Fails in case of CD for example
//crash!(EXIT_ERR, "GetDiskFreeSpaceW failed: {}", unsafe {
//GetLastError()
//});
}
let bytes_per_cluster = sectors_per_cluster as u64 * bytes_per_sector as u64;
FsUsage {
// f_bsize File system block size.
blocksize: bytes_per_cluster as u64,
// f_blocks - Total number of blocks on the file system, in units of f_frsize.
// frsize = Fundamental file system block size (fragment size).
blocks: total_number_of_clusters as u64,
// Total number of free blocks.
bfree: number_of_free_clusters as u64,
// Total number of free blocks available to non-privileged processes.
bavail: 0 as u64,
bavail_top_bit_set: ((bytes_per_sector as u64) & (1u64.rotate_right(1))) != 0,
// Total number of file nodes (inodes) on the file system.
files: 0 as u64, // Not available on windows
// Total number of free file nodes (inodes).
ffree: 4096 as u64, // Meaningless on Windows
}
}
}
impl Filesystem { impl Filesystem {
// TODO: resolve uuid in `mountinfo.dev_name` if exists // TODO: resolve uuid in `mountinfo.dev_name` if exists
fn new(mountinfo: MountInfo) -> Option<Filesystem> { fn new(mountinfo: MountInfo) -> Option<Filesystem> {
@ -550,7 +150,7 @@ impl Filesystem {
unsafe { unsafe {
let path = CString::new(_stat_path).unwrap(); let path = CString::new(_stat_path).unwrap();
let mut statvfs = mem::zeroed(); let mut statvfs = mem::zeroed();
if libc::statvfs(path.as_ptr(), &mut statvfs) < 0 { if statfs_fn(path.as_ptr(), &mut statvfs) < 0 {
None None
} else { } else {
Some(Filesystem { Some(Filesystem {
@ -567,80 +167,6 @@ impl Filesystem {
} }
} }
/// Read file system list.
fn read_fs_list() -> Vec<MountInfo> {
#[cfg(target_os = "linux")]
{
let (file_name, fobj) = File::open(LINUX_MOUNTINFO)
.map(|f| (LINUX_MOUNTINFO, f))
.or_else(|_| File::open(LINUX_MTAB).map(|f| (LINUX_MTAB, f)))
.expect("failed to find mount list files");
let reader = BufReader::new(fobj);
reader
.lines()
.filter_map(|line| line.ok())
.filter_map(|line| {
let raw_data = line.split_whitespace().collect::<Vec<&str>>();
MountInfo::new(file_name, raw_data)
})
.collect::<Vec<_>>()
}
#[cfg(any(target_os = "freebsd", target_vendor = "apple"))]
{
let mut mptr: *mut statfs = ptr::null_mut();
let len = unsafe { getmntinfo(&mut mptr, 1 as c_int) };
if len < 0 {
crash!(EXIT_ERR, "getmntinfo failed");
}
let mounts = unsafe { slice::from_raw_parts(mptr, len as usize) };
mounts
.iter()
.map(|m| MountInfo::from(*m))
.collect::<Vec<_>>()
}
#[cfg(windows)]
{
let mut volume_name_buf = [0u16; MAX_PATH];
// As recommended in the MS documentation, retrieve the first volume before the others
let find_handle = unsafe {
FindFirstVolumeW(volume_name_buf.as_mut_ptr(), volume_name_buf.len() as DWORD)
};
if INVALID_HANDLE_VALUE == find_handle {
crash!(EXIT_ERR, "FindFirstVolumeW failed: {}", unsafe {
GetLastError()
});
}
let mut mounts = Vec::<MountInfo>::new();
loop {
let volume_name = LPWSTR2String(&volume_name_buf);
if !volume_name.starts_with("\\\\?\\") || !volume_name.ends_with('\\') {
show_warning!("A bad path was skipped: {}", volume_name);
continue;
}
if let Some(m) = MountInfo::new(volume_name) {
mounts.push(m);
}
if 0 == unsafe {
FindNextVolumeW(
find_handle,
volume_name_buf.as_mut_ptr(),
volume_name_buf.len() as DWORD,
)
} {
let err = unsafe { GetLastError() };
if err != winapi::shared::winerror::ERROR_NO_MORE_FILES {
crash!(EXIT_ERR, "FindNextVolumeW failed: {}", err);
}
break;
}
}
unsafe {
FindVolumeClose(find_handle);
}
mounts
}
}
fn filter_mount_list(vmi: Vec<MountInfo>, paths: &[String], opt: &Options) -> Vec<MountInfo> { fn filter_mount_list(vmi: Vec<MountInfo>, paths: &[String], opt: &Options) -> Vec<MountInfo> {
vmi.into_iter() vmi.into_iter()
.filter_map(|mi| { .filter_map(|mi| {
@ -871,9 +397,6 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
if matches.is_present(OPT_ALL) { if matches.is_present(OPT_ALL) {
opt.show_all_fs = true; opt.show_all_fs = true;
} }
if matches.is_present(OPT_TOTAL) {
opt.print_grand_total = true;
}
if matches.is_present(OPT_INODES) { if matches.is_present(OPT_INODES) {
opt.show_inode_instead = true; opt.show_inode_instead = true;
} }
@ -921,6 +444,9 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
"Use%", "Use%",
] ]
}); });
if cfg!(target_os = "macos") && !opt.show_inode_instead {
header.insert(header.len() - 1, "Capacity");
}
header.push("Mounted on"); header.push("Mounted on");
for (idx, title) in header.iter().enumerate() { for (idx, title) in header.iter().enumerate() {
@ -975,6 +501,11 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
"{0: >12} ", "{0: >12} ",
human_readable(free_size, opt.human_readable_base) human_readable(free_size, opt.human_readable_base)
); );
if cfg!(target_os = "macos") {
let used = fs.usage.blocks - fs.usage.bfree;
let blocks = used + fs.usage.bavail;
print!("{0: >12} ", use_size(used, blocks));
}
print!("{0: >5} ", use_size(free_size, total_size)); print!("{0: >5} ", use_size(free_size, total_size));
} }
print!("{0: <16}", fs.mountinfo.mount_dir); print!("{0: <16}", fs.mountinfo.mount_dir);

View file

@ -53,7 +53,9 @@ pub fn guess_syntax() -> OutputFmt {
} }
pub fn uumain(args: impl uucore::Args) -> i32 { pub fn uumain(args: impl uucore::Args) -> i32 {
let args = args.collect_str(); let args = args
.collect_str(InvalidEncodingHandling::Ignore)
.accept_any();
let matches = app!(SYNTAX, SUMMARY, LONG_HELP) let matches = app!(SYNTAX, SUMMARY, LONG_HELP)
.optflag("b", "sh", "output Bourne shell code to set LS_COLORS") .optflag("b", "sh", "output Bourne shell code to set LS_COLORS")
@ -103,7 +105,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
if out_format == OutputFmt::Unknown { if out_format == OutputFmt::Unknown {
match guess_syntax() { match guess_syntax() {
OutputFmt::Unknown => { OutputFmt::Unknown => {
show_info!("no SHELL environment variable, and no shell type option given"); show_error!("no SHELL environment variable, and no shell type option given");
return 1; return 1;
} }
fmt => out_format = fmt, fmt => out_format = fmt,
@ -128,7 +130,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
) )
} }
Err(e) => { Err(e) => {
show_info!("{}: {}", matches.free[0], e); show_error!("{}: {}", matches.free[0], e);
return 1; return 1;
} }
} }
@ -139,7 +141,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
0 0
} }
Err(s) => { Err(s) => {
show_info!("{}", s); show_error!("{}", s);
1 1
} }
} }
@ -202,6 +204,8 @@ enum ParseState {
Pass, Pass,
} }
use std::collections::HashMap; use std::collections::HashMap;
use uucore::InvalidEncodingHandling;
fn parse<T>(lines: T, fmt: OutputFmt, fp: &str) -> Result<String, String> fn parse<T>(lines: T, fmt: OutputFmt, fp: &str) -> Result<String, String>
where where
T: IntoIterator, T: IntoIterator,

View file

@ -10,36 +10,44 @@ extern crate uucore;
use clap::{App, Arg}; use clap::{App, Arg};
use std::path::Path; use std::path::Path;
use uucore::InvalidEncodingHandling;
static NAME: &str = "dirname"; static ABOUT: &str = "strip last component from file name";
static SYNTAX: &str = "[OPTION] NAME...";
static SUMMARY: &str = "strip last component from file name";
static VERSION: &str = env!("CARGO_PKG_VERSION"); static VERSION: &str = env!("CARGO_PKG_VERSION");
static LONG_HELP: &str = "
Output each NAME with its last non-slash component and trailing slashes
removed; if NAME contains no /'s, output '.' (meaning the current
directory).
";
mod options { mod options {
pub const ZERO: &str = "zero"; pub const ZERO: &str = "zero";
pub const DIR: &str = "dir"; pub const DIR: &str = "dir";
} }
fn get_usage() -> String {
format!("{0} [OPTION] NAME...", executable!())
}
fn get_long_usage() -> String {
String::from(
"Output each NAME with its last non-slash component and trailing slashes
removed; if NAME contains no /'s, output '.' (meaning the current directory).",
)
}
pub fn uumain(args: impl uucore::Args) -> i32 { pub fn uumain(args: impl uucore::Args) -> i32 {
let args = args.collect_str(); let args = args
.collect_str(InvalidEncodingHandling::ConvertLossy)
.accept_any();
let usage = get_usage();
let after_help = get_long_usage();
let matches = App::new(executable!()) let matches = App::new(executable!())
.name(NAME) .about(ABOUT)
.usage(SYNTAX) .usage(&usage[..])
.about(SUMMARY) .after_help(&after_help[..])
.after_help(LONG_HELP)
.version(VERSION) .version(VERSION)
.arg( .arg(
Arg::with_name(options::ZERO) Arg::with_name(options::ZERO)
.short(options::ZERO) .long(options::ZERO)
.short("z") .short("z")
.takes_value(false)
.help("separate output with NUL rather than newline"), .help("separate output with NUL rather than newline"),
) )
.arg(Arg::with_name(options::DIR).hidden(true).multiple(true)) .arg(Arg::with_name(options::DIR).hidden(true).multiple(true))

View file

@ -15,6 +15,7 @@ edition = "2018"
path = "src/du.rs" path = "src/du.rs"
[dependencies] [dependencies]
clap = "2.33"
chrono = "0.4" chrono = "0.4"
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore = { version=">=0.0.8", package="uucore", path="../../uucore" }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }

View file

@ -12,10 +12,11 @@ extern crate uucore;
use chrono::prelude::DateTime; use chrono::prelude::DateTime;
use chrono::Local; use chrono::Local;
use clap::{App, Arg};
use std::collections::HashSet; use std::collections::HashSet;
use std::env; use std::env;
use std::fs; use std::fs;
use std::io::{stderr, Result, Write}; use std::io::{stderr, ErrorKind, Result, Write};
use std::iter; use std::iter;
#[cfg(not(windows))] #[cfg(not(windows))]
use std::os::unix::fs::MetadataExt; use std::os::unix::fs::MetadataExt;
@ -25,6 +26,7 @@ use std::os::windows::fs::MetadataExt;
use std::os::windows::io::AsRawHandle; use std::os::windows::io::AsRawHandle;
use std::path::PathBuf; use std::path::PathBuf;
use std::time::{Duration, UNIX_EPOCH}; use std::time::{Duration, UNIX_EPOCH};
use uucore::InvalidEncodingHandling;
#[cfg(windows)] #[cfg(windows)]
use winapi::shared::minwindef::{DWORD, LPVOID}; use winapi::shared::minwindef::{DWORD, LPVOID};
#[cfg(windows)] #[cfg(windows)]
@ -36,6 +38,27 @@ use winapi::um::winbase::GetFileInformationByHandleEx;
#[cfg(windows)] #[cfg(windows)]
use winapi::um::winnt::{FILE_ID_128, ULONGLONG}; use winapi::um::winnt::{FILE_ID_128, ULONGLONG};
mod options {
pub const NULL: &str = "0";
pub const ALL: &str = "all";
pub const APPARENT_SIZE: &str = "apparent-size";
pub const BLOCK_SIZE: &str = "B";
pub const BYTES: &str = "b";
pub const TOTAL: &str = "c";
pub const MAX_DEPTH: &str = "d";
pub const HUMAN_READABLE: &str = "h";
pub const BLOCK_SIZE_1K: &str = "k";
pub const COUNT_LINKS: &str = "l";
pub const BLOCK_SIZE_1M: &str = "m";
pub const SEPARATE_DIRS: &str = "S";
pub const SUMMARIZE: &str = "s";
pub const SI: &str = "si";
pub const TIME: &str = "time";
pub const TIME_STYLE: &str = "time-style";
pub const FILE: &str = "FILE";
}
const VERSION: &str = env!("CARGO_PKG_VERSION");
const NAME: &str = "du"; const NAME: &str = "du";
const SUMMARY: &str = "estimate file space usage"; const SUMMARY: &str = "estimate file space usage";
const LONG_HELP: &str = " const LONG_HELP: &str = "
@ -219,14 +242,14 @@ fn unit_string_to_number(s: &str) -> Option<u64> {
Some(number * multiple.pow(unit)) Some(number * multiple.pow(unit))
} }
fn translate_to_pure_number(s: &Option<String>) -> Option<u64> { fn translate_to_pure_number(s: &Option<&str>) -> Option<u64> {
match *s { match *s {
Some(ref s) => unit_string_to_number(s), Some(ref s) => unit_string_to_number(s),
None => None, None => None,
} }
} }
fn read_block_size(s: Option<String>) -> u64 { fn read_block_size(s: Option<&str>) -> u64 {
match translate_to_pure_number(&s) { match translate_to_pure_number(&s) {
Some(v) => v, Some(v) => v,
None => { None => {
@ -235,7 +258,8 @@ fn read_block_size(s: Option<String>) -> u64 {
}; };
for env_var in &["DU_BLOCK_SIZE", "BLOCK_SIZE", "BLOCKSIZE"] { for env_var in &["DU_BLOCK_SIZE", "BLOCK_SIZE", "BLOCKSIZE"] {
if let Some(quantity) = translate_to_pure_number(&env::var(env_var).ok()) { let env_size = env::var(env_var).ok();
if let Some(quantity) = translate_to_pure_number(&env_size.as_deref()) {
return quantity; return quantity;
} }
} }
@ -296,7 +320,21 @@ fn du(
} }
} }
} }
Err(error) => show_error!("{}", error), Err(error) => match error.kind() {
ErrorKind::PermissionDenied => {
let description = format!(
"cannot access '{}'",
entry
.path()
.as_os_str()
.to_str()
.unwrap_or("<Un-printable path>")
);
let error_message = "Permission denied";
show_error_custom_description!(description, "{}", error_message)
}
_ => show_error!("{}", error),
},
}, },
Err(error) => show_error!("{}", error), Err(error) => show_error!("{}", error),
} }
@ -322,7 +360,7 @@ fn convert_size_human(size: u64, multiplier: u64, _block_size: u64) -> String {
} }
} }
if size == 0 { if size == 0 {
return format!("0"); return "0".to_string();
} }
format!("{}B", size) format!("{}B", size)
} }
@ -346,124 +384,189 @@ fn convert_size_other(size: u64, _multiplier: u64, block_size: u64) -> String {
format!("{}", ((size as f64) / (block_size as f64)).ceil()) format!("{}", ((size as f64) / (block_size as f64)).ceil())
} }
fn get_usage() -> String {
format!(
"{0} [OPTION]... [FILE]...
{0} [OPTION]... --files0-from=F",
executable!()
)
}
#[allow(clippy::cognitive_complexity)] #[allow(clippy::cognitive_complexity)]
pub fn uumain(args: impl uucore::Args) -> i32 { pub fn uumain(args: impl uucore::Args) -> i32 {
let args = args.collect_str(); let args = args
.collect_str(InvalidEncodingHandling::Ignore)
.accept_any();
let syntax = format!( let usage = get_usage();
"[OPTION]... [FILE]...
{0} [OPTION]... --files0-from=F",
NAME
);
let matches = app!(&syntax, SUMMARY, LONG_HELP)
// In task
.optflag(
"a",
"all",
" write counts for all files, not just directories",
)
// In main
.optflag(
"",
"apparent-size",
"print apparent sizes, rather than disk usage
although the apparent size is usually smaller, it may be larger due to holes
in ('sparse') files, internal fragmentation, indirect blocks, and the like",
)
// In main
.optopt(
"B",
"block-size",
"scale sizes by SIZE before printing them.
E.g., '-BM' prints sizes in units of 1,048,576 bytes. See SIZE format below.",
"SIZE",
)
// In main
.optflag(
"b",
"bytes",
"equivalent to '--apparent-size --block-size=1'",
)
// In main
.optflag("c", "total", "produce a grand total")
// In task
// opts.optflag("D", "dereference-args", "dereference only symlinks that are listed
// on the command line"),
// In main
// opts.optopt("", "files0-from", "summarize disk usage of the NUL-terminated file
// names specified in file F;
// If F is - then read names from standard input", "F"),
// // In task
// opts.optflag("H", "", "equivalent to --dereference-args (-D)"),
// In main
.optflag(
"h",
"human-readable",
"print sizes in human readable format (e.g., 1K 234M 2G)",
)
// In main
.optflag("", "si", "like -h, but use powers of 1000 not 1024")
// In main
.optflag("k", "", "like --block-size=1K")
// In task
.optflag("l", "count-links", "count sizes many times if hard linked")
// // In main
.optflag("m", "", "like --block-size=1M")
// // In task
// opts.optflag("L", "dereference", "dereference all symbolic links"),
// // In task
// opts.optflag("P", "no-dereference", "don't follow any symbolic links (this is the default)"),
// // In main
.optflag(
"0",
"null",
"end each output line with 0 byte rather than newline",
)
// In main
.optflag(
"S",
"separate-dirs",
"do not include size of subdirectories",
)
// In main
.optflag("s", "summarize", "display only a total for each argument")
// // In task
// opts.optflag("x", "one-file-system", "skip directories on different file systems"),
// // In task
// opts.optopt("X", "exclude-from", "exclude files that match any pattern in FILE", "FILE"),
// // In task
// opts.optopt("", "exclude", "exclude files that match PATTERN", "PATTERN"),
// In main
.optopt(
"d",
"max-depth",
"print the total for a directory (or file, with --all)
only if it is N or fewer levels below the command
line argument; --max-depth=0 is the same as --summarize",
"N",
)
// In main
.optflagopt(
"",
"time",
"show time of the last modification of any file in the
directory, or any of its subdirectories. If WORD is given, show time as WORD instead
of modification time: atime, access, use, ctime or status",
"WORD",
)
// In main
.optopt(
"",
"time-style",
"show times using style STYLE:
full-iso, long-iso, iso, +FORMAT FORMAT is interpreted like 'date'",
"STYLE",
)
.parse(args);
let summarize = matches.opt_present("summarize"); let matches = App::new(executable!())
.version(VERSION)
.about(SUMMARY)
.usage(&usage[..])
.after_help(LONG_HELP)
.arg(
Arg::with_name(options::ALL)
.short("a")
.long(options::ALL)
.help("write counts for all files, not just directories"),
)
.arg(
Arg::with_name(options::APPARENT_SIZE)
.long(options::APPARENT_SIZE)
.help(
"print apparent sizes, rather than disk usage \
although the apparent size is usually smaller, it may be larger due to holes \
in ('sparse') files, internal fragmentation, indirect blocks, and the like"
)
)
.arg(
Arg::with_name(options::BLOCK_SIZE)
.short("B")
.long("block-size")
.value_name("SIZE")
.help(
"scale sizes by SIZE before printing them. \
E.g., '-BM' prints sizes in units of 1,048,576 bytes. See SIZE format below."
)
)
.arg(
Arg::with_name(options::BYTES)
.short("b")
.long("bytes")
.help("equivalent to '--apparent-size --block-size=1'")
)
.arg(
Arg::with_name(options::TOTAL)
.long("total")
.short("c")
.help("produce a grand total")
)
.arg(
Arg::with_name(options::MAX_DEPTH)
.short("d")
.long("max-depth")
.value_name("N")
.help(
"print the total for a directory (or file, with --all) \
only if it is N or fewer levels below the command \
line argument; --max-depth=0 is the same as --summarize"
)
)
.arg(
Arg::with_name(options::HUMAN_READABLE)
.long("human-readable")
.short("h")
.help("print sizes in human readable format (e.g., 1K 234M 2G)")
)
.arg(
Arg::with_name("inodes")
.long("inodes")
.help(
"list inode usage information instead of block usage like --block-size=1K"
)
)
.arg(
Arg::with_name(options::BLOCK_SIZE_1K)
.short("k")
.help("like --block-size=1K")
)
.arg(
Arg::with_name(options::COUNT_LINKS)
.short("l")
.long("count-links")
.help("count sizes many times if hard linked")
)
// .arg(
// Arg::with_name("dereference")
// .short("L")
// .long("dereference")
// .help("dereference all symbolic links")
// )
// .arg(
// Arg::with_name("no-dereference")
// .short("P")
// .long("no-dereference")
// .help("don't follow any symbolic links (this is the default)")
// )
.arg(
Arg::with_name(options::BLOCK_SIZE_1M)
.short("m")
.help("like --block-size=1M")
)
.arg(
Arg::with_name(options::NULL)
.short("0")
.long("null")
.help("end each output line with 0 byte rather than newline")
)
.arg(
Arg::with_name(options::SEPARATE_DIRS)
.short("S")
.long("separate-dirs")
.help("do not include size of subdirectories")
)
.arg(
Arg::with_name(options::SUMMARIZE)
.short("s")
.long("summarize")
.help("display only a total for each argument")
)
.arg(
Arg::with_name(options::SI)
.long(options::SI)
.help("like -h, but use powers of 1000 not 1024")
)
// .arg(
// Arg::with_name("one-file-system")
// .short("x")
// .long("one-file-system")
// .help("skip directories on different file systems")
// )
// .arg(
// Arg::with_name("")
// .short("x")
// .long("exclude-from")
// .value_name("FILE")
// .help("exclude files that match any pattern in FILE")
// )
// .arg(
// Arg::with_name("exclude")
// .long("exclude")
// .value_name("PATTERN")
// .help("exclude files that match PATTERN")
// )
.arg(
Arg::with_name(options::TIME)
.long(options::TIME)
.value_name("WORD")
.require_equals(true)
.min_values(0)
.help(
"show time of the last modification of any file in the \
directory, or any of its subdirectories. If WORD is given, show time as WORD instead \
of modification time: atime, access, use, ctime or status"
)
)
.arg(
Arg::with_name(options::TIME_STYLE)
.long(options::TIME_STYLE)
.value_name("STYLE")
.help(
"show times using style STYLE: \
full-iso, long-iso, iso, +FORMAT FORMAT is interpreted like 'date'"
)
)
.arg(
Arg::with_name(options::FILE)
.hidden(true)
.multiple(true)
)
.get_matches_from(args);
let max_depth_str = matches.opt_str("max-depth"); let summarize = matches.is_present(options::SUMMARIZE);
let max_depth_str = matches.value_of(options::MAX_DEPTH);
let max_depth = max_depth_str.as_ref().and_then(|s| s.parse::<usize>().ok()); let max_depth = max_depth_str.as_ref().and_then(|s| s.parse::<usize>().ok());
match (max_depth_str, max_depth) { match (max_depth_str, max_depth) {
(Some(ref s), _) if summarize => { (Some(ref s), _) if summarize => {
@ -478,34 +581,35 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
} }
let options = Options { let options = Options {
all: matches.opt_present("all"), all: matches.is_present(options::ALL),
program_name: NAME.to_owned(), program_name: NAME.to_owned(),
max_depth, max_depth,
total: matches.opt_present("total"), total: matches.is_present(options::TOTAL),
separate_dirs: matches.opt_present("S"), separate_dirs: matches.is_present(options::SEPARATE_DIRS),
}; };
let strs = if matches.free.is_empty() { let strs = match matches.value_of(options::FILE) {
vec!["./".to_owned()] Some(_) => matches.values_of(options::FILE).unwrap().collect(),
} else { None => {
matches.free.clone() vec!["./"] // TODO: gnu `du` doesn't use trailing "/" here
}
}; };
let block_size = read_block_size(matches.opt_str("block-size")); let block_size = read_block_size(matches.value_of(options::BLOCK_SIZE));
let multiplier: u64 = if matches.opt_present("si") { let multiplier: u64 = if matches.is_present(options::SI) {
1000 1000
} else { } else {
1024 1024
}; };
let convert_size_fn = { let convert_size_fn = {
if matches.opt_present("human-readable") || matches.opt_present("si") { if matches.is_present(options::HUMAN_READABLE) || matches.is_present(options::SI) {
convert_size_human convert_size_human
} else if matches.opt_present("b") { } else if matches.is_present(options::BYTES) {
convert_size_b convert_size_b
} else if matches.opt_present("k") { } else if matches.is_present(options::BLOCK_SIZE_1K) {
convert_size_k convert_size_k
} else if matches.opt_present("m") { } else if matches.is_present(options::BLOCK_SIZE_1M) {
convert_size_m convert_size_m
} else { } else {
convert_size_other convert_size_other
@ -513,8 +617,8 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
}; };
let convert_size = |size| convert_size_fn(size, multiplier, block_size); let convert_size = |size| convert_size_fn(size, multiplier, block_size);
let time_format_str = match matches.opt_str("time-style") { let time_format_str = match matches.value_of("time-style") {
Some(s) => match &s[..] { Some(s) => match s {
"full-iso" => "%Y-%m-%d %H:%M:%S.%f %z", "full-iso" => "%Y-%m-%d %H:%M:%S.%f %z",
"long-iso" => "%Y-%m-%d %H:%M", "long-iso" => "%Y-%m-%d %H:%M",
"iso" => "%Y-%m-%d", "iso" => "%Y-%m-%d",
@ -535,7 +639,11 @@ Try '{} --help' for more information.",
None => "%Y-%m-%d %H:%M", None => "%Y-%m-%d %H:%M",
}; };
let line_separator = if matches.opt_present("0") { "\0" } else { "\n" }; let line_separator = if matches.is_present(options::NULL) {
"\0"
} else {
"\n"
};
let mut grand_total = 0; let mut grand_total = 0;
for path_str in strs { for path_str in strs {
@ -548,18 +656,20 @@ Try '{} --help' for more information.",
let (_, len) = iter.size_hint(); let (_, len) = iter.size_hint();
let len = len.unwrap(); let len = len.unwrap();
for (index, stat) in iter.enumerate() { for (index, stat) in iter.enumerate() {
let size = if matches.opt_present("apparent-size") || matches.opt_present("b") { let size = if matches.is_present(options::APPARENT_SIZE)
|| matches.is_present(options::BYTES)
{
stat.size stat.size
} else { } else {
// C's stat is such that each block is assume to be 512 bytes // C's stat is such that each block is assume to be 512 bytes
// See: http://linux.die.net/man/2/stat // See: http://linux.die.net/man/2/stat
stat.blocks * 512 stat.blocks * 512
}; };
if matches.opt_present("time") { if matches.is_present(options::TIME) {
let tm = { let tm = {
let secs = { let secs = {
match matches.opt_str("time") { match matches.value_of(options::TIME) {
Some(s) => match &s[..] { Some(s) => match s {
"accessed" => stat.accessed, "accessed" => stat.accessed,
"created" => stat.created, "created" => stat.created,
"modified" => stat.modified, "modified" => stat.modified,
@ -632,8 +742,8 @@ mod test_du {
(Some("900KB".to_string()), Some(900 * 1000)), (Some("900KB".to_string()), Some(900 * 1000)),
(Some("BAD_STRING".to_string()), None), (Some("BAD_STRING".to_string()), None),
]; ];
for it in test_data.into_iter() { for it in test_data.iter() {
assert_eq!(translate_to_pure_number(&it.0), it.1); assert_eq!(translate_to_pure_number(&it.0.as_deref()), it.1);
} }
} }
@ -644,8 +754,8 @@ mod test_du {
(None, 1024), (None, 1024),
(Some("BAD_STRING".to_string()), 1024), (Some("BAD_STRING".to_string()), 1024),
]; ];
for it in test_data.into_iter() { for it in test_data.iter() {
assert_eq!(read_block_size(it.0.clone()), it.1); assert_eq!(read_block_size(it.0.as_deref()), it.1);
} }
} }
} }

View file

@ -13,6 +13,7 @@ use clap::{crate_version, App, Arg};
use std::io::{self, Write}; use std::io::{self, Write};
use std::iter::Peekable; use std::iter::Peekable;
use std::str::Chars; use std::str::Chars;
use uucore::InvalidEncodingHandling;
const NAME: &str = "echo"; const NAME: &str = "echo";
const SUMMARY: &str = "display a line of text"; const SUMMARY: &str = "display a line of text";
@ -113,6 +114,9 @@ fn print_escaped(input: &str, mut output: impl Write) -> io::Result<bool> {
} }
pub fn uumain(args: impl uucore::Args) -> i32 { pub fn uumain(args: impl uucore::Args) -> i32 {
let args = args
.collect_str(InvalidEncodingHandling::ConvertLossy)
.accept_any();
let matches = App::new(executable!()) let matches = App::new(executable!())
.name(NAME) .name(NAME)
// TrailingVarArg specifies the final positional argument is a VarArg // TrailingVarArg specifies the final positional argument is a VarArg

View file

@ -8,6 +8,8 @@
#[macro_use] #[macro_use]
extern crate uucore; extern crate uucore;
use uucore::InvalidEncodingHandling;
mod syntax_tree; mod syntax_tree;
mod tokens; mod tokens;
@ -15,7 +17,9 @@ static NAME: &str = "expr";
static VERSION: &str = env!("CARGO_PKG_VERSION"); static VERSION: &str = env!("CARGO_PKG_VERSION");
pub fn uumain(args: impl uucore::Args) -> i32 { pub fn uumain(args: impl uucore::Args) -> i32 {
let args = args.collect_str(); let args = args
.collect_str(InvalidEncodingHandling::ConvertLossy)
.accept_any();
// For expr utility we do not want getopts. // For expr utility we do not want getopts.
// The following usage should work without escaping hyphens: `expr -15 = 1 + 2 \* \( 3 - -4 \)` // The following usage should work without escaping hyphens: `expr -15 = 1 + 2 \* \( 3 - -4 \)`
@ -51,7 +55,7 @@ fn print_expr_error(expr_error: &str) -> ! {
crash!(2, "{}", expr_error) crash!(2, "{}", expr_error)
} }
fn evaluate_ast(maybe_ast: Result<Box<syntax_tree::ASTNode>, String>) -> Result<String, String> { fn evaluate_ast(maybe_ast: Result<Box<syntax_tree::AstNode>, String>) -> Result<String, String> {
if maybe_ast.is_err() { if maybe_ast.is_err() {
Err(maybe_ast.err().unwrap()) Err(maybe_ast.err().unwrap())
} else { } else {

View file

@ -17,10 +17,10 @@ use onig::{Regex, RegexOptions, Syntax};
use crate::tokens::Token; use crate::tokens::Token;
type TokenStack = Vec<(usize, Token)>; type TokenStack = Vec<(usize, Token)>;
pub type OperandsList = Vec<Box<ASTNode>>; pub type OperandsList = Vec<Box<AstNode>>;
#[derive(Debug)] #[derive(Debug)]
pub enum ASTNode { pub enum AstNode {
Leaf { Leaf {
token_idx: usize, token_idx: usize,
value: String, value: String,
@ -31,7 +31,7 @@ pub enum ASTNode {
operands: OperandsList, operands: OperandsList,
}, },
} }
impl ASTNode { impl AstNode {
fn debug_dump(&self) { fn debug_dump(&self) {
self.debug_dump_impl(1); self.debug_dump_impl(1);
} }
@ -40,7 +40,7 @@ impl ASTNode {
print!("\t",); print!("\t",);
} }
match *self { match *self {
ASTNode::Leaf { AstNode::Leaf {
ref token_idx, ref token_idx,
ref value, ref value,
} => println!( } => println!(
@ -49,7 +49,7 @@ impl ASTNode {
token_idx, token_idx,
self.evaluate() self.evaluate()
), ),
ASTNode::Node { AstNode::Node {
ref token_idx, ref token_idx,
ref op_type, ref op_type,
ref operands, ref operands,
@ -67,23 +67,23 @@ impl ASTNode {
} }
} }
fn new_node(token_idx: usize, op_type: &str, operands: OperandsList) -> Box<ASTNode> { fn new_node(token_idx: usize, op_type: &str, operands: OperandsList) -> Box<AstNode> {
Box::new(ASTNode::Node { Box::new(AstNode::Node {
token_idx, token_idx,
op_type: op_type.into(), op_type: op_type.into(),
operands, operands,
}) })
} }
fn new_leaf(token_idx: usize, value: &str) -> Box<ASTNode> { fn new_leaf(token_idx: usize, value: &str) -> Box<AstNode> {
Box::new(ASTNode::Leaf { Box::new(AstNode::Leaf {
token_idx, token_idx,
value: value.into(), value: value.into(),
}) })
} }
pub fn evaluate(&self) -> Result<String, String> { pub fn evaluate(&self) -> Result<String, String> {
match *self { match *self {
ASTNode::Leaf { ref value, .. } => Ok(value.clone()), AstNode::Leaf { ref value, .. } => Ok(value.clone()),
ASTNode::Node { ref op_type, .. } => match self.operand_values() { AstNode::Node { ref op_type, .. } => match self.operand_values() {
Err(reason) => Err(reason), Err(reason) => Err(reason),
Ok(operand_values) => match op_type.as_ref() { Ok(operand_values) => match op_type.as_ref() {
"+" => infix_operator_two_ints( "+" => infix_operator_two_ints(
@ -153,7 +153,7 @@ impl ASTNode {
":" | "match" => operator_match(&operand_values), ":" | "match" => operator_match(&operand_values),
"length" => Ok(prefix_operator_length(&operand_values)), "length" => Ok(prefix_operator_length(&operand_values)),
"index" => Ok(prefix_operator_index(&operand_values)), "index" => Ok(prefix_operator_index(&operand_values)),
"substr" => prefix_operator_substr(&operand_values), "substr" => Ok(prefix_operator_substr(&operand_values)),
_ => Err(format!("operation not implemented: {}", op_type)), _ => Err(format!("operation not implemented: {}", op_type)),
}, },
@ -161,7 +161,7 @@ impl ASTNode {
} }
} }
pub fn operand_values(&self) -> Result<Vec<String>, String> { pub fn operand_values(&self) -> Result<Vec<String>, String> {
if let ASTNode::Node { ref operands, .. } = *self { if let AstNode::Node { ref operands, .. } = *self {
let mut out = Vec::with_capacity(operands.len()); let mut out = Vec::with_capacity(operands.len());
for operand in operands { for operand in operands {
match operand.evaluate() { match operand.evaluate() {
@ -178,7 +178,7 @@ impl ASTNode {
pub fn tokens_to_ast( pub fn tokens_to_ast(
maybe_tokens: Result<Vec<(usize, Token)>, String>, maybe_tokens: Result<Vec<(usize, Token)>, String>,
) -> Result<Box<ASTNode>, String> { ) -> Result<Box<AstNode>, String> {
if maybe_tokens.is_err() { if maybe_tokens.is_err() {
Err(maybe_tokens.err().unwrap()) Err(maybe_tokens.err().unwrap())
} else { } else {
@ -212,7 +212,7 @@ pub fn tokens_to_ast(
} }
} }
fn maybe_dump_ast(result: &Result<Box<ASTNode>, String>) { fn maybe_dump_ast(result: &Result<Box<AstNode>, String>) {
use std::env; use std::env;
if let Ok(debug_var) = env::var("EXPR_DEBUG_AST") { if let Ok(debug_var) = env::var("EXPR_DEBUG_AST") {
if debug_var == "1" { if debug_var == "1" {
@ -238,11 +238,11 @@ fn maybe_dump_rpn(rpn: &TokenStack) {
} }
} }
fn ast_from_rpn(rpn: &mut TokenStack) -> Result<Box<ASTNode>, String> { fn ast_from_rpn(rpn: &mut TokenStack) -> Result<Box<AstNode>, String> {
match rpn.pop() { match rpn.pop() {
None => Err("syntax error (premature end of expression)".to_owned()), None => Err("syntax error (premature end of expression)".to_owned()),
Some((token_idx, Token::Value { value })) => Ok(ASTNode::new_leaf(token_idx, &value)), Some((token_idx, Token::Value { value })) => Ok(AstNode::new_leaf(token_idx, &value)),
Some((token_idx, Token::InfixOp { value, .. })) => { Some((token_idx, Token::InfixOp { value, .. })) => {
maybe_ast_node(token_idx, &value, 2, rpn) maybe_ast_node(token_idx, &value, 2, rpn)
@ -262,7 +262,7 @@ fn maybe_ast_node(
op_type: &str, op_type: &str,
arity: usize, arity: usize,
rpn: &mut TokenStack, rpn: &mut TokenStack,
) -> Result<Box<ASTNode>, String> { ) -> Result<Box<AstNode>, String> {
let mut operands = Vec::with_capacity(arity); let mut operands = Vec::with_capacity(arity);
for _ in 0..arity { for _ in 0..arity {
match ast_from_rpn(rpn) { match ast_from_rpn(rpn) {
@ -271,7 +271,7 @@ fn maybe_ast_node(
} }
} }
operands.reverse(); operands.reverse();
Ok(ASTNode::new_node(token_idx, op_type, operands)) Ok(AstNode::new_node(token_idx, op_type, operands))
} }
fn move_rest_of_ops_to_out( fn move_rest_of_ops_to_out(
@ -522,35 +522,23 @@ fn prefix_operator_index(values: &[String]) -> String {
"0".to_string() "0".to_string()
} }
fn prefix_operator_substr(values: &[String]) -> Result<String, String> { fn prefix_operator_substr(values: &[String]) -> String {
assert!(values.len() == 3); assert!(values.len() == 3);
let subj = &values[0]; let subj = &values[0];
let mut idx = match values[1].parse::<i64>() { let idx = match values[1]
Ok(i) => i, .parse::<usize>()
Err(_) => return Err("expected integer as POS arg to 'substr'".to_string()), .ok()
.and_then(|v| v.checked_sub(1))
{
Some(i) => i,
None => return String::new(),
}; };
let mut len = match values[2].parse::<i64>() { let len = match values[2].parse::<usize>() {
Ok(i) => i, Ok(i) => i,
Err(_) => return Err("expected integer as LENGTH arg to 'substr'".to_string()), Err(_) => return String::new(),
}; };
if idx <= 0 || len <= 0 { subj.chars().skip(idx).take(len).collect()
return Ok("".to_string());
}
let mut out_str = String::new();
for ch in subj.chars() {
idx -= 1;
if idx <= 0 {
if len <= 0 {
break;
}
len -= 1;
out_str.push(ch);
}
}
Ok(out_str)
} }
fn bool_as_int(b: bool) -> i64 { fn bool_as_int(b: bool) -> i64 {

View file

@ -63,12 +63,7 @@ impl Token {
} }
} }
fn is_a_close_paren(&self) -> bool { fn is_a_close_paren(&self) -> bool {
#[allow(clippy::match_like_matches_macro)] matches!(*self, Token::ParClose)
// `matches!(...)` macro not stabilized until rust v1.42
match *self {
Token::ParClose => true,
_ => false,
}
} }
} }

View file

@ -0,0 +1,116 @@
# Benchmarking `factor`
The benchmarks for `factor` are located under `tests/benches/factor`
and can be invoked with `cargo bench` in that directory.
They are located outside the `uu_factor` crate, as they do not comply
with the project's minimum supported Rust version, *i.e.* may require
a newer version of `rustc`.
## Microbenchmarking deterministic functions
We currently use [`criterion`] to benchmark deterministic functions,
such as `gcd` and `table::factor`.
However, µbenchmarks are by nature unstable: not only are they specific to
the hardware, operating system version, etc., but they are noisy and affected
by other tasks on the system (browser, compile jobs, etc.), which can cause
`criterion` to report spurious performance improvements and regressions.
This can be mitigated by getting as close to [idealised conditions][lemire]
as possible:
- minimize the amount of computation and I/O running concurrently to the
benchmark, *i.e.* close your browser and IM clients, don't compile at the
same time, etc. ;
- ensure the CPU's [frequency stays constant] during the benchmark ;
- [isolate a **physical** core], set it to `nohz_full`, and pin the benchmark
to it, so it won't be preempted in the middle of a measurement ;
- disable ASLR by running `setarch -R cargo bench`, so we can compare results
across multiple executions.
[`criterion`]: https://bheisler.github.io/criterion.rs/book/index.html
[lemire]: https://lemire.me/blog/2018/01/16/microbenchmarking-calls-for-idealized-conditions/
[isolate a **physical** core]: https://pyperf.readthedocs.io/en/latest/system.html#isolate-cpus-on-linux
[frequency stays constant]: XXXTODO
### Guidance for designing µbenchmarks
*Note:* this guidance is specific to `factor` and takes its application domain
into account; do not expect it to generalise to other projects. It is based
on Daniel Lemire's [*Microbenchmarking calls for idealized conditions*][lemire],
which I recommend reading if you want to add benchmarks to `factor`.
1. Select a small, self-contained, deterministic component
`gcd` and `table::factor` are good example of such:
- no I/O or access to external data structures ;
- no call into other components ;
- behaviour is deterministic: no RNG, no concurrency, ... ;
- the test's body is *fast* (~100ns for `gcd`, ~10µs for `factor::table`),
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`)
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
We are interested in overall performance, rather than specific edge-cases;
use **reproducibly-randomised inputs**, sampling from either all possible
input values or some subset of interest.
4. Use [`criterion`], `criterion::black_box`, ...
`criterion` isn't perfect, but it is also much better than ad-hoc
solutions in each benchmark.
## Wishlist
### Configurable statistical estimators
`criterion` always uses the arithmetic average as estimator; in µbenchmarks,
where the code under test is fully deterministic and the measurements are
subject to additive, positive noise, [the minimum is more appropriate][lemire].
### CI & reproducible performance testing
Measuring performance on real hardware is important, as it relates directly
to what users of `factor` experience; however, such measurements are subject
to the constraints of the real-world, and aren't perfectly reproducible.
Moreover, the mitigations for it (described above) aren't achievable in
virtualized, multi-tenant environments such as CI.
Instead, we could run the µbenchmarks in a simulated CPU with [`cachegrind`],
measure execution “time” in that model (in CI), and use it to detect and report
performance improvements and regressions.
[`iai`] is an implementation of this idea for Rust.
[`cachegrind`]: https://www.valgrind.org/docs/manual/cg-manual.html
[`iai`]: https://bheisler.github.io/criterion.rs/book/iai/iai.html
### Comparing randomised implementations across multiple inputs
`factor` is a challenging target for system benchmarks as it combines two
characteristics:
1. integer factoring algorithms are randomised, with large variance in
execution time ;
2. 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
compare 2 implementations (2 successive versions, or `uutils` and GNU) using
a scatter plot, where each axis corresponds to the perf. of one implementation.
Similarly, without (2) we could plot numbers on the X axis and their factoring
time on the Y axis, using multiple lines for various quantiles. The large
differences in factoring times for successive numbers, mean that such a plot
would be unreadable.

View file

@ -14,23 +14,19 @@ edition = "2018"
[build-dependencies] [build-dependencies]
num-traits = "0.2.13" # used in src/numerics.rs, which is included by build.rs num-traits = "0.2.13" # used in src/numerics.rs, which is included by build.rs
[dependencies] [dependencies]
coz = { version = "0.1.3", optional = true }
num-traits = "0.2.13" # Needs at least version 0.2.13 for "OverflowingAdd" num-traits = "0.2.13" # Needs at least version 0.2.13 for "OverflowingAdd"
rand = { version="0.7", features=["small_rng"] } rand = { version = "0.7", features = ["small_rng"] }
smallvec = { version="0.6.14, < 1.0" } smallvec = { version = "0.6.14, < 1.0" }
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore = { version = ">=0.0.8", package = "uucore", path = "../../uucore" }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } uucore_procs = { version = ">=0.0.5", package = "uucore_procs", path = "../../uucore_procs" }
clap = "2.33"
[dev-dependencies] [dev-dependencies]
criterion = "0.3"
paste = "0.1.18" paste = "0.1.18"
quickcheck = "0.9.2" quickcheck = "0.9.2"
rand_chacha = "0.2.2"
[[bench]]
name = "gcd"
harness = false
[[bin]] [[bin]]
name = "factor" name = "factor"

View file

@ -13,17 +13,21 @@ use std::error::Error;
use std::io::{self, stdin, stdout, BufRead, Write}; use std::io::{self, stdin, stdout, BufRead, Write};
mod factor; mod factor;
pub(crate) use factor::*; use clap::{App, Arg};
pub use factor::*;
mod miller_rabin; mod miller_rabin;
pub mod numeric; pub mod numeric;
mod rho; mod rho;
mod table; pub mod table;
static SYNTAX: &str = "[OPTION] [NUMBER]..."; static VERSION: &str = env!("CARGO_PKG_VERSION");
static SUMMARY: &str = "Print the prime factors of the given number(s). static SUMMARY: &str = "Print the prime factors of the given NUMBER(s).
If none are specified, read from standard input."; If none are specified, read from standard input.";
static LONG_HELP: &str = "";
mod options {
pub static NUMBER: &str = "NUMBER";
}
fn print_factors_str(num_str: &str, w: &mut impl io::Write) -> Result<(), Box<dyn Error>> { fn print_factors_str(num_str: &str, w: &mut impl io::Write) -> Result<(), Box<dyn Error>> {
num_str num_str
@ -33,11 +37,21 @@ fn print_factors_str(num_str: &str, w: &mut impl io::Write) -> Result<(), Box<dy
} }
pub fn uumain(args: impl uucore::Args) -> i32 { pub fn uumain(args: impl uucore::Args) -> i32 {
let matches = app!(SYNTAX, SUMMARY, LONG_HELP).parse(args.collect_str()); let matches = App::new(executable!())
.version(VERSION)
.about(SUMMARY)
.arg(Arg::with_name(options::NUMBER).multiple(true))
.get_matches_from(args);
let stdout = stdout(); let stdout = stdout();
let mut w = io::BufWriter::new(stdout.lock()); let mut w = io::BufWriter::new(stdout.lock());
if matches.free.is_empty() { if let Some(values) = matches.values_of(options::NUMBER) {
for number in values {
if let Err(e) = print_factors_str(number, &mut w) {
show_warning!("{}: {}", number, e);
}
}
} else {
let stdin = stdin(); let stdin = stdin();
for line in stdin.lock().lines() { for line in stdin.lock().lines() {
@ -47,12 +61,6 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
} }
} }
} }
} else {
for number in &matches.free {
if let Err(e) = print_factors_str(number, &mut w) {
show_warning!("{}: {}", number, e);
}
}
} }
if let Err(e) = w.flush() { if let Err(e) = w.flush() {

View file

@ -125,6 +125,8 @@ fn _factor<A: Arithmetic + miller_rabin::Basis>(num: u64, f: Factors) -> Factors
let n = A::new(num); let n = A::new(num);
let divisor = match miller_rabin::test::<A>(n) { let divisor = match miller_rabin::test::<A>(n) {
Prime => { Prime => {
#[cfg(feature = "coz")]
coz::progress!("factor found");
let mut r = f; let mut r = f;
r.push(num); r.push(num);
return r; return r;
@ -139,6 +141,8 @@ fn _factor<A: Arithmetic + miller_rabin::Basis>(num: u64, f: Factors) -> Factors
} }
pub fn factor(mut n: u64) -> Factors { pub fn factor(mut n: u64) -> Factors {
#[cfg(feature = "coz")]
coz::begin!("factorization");
let mut factors = Factors::one(); let mut factors = Factors::one();
if n < 2 { if n < 2 {
@ -152,16 +156,24 @@ pub fn factor(mut n: u64) -> Factors {
} }
if n == 1 { if n == 1 {
#[cfg(feature = "coz")]
coz::end!("factorization");
return factors; return factors;
} }
let (factors, n) = table::factor(n, factors); table::factor(&mut n, &mut factors);
if n < (1 << 32) { #[allow(clippy::let_and_return)]
let r = if n < (1 << 32) {
_factor::<Montgomery<u32>>(n, factors) _factor::<Montgomery<u32>>(n, factors)
} else { } else {
_factor::<Montgomery<u64>>(n, factors) _factor::<Montgomery<u64>>(n, factors)
} };
#[cfg(feature = "coz")]
coz::end!("factorization");
r
} }
#[cfg(test)] #[cfg(test)]
@ -227,9 +239,13 @@ mod tests {
} }
#[cfg(test)] #[cfg(test)]
impl quickcheck::Arbitrary for Factors { use rand::{
fn arbitrary<G: quickcheck::Gen>(gen: &mut G) -> Self { distributions::{Distribution, Standard},
use rand::Rng; Rng,
};
#[cfg(test)]
impl Distribution<Factors> for Standard {
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Factors {
let mut f = Factors::one(); let mut f = Factors::one();
let mut g = 1u64; let mut g = 1u64;
let mut n = u64::MAX; let mut n = u64::MAX;
@ -240,7 +256,7 @@ impl quickcheck::Arbitrary for Factors {
// See Generating Random Factored Numbers, Easily, J. Cryptology (2003) // See Generating Random Factored Numbers, Easily, J. Cryptology (2003)
'attempt: loop { 'attempt: loop {
while n > 1 { while n > 1 {
n = gen.gen_range(1, n); n = rng.gen_range(1, n);
if miller_rabin::is_prime(n) { if miller_rabin::is_prime(n) {
if let Some(h) = g.checked_mul(n) { if let Some(h) = g.checked_mul(n) {
f.push(n); f.push(n);
@ -257,6 +273,13 @@ impl quickcheck::Arbitrary for Factors {
} }
} }
#[cfg(test)]
impl quickcheck::Arbitrary for Factors {
fn arbitrary<G: quickcheck::Gen>(g: &mut G) -> Self {
g.gen()
}
}
#[cfg(test)] #[cfg(test)]
impl std::ops::BitXor<Exponent> for Factors { impl std::ops::BitXor<Exponent> for Factors {
type Output = Self; type Output = Self;
@ -269,6 +292,6 @@ impl std::ops::BitXor<Exponent> for Factors {
} }
debug_assert_eq!(r.product(), self.product().pow(rhs.into())); debug_assert_eq!(r.product(), self.product().pow(rhs.into()));
return r; r
} }
} }

View file

@ -8,15 +8,13 @@
// spell-checker: ignore (ToDO) INVS // spell-checker: ignore (ToDO) INVS
use std::num::Wrapping;
use crate::Factors; use crate::Factors;
include!(concat!(env!("OUT_DIR"), "/prime_table.rs")); include!(concat!(env!("OUT_DIR"), "/prime_table.rs"));
pub(crate) fn factor(mut num: u64, mut factors: Factors) -> (Factors, u64) { pub fn factor(num: &mut u64, factors: &mut Factors) {
for &(prime, inv, ceil) in P_INVS_U64 { for &(prime, inv, ceil) in P_INVS_U64 {
if num == 1 { if *num == 1 {
break; break;
} }
@ -27,12 +25,14 @@ pub(crate) fn factor(mut num: u64, mut factors: Factors) -> (Factors, u64) {
// for a nice explanation. // for a nice explanation.
let mut k = 0; let mut k = 0;
loop { loop {
let Wrapping(x) = Wrapping(num) * Wrapping(inv); let x = num.wrapping_mul(inv);
// While prime divides num // While prime divides num
if x <= ceil { if x <= ceil {
num = x; *num = x;
k += 1; k += 1;
#[cfg(feature = "coz")]
coz::progress!("factor found");
} else { } else {
if k > 0 { if k > 0 {
factors.add(prime, k); factors.add(prime, k);
@ -41,6 +41,61 @@ pub(crate) fn factor(mut num: u64, mut factors: Factors) -> (Factors, u64) {
} }
} }
} }
}
(factors, num)
pub const CHUNK_SIZE: usize = 8;
pub fn factor_chunk(n_s: &mut [u64; CHUNK_SIZE], f_s: &mut [Factors; CHUNK_SIZE]) {
for &(prime, inv, ceil) in P_INVS_U64 {
if n_s[0] == 1 && n_s[1] == 1 && n_s[2] == 1 && n_s[3] == 1 {
break;
}
for (num, factors) in n_s.iter_mut().zip(f_s.iter_mut()) {
if *num == 1 {
continue;
}
let mut k = 0;
loop {
let x = num.wrapping_mul(inv);
// While prime divides num
if x <= ceil {
*num = x;
k += 1;
} else {
if k > 0 {
factors.add(prime, k);
}
break;
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Factors;
use quickcheck::quickcheck;
use rand::{rngs::SmallRng, Rng, SeedableRng};
quickcheck! {
fn chunk_vs_iter(seed: u64) -> () {
let mut rng = SmallRng::seed_from_u64(seed);
let mut n_c: [u64; CHUNK_SIZE] = rng.gen();
let mut f_c: [Factors; CHUNK_SIZE] = rng.gen();
let mut n_i = n_c.clone();
let mut f_i = f_c.clone();
for (n, f) in n_i.iter_mut().zip(f_i.iter_mut()) {
factor(n, f);
}
factor_chunk(&mut n_c, &mut f_c);
assert_eq!(n_i, n_c);
assert_eq!(f_i, f_c);
}
}
} }

View file

@ -296,7 +296,7 @@ fn find_kp_breakpoints<'a, T: Iterator<Item = &'a WordInfo<'a>>>(
(0, 0.0) (0, 0.0)
} else { } else {
compute_demerits( compute_demerits(
(args.opts.goal - tlen) as isize, args.opts.goal as isize - tlen as isize,
stretch, stretch,
w.word_nchars as isize, w.word_nchars as isize,
active.prev_rat, active.prev_rat,

View file

@ -264,12 +264,9 @@ impl<'a> ParagraphStream<'a> {
return false; return false;
} }
#[allow(clippy::match_like_matches_macro)] l_slice[..colon_posn]
// `matches!(...)` macro not stabilized until rust v1.42 .chars()
l_slice[..colon_posn].chars().all(|x| match x as usize { .all(|x| !matches!(x as usize, y if !(33..=126).contains(&y)))
y if y < 33 || y > 126 => false,
_ => true,
})
} }
} }
} }
@ -541,12 +538,7 @@ impl<'a> WordSplit<'a> {
} }
fn is_punctuation(c: char) -> bool { fn is_punctuation(c: char) -> bool {
#[allow(clippy::match_like_matches_macro)] matches!(c, '!' | '.' | '?')
// `matches!(...)` macro not stabilized until rust v1.42
match c {
'!' | '.' | '?' => true,
_ => false,
}
} }
} }

View file

@ -14,6 +14,7 @@ use clap::{App, Arg};
use std::fs::File; use std::fs::File;
use std::io::{stdin, BufRead, BufReader, Read}; use std::io::{stdin, BufRead, BufReader, Read};
use std::path::Path; use std::path::Path;
use uucore::InvalidEncodingHandling;
const TAB_WIDTH: usize = 8; const TAB_WIDTH: usize = 8;
@ -31,7 +32,9 @@ mod options {
} }
pub fn uumain(args: impl uucore::Args) -> i32 { pub fn uumain(args: impl uucore::Args) -> i32 {
let args = args.collect_str(); let args = args
.collect_str(InvalidEncodingHandling::ConvertLossy)
.accept_any();
let (args, obs_width) = handle_obsolete(&args[..]); let (args, obs_width) = handle_obsolete(&args[..]);
let matches = App::new(executable!()) let matches = App::new(executable!())
@ -66,7 +69,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.takes_value(true), .takes_value(true),
) )
.arg(Arg::with_name(options::FILE).hidden(true).multiple(true)) .arg(Arg::with_name(options::FILE).hidden(true).multiple(true))
.get_matches_from(args.clone()); .get_matches_from(args);
let bytes = matches.is_present(options::BYTES); let bytes = matches.is_present(options::BYTES);
let spaces = matches.is_present(options::SPACES); let spaces = matches.is_present(options::SPACES);

View file

@ -51,14 +51,23 @@ struct Options {
} }
fn is_custom_binary(program: &str) -> bool { fn is_custom_binary(program: &str) -> bool {
#[allow(clippy::match_like_matches_macro)] matches!(
// `matches!(...)` macro not stabilized until rust v1.42 program,
match program { "md5sum"
"md5sum" | "sha1sum" | "sha224sum" | "sha256sum" | "sha384sum" | "sha512sum" | "sha1sum"
| "sha3sum" | "sha3-224sum" | "sha3-256sum" | "sha3-384sum" | "sha3-512sum" | "sha224sum"
| "shake128sum" | "shake256sum" | "b2sum" => true, | "sha256sum"
_ => false, | "sha384sum"
} | "sha512sum"
| "sha3sum"
| "sha3-224sum"
| "sha3-256sum"
| "sha3-384sum"
| "sha3-512sum"
| "shake128sum"
| "shake256sum"
| "b2sum"
)
} }
#[allow(clippy::cognitive_complexity)] #[allow(clippy::cognitive_complexity)]
@ -78,7 +87,7 @@ fn detect_algo<'a>(
"sha512sum" => ("SHA512", Box::new(Sha512::new()) as Box<dyn Digest>, 512), "sha512sum" => ("SHA512", Box::new(Sha512::new()) as Box<dyn Digest>, 512),
"b2sum" => ("BLAKE2", Box::new(Blake2b::new(64)) as Box<dyn Digest>, 512), "b2sum" => ("BLAKE2", Box::new(Blake2b::new(64)) as Box<dyn Digest>, 512),
"sha3sum" => match matches.value_of("bits") { "sha3sum" => match matches.value_of("bits") {
Some(bits_str) => match usize::from_str_radix(&bits_str, 10) { Some(bits_str) => match (&bits_str).parse::<usize>() {
Ok(224) => ( Ok(224) => (
"SHA3-224", "SHA3-224",
Box::new(Sha3_224::new()) as Box<dyn Digest>, Box::new(Sha3_224::new()) as Box<dyn Digest>,
@ -128,7 +137,7 @@ fn detect_algo<'a>(
512, 512,
), ),
"shake128sum" => match matches.value_of("bits") { "shake128sum" => match matches.value_of("bits") {
Some(bits_str) => match usize::from_str_radix(&bits_str, 10) { Some(bits_str) => match (&bits_str).parse::<usize>() {
Ok(bits) => ( Ok(bits) => (
"SHAKE128", "SHAKE128",
Box::new(Shake128::new()) as Box<dyn Digest>, Box::new(Shake128::new()) as Box<dyn Digest>,
@ -139,7 +148,7 @@ fn detect_algo<'a>(
None => crash!(1, "--bits required for SHAKE-128"), None => crash!(1, "--bits required for SHAKE-128"),
}, },
"shake256sum" => match matches.value_of("bits") { "shake256sum" => match matches.value_of("bits") {
Some(bits_str) => match usize::from_str_radix(&bits_str, 10) { Some(bits_str) => match (&bits_str).parse::<usize>() {
Ok(bits) => ( Ok(bits) => (
"SHAKE256", "SHAKE256",
Box::new(Shake256::new()) as Box<dyn Digest>, Box::new(Shake256::new()) as Box<dyn Digest>,
@ -182,7 +191,7 @@ fn detect_algo<'a>(
} }
if matches.is_present("sha3") { if matches.is_present("sha3") {
match matches.value_of("bits") { match matches.value_of("bits") {
Some(bits_str) => match usize::from_str_radix(&bits_str, 10) { Some(bits_str) => match (&bits_str).parse::<usize>() {
Ok(224) => set_or_crash( Ok(224) => set_or_crash(
"SHA3-224", "SHA3-224",
Box::new(Sha3_224::new()) as Box<dyn Digest>, Box::new(Sha3_224::new()) as Box<dyn Digest>,
@ -226,7 +235,7 @@ fn detect_algo<'a>(
} }
if matches.is_present("shake128") { if matches.is_present("shake128") {
match matches.value_of("bits") { match matches.value_of("bits") {
Some(bits_str) => match usize::from_str_radix(&bits_str, 10) { Some(bits_str) => match (&bits_str).parse::<usize>() {
Ok(bits) => set_or_crash("SHAKE128", Box::new(Shake128::new()), bits), Ok(bits) => set_or_crash("SHAKE128", Box::new(Shake128::new()), bits),
Err(err) => crash!(1, "{}", err), Err(err) => crash!(1, "{}", err),
}, },
@ -235,7 +244,7 @@ fn detect_algo<'a>(
} }
if matches.is_present("shake256") { if matches.is_present("shake256") {
match matches.value_of("bits") { match matches.value_of("bits") {
Some(bits_str) => match usize::from_str_radix(&bits_str, 10) { Some(bits_str) => match (&bits_str).parse::<usize>() {
Ok(bits) => set_or_crash("SHAKE256", Box::new(Shake256::new()), bits), Ok(bits) => set_or_crash("SHAKE256", Box::new(Shake256::new()), bits),
Err(err) => crash!(1, "{}", err), Err(err) => crash!(1, "{}", err),
}, },
@ -253,7 +262,7 @@ fn detect_algo<'a>(
// TODO: return custom error type // TODO: return custom error type
fn parse_bit_num(arg: &str) -> Result<usize, ParseIntError> { fn parse_bit_num(arg: &str) -> Result<usize, ParseIntError> {
usize::from_str_radix(arg, 10) arg.parse()
} }
fn is_valid_bit_num(arg: String) -> Result<(), String> { fn is_valid_bit_num(arg: String) -> Result<(), String> {

View file

@ -16,7 +16,7 @@ path = "src/head.rs"
[dependencies] [dependencies]
clap = "2.33" clap = "2.33"
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["ringbuffer"] }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
[[bin]] [[bin]]

View file

@ -1,8 +1,8 @@
use clap::{App, Arg}; use clap::{App, Arg};
use std::convert::TryFrom; use std::convert::TryFrom;
use std::ffi::OsString; use std::ffi::OsString;
use std::io::{ErrorKind, Read, Seek, SeekFrom, Write}; use std::io::{self, ErrorKind, Read, Seek, SeekFrom, Write};
use uucore::{crash, executable, show_error}; use uucore::{crash, executable, show_error, show_error_custom_description};
const EXIT_FAILURE: i32 = 1; const EXIT_FAILURE: i32 = 1;
const EXIT_SUCCESS: i32 = 0; const EXIT_SUCCESS: i32 = 0;
@ -27,8 +27,12 @@ mod options {
pub const ZERO_NAME: &str = "ZERO"; pub const ZERO_NAME: &str = "ZERO";
pub const FILES_NAME: &str = "FILE"; pub const FILES_NAME: &str = "FILE";
} }
mod lines;
mod parse; mod parse;
mod split; mod split;
mod take;
use lines::zlines;
use take::take_all_but;
fn app<'a>() -> App<'a, 'a> { fn app<'a>() -> App<'a, 'a> {
App::new(executable!()) App::new(executable!())
@ -206,38 +210,20 @@ impl Default for HeadOptions {
} }
} }
fn rbuf_n_bytes(input: &mut impl std::io::BufRead, n: usize) -> std::io::Result<()> { fn rbuf_n_bytes<R>(input: R, n: usize) -> std::io::Result<()>
if n == 0 { where
return Ok(()); R: Read,
} {
let mut readbuf = [0u8; BUF_SIZE]; // Read the first `n` bytes from the `input` reader.
let mut i = 0usize; let mut reader = input.take(n as u64);
// Write those bytes to `stdout`.
let stdout = std::io::stdout(); let stdout = std::io::stdout();
let mut stdout = stdout.lock(); let mut stdout = stdout.lock();
loop { io::copy(&mut reader, &mut stdout)?;
let read = loop {
match input.read(&mut readbuf) { Ok(())
Ok(n) => break n,
Err(e) => match e.kind() {
ErrorKind::Interrupted => {}
_ => return Err(e),
},
}
};
if read == 0 {
// might be unexpected if
// we haven't read `n` bytes
// but this mirrors GNU's behavior
return Ok(());
}
stdout.write_all(&readbuf[..read.min(n - i)])?;
i += read.min(n - i);
if i == n {
return Ok(());
}
}
} }
fn rbuf_n_lines(input: &mut impl std::io::BufRead, n: usize, zero: bool) -> std::io::Result<()> { fn rbuf_n_lines(input: &mut impl std::io::BufRead, n: usize, zero: bool) -> std::io::Result<()> {
@ -311,36 +297,22 @@ fn rbuf_but_last_n_bytes(input: &mut impl std::io::BufRead, n: usize) -> std::io
} }
fn rbuf_but_last_n_lines( fn rbuf_but_last_n_lines(
input: &mut impl std::io::BufRead, input: impl std::io::BufRead,
n: usize, n: usize,
zero: bool, zero: bool,
) -> std::io::Result<()> { ) -> std::io::Result<()> {
if n == 0 { if zero {
//prints everything let stdout = std::io::stdout();
return rbuf_n_bytes(input, std::usize::MAX); let mut stdout = stdout.lock();
for bytes in take_all_but(zlines(input), n) {
stdout.write_all(&bytes?)?;
}
} else {
for line in take_all_but(input.lines(), n) {
println!("{}", line?);
}
} }
let mut ringbuf = vec![Vec::new(); n]; Ok(())
let stdout = std::io::stdout();
let mut stdout = stdout.lock();
let mut line = Vec::new();
let mut lines = 0usize;
split::walk_lines(input, zero, |e| match e {
split::Event::Data(dat) => {
line.extend_from_slice(dat);
Ok(true)
}
split::Event::Line => {
if lines < n {
ringbuf[lines] = std::mem::replace(&mut line, Vec::new());
lines += 1;
} else {
stdout.write_all(&ringbuf[0])?;
ringbuf.rotate_left(1);
ringbuf[n - 1] = std::mem::replace(&mut line, Vec::new());
}
Ok(true)
}
})
} }
fn head_backwards_file(input: &mut std::fs::File, options: &HeadOptions) -> std::io::Result<()> { fn head_backwards_file(input: &mut std::fs::File, options: &HeadOptions) -> std::io::Result<()> {
@ -418,12 +390,13 @@ fn head_file(input: &mut std::fs::File, options: &HeadOptions) -> std::io::Resul
} }
} }
fn uu_head(options: &HeadOptions) { fn uu_head(options: &HeadOptions) -> Result<(), u32> {
let mut error_count = 0;
let mut first = true; let mut first = true;
for fname in &options.files { for fname in &options.files {
let res = match fname.as_str() { let res = match fname.as_str() {
"-" => { "-" => {
if options.verbose { if (options.files.len() > 1 && !options.quiet) || options.verbose {
if !first { if !first {
println!(); println!();
} }
@ -451,53 +424,49 @@ fn uu_head(options: &HeadOptions) {
name => { name => {
let mut file = match std::fs::File::open(name) { let mut file = match std::fs::File::open(name) {
Ok(f) => f, Ok(f) => f,
Err(err) => match err.kind() { Err(err) => {
ErrorKind::NotFound => { let prefix = format!("cannot open '{}' for reading", name);
crash!( match err.kind() {
EXIT_FAILURE, ErrorKind::NotFound => {
"head: cannot open '{}' for reading: No such file or directory", show_error_custom_description!(prefix, "No such file or directory");
name }
); ErrorKind::PermissionDenied => {
show_error_custom_description!(prefix, "Permission denied");
}
_ => {
show_error_custom_description!(prefix, "{}", err);
}
} }
ErrorKind::PermissionDenied => { error_count += 1;
crash!( continue;
EXIT_FAILURE, }
"head: cannot open '{}' for reading: Permission denied",
name
);
}
_ => {
crash!(
EXIT_FAILURE,
"head: cannot open '{}' for reading: {}",
name,
err
);
}
},
}; };
if (options.files.len() > 1 && !options.quiet) || options.verbose { if (options.files.len() > 1 && !options.quiet) || options.verbose {
if !first {
println!();
}
println!("==> {} <==", name) println!("==> {} <==", name)
} }
head_file(&mut file, options) head_file(&mut file, options)
} }
}; };
if res.is_err() { if res.is_err() {
if fname.as_str() == "-" { let name = if fname.as_str() == "-" {
crash!( "standard input"
EXIT_FAILURE,
"head: error reading standard input: Input/output error"
);
} else { } else {
crash!( fname
EXIT_FAILURE, };
"head: error reading {}: Input/output error", let prefix = format!("error reading {}", name);
fname show_error_custom_description!(prefix, "Input/output error");
); error_count += 1;
}
} }
first = false; first = false;
} }
if error_count > 0 {
Err(error_count)
} else {
Ok(())
}
} }
pub fn uumain(args: impl uucore::Args) -> i32 { pub fn uumain(args: impl uucore::Args) -> i32 {
@ -507,9 +476,10 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
crash!(EXIT_FAILURE, "head: {}", s); crash!(EXIT_FAILURE, "head: {}", s);
} }
}; };
uu_head(&args); match uu_head(&args) {
Ok(_) => EXIT_SUCCESS,
EXIT_SUCCESS Err(_) => EXIT_FAILURE,
}
} }
#[cfg(test)] #[cfg(test)]
@ -625,7 +595,7 @@ mod tests {
assert_eq!(arg_outputs("head"), Ok("head".to_owned())); assert_eq!(arg_outputs("head"), Ok("head".to_owned()));
} }
#[test] #[test]
#[cfg(linux)] #[cfg(target_os = "linux")]
fn test_arg_iterate_bad_encoding() { fn test_arg_iterate_bad_encoding() {
let invalid = unsafe { std::str::from_utf8_unchecked(b"\x80\x81") }; let invalid = unsafe { std::str::from_utf8_unchecked(b"\x80\x81") };
// this arises from a conversion from OsString to &str // this arises from a conversion from OsString to &str

73
src/uu/head/src/lines.rs Normal file
View file

@ -0,0 +1,73 @@
//! Iterate over zero-terminated lines.
use std::io::BufRead;
/// The zero byte, representing the null character.
const ZERO: u8 = 0;
/// Returns an iterator over the lines of the given reader.
///
/// The iterator returned from this function will yield instances of
/// [`io::Result`]<[`Vec`]<[`u8`]>>, representing the bytes of the line
/// *including* the null character (with the possible exception of the
/// last line, which may not have one).
///
/// # Examples
///
/// ```rust,ignore
/// use std::io::Cursor;
///
/// let cursor = Cursor::new(b"x\0y\0z\0");
/// let mut iter = zlines(cursor).map(|l| l.unwrap());
/// assert_eq!(iter.next(), Some(b"x\0".to_vec()));
/// assert_eq!(iter.next(), Some(b"y\0".to_vec()));
/// assert_eq!(iter.next(), Some(b"z\0".to_vec()));
/// assert_eq!(iter.next(), None);
/// ```
pub fn zlines<B>(buf: B) -> ZLines<B> {
ZLines { buf }
}
/// An iterator over the zero-terminated lines of an instance of `BufRead`.
pub struct ZLines<B> {
buf: B,
}
impl<B: BufRead> Iterator for ZLines<B> {
type Item = std::io::Result<Vec<u8>>;
fn next(&mut self) -> Option<std::io::Result<Vec<u8>>> {
let mut buf = Vec::new();
match self.buf.read_until(ZERO, &mut buf) {
Ok(0) => None,
Ok(_) => Some(Ok(buf)),
Err(e) => Some(Err(e)),
}
}
}
#[cfg(test)]
mod tests {
use crate::lines::zlines;
use std::io::Cursor;
#[test]
fn test_null_terminated() {
let cursor = Cursor::new(b"x\0y\0z\0");
let mut iter = zlines(cursor).map(|l| l.unwrap());
assert_eq!(iter.next(), Some(b"x\0".to_vec()));
assert_eq!(iter.next(), Some(b"y\0".to_vec()));
assert_eq!(iter.next(), Some(b"z\0".to_vec()));
assert_eq!(iter.next(), None);
}
#[test]
fn test_not_null_terminated() {
let cursor = Cursor::new(b"x\0y\0z");
let mut iter = zlines(cursor).map(|l| l.unwrap());
assert_eq!(iter.next(), Some(b"x\0".to_vec()));
assert_eq!(iter.next(), Some(b"y\0".to_vec()));
assert_eq!(iter.next(), Some(b"z".to_vec()));
assert_eq!(iter.next(), None);
}
}

93
src/uu/head/src/take.rs Normal file
View file

@ -0,0 +1,93 @@
//! Take all but the last elements of an iterator.
use uucore::ringbuffer::RingBuffer;
/// Create an iterator over all but the last `n` elements of `iter`.
///
/// # Examples
///
/// ```rust,ignore
/// let data = [1, 2, 3, 4, 5];
/// let n = 2;
/// let mut iter = take_all_but(data.iter(), n);
/// assert_eq!(Some(4), iter.next());
/// assert_eq!(Some(5), iter.next());
/// assert_eq!(None, iter.next());
/// ```
pub fn take_all_but<I: Iterator>(iter: I, n: usize) -> TakeAllBut<I> {
TakeAllBut::new(iter, n)
}
/// An iterator that only iterates over the last elements of another iterator.
pub struct TakeAllBut<I: Iterator> {
iter: I,
buf: RingBuffer<<I as Iterator>::Item>,
}
impl<I: Iterator> TakeAllBut<I> {
pub fn new(mut iter: I, n: usize) -> TakeAllBut<I> {
// Create a new ring buffer and fill it up.
//
// If there are fewer than `n` elements in `iter`, then we
// exhaust the iterator so that whenever `TakeAllBut::next()` is
// called, it will return `None`, as expected.
let mut buf = RingBuffer::new(n);
for _ in 0..n {
let value = match iter.next() {
None => {
break;
}
Some(x) => x,
};
buf.push_back(value);
}
TakeAllBut { iter, buf }
}
}
impl<I: Iterator> Iterator for TakeAllBut<I>
where
I: Iterator,
{
type Item = <I as Iterator>::Item;
fn next(&mut self) -> Option<<I as Iterator>::Item> {
match self.iter.next() {
Some(value) => self.buf.push_back(value),
None => None,
}
}
}
#[cfg(test)]
mod tests {
use crate::take::take_all_but;
#[test]
fn test_fewer_elements() {
let mut iter = take_all_but([0, 1, 2].iter(), 2);
assert_eq!(Some(&0), iter.next());
assert_eq!(None, iter.next());
}
#[test]
fn test_same_number_of_elements() {
let mut iter = take_all_but([0, 1].iter(), 2);
assert_eq!(None, iter.next());
}
#[test]
fn test_more_elements() {
let mut iter = take_all_but([0].iter(), 2);
assert_eq!(None, iter.next());
}
#[test]
fn test_zero_elements() {
let mut iter = take_all_but([0, 1, 2].iter(), 0);
assert_eq!(Some(&0), iter.next());
assert_eq!(Some(&1), iter.next());
assert_eq!(Some(&2), iter.next());
assert_eq!(None, iter.next());
}
}

View file

@ -11,6 +11,7 @@
extern crate uucore; extern crate uucore;
use libc::c_long; use libc::c_long;
use uucore::InvalidEncodingHandling;
static SYNTAX: &str = "[options]"; static SYNTAX: &str = "[options]";
static SUMMARY: &str = ""; static SUMMARY: &str = "";
@ -22,7 +23,10 @@ extern "C" {
} }
pub fn uumain(args: impl uucore::Args) -> i32 { pub fn uumain(args: impl uucore::Args) -> i32 {
app!(SYNTAX, SUMMARY, LONG_HELP).parse(args.collect_str()); app!(SYNTAX, SUMMARY, LONG_HELP).parse(
args.collect_str(InvalidEncodingHandling::ConvertLossy)
.accept_any(),
);
hostid(); hostid();
0 0
} }

View file

@ -23,9 +23,11 @@ use std::fs;
use std::fs::File; use std::fs::File;
use std::os::unix::fs::MetadataExt; use std::os::unix::fs::MetadataExt;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::process::Command;
use std::result::Result; use std::result::Result;
const DEFAULT_MODE: u32 = 0o755; const DEFAULT_MODE: u32 = 0o755;
const DEFAULT_STRIP_PROGRAM: &str = "strip";
#[allow(dead_code)] #[allow(dead_code)]
pub struct Behavior { pub struct Behavior {
@ -37,6 +39,9 @@ pub struct Behavior {
verbose: bool, verbose: bool,
preserve_timestamps: bool, preserve_timestamps: bool,
compare: bool, compare: bool,
strip: bool,
strip_program: String,
create_leading: bool,
} }
#[derive(Clone, Eq, PartialEq)] #[derive(Clone, Eq, PartialEq)]
@ -66,7 +71,7 @@ static OPT_BACKUP: &str = "backup";
static OPT_BACKUP_2: &str = "backup2"; static OPT_BACKUP_2: &str = "backup2";
static OPT_DIRECTORY: &str = "directory"; static OPT_DIRECTORY: &str = "directory";
static OPT_IGNORED: &str = "ignored"; static OPT_IGNORED: &str = "ignored";
static OPT_CREATED: &str = "created"; static OPT_CREATE_LEADING: &str = "create-leading";
static OPT_GROUP: &str = "group"; static OPT_GROUP: &str = "group";
static OPT_MODE: &str = "mode"; static OPT_MODE: &str = "mode";
static OPT_OWNER: &str = "owner"; static OPT_OWNER: &str = "owner";
@ -129,9 +134,9 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.arg( .arg(
// TODO implement flag // TODO implement flag
Arg::with_name(OPT_CREATED) Arg::with_name(OPT_CREATE_LEADING)
.short("D") .short("D")
.help("(unimplemented) create all leading components of DEST except the last, then copy SOURCE to DEST") .help("create all leading components of DEST except the last, then copy SOURCE to DEST")
) )
.arg( .arg(
Arg::with_name(OPT_GROUP) Arg::with_name(OPT_GROUP)
@ -164,17 +169,15 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.help("apply access/modification times of SOURCE files to corresponding destination files") .help("apply access/modification times of SOURCE files to corresponding destination files")
) )
.arg( .arg(
// TODO implement flag
Arg::with_name(OPT_STRIP) Arg::with_name(OPT_STRIP)
.short("s") .short("s")
.long(OPT_STRIP) .long(OPT_STRIP)
.help("(unimplemented) strip symbol tables") .help("strip symbol tables (no action Windows)")
) )
.arg( .arg(
// TODO implement flag
Arg::with_name(OPT_STRIP_PROGRAM) Arg::with_name(OPT_STRIP_PROGRAM)
.long(OPT_STRIP_PROGRAM) .long(OPT_STRIP_PROGRAM)
.help("(unimplemented) program used to strip binaries") .help("program used to strip binaries (no action Windows)")
.value_name("PROGRAM") .value_name("PROGRAM")
) )
.arg( .arg(
@ -264,12 +267,6 @@ fn check_unimplemented<'a>(matches: &ArgMatches) -> Result<(), &'a str> {
Err("--backup") Err("--backup")
} else if matches.is_present(OPT_BACKUP_2) { } else if matches.is_present(OPT_BACKUP_2) {
Err("-b") Err("-b")
} else if matches.is_present(OPT_CREATED) {
Err("-D")
} else if matches.is_present(OPT_STRIP) {
Err("--strip, -s")
} else if matches.is_present(OPT_STRIP_PROGRAM) {
Err("--strip-program")
} else if matches.is_present(OPT_SUFFIX) { } else if matches.is_present(OPT_SUFFIX) {
Err("--suffix, -S") Err("--suffix, -S")
} else if matches.is_present(OPT_TARGET_DIRECTORY) { } else if matches.is_present(OPT_TARGET_DIRECTORY) {
@ -304,7 +301,7 @@ fn behavior(matches: &ArgMatches) -> Result<Behavior, i32> {
let specified_mode: Option<u32> = if matches.is_present(OPT_MODE) { let specified_mode: Option<u32> = if matches.is_present(OPT_MODE) {
match matches.value_of(OPT_MODE) { match matches.value_of(OPT_MODE) {
Some(x) => match mode::parse(&x[..], considering_dir) { Some(x) => match mode::parse(x, considering_dir) {
Ok(y) => Some(y), Ok(y) => Some(y),
Err(err) => { Err(err) => {
show_error!("Invalid mode string: {}", err); show_error!("Invalid mode string: {}", err);
@ -339,6 +336,13 @@ fn behavior(matches: &ArgMatches) -> Result<Behavior, i32> {
verbose: matches.is_present(OPT_VERBOSE), verbose: matches.is_present(OPT_VERBOSE),
preserve_timestamps: matches.is_present(OPT_PRESERVE_TIMESTAMPS), preserve_timestamps: matches.is_present(OPT_PRESERVE_TIMESTAMPS),
compare: matches.is_present(OPT_COMPARE), compare: matches.is_present(OPT_COMPARE),
strip: matches.is_present(OPT_STRIP),
strip_program: String::from(
matches
.value_of(OPT_STRIP_PROGRAM)
.unwrap_or(DEFAULT_STRIP_PROGRAM),
),
create_leading: matches.is_present(OPT_CREATE_LEADING),
}) })
} }
@ -366,13 +370,13 @@ fn directory(paths: Vec<String>, b: Behavior) -> i32 {
// created ancestor directories will have the default mode. Hence it is safe to use // created ancestor directories will have the default mode. Hence it is safe to use
// fs::create_dir_all and then only modify the target's dir mode. // fs::create_dir_all and then only modify the target's dir mode.
if let Err(e) = fs::create_dir_all(path) { if let Err(e) = fs::create_dir_all(path) {
show_info!("{}: {}", path.display(), e); show_error!("{}: {}", path.display(), e);
all_successful = false; all_successful = false;
continue; continue;
} }
if b.verbose { if b.verbose {
show_info!("creating directory '{}'", path.display()); show_error!("creating directory '{}'", path.display());
} }
} }
@ -406,12 +410,35 @@ fn standard(paths: Vec<String>, b: Behavior) -> i32 {
.iter() .iter()
.map(PathBuf::from) .map(PathBuf::from)
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let target = Path::new(paths.last().unwrap()); let target = Path::new(paths.last().unwrap());
if (target.is_file() || is_new_file_path(target)) && sources.len() == 1 { if sources.len() > 1 || (target.exists() && target.is_dir()) {
copy_file_to_file(&sources[0], &target.to_path_buf(), &b)
} else {
copy_files_into_dir(sources, &target.to_path_buf(), &b) copy_files_into_dir(sources, &target.to_path_buf(), &b)
} else {
if let Some(parent) = target.parent() {
if !parent.exists() && b.create_leading {
if let Err(e) = fs::create_dir_all(parent) {
show_error!("failed to create {}: {}", parent.display(), e);
return 1;
}
if mode::chmod(&parent, b.mode()).is_err() {
show_error!("failed to chmod {}", parent.display());
return 1;
}
}
}
if target.is_file() || is_new_file_path(target) {
copy_file_to_file(&sources[0], &target.to_path_buf(), &b)
} else {
show_error!(
"invalid target {}: No such file or directory",
target.display()
);
1
}
} }
} }
@ -425,7 +452,7 @@ fn standard(paths: Vec<String>, b: Behavior) -> i32 {
/// _files_ must all exist as non-directories. /// _files_ must all exist as non-directories.
/// _target_dir_ must be a directory. /// _target_dir_ must be a directory.
/// ///
fn copy_files_into_dir(files: &[PathBuf], target_dir: &PathBuf, b: &Behavior) -> i32 { fn copy_files_into_dir(files: &[PathBuf], target_dir: &Path, b: &Behavior) -> i32 {
if !target_dir.is_dir() { if !target_dir.is_dir() {
show_error!("target '{}' is not a directory", target_dir.display()); show_error!("target '{}' is not a directory", target_dir.display());
return 1; return 1;
@ -434,7 +461,7 @@ fn copy_files_into_dir(files: &[PathBuf], target_dir: &PathBuf, b: &Behavior) ->
let mut all_successful = true; let mut all_successful = true;
for sourcepath in files.iter() { for sourcepath in files.iter() {
if !sourcepath.exists() { if !sourcepath.exists() {
show_info!( show_error!(
"cannot stat '{}': No such file or directory", "cannot stat '{}': No such file or directory",
sourcepath.display() sourcepath.display()
); );
@ -444,12 +471,12 @@ fn copy_files_into_dir(files: &[PathBuf], target_dir: &PathBuf, b: &Behavior) ->
} }
if sourcepath.is_dir() { if sourcepath.is_dir() {
show_info!("omitting directory '{}'", sourcepath.display()); show_error!("omitting directory '{}'", sourcepath.display());
all_successful = false; all_successful = false;
continue; continue;
} }
let mut targetpath = target_dir.clone().to_path_buf(); let mut targetpath = target_dir.to_path_buf();
let filename = sourcepath.components().last().unwrap(); let filename = sourcepath.components().last().unwrap();
targetpath.push(filename); targetpath.push(filename);
@ -474,7 +501,7 @@ fn copy_files_into_dir(files: &[PathBuf], target_dir: &PathBuf, b: &Behavior) ->
/// _file_ must exist as a non-directory. /// _file_ must exist as a non-directory.
/// _target_ must be a non-directory /// _target_ must be a non-directory
/// ///
fn copy_file_to_file(file: &PathBuf, target: &PathBuf, b: &Behavior) -> i32 { fn copy_file_to_file(file: &Path, target: &Path, b: &Behavior) -> i32 {
if copy(file, &target, b).is_err() { if copy(file, &target, b).is_err() {
1 1
} else { } else {
@ -493,7 +520,7 @@ fn copy_file_to_file(file: &PathBuf, target: &PathBuf, b: &Behavior) -> i32 {
/// ///
/// If the copy system call fails, we print a verbose error and return an empty error value. /// If the copy system call fails, we print a verbose error and return an empty error value.
/// ///
fn copy(from: &PathBuf, to: &PathBuf, b: &Behavior) -> Result<(), ()> { fn copy(from: &Path, to: &Path, b: &Behavior) -> Result<(), ()> {
if b.compare && !need_copy(from, to, b) { if b.compare && !need_copy(from, to, b) {
return Ok(()); return Ok(());
} }
@ -521,6 +548,21 @@ fn copy(from: &PathBuf, to: &PathBuf, b: &Behavior) -> Result<(), ()> {
return Err(()); return Err(());
} }
if b.strip && cfg!(not(windows)) {
match Command::new(&b.strip_program).arg(to).output() {
Ok(o) => {
if !o.status.success() {
crash!(
1,
"strip program failed: {}",
String::from_utf8(o.stderr).unwrap_or_default()
);
}
}
Err(e) => crash!(1, "strip program execution failed: {}", e),
}
}
if mode::chmod(&to, b.mode()).is_err() { if mode::chmod(&to, b.mode()).is_err() {
return Err(()); return Err(());
} }
@ -537,7 +579,7 @@ fn copy(from: &PathBuf, to: &PathBuf, b: &Behavior) -> Result<(), ()> {
}; };
let gid = meta.gid(); let gid = meta.gid();
match wrap_chown( match wrap_chown(
to.as_path(), to,
&meta, &meta,
Some(owner_id), Some(owner_id),
Some(gid), Some(gid),
@ -546,10 +588,10 @@ fn copy(from: &PathBuf, to: &PathBuf, b: &Behavior) -> Result<(), ()> {
) { ) {
Ok(n) => { Ok(n) => {
if !n.is_empty() { if !n.is_empty() {
show_info!("{}", n); show_error!("{}", n);
} }
} }
Err(e) => show_info!("{}", e), Err(e) => show_error!("{}", e),
} }
} }
@ -563,13 +605,13 @@ fn copy(from: &PathBuf, to: &PathBuf, b: &Behavior) -> Result<(), ()> {
Ok(g) => g, Ok(g) => g,
_ => crash!(1, "no such group: {}", b.group), _ => crash!(1, "no such group: {}", b.group),
}; };
match wrap_chgrp(to.as_path(), &meta, group_id, false, Verbosity::Normal) { match wrap_chgrp(to, &meta, group_id, false, Verbosity::Normal) {
Ok(n) => { Ok(n) => {
if !n.is_empty() { if !n.is_empty() {
show_info!("{}", n); show_error!("{}", n);
} }
} }
Err(e) => show_info!("{}", e), Err(e) => show_error!("{}", e),
} }
} }
@ -582,14 +624,14 @@ fn copy(from: &PathBuf, to: &PathBuf, b: &Behavior) -> Result<(), ()> {
let modified_time = FileTime::from_last_modification_time(&meta); let modified_time = FileTime::from_last_modification_time(&meta);
let accessed_time = FileTime::from_last_access_time(&meta); let accessed_time = FileTime::from_last_access_time(&meta);
match set_file_times(to.as_path(), accessed_time, modified_time) { match set_file_times(to, accessed_time, modified_time) {
Ok(_) => {} Ok(_) => {}
Err(e) => show_info!("{}", e), Err(e) => show_error!("{}", e),
} }
} }
if b.verbose { if b.verbose {
show_info!("'{}' -> '{}'", from.display(), to.display()); show_error!("'{}' -> '{}'", from.display(), to.display());
} }
Ok(()) Ok(())
@ -611,7 +653,7 @@ fn copy(from: &PathBuf, to: &PathBuf, b: &Behavior) -> Result<(), ()> {
/// ///
/// Crashes the program if a nonexistent owner or group is specified in _b_. /// Crashes the program if a nonexistent owner or group is specified in _b_.
/// ///
fn need_copy(from: &PathBuf, to: &PathBuf, b: &Behavior) -> bool { fn need_copy(from: &Path, to: &Path, b: &Behavior) -> bool {
let from_meta = match fs::metadata(from) { let from_meta = match fs::metadata(from) {
Ok(meta) => meta, Ok(meta) => meta,
Err(_) => return true, Err(_) => return true,

View file

@ -23,7 +23,7 @@ pub fn parse(mode_string: &str, considering_dir: bool) -> Result<u32, String> {
pub fn chmod(path: &Path, mode: u32) -> Result<(), ()> { pub fn chmod(path: &Path, mode: u32) -> Result<(), ()> {
use std::os::unix::fs::PermissionsExt; use std::os::unix::fs::PermissionsExt;
fs::set_permissions(path, fs::Permissions::from_mode(mode)).map_err(|err| { fs::set_permissions(path, fs::Permissions::from_mode(mode)).map_err(|err| {
show_info!("{}: chmod failed with error {}", path.display(), err); show_error!("{}: chmod failed with error {}", path.display(), err);
}) })
} }

View file

@ -15,6 +15,7 @@ edition = "2018"
path = "src/kill.rs" path = "src/kill.rs"
[dependencies] [dependencies]
clap = "2.33"
libc = "0.2.42" libc = "0.2.42"
uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["signals"] } uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["signals"] }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }

View file

@ -10,17 +10,26 @@
#[macro_use] #[macro_use]
extern crate uucore; extern crate uucore;
use clap::{App, Arg};
use libc::{c_int, pid_t}; use libc::{c_int, pid_t};
use std::io::Error; use std::io::Error;
use uucore::signals::ALL_SIGNALS; use uucore::signals::ALL_SIGNALS;
use uucore::InvalidEncodingHandling;
static SYNTAX: &str = "[options] <pid> [...]"; static VERSION: &str = env!("CARGO_PKG_VERSION");
static SUMMARY: &str = ""; static ABOUT: &str = "Send signal to processes or list information about signals.";
static LONG_HELP: &str = "";
static EXIT_OK: i32 = 0; static EXIT_OK: i32 = 0;
static EXIT_ERR: i32 = 1; static EXIT_ERR: i32 = 1;
pub mod options {
pub static PIDS_OR_SIGNALS: &str = "pids_of_signals";
pub static LIST: &str = "list";
pub static TABLE: &str = "table";
pub static TABLE_OLD: &str = "table_old";
pub static SIGNAL: &str = "signal";
}
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
pub enum Mode { pub enum Mode {
Kill, Kill,
@ -29,42 +38,73 @@ pub enum Mode {
} }
pub fn uumain(args: impl uucore::Args) -> i32 { pub fn uumain(args: impl uucore::Args) -> i32 {
let args = args.collect_str(); let args = args
.collect_str(InvalidEncodingHandling::Ignore)
.accept_any();
let (args, obs_signal) = handle_obsolete(args); let (args, obs_signal) = handle_obsolete(args);
let matches = app!(SYNTAX, SUMMARY, LONG_HELP)
.optopt("s", "signal", "specify the <signal> to be sent", "SIGNAL")
.optflagopt(
"l",
"list",
"list all signal names, or convert one to a name",
"LIST",
)
.optflag("L", "table", "list all signal names in a nice table")
.parse(args);
let mode = if matches.opt_present("table") { let usage = format!("{} [OPTIONS]... PID...", executable!());
let matches = App::new(executable!())
.version(VERSION)
.about(ABOUT)
.usage(&usage[..])
.arg(
Arg::with_name(options::LIST)
.short("l")
.long(options::LIST)
.help("Lists signals")
.conflicts_with(options::TABLE)
.conflicts_with(options::TABLE_OLD),
)
.arg(
Arg::with_name(options::TABLE)
.short("t")
.long(options::TABLE)
.help("Lists table of signals"),
)
.arg(Arg::with_name(options::TABLE_OLD).short("L").hidden(true))
.arg(
Arg::with_name(options::SIGNAL)
.short("s")
.long(options::SIGNAL)
.help("Sends given signal")
.takes_value(true),
)
.arg(
Arg::with_name(options::PIDS_OR_SIGNALS)
.hidden(true)
.multiple(true),
)
.get_matches_from(args);
let mode = if matches.is_present(options::TABLE) || matches.is_present(options::TABLE_OLD) {
Mode::Table Mode::Table
} else if matches.opt_present("list") { } else if matches.is_present(options::LIST) {
Mode::List Mode::List
} else { } else {
Mode::Kill Mode::Kill
}; };
let pids_or_signals: Vec<String> = matches
.values_of(options::PIDS_OR_SIGNALS)
.map(|v| v.map(ToString::to_string).collect())
.unwrap_or_default();
match mode { match mode {
Mode::Kill => { Mode::Kill => {
return kill( let sig = match (obs_signal, matches.value_of(options::SIGNAL)) {
&matches (Some(s), Some(_)) => s, // -s takes precedence
.opt_str("signal") (Some(s), None) => s,
.unwrap_or_else(|| obs_signal.unwrap_or_else(|| "9".to_owned())), (None, Some(s)) => s.to_owned(),
matches.free, (None, None) => "TERM".to_owned(),
) };
return kill(&sig, &pids_or_signals);
} }
Mode::Table => table(), Mode::Table => table(),
Mode::List => list(matches.opt_str("list")), Mode::List => list(pids_or_signals.get(0).cloned()),
} }
0 EXIT_OK
} }
fn handle_obsolete(mut args: Vec<String>) -> (Vec<String>, Option<String>) { fn handle_obsolete(mut args: Vec<String>) -> (Vec<String>, Option<String>) {
@ -145,14 +185,14 @@ fn list(arg: Option<String>) {
}; };
} }
fn kill(signalname: &str, pids: std::vec::Vec<String>) -> i32 { fn kill(signalname: &str, pids: &[String]) -> i32 {
let mut status = 0; let mut status = 0;
let optional_signal_value = uucore::signals::signal_by_name_or_value(signalname); let optional_signal_value = uucore::signals::signal_by_name_or_value(signalname);
let signal_value = match optional_signal_value { let signal_value = match optional_signal_value {
Some(x) => x, Some(x) => x,
None => crash!(EXIT_ERR, "unknown signal name {}", signalname), None => crash!(EXIT_ERR, "unknown signal name {}", signalname),
}; };
for pid in &pids { for pid in pids {
match pid.parse::<usize>() { match pid.parse::<usize>() {
Ok(x) => { Ok(x) => {
if unsafe { libc::kill(x as pid_t, signal_value as c_int) } != 0 { if unsafe { libc::kill(x as pid_t, signal_value as c_int) } != 0 {

View file

@ -18,6 +18,7 @@ path = "src/link.rs"
libc = "0.2.42" libc = "0.2.42"
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore = { version=">=0.0.8", package="uucore", path="../../uucore" }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
clap = "2.33"
[[bin]] [[bin]]
name = "link" name = "link"

View file

@ -8,13 +8,21 @@
#[macro_use] #[macro_use]
extern crate uucore; extern crate uucore;
use clap::{App, Arg};
use std::fs::hard_link; use std::fs::hard_link;
use std::io::Error; use std::io::Error;
use std::path::Path; use std::path::Path;
static SYNTAX: &str = "[OPTIONS] FILE1 FILE2"; static VERSION: &str = env!("CARGO_PKG_VERSION");
static SUMMARY: &str = "Create a link named FILE2 to FILE1"; static ABOUT: &str = "Call the link function to create a link named FILE2 to an existing FILE1.";
static LONG_HELP: &str = "";
pub mod options {
pub static FILES: &str = "FILES";
}
fn get_usage() -> String {
format!("{0} FILE1 FILE2", executable!())
}
pub fn normalize_error_message(e: Error) -> String { pub fn normalize_error_message(e: Error) -> String {
match e.raw_os_error() { match e.raw_os_error() {
@ -24,13 +32,27 @@ pub fn normalize_error_message(e: Error) -> String {
} }
pub fn uumain(args: impl uucore::Args) -> i32 { pub fn uumain(args: impl uucore::Args) -> i32 {
let matches = app!(SYNTAX, SUMMARY, LONG_HELP).parse(args.collect_str()); let usage = get_usage();
if matches.free.len() != 2 { let matches = App::new(executable!())
crash!(1, "{}", msg_wrong_number_of_arguments!(2)); .version(VERSION)
} .about(ABOUT)
.usage(&usage[..])
.arg(
Arg::with_name(options::FILES)
.hidden(true)
.required(true)
.min_values(2)
.max_values(2)
.takes_value(true),
)
.get_matches_from(args);
let old = Path::new(&matches.free[0]); let files: Vec<_> = matches
let new = Path::new(&matches.free[1]); .values_of_os(options::FILES)
.unwrap_or_default()
.collect();
let old = Path::new(files[0]);
let new = Path::new(files[1]);
match hard_link(old, new) { match hard_link(old, new) {
Ok(_) => 0, Ok(_) => 0,

View file

@ -303,7 +303,7 @@ fn exec(files: &[PathBuf], settings: &Settings) -> i32 {
} }
} }
fn link_files_in_dir(files: &[PathBuf], target_dir: &PathBuf, settings: &Settings) -> i32 { fn link_files_in_dir(files: &[PathBuf], target_dir: &Path, settings: &Settings) -> i32 {
if !target_dir.is_dir() { if !target_dir.is_dir() {
show_error!("target '{}' is not a directory", target_dir.display()); show_error!("target '{}' is not a directory", target_dir.display());
return 1; return 1;
@ -329,7 +329,7 @@ fn link_files_in_dir(files: &[PathBuf], target_dir: &PathBuf, settings: &Setting
}; };
} }
} }
target_dir.clone() target_dir.to_path_buf()
} else { } else {
match srcpath.as_os_str().to_str() { match srcpath.as_os_str().to_str() {
Some(name) => { Some(name) => {
@ -370,7 +370,7 @@ fn link_files_in_dir(files: &[PathBuf], target_dir: &PathBuf, settings: &Setting
} }
} }
fn relative_path<'a>(src: &PathBuf, dst: &PathBuf) -> Result<Cow<'a, Path>> { fn relative_path<'a>(src: &Path, dst: &Path) -> Result<Cow<'a, Path>> {
let abssrc = canonicalize(src, CanonicalizeMode::Normal)?; let abssrc = canonicalize(src, CanonicalizeMode::Normal)?;
let absdst = canonicalize(dst, CanonicalizeMode::Normal)?; let absdst = canonicalize(dst, CanonicalizeMode::Normal)?;
let suffix_pos = abssrc let suffix_pos = abssrc
@ -390,7 +390,7 @@ fn relative_path<'a>(src: &PathBuf, dst: &PathBuf) -> Result<Cow<'a, Path>> {
Ok(result.into()) Ok(result.into())
} }
fn link(src: &PathBuf, dst: &PathBuf, settings: &Settings) -> Result<()> { fn link(src: &Path, dst: &Path, settings: &Settings) -> Result<()> {
let mut backup_path = None; let mut backup_path = None;
let source: Cow<'_, Path> = if settings.relative { let source: Cow<'_, Path> = if settings.relative {
relative_path(&src, dst)? relative_path(&src, dst)?
@ -453,13 +453,13 @@ fn read_yes() -> bool {
} }
} }
fn simple_backup_path(path: &PathBuf, suffix: &str) -> PathBuf { fn simple_backup_path(path: &Path, suffix: &str) -> PathBuf {
let mut p = path.as_os_str().to_str().unwrap().to_owned(); let mut p = path.as_os_str().to_str().unwrap().to_owned();
p.push_str(suffix); p.push_str(suffix);
PathBuf::from(p) PathBuf::from(p)
} }
fn numbered_backup_path(path: &PathBuf) -> PathBuf { fn numbered_backup_path(path: &Path) -> PathBuf {
let mut i: u64 = 1; let mut i: u64 = 1;
loop { loop {
let new_path = simple_backup_path(path, &format!(".~{}~", i)); let new_path = simple_backup_path(path, &format!(".~{}~", i));
@ -470,7 +470,7 @@ fn numbered_backup_path(path: &PathBuf) -> PathBuf {
} }
} }
fn existing_backup_path(path: &PathBuf, suffix: &str) -> PathBuf { fn existing_backup_path(path: &Path, suffix: &str) -> PathBuf {
let test_path = simple_backup_path(path, &".~1~".to_owned()); let test_path = simple_backup_path(path, &".~1~".to_owned());
if test_path.exists() { if test_path.exists() {
return numbered_backup_path(path); return numbered_backup_path(path);

View file

@ -16,6 +16,7 @@ path = "src/logname.rs"
[dependencies] [dependencies]
libc = "0.2.42" libc = "0.2.42"
clap = "2.33"
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore = { version=">=0.0.8", package="uucore", path="../../uucore" }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }

View file

@ -13,6 +13,9 @@
extern crate uucore; extern crate uucore;
use std::ffi::CStr; use std::ffi::CStr;
use uucore::InvalidEncodingHandling;
use clap::App;
extern "C" { extern "C" {
// POSIX requires using getlogin (or equivalent code) // POSIX requires using getlogin (or equivalent code)
@ -30,12 +33,24 @@ fn get_userlogin() -> Option<String> {
} }
} }
static SYNTAX: &str = "";
static SUMMARY: &str = "Print user's login name"; static SUMMARY: &str = "Print user's login name";
static LONG_HELP: &str = ""; static VERSION: &str = env!("CARGO_PKG_VERSION");
fn get_usage() -> String {
String::from(executable!())
}
pub fn uumain(args: impl uucore::Args) -> i32 { pub fn uumain(args: impl uucore::Args) -> i32 {
app!(SYNTAX, SUMMARY, LONG_HELP).parse(args.collect_str()); let args = args
.collect_str(InvalidEncodingHandling::Ignore)
.accept_any();
let usage = get_usage();
let _ = App::new(executable!())
.version(VERSION)
.about(SUMMARY)
.usage(&usage[..])
.get_matches_from(args);
match get_userlogin() { match get_userlogin() {
Some(userlogin) => println!("{}", userlogin), Some(userlogin) => println!("{}", userlogin),

59
src/uu/ls/BENCHMARKING.md Normal file
View file

@ -0,0 +1,59 @@
# Benchmarking ls
ls majorly involves fetching a lot of details (depending upon what details are requested, eg. time/date, inode details, etc) for each path using system calls. Ideally, any system call should be done only once for each of the paths - not adhering to this principle leads to a lot of system call overhead multiplying and bubbling up, especially for recursive ls, therefore it is important to always benchmark multiple scenarios.
This is an overwiew over what was benchmarked, and if you make changes to `ls`, you are encouraged to check
how performance was affected for the workloads listed below. Feel free to add other workloads to the
list that we should improve / make sure not to regress.
Run `cargo build --release` before benchmarking after you make a change!
## Simple recursive ls
- Get a large tree, for example linux kernel source tree.
- Benchmark simple recursive ls with hyperfine: `hyperfine --warmup 2 "target/release/coreutils ls -R tree > /dev/null"`.
## Recursive ls with all and long options
- Same tree as above
- Benchmark recursive ls with -al -R options with hyperfine: `hyperfine --warmup 2 "target/release/coreutils ls -al -R tree > /dev/null"`.
## Comparing with GNU ls
Hyperfine accepts multiple commands to run and will compare them. To compare performance with GNU ls
duplicate the string you passed to hyperfine but remove the `target/release/coreutils` bit from it.
Example: `hyperfine --warmup 2 "target/release/coreutils ls -al -R tree > /dev/null"` becomes
`hyperfine --warmup 2 "target/release/coreutils ls -al -R tree > /dev/null" "ls -al -R tree > /dev/null"`
(This assumes GNU ls is installed as `ls`)
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
#!/bin/bash
cargo build --no-default-features --features ls --release
args="$@"
hyperfine "ls $args" "target/release/coreutils ls $args"
```
**Note**: No localization is currently implemented. This means that the comparison above is not really fair. We can fix this by setting `LC_ALL=C`, so GNU `ls` can ignore localization.
## Checking system call count
- Another thing to look at would be system calls count using strace (on linux) or equivalent on other operating systems.
- Example: `strace -c target/release/coreutils ls -al -R tree`
## Cargo Flamegraph
With Cargo Flamegraph you can easily make a flamegraph of `ls`:
```bash
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
#!/bin/bash
cargo build --release --no-default-features --features ls
perf record target/release/coreutils ls "$@"
perf script | uniq | inferno-collapse-perf | inferno-flamegraph > flamegraph.svg
```

View file

@ -15,19 +15,22 @@ edition = "2018"
path = "src/ls.rs" path = "src/ls.rs"
[dependencies] [dependencies]
locale = "0.2.2"
chrono = "0.4.19"
clap = "2.33" clap = "2.33"
lazy_static = "1.0.1" unicode-width = "0.1.8"
number_prefix = "0.4" number_prefix = "0.4"
term_grid = "0.1.5" term_grid = "0.1.5"
termsize = "0.1.6" termsize = "0.1.6"
time = "0.1.40"
unicode-width = "0.1.5"
globset = "0.4.6" globset = "0.4.6"
uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["entries", "fs"] } lscolors = { version = "0.7.1", features = ["ansi_term"] }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } uucore = { version = ">=0.0.8", package = "uucore", path = "../../uucore", features = ["entries", "fs"] }
uucore_procs = { version = ">=0.0.5", package = "uucore_procs", path = "../../uucore_procs" }
once_cell = "1.7.2"
atty = "0.2"
[target.'cfg(unix)'.dependencies] [target.'cfg(unix)'.dependencies]
atty = "0.2" lazy_static = "1.4.0"
[[bin]] [[bin]]
name = "ls" name = "ls"

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
use std::char::from_digit; use std::char::from_digit;
const SPECIAL_SHELL_CHARS: &str = "~`#$&*()\\|[]{};'\"<>?! "; const SPECIAL_SHELL_CHARS: &str = "~`#$&*()|[]{};\\'\"<>?! ";
pub(super) enum QuotingStyle { pub(super) enum QuotingStyle {
Shell { Shell {
@ -27,12 +27,10 @@ pub(super) enum Quotes {
// This implementation is heavily inspired by the std::char::EscapeDefault implementation // This implementation is heavily inspired by the std::char::EscapeDefault implementation
// in the Rust standard library. This custom implementation is needed because the // in the Rust standard library. This custom implementation is needed because the
// characters \a, \b, \e, \f & \v are not recognized by Rust. // characters \a, \b, \e, \f & \v are not recognized by Rust.
#[derive(Clone, Debug)]
struct EscapedChar { struct EscapedChar {
state: EscapeState, state: EscapeState,
} }
#[derive(Clone, Debug)]
enum EscapeState { enum EscapeState {
Done, Done,
Char(char), Char(char),
@ -41,14 +39,12 @@ enum EscapeState {
Octal(EscapeOctal), Octal(EscapeOctal),
} }
#[derive(Clone, Debug)]
struct EscapeOctal { struct EscapeOctal {
c: char, c: char,
state: EscapeOctalState, state: EscapeOctalState,
idx: usize, idx: usize,
} }
#[derive(Clone, Debug)]
enum EscapeOctalState { enum EscapeOctalState {
Done, Done,
Backslash, Backslash,
@ -135,7 +131,6 @@ impl EscapedChar {
'\x0B' => Backslash('v'), '\x0B' => Backslash('v'),
'\x0C' => Backslash('f'), '\x0C' => Backslash('f'),
'\r' => Backslash('r'), '\r' => Backslash('r'),
'\\' => Backslash('\\'),
'\x00'..='\x1F' | '\x7F' => Octal(EscapeOctal::from(c)), '\x00'..='\x1F' | '\x7F' => Octal(EscapeOctal::from(c)),
'\'' => match quotes { '\'' => match quotes {
Quotes::Single => Backslash('\''), Quotes::Single => Backslash('\''),
@ -176,7 +171,7 @@ impl Iterator for EscapedChar {
} }
} }
fn shell_without_escape(name: String, quotes: Quotes, show_control_chars: bool) -> (String, bool) { fn shell_without_escape(name: &str, quotes: Quotes, show_control_chars: bool) -> (String, bool) {
let mut must_quote = false; let mut must_quote = false;
let mut escaped_str = String::with_capacity(name.len()); let mut escaped_str = String::with_capacity(name.len());
@ -206,7 +201,7 @@ fn shell_without_escape(name: String, quotes: Quotes, show_control_chars: bool)
(escaped_str, must_quote) (escaped_str, must_quote)
} }
fn shell_with_escape(name: String, quotes: Quotes) -> (String, bool) { fn shell_with_escape(name: &str, quotes: Quotes) -> (String, bool) {
// We need to keep track of whether we are in a dollar expression // We need to keep track of whether we are in a dollar expression
// because e.g. \b\n is escaped as $'\b\n' and not like $'b'$'n' // because e.g. \b\n is escaped as $'\b\n' and not like $'b'$'n'
let mut in_dollar = false; let mut in_dollar = false;
@ -254,7 +249,7 @@ fn shell_with_escape(name: String, quotes: Quotes) -> (String, bool) {
(escaped_str, must_quote) (escaped_str, must_quote)
} }
pub(super) fn escape_name(name: String, style: &QuotingStyle) -> String { pub(super) fn escape_name(name: &str, style: &QuotingStyle) -> String {
match style { match style {
QuotingStyle::Literal { show_control } => { QuotingStyle::Literal { show_control } => {
if !show_control { if !show_control {
@ -262,7 +257,7 @@ pub(super) fn escape_name(name: String, style: &QuotingStyle) -> String {
.flat_map(|c| EscapedChar::new_literal(c).hide_control()) .flat_map(|c| EscapedChar::new_literal(c).hide_control())
.collect() .collect()
} else { } else {
name name.into()
} }
} }
QuotingStyle::C { quotes } => { QuotingStyle::C { quotes } => {
@ -359,7 +354,7 @@ mod tests {
fn check_names(name: &str, map: Vec<(&str, &str)>) { fn check_names(name: &str, map: Vec<(&str, &str)>) {
assert_eq!( assert_eq!(
map.iter() map.iter()
.map(|(_, style)| escape_name(name.to_string(), &get_style(style))) .map(|(_, style)| escape_name(name, &get_style(style)))
.collect::<Vec<String>>(), .collect::<Vec<String>>(),
map.iter() map.iter()
.map(|(correct, _)| correct.to_string()) .map(|(correct, _)| correct.to_string())
@ -511,6 +506,23 @@ mod tests {
], ],
); );
// A control character followed by a special shell character
check_names(
"one\n&two",
vec![
("one?&two", "literal"),
("one\n&two", "literal-show"),
("one\\n&two", "escape"),
("\"one\\n&two\"", "c"),
("'one?&two'", "shell"),
("'one\n&two'", "shell-show"),
("'one?&two'", "shell-always"),
("'one\n&two'", "shell-always-show"),
("'one'$'\\n''&two'", "shell-escape"),
("'one'$'\\n''&two'", "shell-escape-always"),
],
);
// The first 16 control characters. NUL is also included, even though it is of // The first 16 control characters. NUL is also included, even though it is of
// no importance for file names. // no importance for file names.
check_names( check_names(
@ -627,4 +639,22 @@ mod tests {
], ],
); );
} }
#[test]
fn test_backslash() {
// Escaped in C-style, but not in Shell-style escaping
check_names(
"one\\two",
vec![
("one\\two", "literal"),
("one\\two", "literal-show"),
("one\\\\two", "escape"),
("\"one\\\\two\"", "c"),
("'one\\two'", "shell"),
("\'one\\two\'", "shell-always"),
("'one\\two'", "shell-escape"),
("'one\\two'", "shell-escape-always"),
],
);
}
} }

View file

@ -1,8 +1,9 @@
use std::{cmp::Ordering, path::PathBuf}; use std::cmp::Ordering;
use std::path::Path;
/// Compare pathbufs in a way that matches the GNU version sort, meaning that /// Compare paths in a way that matches the GNU version sort, meaning that
/// numbers get sorted in a natural way. /// numbers get sorted in a natural way.
pub(crate) fn version_cmp(a: &PathBuf, b: &PathBuf) -> Ordering { pub(crate) fn version_cmp(a: &Path, b: &Path) -> Ordering {
let a_string = a.to_string_lossy(); let a_string = a.to_string_lossy();
let b_string = b.to_string_lossy(); let b_string = b.to_string_lossy();
let mut a = a_string.chars().peekable(); let mut a = a_string.chars().peekable();

View file

@ -101,7 +101,7 @@ fn exec(dirs: Vec<String>, recursive: bool, mode: u16, verbose: bool) -> i32 {
if !recursive { if !recursive {
if let Some(parent) = path.parent() { if let Some(parent) = path.parent() {
if parent != empty && !parent.exists() { if parent != empty && !parent.exists() {
show_info!( show_error!(
"cannot create directory '{}': No such file or directory", "cannot create directory '{}': No such file or directory",
path.display() path.display()
); );
@ -125,7 +125,7 @@ fn mkdir(path: &Path, recursive: bool, mode: u16, verbose: bool) -> i32 {
fs::create_dir fs::create_dir
}; };
if let Err(e) = create_dir(path) { if let Err(e) = create_dir(path) {
show_info!("{}: {}", path.display(), e.to_string()); show_error!("{}: {}", path.display(), e.to_string());
return 1; return 1;
} }

View file

@ -11,6 +11,7 @@ extern crate uucore;
use clap::{App, Arg}; use clap::{App, Arg};
use libc::mkfifo; use libc::mkfifo;
use std::ffi::CString; use std::ffi::CString;
use uucore::InvalidEncodingHandling;
static NAME: &str = "mkfifo"; static NAME: &str = "mkfifo";
static VERSION: &str = env!("CARGO_PKG_VERSION"); static VERSION: &str = env!("CARGO_PKG_VERSION");
@ -25,7 +26,9 @@ mod options {
} }
pub fn uumain(args: impl uucore::Args) -> i32 { pub fn uumain(args: impl uucore::Args) -> i32 {
let args = args.collect_str(); let args = args
.collect_str(InvalidEncodingHandling::Ignore)
.accept_any();
let matches = App::new(executable!()) let matches = App::new(executable!())
.name(NAME) .name(NAME)

View file

@ -16,7 +16,7 @@ name = "uu_mknod"
path = "src/mknod.rs" path = "src/mknod.rs"
[dependencies] [dependencies]
getopts = "0.2.18" clap = "2.33"
libc = "^0.2.42" libc = "^0.2.42"
uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["mode"] } uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["mode"] }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }

View file

@ -5,22 +5,41 @@
// For the full copyright and license information, please view the LICENSE // For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code. // file that was distributed with this source code.
// spell-checker:ignore (ToDO) parsemode makedev sysmacros makenod newmode perror IFBLK IFCHR IFIFO // spell-checker:ignore (ToDO) parsemode makedev sysmacros perror IFBLK IFCHR IFIFO
mod parsemode;
#[macro_use] #[macro_use]
extern crate uucore; extern crate uucore;
use std::ffi::CString;
use clap::{App, Arg, ArgMatches};
use libc::{dev_t, mode_t}; use libc::{dev_t, mode_t};
use libc::{S_IFBLK, S_IFCHR, S_IFIFO, S_IRGRP, S_IROTH, S_IRUSR, S_IWGRP, S_IWOTH, S_IWUSR}; use libc::{S_IFBLK, S_IFCHR, S_IFIFO, S_IRGRP, S_IROTH, S_IRUSR, S_IWGRP, S_IWOTH, S_IWUSR};
use getopts::Options; use uucore::InvalidEncodingHandling;
use std::ffi::CString;
static NAME: &str = "mknod"; static NAME: &str = "mknod";
static VERSION: &str = env!("CARGO_PKG_VERSION"); static VERSION: &str = env!("CARGO_PKG_VERSION");
static ABOUT: &str = "Create the special file NAME of the given TYPE.";
static USAGE: &str = "mknod [OPTION]... NAME TYPE [MAJOR MINOR]";
static LONG_HELP: &str = "Mandatory arguments to long options are mandatory for short options too.
-m, --mode=MODE set file permission bits to MODE, not a=rw - umask
--help display this help and exit
--version output version information and exit
Both MAJOR and MINOR must be specified when TYPE is b, c, or u, and they
must be omitted when TYPE is p. If MAJOR or MINOR begins with 0x or 0X,
it is interpreted as hexadecimal; otherwise, if it begins with 0, as octal;
otherwise, as decimal. TYPE may be:
b create a block (buffered) special file
c, u create a character (unbuffered) special file
p create a FIFO
NOTE: your shell may have its own version of mknod, which usually supersedes
the version described here. Please refer to your shell's documentation
for details about the options it supports.
";
const MODE_RW_UGO: mode_t = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH; const MODE_RW_UGO: mode_t = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH;
@ -31,168 +50,172 @@ fn makedev(maj: u64, min: u64) -> dev_t {
} }
#[cfg(windows)] #[cfg(windows)]
fn _makenod(path: CString, mode: mode_t, dev: dev_t) -> i32 { fn _makenod(file_name: &str, mode: mode_t, dev: dev_t) -> i32 {
panic!("Unsupported for windows platform") panic!("Unsupported for windows platform")
} }
#[cfg(unix)] #[cfg(unix)]
fn _makenod(path: CString, mode: mode_t, dev: dev_t) -> i32 { fn _makenod(file_name: &str, mode: mode_t, dev: dev_t) -> i32 {
unsafe { libc::mknod(path.as_ptr(), mode, dev) } let c_str = CString::new(file_name).expect("Failed to convert to CString");
// the user supplied a mode
let set_umask = mode & MODE_RW_UGO != MODE_RW_UGO;
unsafe {
// store prev umask
let last_umask = if set_umask { libc::umask(0) } else { 0 };
let errno = libc::mknod(c_str.as_ptr(), mode, dev);
// set umask back to original value
if set_umask {
libc::umask(last_umask);
}
if errno == -1 {
let c_str = CString::new(NAME).expect("Failed to convert to CString");
// shows the error from the mknod syscall
libc::perror(c_str.as_ptr());
}
errno
}
} }
#[allow(clippy::cognitive_complexity)] #[allow(clippy::cognitive_complexity)]
pub fn uumain(args: impl uucore::Args) -> i32 { pub fn uumain(args: impl uucore::Args) -> i32 {
let args = args.collect_str(); let args = args
.collect_str(InvalidEncodingHandling::Ignore)
let mut opts = Options::new(); .accept_any();
// Linux-specific options, not implemented // Linux-specific options, not implemented
// opts.optflag("Z", "", "set the SELinux security context to default type"); // opts.optflag("Z", "", "set the SELinux security context to default type");
// opts.optopt("", "context", "like -Z, or if CTX is specified then set the SELinux or SMACK security context to CTX"); // opts.optopt("", "context", "like -Z, or if CTX is specified then set the SELinux or SMACK security context to CTX");
opts.optopt(
"m",
"mode",
"set file permission bits to MODE, not a=rw - umask",
"MODE",
);
opts.optflag("", "help", "display this help and exit"); let matches = App::new(executable!())
opts.optflag("", "version", "output version information and exit"); .version(VERSION)
.usage(USAGE)
.after_help(LONG_HELP)
.about(ABOUT)
.arg(
Arg::with_name("mode")
.short("m")
.long("mode")
.value_name("MODE")
.help("set file permission bits to MODE, not a=rw - umask"),
)
.arg(
Arg::with_name("name")
.value_name("NAME")
.help("name of the new file")
.required(true)
.index(1),
)
.arg(
Arg::with_name("type")
.value_name("TYPE")
.help("type of the new file (b, c, u or p)")
.required(true)
.validator(valid_type)
.index(2),
)
.arg(
Arg::with_name("major")
.value_name("MAJOR")
.help("major file type")
.validator(valid_u64)
.index(3),
)
.arg(
Arg::with_name("minor")
.value_name("MINOR")
.help("minor file type")
.validator(valid_u64)
.index(4),
)
.get_matches_from(args);
let matches = match opts.parse(&args[1..]) { let mode = match get_mode(&matches) {
Ok(m) => m, Ok(mode) => mode,
Err(f) => crash!(1, "{}\nTry '{} --help' for more information.", f, NAME), Err(err) => {
show_error!("{}", err);
return 1;
}
}; };
if matches.opt_present("help") { let file_name = matches.value_of("name").expect("Missing argument 'NAME'");
println!(
"Usage: {0} [OPTION]... NAME TYPE [MAJOR MINOR]
Mandatory arguments to long options are mandatory for short options too. // Only check the first character, to allow mnemonic usage like
-m, --mode=MODE set file permission bits to MODE, not a=rw - umask // 'mknod /dev/rst0 character 18 0'.
--help display this help and exit let ch = matches
--version output version information and exit .value_of("type")
.expect("Missing argument 'TYPE'")
.chars()
.next()
.expect("Failed to get the first char");
Both MAJOR and MINOR must be specified when TYPE is b, c, or u, and they if ch == 'p' {
must be omitted when TYPE is p. If MAJOR or MINOR begins with 0x or 0X, if matches.is_present("major") || matches.is_present("minor") {
it is interpreted as hexadecimal; otherwise, if it begins with 0, as octal; eprintln!("Fifos do not have major and minor device numbers.");
otherwise, as decimal. TYPE may be: eprintln!("Try '{} --help' for more information.", NAME);
1
b create a block (buffered) special file } else {
c, u create a character (unbuffered) special file _makenod(file_name, S_IFIFO | mode, 0)
p create a FIFO
NOTE: your shell may have its own version of mknod, which usually supersedes
the version described here. Please refer to your shell's documentation
for details about the options it supports.",
NAME
);
return 0;
}
if matches.opt_present("version") {
println!("{} {}", NAME, VERSION);
return 0;
}
let mut last_umask: mode_t = 0;
let mut newmode: mode_t = MODE_RW_UGO;
if matches.opt_present("mode") {
match parsemode::parse_mode(matches.opt_str("mode")) {
Ok(parsed) => {
if parsed > 0o777 {
show_info!("mode must specify only file permission bits");
return 1;
}
newmode = parsed;
}
Err(e) => {
show_info!("{}", e);
return 1;
}
} }
unsafe { } else {
last_umask = libc::umask(0); match (matches.value_of("major"), matches.value_of("minor")) {
} (None, None) | (_, None) | (None, _) => {
} eprintln!("Special files require major and minor device numbers.");
eprintln!("Try '{} --help' for more information.", NAME);
1
}
(Some(major), Some(minor)) => {
let major = major.parse::<u64>().expect("validated by clap");
let minor = minor.parse::<u64>().expect("validated by clap");
let mut ret = 0i32; let dev = makedev(major, minor);
match matches.free.len() {
0 => show_usage_error!("missing operand"),
1 => show_usage_error!("missing operand after {}", matches.free[0]),
_ => {
let args = &matches.free;
let c_str = CString::new(args[0].as_str()).expect("Failed to convert to CString");
// Only check the first character, to allow mnemonic usage like
// 'mknod /dev/rst0 character 18 0'.
let ch = args[1]
.chars()
.next()
.expect("Failed to get the first char");
if ch == 'p' {
if args.len() > 2 {
show_info!("{}: extra operand {}", NAME, args[2]);
if args.len() == 4 {
eprintln!("Fifos do not have major and minor device numbers.");
}
eprintln!("Try '{} --help' for more information.", NAME);
return 1;
}
ret = _makenod(c_str, S_IFIFO | newmode, 0);
} else {
if args.len() < 4 {
show_info!("missing operand after {}", args[args.len() - 1]);
if args.len() == 2 {
eprintln!("Special files require major and minor device numbers.");
}
eprintln!("Try '{} --help' for more information.", NAME);
return 1;
} else if args.len() > 4 {
show_usage_error!("extra operand {}", args[4]);
return 1;
} else if !"bcu".contains(ch) {
show_usage_error!("invalid device type {}", args[1]);
return 1;
}
let maj = args[2].parse::<u64>();
let min = args[3].parse::<u64>();
if maj.is_err() {
show_info!("invalid major device number {}", args[2]);
return 1;
} else if min.is_err() {
show_info!("invalid minor device number {}", args[3]);
return 1;
}
let (maj, min) = (maj.unwrap(), min.unwrap());
let dev = makedev(maj, min);
if ch == 'b' { if ch == 'b' {
// block special file // block special file
ret = _makenod(c_str, S_IFBLK | newmode, dev); _makenod(file_name, S_IFBLK | mode, dev)
} else { } else if ch == 'c' || ch == 'u' {
// char special file // char special file
ret = _makenod(c_str, S_IFCHR | newmode, dev); _makenod(file_name, S_IFCHR | mode, dev)
} else {
unreachable!("{} was validated to be only b, c or u", ch);
} }
} }
} }
} }
}
if last_umask != 0 {
unsafe { fn get_mode(matches: &ArgMatches) -> Result<mode_t, String> {
libc::umask(last_umask); match matches.value_of("mode") {
} None => Ok(MODE_RW_UGO),
} Some(str_mode) => uucore::mode::parse_mode(str_mode)
if ret == -1 { .map_err(|e| format!("invalid mode ({})", e))
let c_str = CString::new(format!("{}: {}", NAME, matches.free[0]).as_str()) .and_then(|mode| {
.expect("Failed to convert to CString"); if mode > 0o777 {
unsafe { Err("mode must specify only file permission bits".to_string())
libc::perror(c_str.as_ptr()); } else {
} Ok(mode)
} }
}),
ret }
}
fn valid_type(tpe: String) -> Result<(), String> {
// Only check the first character, to allow mnemonic usage like
// 'mknod /dev/rst0 character 18 0'.
tpe.chars()
.next()
.ok_or_else(|| "missing device type".to_string())
.and_then(|first_char| {
if vec!['b', 'c', 'u', 'p'].contains(&first_char) {
Ok(())
} else {
Err(format!("invalid device type {}", tpe))
}
})
}
fn valid_u64(num: String) -> Result<(), String> {
num.parse::<u64>().map(|_| ()).map_err(|_| num)
} }

View file

@ -4,19 +4,16 @@ use libc::{mode_t, S_IRGRP, S_IROTH, S_IRUSR, S_IWGRP, S_IWOTH, S_IWUSR};
use uucore::mode; use uucore::mode;
pub fn parse_mode(mode: Option<String>) -> Result<mode_t, String> { pub const MODE_RW_UGO: mode_t = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH;
let fperm = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH;
if let Some(mode) = mode { pub fn parse_mode(mode: &str) -> Result<mode_t, String> {
let arr: &[char] = &['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']; let arr: &[char] = &['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
let result = if mode.contains(arr) { let result = if mode.contains(arr) {
mode::parse_numeric(fperm as u32, mode.as_str()) mode::parse_numeric(MODE_RW_UGO as u32, mode)
} else {
mode::parse_symbolic(fperm as u32, mode.as_str(), true)
};
result.map(|mode| mode as mode_t)
} else { } else {
Ok(fperm) mode::parse_symbolic(MODE_RW_UGO as u32, mode, true)
} };
result.map(|mode| mode as mode_t)
} }
#[cfg(test)] #[cfg(test)]
@ -39,20 +36,19 @@ mod test {
#[test] #[test]
fn symbolic_modes() { fn symbolic_modes() {
assert_eq!(super::parse_mode(Some("u+x".to_owned())).unwrap(), 0o766); assert_eq!(super::parse_mode("u+x").unwrap(), 0o766);
assert_eq!( assert_eq!(
super::parse_mode(Some("+x".to_owned())).unwrap(), super::parse_mode("+x").unwrap(),
if !is_wsl() { 0o777 } else { 0o776 } if !is_wsl() { 0o777 } else { 0o776 }
); );
assert_eq!(super::parse_mode(Some("a-w".to_owned())).unwrap(), 0o444); assert_eq!(super::parse_mode("a-w").unwrap(), 0o444);
assert_eq!(super::parse_mode(Some("g-r".to_owned())).unwrap(), 0o626); assert_eq!(super::parse_mode("g-r").unwrap(), 0o626);
} }
#[test] #[test]
fn numeric_modes() { fn numeric_modes() {
assert_eq!(super::parse_mode(Some("644".to_owned())).unwrap(), 0o644); assert_eq!(super::parse_mode("644").unwrap(), 0o644);
assert_eq!(super::parse_mode(Some("+100".to_owned())).unwrap(), 0o766); assert_eq!(super::parse_mode("+100").unwrap(), 0o766);
assert_eq!(super::parse_mode(Some("-4".to_owned())).unwrap(), 0o662); assert_eq!(super::parse_mode("-4").unwrap(), 0o662);
assert_eq!(super::parse_mode(None).unwrap(), 0o666);
} }
} }

View file

@ -15,14 +15,11 @@ use clap::{App, Arg};
use std::env; use std::env;
use std::iter; use std::iter;
use std::mem::forget;
use std::path::{is_separator, PathBuf}; use std::path::{is_separator, PathBuf};
use rand::Rng; use rand::Rng;
use tempfile::Builder; use tempfile::Builder;
mod tempdir;
static ABOUT: &str = "create a temporary file or directory."; static ABOUT: &str = "create a temporary file or directory.";
static VERSION: &str = env!("CARGO_PKG_VERSION"); static VERSION: &str = env!("CARGO_PKG_VERSION");
@ -157,7 +154,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
} }
if matches.is_present(OPT_TMPDIR) && PathBuf::from(prefix).is_absolute() { if matches.is_present(OPT_TMPDIR) && PathBuf::from(prefix).is_absolute() {
show_info!( show_error!(
"invalid template, {}; with --tmpdir, it may not be absolute", "invalid template, {}; with --tmpdir, it may not be absolute",
template template
); );
@ -213,50 +210,48 @@ pub fn dry_exec(mut tmpdir: PathBuf, prefix: &str, rand: usize, suffix: &str) ->
0 0
} }
fn exec( fn exec(dir: PathBuf, prefix: &str, rand: usize, suffix: &str, make_dir: bool, quiet: bool) -> i32 {
tmpdir: PathBuf, let res = if make_dir {
prefix: &str, let tmpdir = Builder::new()
rand: usize, .prefix(prefix)
suffix: &str, .rand_bytes(rand)
make_dir: bool, .suffix(suffix)
quiet: bool, .tempdir_in(&dir);
) -> i32 {
if make_dir { // `into_path` consumes the TempDir without removing it
match tempdir::new_in(&tmpdir, prefix, rand, suffix) { tmpdir.map(|d| d.into_path().to_string_lossy().to_string())
Ok(ref f) => { } else {
println!("{}", f); let tmpfile = Builder::new()
return 0; .prefix(prefix)
} .rand_bytes(rand)
Err(e) => { .suffix(suffix)
if !quiet { .tempfile_in(&dir);
show_info!("{}: {}", e, tmpdir.display());
match tmpfile {
Ok(f) => {
// `keep` ensures that the file is not deleted
match f.keep() {
Ok((_, p)) => Ok(p.to_string_lossy().to_string()),
Err(e) => {
show_error!("'{}': {}", dir.display(), e);
return 1;
}
} }
return 1;
} }
} Err(x) => Err(x),
}
let tmpfile = Builder::new()
.prefix(prefix)
.rand_bytes(rand)
.suffix(suffix)
.tempfile_in(tmpdir);
let tmpfile = match tmpfile {
Ok(f) => f,
Err(e) => {
if !quiet {
show_info!("failed to create tempfile: {}", e);
}
return 1;
} }
}; };
let tmpname = tmpfile.path().to_string_lossy().to_string(); match res {
Ok(ref f) => {
println!("{}", tmpname); println!("{}", f);
0
// CAUTION: Not to call `drop` of tmpfile, which removes the tempfile, }
// I call a dangerous function `forget`. Err(e) => {
forget(tmpfile); if !quiet {
show_error!("{}: {}", e, dir.display());
0 }
1
}
}
} }

View file

@ -1,51 +0,0 @@
// spell-checker:ignore (ToDO) tempdir tmpdir
// Mainly taken from crate `tempdir`
use rand::distributions::Alphanumeric;
use rand::{thread_rng, Rng};
use std::io::Result as IOResult;
use std::io::{Error, ErrorKind};
use std::path::Path;
// How many times should we (re)try finding an unused random name? It should be
// enough that an attacker will run out of luck before we run out of patience.
const NUM_RETRIES: u32 = 1 << 31;
#[cfg(any(unix, target_os = "redox"))]
fn create_dir<P: AsRef<Path>>(path: P) -> IOResult<()> {
use std::fs::DirBuilder;
use std::os::unix::fs::DirBuilderExt;
DirBuilder::new().mode(0o700).create(path)
}
#[cfg(windows)]
fn create_dir<P: AsRef<Path>>(path: P) -> IOResult<()> {
::std::fs::create_dir(path)
}
pub fn new_in<P: AsRef<Path>>(
tmpdir: P,
prefix: &str,
rand: usize,
suffix: &str,
) -> IOResult<String> {
let mut rng = thread_rng();
for _ in 0..NUM_RETRIES {
let rand_chars: String = rng.sample_iter(&Alphanumeric).take(rand).collect();
let leaf = format!("{}{}{}", prefix, rand_chars, suffix);
let path = tmpdir.as_ref().join(&leaf);
match create_dir(&path) {
Ok(_) => return Ok(path.to_string_lossy().into_owned()),
Err(ref e) if e.kind() == ErrorKind::AlreadyExists => {}
Err(e) => return Err(e),
}
}
Err(Error::new(
ErrorKind::AlreadyExists,
"too many temporary directories already exist",
))
}

View file

@ -17,6 +17,7 @@ use std::io::{stdin, stdout, BufRead, BufReader, Read, Write};
extern crate nix; extern crate nix;
#[cfg(all(unix, not(target_os = "fuchsia")))] #[cfg(all(unix, not(target_os = "fuchsia")))]
use nix::sys::termios::{self, LocalFlags, SetArg}; use nix::sys::termios::{self, LocalFlags, SetArg};
use uucore::InvalidEncodingHandling;
#[cfg(target_os = "redox")] #[cfg(target_os = "redox")]
extern crate redox_termios; extern crate redox_termios;
@ -38,6 +39,9 @@ fn get_usage() -> String {
pub fn uumain(args: impl uucore::Args) -> i32 { pub fn uumain(args: impl uucore::Args) -> i32 {
let usage = get_usage(); let usage = get_usage();
let args = args
.collect_str(InvalidEncodingHandling::ConvertLossy)
.accept_any();
let matches = App::new(executable!()) let matches = App::new(executable!())
.version(VERSION) .version(VERSION)

View file

@ -20,6 +20,7 @@ use std::os::unix;
#[cfg(windows)] #[cfg(windows)]
use std::os::windows; use std::os::windows;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use uucore::backup_control::{self, BackupMode};
use fs_extra::dir::{move_dir, CopyOptions as DirCopyOptions}; use fs_extra::dir::{move_dir, CopyOptions as DirCopyOptions};
@ -40,16 +41,9 @@ pub enum OverwriteMode {
Force, Force,
} }
#[derive(Clone, Copy, Eq, PartialEq)]
pub enum BackupMode {
NoBackup,
SimpleBackup,
NumberedBackup,
ExistingBackup,
}
static ABOUT: &str = "Move SOURCE to DEST, or multiple SOURCE(s) to DIRECTORY."; static ABOUT: &str = "Move SOURCE to DEST, or multiple SOURCE(s) to DIRECTORY.";
static VERSION: &str = env!("CARGO_PKG_VERSION"); static VERSION: &str = env!("CARGO_PKG_VERSION");
static LONG_HELP: &str = "";
static OPT_BACKUP: &str = "backup"; static OPT_BACKUP: &str = "backup";
static OPT_BACKUP_NO_ARG: &str = "b"; static OPT_BACKUP_NO_ARG: &str = "b";
@ -80,20 +74,16 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
let matches = App::new(executable!()) let matches = App::new(executable!())
.version(VERSION) .version(VERSION)
.about(ABOUT) .about(ABOUT)
.after_help(&*format!("{}\n{}", LONG_HELP, backup_control::BACKUP_CONTROL_LONG_HELP))
.usage(&usage[..]) .usage(&usage[..])
.arg( .arg(
Arg::with_name(OPT_BACKUP) Arg::with_name(OPT_BACKUP)
.long(OPT_BACKUP) .long(OPT_BACKUP)
.help("make a backup of each existing destination file") .help("make a backup of each existing destination file")
.takes_value(true) .takes_value(true)
.possible_value("simple") .require_equals(true)
.possible_value("never") .min_values(0)
.possible_value("numbered") .possible_values(backup_control::BACKUP_CONTROL_VALUES)
.possible_value("t")
.possible_value("existing")
.possible_value("nil")
.possible_value("none")
.possible_value("off")
.value_name("CONTROL") .value_name("CONTROL")
) )
.arg( .arg(
@ -172,18 +162,17 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.unwrap_or_default(); .unwrap_or_default();
let overwrite_mode = determine_overwrite_mode(&matches); let overwrite_mode = determine_overwrite_mode(&matches);
let backup_mode = determine_backup_mode(&matches); let backup_mode = backup_control::determine_backup_mode(
matches.is_present(OPT_BACKUP_NO_ARG) || matches.is_present(OPT_BACKUP),
matches.value_of(OPT_BACKUP),
);
if overwrite_mode == OverwriteMode::NoClobber && backup_mode != BackupMode::NoBackup { if overwrite_mode == OverwriteMode::NoClobber && backup_mode != BackupMode::NoBackup {
show_error!( show_usage_error!("options --backup and --no-clobber are mutually exclusive");
"options --backup and --no-clobber are mutually exclusive\n\
Try '{} --help' for more information.",
executable!()
);
return 1; return 1;
} }
let backup_suffix = determine_backup_suffix(backup_mode, &matches); let backup_suffix = backup_control::determine_backup_suffix(matches.value_of(OPT_SUFFIX));
let behavior = Behavior { let behavior = Behavior {
overwrite: overwrite_mode, overwrite: overwrite_mode,
@ -227,37 +216,6 @@ fn determine_overwrite_mode(matches: &ArgMatches) -> OverwriteMode {
} }
} }
fn determine_backup_mode(matches: &ArgMatches) -> BackupMode {
if matches.is_present(OPT_BACKUP_NO_ARG) {
BackupMode::SimpleBackup
} else if matches.is_present(OPT_BACKUP) {
match matches.value_of(OPT_BACKUP).map(String::from) {
None => BackupMode::SimpleBackup,
Some(mode) => match &mode[..] {
"simple" | "never" => BackupMode::SimpleBackup,
"numbered" | "t" => BackupMode::NumberedBackup,
"existing" | "nil" => BackupMode::ExistingBackup,
"none" | "off" => BackupMode::NoBackup,
_ => panic!(), // cannot happen as it is managed by clap
},
}
} else {
BackupMode::NoBackup
}
}
fn determine_backup_suffix(backup_mode: BackupMode, matches: &ArgMatches) -> String {
if matches.is_present(OPT_SUFFIX) {
matches.value_of(OPT_SUFFIX).map(String::from).unwrap()
} else if let (Ok(s), BackupMode::SimpleBackup) =
(env::var("SIMPLE_BACKUP_SUFFIX"), backup_mode)
{
s
} else {
"~".to_owned()
}
}
fn exec(files: &[PathBuf], b: Behavior) -> i32 { fn exec(files: &[PathBuf], b: Behavior) -> i32 {
if let Some(ref name) = b.target_dir { if let Some(ref name) = b.target_dir {
return move_files_into_dir(files, &PathBuf::from(name), &b); return move_files_into_dir(files, &PathBuf::from(name), &b);
@ -295,7 +253,7 @@ fn exec(files: &[PathBuf], b: Behavior) -> i32 {
"cannot move {} to {}: {}", "cannot move {} to {}: {}",
source.display(), source.display(),
target.display(), target.display(),
e e.to_string()
); );
1 1
} }
@ -335,7 +293,7 @@ fn exec(files: &[PathBuf], b: Behavior) -> i32 {
0 0
} }
fn move_files_into_dir(files: &[PathBuf], target_dir: &PathBuf, b: &Behavior) -> i32 { fn move_files_into_dir(files: &[PathBuf], target_dir: &Path, b: &Behavior) -> i32 {
if !target_dir.is_dir() { if !target_dir.is_dir() {
show_error!("target {} is not a directory", target_dir.display()); show_error!("target {} is not a directory", target_dir.display());
return 1; return 1;
@ -358,14 +316,15 @@ fn move_files_into_dir(files: &[PathBuf], target_dir: &PathBuf, b: &Behavior) ->
if let Err(e) = rename(sourcepath, &targetpath, b) { if let Err(e) = rename(sourcepath, &targetpath, b) {
show_error!( show_error!(
"mv: cannot move {} to {}: {}", "cannot move {} to {}: {}",
sourcepath.display(), sourcepath.display(),
targetpath.display(), targetpath.display(),
e e.to_string()
); );
all_successful = false; all_successful = false;
} }
} }
if all_successful { if all_successful {
0 0
} else { } else {
@ -373,7 +332,7 @@ fn move_files_into_dir(files: &[PathBuf], target_dir: &PathBuf, b: &Behavior) ->
} }
} }
fn rename(from: &PathBuf, to: &PathBuf, b: &Behavior) -> io::Result<()> { fn rename(from: &Path, to: &Path, b: &Behavior) -> io::Result<()> {
let mut backup_path = None; let mut backup_path = None;
if to.exists() { if to.exists() {
@ -388,12 +347,7 @@ fn rename(from: &PathBuf, to: &PathBuf, b: &Behavior) -> io::Result<()> {
OverwriteMode::Force => {} OverwriteMode::Force => {}
}; };
backup_path = match b.backup { backup_path = backup_control::get_backup_path(b.backup, to, &b.suffix);
BackupMode::NoBackup => None,
BackupMode::SimpleBackup => Some(simple_backup_path(to, &b.suffix)),
BackupMode::NumberedBackup => Some(numbered_backup_path(to)),
BackupMode::ExistingBackup => Some(existing_backup_path(to, &b.suffix)),
};
if let Some(ref backup_path) = backup_path { if let Some(ref backup_path) = backup_path {
rename_with_fallback(to, backup_path)?; rename_with_fallback(to, backup_path)?;
} }
@ -429,7 +383,7 @@ fn rename(from: &PathBuf, to: &PathBuf, b: &Behavior) -> io::Result<()> {
/// A wrapper around `fs::rename`, so that if it fails, we try falling back on /// A wrapper around `fs::rename`, so that if it fails, we try falling back on
/// copying and removing. /// copying and removing.
fn rename_with_fallback(from: &PathBuf, to: &PathBuf) -> io::Result<()> { fn rename_with_fallback(from: &Path, to: &Path) -> io::Result<()> {
if fs::rename(from, to).is_err() { if fs::rename(from, to).is_err() {
// Get metadata without following symlinks // Get metadata without following symlinks
let metadata = from.symlink_metadata()?; let metadata = from.symlink_metadata()?;
@ -452,7 +406,13 @@ fn rename_with_fallback(from: &PathBuf, to: &PathBuf) -> io::Result<()> {
..DirCopyOptions::new() ..DirCopyOptions::new()
}; };
if let Err(err) = move_dir(from, to, &options) { if let Err(err) = move_dir(from, to, &options) {
return Err(io::Error::new(io::ErrorKind::Other, format!("{:?}", err))); return match err.kind {
fs_extra::error::ErrorKind::PermissionDenied => Err(io::Error::new(
io::ErrorKind::PermissionDenied,
"Permission denied",
)),
_ => Err(io::Error::new(io::ErrorKind::Other, format!("{:?}", err))),
};
} }
} else { } else {
fs::copy(from, to).and_then(|_| fs::remove_file(from))?; fs::copy(from, to).and_then(|_| fs::remove_file(from))?;
@ -464,7 +424,7 @@ fn rename_with_fallback(from: &PathBuf, to: &PathBuf) -> io::Result<()> {
/// Move the given symlink to the given destination. On Windows, dangling /// Move the given symlink to the given destination. On Windows, dangling
/// symlinks return an error. /// symlinks return an error.
#[inline] #[inline]
fn rename_symlink_fallback(from: &PathBuf, to: &PathBuf) -> io::Result<()> { fn rename_symlink_fallback(from: &Path, to: &Path) -> io::Result<()> {
let path_symlink_points_to = fs::read_link(from)?; let path_symlink_points_to = fs::read_link(from)?;
#[cfg(unix)] #[cfg(unix)]
{ {
@ -507,29 +467,7 @@ fn read_yes() -> bool {
} }
} }
fn simple_backup_path(path: &PathBuf, suffix: &str) -> PathBuf { fn is_empty_dir(path: &Path) -> bool {
let mut p = path.to_string_lossy().into_owned();
p.push_str(suffix);
PathBuf::from(p)
}
fn numbered_backup_path(path: &PathBuf) -> PathBuf {
(1_u64..)
.map(|i| path.with_extension(format!("~{}~", i)))
.find(|p| !p.exists())
.expect("cannot create backup")
}
fn existing_backup_path(path: &PathBuf, suffix: &str) -> PathBuf {
let test_path = path.with_extension("~1~");
if test_path.exists() {
numbered_backup_path(path)
} else {
simple_backup_path(path, suffix)
}
}
fn is_empty_dir(path: &PathBuf) -> bool {
match fs::read_dir(path) { match fs::read_dir(path) {
Ok(contents) => contents.peekable().peek().is_none(), Ok(contents) => contents.peekable().peek().is_none(),
Err(_e) => false, Err(_e) => false,

View file

@ -16,6 +16,7 @@ use std::fs::File;
use std::io::{stdin, BufRead, BufReader, Read}; use std::io::{stdin, BufRead, BufReader, Read};
use std::iter::repeat; use std::iter::repeat;
use std::path::Path; use std::path::Path;
use uucore::InvalidEncodingHandling;
mod helper; mod helper;
@ -84,7 +85,9 @@ pub mod options {
} }
pub fn uumain(args: impl uucore::Args) -> i32 { pub fn uumain(args: impl uucore::Args) -> i32 {
let args = args.collect_str(); let args = args
.collect_str(InvalidEncodingHandling::ConvertLossy)
.accept_any();
let matches = App::new(executable!()) let matches = App::new(executable!())
.name(NAME) .name(NAME)

View file

@ -20,6 +20,7 @@ use std::io::Error;
use std::os::unix::prelude::*; use std::os::unix::prelude::*;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use uucore::fs::{is_stderr_interactive, is_stdin_interactive, is_stdout_interactive}; use uucore::fs::{is_stderr_interactive, is_stdin_interactive, is_stdout_interactive};
use uucore::InvalidEncodingHandling;
static VERSION: &str = env!("CARGO_PKG_VERSION"); static VERSION: &str = env!("CARGO_PKG_VERSION");
static ABOUT: &str = "Run COMMAND ignoring hangup signals."; static ABOUT: &str = "Run COMMAND ignoring hangup signals.";
@ -42,6 +43,9 @@ mod options {
pub fn uumain(args: impl uucore::Args) -> i32 { pub fn uumain(args: impl uucore::Args) -> i32 {
let usage = get_usage(); let usage = get_usage();
let args = args
.collect_str(InvalidEncodingHandling::ConvertLossy)
.accept_any();
let matches = App::new(executable!()) let matches = App::new(executable!())
.version(VERSION) .version(VERSION)
@ -118,13 +122,13 @@ fn find_stdout() -> File {
.open(Path::new(NOHUP_OUT)) .open(Path::new(NOHUP_OUT))
{ {
Ok(t) => { Ok(t) => {
show_info!("ignoring input and appending output to '{}'", NOHUP_OUT); show_error!("ignoring input and appending output to '{}'", NOHUP_OUT);
t t
} }
Err(e1) => { Err(e1) => {
let home = match env::var("HOME") { let home = match env::var("HOME") {
Err(_) => { Err(_) => {
show_info!("failed to open '{}': {}", NOHUP_OUT, e1); show_error!("failed to open '{}': {}", NOHUP_OUT, e1);
exit!(internal_failure_code) exit!(internal_failure_code)
} }
Ok(h) => h, Ok(h) => h,
@ -139,12 +143,12 @@ fn find_stdout() -> File {
.open(&homeout) .open(&homeout)
{ {
Ok(t) => { Ok(t) => {
show_info!("ignoring input and appending output to '{}'", homeout_str); show_error!("ignoring input and appending output to '{}'", homeout_str);
t t
} }
Err(e2) => { Err(e2) => {
show_info!("failed to open '{}': {}", NOHUP_OUT, e1); show_error!("failed to open '{}': {}", NOHUP_OUT, e1);
show_info!("failed to open '{}': {}", homeout_str, e2); show_error!("failed to open '{}': {}", homeout_str, e2);
exit!(internal_failure_code) exit!(internal_failure_code)
} }
} }

View file

@ -216,7 +216,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
match result { match result {
Err(e) => { Err(e) => {
std::io::stdout().flush().expect("error flushing stdout"); std::io::stdout().flush().expect("error flushing stdout");
show_info!("{}", e); show_error!("{}", e);
1 1
} }
_ => 0, _ => 0,

View file

@ -42,6 +42,7 @@ use crate::partialreader::*;
use crate::peekreader::*; use crate::peekreader::*;
use crate::prn_char::format_ascii_dump; use crate::prn_char::format_ascii_dump;
use clap::{self, AppSettings, Arg, ArgMatches}; use clap::{self, AppSettings, Arg, ArgMatches};
use uucore::InvalidEncodingHandling;
static VERSION: &str = env!("CARGO_PKG_VERSION"); static VERSION: &str = env!("CARGO_PKG_VERSION");
const PEEK_BUFFER_SIZE: usize = 4; // utf-8 can be 4 bytes const PEEK_BUFFER_SIZE: usize = 4; // utf-8 can be 4 bytes
@ -118,7 +119,7 @@ struct OdOptions {
} }
impl OdOptions { impl OdOptions {
fn new<'a>(matches: ArgMatches<'a>, args: Vec<String>) -> Result<OdOptions, String> { fn new(matches: ArgMatches, args: Vec<String>) -> Result<OdOptions, String> {
let byte_order = match matches.value_of(options::ENDIAN) { let byte_order = match matches.value_of(options::ENDIAN) {
None => ByteOrder::Native, None => ByteOrder::Native,
Some("little") => ByteOrder::Little, Some("little") => ByteOrder::Little,
@ -221,7 +222,9 @@ impl OdOptions {
/// parses and validates command line parameters, prepares data structures, /// parses and validates command line parameters, prepares data structures,
/// opens the input and calls `odfunc` to process the input. /// opens the input and calls `odfunc` to process the input.
pub fn uumain(args: impl uucore::Args) -> i32 { pub fn uumain(args: impl uucore::Args) -> i32 {
let args = args.collect_str(); let args = args
.collect_str(InvalidEncodingHandling::Ignore)
.accept_any();
let clap_opts = clap::App::new(executable!()) let clap_opts = clap::App::new(executable!())
.version(VERSION) .version(VERSION)

View file

@ -85,12 +85,7 @@ fn od_format_type(type_char: FormatType, byte_size: u8) -> Option<FormatterItemI
} }
fn od_argument_with_option(ch: char) -> bool { fn od_argument_with_option(ch: char) -> bool {
#[allow(clippy::match_like_matches_macro)] matches!(ch, 'A' | 'j' | 'N' | 'S' | 'w')
// `matches!(...)` macro not stabilized until rust v1.42
match ch {
'A' | 'j' | 'N' | 'S' | 'w' => true,
_ => false,
}
} }
/// Parses format flags from command line /// Parses format flags from command line

View file

@ -63,7 +63,7 @@ pub fn parse_inputs(matches: &dyn CommandLineOpts) -> Result<CommandLineInputs,
} }
if input_strings.len() == 2 { if input_strings.len() == 2 {
return Ok(CommandLineInputs::FileAndOffset(( return Ok(CommandLineInputs::FileAndOffset((
input_strings[0].clone().to_owned(), input_strings[0].to_string(),
n, n,
None, None,
))); )));
@ -106,7 +106,7 @@ pub fn parse_inputs_traditional(input_strings: Vec<&str>) -> Result<CommandLineI
Some(m), Some(m),
))), ))),
(_, Ok(m)) => Ok(CommandLineInputs::FileAndOffset(( (_, Ok(m)) => Ok(CommandLineInputs::FileAndOffset((
input_strings[0].clone().to_owned(), input_strings[0].to_string(),
m, m,
None, None,
))), ))),
@ -118,7 +118,7 @@ pub fn parse_inputs_traditional(input_strings: Vec<&str>) -> Result<CommandLineI
let label = parse_offset_operand(&input_strings[2]); let label = parse_offset_operand(&input_strings[2]);
match (offset, label) { match (offset, label) {
(Ok(n), Ok(m)) => Ok(CommandLineInputs::FileAndOffset(( (Ok(n), Ok(m)) => Ok(CommandLineInputs::FileAndOffset((
input_strings[0].clone().to_owned(), input_strings[0].to_string(),
n, n,
Some(m), Some(m),
))), ))),

View file

@ -15,6 +15,7 @@ extern crate uucore;
use clap::{App, Arg}; use clap::{App, Arg};
use std::fs; use std::fs;
use std::io::{ErrorKind, Write}; use std::io::{ErrorKind, Write};
use uucore::InvalidEncodingHandling;
// operating mode // operating mode
enum Mode { enum Mode {
@ -45,6 +46,9 @@ fn get_usage() -> String {
pub fn uumain(args: impl uucore::Args) -> i32 { pub fn uumain(args: impl uucore::Args) -> i32 {
let usage = get_usage(); let usage = get_usage();
let args = args
.collect_str(InvalidEncodingHandling::ConvertLossy)
.accept_any();
let matches = App::new(executable!()) let matches = App::new(executable!())
.version(VERSION) .version(VERSION)

View file

@ -17,6 +17,7 @@ path = "src/pinky.rs"
[dependencies] [dependencies]
uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["utmpx", "entries"] } uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["utmpx", "entries"] }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
clap = "2.33.3"
[[bin]] [[bin]]
name = "pinky" name = "pinky"

View file

@ -15,69 +15,114 @@ use uucore::utmpx::{self, time, Utmpx};
use std::io::prelude::*; use std::io::prelude::*;
use std::io::BufReader; use std::io::BufReader;
use std::io::Result as IOResult;
use std::fs::File; use std::fs::File;
use std::os::unix::fs::MetadataExt; use std::os::unix::fs::MetadataExt;
use clap::{App, Arg};
use std::path::PathBuf; use std::path::PathBuf;
use uucore::InvalidEncodingHandling;
static SYNTAX: &str = "[OPTION]... [USER]...";
static SUMMARY: &str = "A lightweight 'finger' program; print user information.";
const BUFSIZE: usize = 1024; const BUFSIZE: usize = 1024;
pub fn uumain(args: impl uucore::Args) -> i32 { static VERSION: &str = env!("CARGO_PKG_VERSION");
let args = args.collect_str(); static ABOUT: &str = "pinky - lightweight finger";
let long_help = &format!( mod options {
" pub const LONG_FORMAT: &str = "long_format";
-l produce long format output for the specified USERs pub const OMIT_HOME_DIR: &str = "omit_home_dir";
-b omit the user's home directory and shell in long format pub const OMIT_PROJECT_FILE: &str = "omit_project_file";
-h omit the user's project file in long format pub const OMIT_PLAN_FILE: &str = "omit_plan_file";
-p omit the user's plan file in long format pub const SHORT_FORMAT: &str = "short_format";
-s do short format output, this is the default pub const OMIT_HEADINGS: &str = "omit_headings";
-f omit the line of column headings in short format pub const OMIT_NAME: &str = "omit_name";
-w omit the user's full name in short format pub const OMIT_NAME_HOST: &str = "omit_name_host";
-i omit the user's full name and remote host in short format pub const OMIT_NAME_HOST_TIME: &str = "omit_name_host_time";
-q omit the user's full name, remote host and idle time pub const USER: &str = "user";
in short format }
--help display this help and exit
--version output version information and exit
The utmp file will be {}", fn get_usage() -> String {
format!("{0} [OPTION]... [USER]...", executable!())
}
fn get_long_usage() -> String {
format!(
"A lightweight 'finger' program; print user information.\n\
The utmp file will be {}.",
utmpx::DEFAULT_FILE utmpx::DEFAULT_FILE
); )
let mut opts = app!(SYNTAX, SUMMARY, &long_help); }
opts.optflag(
"l",
"",
"produce long format output for the specified USERs",
);
opts.optflag(
"b",
"",
"omit the user's home directory and shell in long format",
);
opts.optflag("h", "", "omit the user's project file in long format");
opts.optflag("p", "", "omit the user's plan file in long format");
opts.optflag("s", "", "do short format output, this is the default");
opts.optflag("f", "", "omit the line of column headings in short format");
opts.optflag("w", "", "omit the user's full name in short format");
opts.optflag(
"i",
"",
"omit the user's full name and remote host in short format",
);
opts.optflag(
"q",
"",
"omit the user's full name, remote host and idle time in short format",
);
opts.optflag("", "help", "display this help and exit");
opts.optflag("", "version", "output version information and exit");
let matches = opts.parse(args); pub fn uumain(args: impl uucore::Args) -> i32 {
let args = args
.collect_str(InvalidEncodingHandling::Ignore)
.accept_any();
let usage = get_usage();
let after_help = get_long_usage();
let matches = App::new(executable!())
.version(VERSION)
.about(ABOUT)
.usage(&usage[..])
.after_help(&after_help[..])
.arg(
Arg::with_name(options::LONG_FORMAT)
.short("l")
.requires(options::USER)
.help("produce long format output for the specified USERs"),
)
.arg(
Arg::with_name(options::OMIT_HOME_DIR)
.short("b")
.help("omit the user's home directory and shell in long format"),
)
.arg(
Arg::with_name(options::OMIT_PROJECT_FILE)
.short("h")
.help("omit the user's project file in long format"),
)
.arg(
Arg::with_name(options::OMIT_PLAN_FILE)
.short("p")
.help("omit the user's plan file in long format"),
)
.arg(
Arg::with_name(options::SHORT_FORMAT)
.short("s")
.help("do short format output, this is the default"),
)
.arg(
Arg::with_name(options::OMIT_HEADINGS)
.short("f")
.help("omit the line of column headings in short format"),
)
.arg(
Arg::with_name(options::OMIT_NAME)
.short("w")
.help("omit the user's full name in short format"),
)
.arg(
Arg::with_name(options::OMIT_NAME_HOST)
.short("i")
.help("omit the user's full name and remote host in short format"),
)
.arg(
Arg::with_name(options::OMIT_NAME_HOST_TIME)
.short("q")
.help("omit the user's full name, remote host and idle time in short format"),
)
.arg(
Arg::with_name(options::USER)
.takes_value(true)
.multiple(true),
)
.get_matches_from(args);
let users: Vec<String> = matches
.values_of(options::USER)
.map(|v| v.map(ToString::to_string).collect())
.unwrap_or_default();
// If true, display the hours:minutes since each user has touched // If true, display the hours:minutes since each user has touched
// the keyboard, or blank if within the last minute, or days followed // the keyboard, or blank if within the last minute, or days followed
@ -85,45 +130,40 @@ The utmp file will be {}",
let mut include_idle = true; let mut include_idle = true;
// If true, display a line at the top describing each field. // If true, display a line at the top describing each field.
let include_heading = !matches.opt_present("f"); let include_heading = !matches.is_present(options::OMIT_HEADINGS);
// if true, display the user's full name from pw_gecos. // if true, display the user's full name from pw_gecos.
let mut include_fullname = true; let mut include_fullname = true;
// if true, display the user's ~/.project file when doing long format. // if true, display the user's ~/.project file when doing long format.
let include_project = !matches.opt_present("h"); let include_project = !matches.is_present(options::OMIT_PROJECT_FILE);
// if true, display the user's ~/.plan file when doing long format. // if true, display the user's ~/.plan file when doing long format.
let include_plan = !matches.opt_present("p"); let include_plan = !matches.is_present(options::OMIT_PLAN_FILE);
// if true, display the user's home directory and shell // if true, display the user's home directory and shell
// when doing long format. // when doing long format.
let include_home_and_shell = !matches.opt_present("b"); let include_home_and_shell = !matches.is_present(options::OMIT_HOME_DIR);
// if true, use the "short" output format. // if true, use the "short" output format.
let do_short_format = !matches.opt_present("l"); let do_short_format = !matches.is_present(options::LONG_FORMAT);
/* if true, display the ut_host field. */ /* if true, display the ut_host field. */
let mut include_where = true; let mut include_where = true;
if matches.opt_present("w") { if matches.is_present(options::OMIT_NAME) {
include_fullname = false; include_fullname = false;
} }
if matches.opt_present("i") { if matches.is_present(options::OMIT_NAME_HOST) {
include_fullname = false; include_fullname = false;
include_where = false; include_where = false;
} }
if matches.opt_present("q") { if matches.is_present(options::OMIT_NAME_HOST_TIME) {
include_fullname = false; include_fullname = false;
include_idle = false; include_idle = false;
include_where = false; include_where = false;
} }
if !do_short_format && matches.free.is_empty() {
show_usage_error!("no username specified; at least one must be specified when using -l");
return 1;
}
let pk = Pinky { let pk = Pinky {
include_idle, include_idle,
include_heading, include_heading,
@ -132,16 +172,12 @@ The utmp file will be {}",
include_plan, include_plan,
include_home_and_shell, include_home_and_shell,
include_where, include_where,
names: matches.free, names: users,
}; };
if do_short_format { if do_short_format {
if let Err(e) = pk.short_pinky() { pk.short_pinky();
show_usage_error!("{}", e); 0
1
} else {
0
}
} else { } else {
pk.long_pinky() pk.long_pinky()
} }
@ -250,17 +286,10 @@ impl Pinky {
print!(" {}", time_string(&ut)); print!(" {}", time_string(&ut));
if self.include_where && !ut.host().is_empty() { let mut s = ut.host();
let ut_host = ut.host(); if self.include_where && !s.is_empty() {
let mut res = ut_host.splitn(2, ':'); s = safe_unwrap!(ut.canon_host());
let host = match res.next() { print!(" {}", s);
Some(_) => ut.canon_host().unwrap_or_else(|_| ut_host.clone()),
None => ut_host.clone(),
};
match res.next() {
Some(d) => print!(" {}:{}", host, d),
None => print!(" {}", host),
}
} }
println!(); println!();
@ -282,7 +311,7 @@ impl Pinky {
println!(); println!();
} }
fn short_pinky(&self) -> IOResult<()> { fn short_pinky(&self) {
if self.include_heading { if self.include_heading {
self.print_heading(); self.print_heading();
} }
@ -295,7 +324,6 @@ impl Pinky {
} }
} }
} }
Ok(())
} }
fn long_pinky(&self) -> i32 { fn long_pinky(&self) -> i32 {

View file

@ -178,7 +178,9 @@ quick_error! {
} }
pub fn uumain(args: impl uucore::Args) -> i32 { pub fn uumain(args: impl uucore::Args) -> i32 {
let args = args.collect_str(); let args = args
.collect_str(uucore::InvalidEncodingHandling::Ignore)
.accept_any();
let mut opts = getopts::Options::new(); let mut opts = getopts::Options::new();
opts.opt( opts.opt(

View file

@ -18,18 +18,15 @@ pub fn err_msg(msg: &str) {
// by default stdout only flushes // by default stdout only flushes
// to console when a newline is passed. // to console when a newline is passed.
#[allow(unused_must_use)]
pub fn flush_char(c: char) { pub fn flush_char(c: char) {
print!("{}", c); print!("{}", c);
stdout().flush(); let _ = stdout().flush();
} }
#[allow(unused_must_use)]
pub fn flush_str(s: &str) { pub fn flush_str(s: &str) {
print!("{}", s); print!("{}", s);
stdout().flush(); let _ = stdout().flush();
} }
#[allow(unused_must_use)]
pub fn flush_bytes(bslice: &[u8]) { pub fn flush_bytes(bslice: &[u8]) {
stdout().write(bslice); let _ = stdout().write(bslice);
stdout().flush(); let _ = stdout().flush();
} }

View file

@ -1,15 +1,15 @@
#![allow(dead_code)] #![allow(dead_code)]
// spell-checker:ignore (change!) each's // spell-checker:ignore (change!) each's
// spell-checker:ignore (ToDO) LONGHELP FORMATSTRING templating parameterizing formatstr // spell-checker:ignore (ToDO) LONGHELP FORMATSTRING templating parameterizing formatstr
use uucore::InvalidEncodingHandling;
mod cli; mod cli;
mod memo; mod memo;
mod tokenize; mod tokenize;
static NAME: &str = "printf"; static NAME: &str = "printf";
static VERSION: &str = env!("CARGO_PKG_VERSION"); static VERSION: &str = env!("CARGO_PKG_VERSION");
static SHORT_USAGE: &str = "printf: usage: printf [-v var] format [arguments]";
static LONGHELP_LEAD: &str = "printf static LONGHELP_LEAD: &str = "printf
USAGE: printf FORMATSTRING [ARGUMENT]... USAGE: printf FORMATSTRING [ARGUMENT]...
@ -273,7 +273,9 @@ COPYRIGHT :
"; ";
pub fn uumain(args: impl uucore::Args) -> i32 { pub fn uumain(args: impl uucore::Args) -> i32 {
let args = args.collect_str(); let args = args
.collect_str(InvalidEncodingHandling::Ignore)
.accept_any();
let location = &args[0]; let location = &args[0];
if args.len() <= 1 { if args.len() <= 1 {

View file

@ -28,8 +28,7 @@ pub fn arrnum_int_mult(arr_num: &[u8], basenum: u8, base_ten_int_fact: u8) -> Ve
} }
} }
} }
#[allow(clippy::map_clone)] let ret: Vec<u8> = ret_rev.into_iter().rev().collect();
let ret: Vec<u8> = ret_rev.iter().rev().map(|x| *x).collect();
ret ret
} }
@ -102,70 +101,6 @@ pub fn arrnum_int_div_step(
remainder: rem_out, remainder: rem_out,
} }
} }
// pub struct ArrFloat {
// pub leading_zeros: u8,
// pub values: Vec<u8>,
// pub basenum: u8
// }
//
// pub struct ArrFloatDivOut {
// pub quotient: u8,
// pub remainder: ArrFloat
// }
//
// pub fn arrfloat_int_div(
// arrfloat_in : &ArrFloat,
// base_ten_int_divisor : u8,
// precision : u16
// ) -> DivOut {
//
// let mut remainder = ArrFloat {
// basenum: arrfloat_in.basenum,
// leading_zeros: arrfloat_in.leading_zeroes,
// values: Vec<u8>::new()
// }
// let mut quotient = 0;
//
// let mut bufferval : u16 = 0;
// let base : u16 = arrfloat_in.basenum as u16;
// let divisor : u16 = base_ten_int_divisor as u16;
//
// let mut it_f = arrfloat_in.values.iter();
// let mut position = 0 + arrfloat_in.leading_zeroes as u16;
// let mut at_end = false;
// while position< precision {
// let next_digit = match it_f.next() {
// Some(c) => {}
// None => { 0 }
// }
// match u_cur {
// Some(u) => {
// bufferval += u.clone() as u16;
// if bufferval > divisor {
// while bufferval >= divisor {
// quotient+=1;
// bufferval -= divisor;
// }
// if bufferval == 0 {
// rem_out.position +=1;
// } else {
// rem_out.replace = Some(bufferval as u8);
// }
// break;
// } else {
// bufferval *= base;
// }
// },
// None => {
// break;
// }
// }
// u_cur = it_f.next().clone();
// rem_out.position+=1;
// }
// ArrFloatDivOut { quotient: quotient, remainder: remainder }
// }
//
pub fn arrnum_int_add(arrnum: &[u8], basenum: u8, base_ten_int_term: u8) -> Vec<u8> { pub fn arrnum_int_add(arrnum: &[u8], basenum: u8, base_ten_int_term: u8) -> Vec<u8> {
let mut carry: u16 = u16::from(base_ten_int_term); let mut carry: u16 = u16::from(base_ten_int_term);
let mut rem: u16; let mut rem: u16;
@ -193,14 +128,12 @@ pub fn arrnum_int_add(arrnum: &[u8], basenum: u8, base_ten_int_term: u8) -> Vec<
} }
} }
} }
#[allow(clippy::map_clone)] let ret: Vec<u8> = ret_rev.into_iter().rev().collect();
let ret: Vec<u8> = ret_rev.iter().rev().map(|x| *x).collect();
ret ret
} }
pub fn base_conv_vec(src: &[u8], radix_src: u8, radix_dest: u8) -> Vec<u8> { pub fn base_conv_vec(src: &[u8], radix_src: u8, radix_dest: u8) -> Vec<u8> {
let mut result: Vec<u8> = Vec::new(); let mut result = vec![0];
result.push(0);
for i in src { for i in src {
result = arrnum_int_mult(&result, radix_dest, radix_src); result = arrnum_int_mult(&result, radix_dest, radix_src);
result = arrnum_int_add(&result, radix_dest, *i); result = arrnum_int_add(&result, radix_dest, *i);
@ -220,14 +153,11 @@ pub fn unsigned_to_arrnum(src: u16) -> Vec<u8> {
} }
// temporary needs-improvement-function // temporary needs-improvement-function
#[allow(unused_variables)] pub fn base_conv_float(src: &[u8], radix_src: u8, _radix_dest: u8) -> f64 {
pub fn base_conv_float(src: &[u8], radix_src: u8, radix_dest: u8) -> f64 {
// it would require a lot of addl code // it would require a lot of addl code
// to implement this for arbitrary string input. // to implement this for arbitrary string input.
// until then, the below operates as an outline // until then, the below operates as an outline
// of how it would work. // of how it would work.
let mut result: Vec<u8> = Vec::new();
result.push(0);
let mut factor: f64 = 1_f64; let mut factor: f64 = 1_f64;
let radix_src_float: f64 = f64::from(radix_src); let radix_src_float: f64 = f64::from(radix_src);
let mut r: f64 = 0_f64; let mut r: f64 = 0_f64;
@ -269,7 +199,6 @@ pub fn arrnum_to_str(src: &[u8], radix_def_dest: &dyn RadixDef) -> String {
str_out str_out
} }
#[allow(unused_variables)]
pub fn base_conv_str( pub fn base_conv_str(
src: &str, src: &str,
radix_def_src: &dyn RadixDef, radix_def_src: &dyn RadixDef,

View file

@ -43,45 +43,15 @@ impl Formatter for CninetyNineHexFloatf {
// c99 hex has unique requirements of all floating point subs in pretty much every part of building a primitive, from prefix and suffix to need for base conversion (in all other cases if you don't have decimal you must have decimal, here it's the other way around) // c99 hex has unique requirements of all floating point subs in pretty much every part of building a primitive, from prefix and suffix to need for base conversion (in all other cases if you don't have decimal you must have decimal, here it's the other way around)
// on the todo list is to have a trait for get_primitive that is implemented by each float formatter and can override a default. when that happens we can take the parts of get_primitive_dec specific to dec and spin them out to their own functions that can be overridden. // on the todo list is to have a trait for get_primitive that is implemented by each float formatter and can override a default. when that happens we can take the parts of get_primitive_dec specific to dec and spin them out to their own functions that can be overridden.
#[allow(unused_variables)]
#[allow(unused_assignments)]
fn get_primitive_hex( fn get_primitive_hex(
inprefix: &InPrefix, inprefix: &InPrefix,
str_in: &str, _str_in: &str,
analysis: &FloatAnalysis, _analysis: &FloatAnalysis,
last_dec_place: usize, _last_dec_place: usize,
capitalized: bool, capitalized: bool,
) -> FormatPrimitive { ) -> FormatPrimitive {
let prefix = Some(String::from(if inprefix.sign == -1 { "-0x" } else { "0x" })); let prefix = Some(String::from(if inprefix.sign == -1 { "-0x" } else { "0x" }));
// assign the digits before and after the decimal points
// to separate slices. If no digits after decimal point,
// assign 0
let (mut first_segment_raw, second_segment_raw) = match analysis.decimal_pos {
Some(pos) => (&str_in[..pos], &str_in[pos + 1..]),
None => (str_in, "0"),
};
if first_segment_raw.is_empty() {
first_segment_raw = "0";
}
// convert to string, hexifying if input is in dec.
// let (first_segment, second_segment) =
// match inprefix.radix_in {
// Base::Ten => {
// (to_hex(first_segment_raw, true),
// to_hex(second_segment_raw, false))
// }
// _ => {
// (String::from(first_segment_raw),
// String::from(second_segment_raw))
// }
// };
//
//
// f.pre_decimal = Some(first_segment);
// f.post_decimal = Some(second_segment);
//
// TODO actual conversion, make sure to get back mantissa. // TODO actual conversion, make sure to get back mantissa.
// for hex to hex, it's really just a matter of moving the // for hex to hex, it's really just a matter of moving the
// decimal point and calculating the mantissa by its initial // decimal point and calculating the mantissa by its initial

View file

@ -22,12 +22,11 @@ fn get_len_fprim(fprim: &FormatPrimitive) -> usize {
len len
} }
pub struct Decf { pub struct Decf;
as_num: f64,
}
impl Decf { impl Decf {
pub fn new() -> Decf { pub fn new() -> Decf {
Decf { as_num: 0.0 } Decf
} }
} }
impl Formatter for Decf { impl Formatter for Decf {

View file

@ -5,12 +5,10 @@ use super::super::format_field::FormatField;
use super::super::formatter::{FormatPrimitive, Formatter, InPrefix}; use super::super::formatter::{FormatPrimitive, Formatter, InPrefix};
use super::float_common::{get_primitive_dec, primitive_to_str_common, FloatAnalysis}; use super::float_common::{get_primitive_dec, primitive_to_str_common, FloatAnalysis};
pub struct Floatf { pub struct Floatf;
as_num: f64,
}
impl Floatf { impl Floatf {
pub fn new() -> Floatf { pub fn new() -> Floatf {
Floatf { as_num: 0.0 } Floatf
} }
} }
impl Formatter for Floatf { impl Formatter for Floatf {

Some files were not shown because too many files have changed in this diff Show more