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 73660 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:
name: stable x86_64-unknown-freebsd-12
freebsd_instance:
image: freebsd-12-1-release-amd64
image: freebsd-12-2-release-amd64
setup_script:
- pkg install -y curl gmake
- curl https://sh.rustup.rs -sSf --output rustup.sh

View file

@ -11,7 +11,7 @@ env:
PROJECT_NAME: coreutils
PROJECT_DESC: "Core universal (cross-platform) utilities"
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
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 ;;
aarch64-unknown-linux-gnu) sudo apt-get -y update ; sudo apt-get -y install gcc-aarch64-linux-gnu ;;
esac
case '${{ matrix.job.os }}' in
macos-latest) brew install coreutils ;; # needed for testing
esac
- name: Initialize workflow variables
id: vars
shell: bash
@ -360,6 +363,10 @@ jobs:
mkdir -p '${{ steps.vars.outputs.STAGING }}/dpkg'
- name: rust toolchain ~ install
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:
toolchain: ${{ steps.vars.outputs.TOOLCHAIN }}
target: ${{ matrix.job.target }}
@ -486,6 +493,13 @@ jobs:
- { os: windows-latest , features: windows }
steps:
- 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
# run: git checkout ${{ github.head_ref }}
- name: Initialize workflow variables

View file

@ -12,16 +12,18 @@ jobs:
uses: actions/checkout@v2
with:
path: 'uutils'
- name: Chechout GNU coreutils
- name: Checkout GNU coreutils
uses: actions/checkout@v2
with:
repository: 'coreutils/coreutils'
path: 'gnu'
- name: Chechout GNU corelib
ref: v8.32
- name: Checkout GNU corelib
uses: actions/checkout@v2
with:
repository: 'coreutils/gnulib'
path: 'gnulib'
ref: 8e99f24c0931a38880c6ee9b8287c7da80b0036b
fetch-depth: 0 # gnu gets upset if gnulib is a shallow checkout
- name: Install `rust` toolchain
uses: actions-rs/toolchain@v1
@ -30,100 +32,43 @@ jobs:
default: true
profile: minimal # minimal component installation (ie, no documentation)
components: rustfmt
- name: Build binaries
- name: Install deps
shell: bash
run: |
sudo apt-get update
sudo apt-get install autoconf autopoint bison texinfo gperf gcc g++ gdb python-pyinotify python3-sphinx
pushd uutils
make PROFILE=release
BUILDDIR="$PWD/target/release/"
cp "${BUILDDIR}/install" "${BUILDDIR}/ginstall" # The GNU tests rename this script before running, to avoid confusion with the make target
# Create *sum binaries
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}"
sudo apt-get install autoconf autopoint bison texinfo gperf gcc g++ gdb python-pyinotify python3-sphinx jq
- name: Build binaries
shell: bash
run: |
cd uutils
bash util/build-gnu.sh
- name: Run GNU tests
shell: bash
run: |
BUILDDIR="${PWD}/uutils/target/release"
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
bash uutils/util/run-gnu-test.sh
- name: Extract tests info
shell: bash
run: |
if test -f gnu/tests/test-suite.log
LOG_FILE=gnu/tests/test-suite.log
if test -f "$LOG_FILE"
then
TOTAL=$( grep "# TOTAL:" gnu/tests/test-suite.log|cut -d' ' -f2-)
PASS=$( grep "# PASS:" gnu/tests/test-suite.log|cut -d' ' -f2-)
SKIP=$( grep "# SKIP:" gnu/tests/test-suite.log|cut -d' ' -f2-)
FAIL=$( grep "# FAIL:" gnu/tests/test-suite.log|cut -d' ' -f2-)
XPASS=$( grep "# XPASS:" gnu/tests/test-suite.log|cut -d' ' -f2-)
ERROR=$( grep "# ERROR:" gnu/tests/test-suite.log|cut -d' ' -f2-)
echo "::warning ::GNU testsuite = $TOTAL / $PASS / $FAIL / $ERROR"
TOTAL=$(sed -n "s/.*# TOTAL: \(.*\)/\1/p" "$LOG_FILE"|tr -d '\r'|head -n1)
PASS=$(sed -n "s/.*# PASS: \(.*\)/\1/p" "$LOG_FILE"|tr -d '\r'|head -n1)
SKIP=$(sed -n "s/.*# SKIP: \(.*\)/\1/p" "$LOG_FILE"|tr -d '\r'|head -n1)
FAIL=$(sed -n "s/.*# FAIL: \(.*\)/\1/p" "$LOG_FILE"|tr -d '\r'|head -n1)
XPASS=$(sed -n "s/.*# XPASS: \(.*\)/\1/p" "$LOG_FILE"|tr -d '\r'|head -n1)
ERROR=$(sed -n "s/.*# ERROR: \(.*\)/\1/p" "$LOG_FILE"|tr -d '\r'|head -n1)
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
echo "::error ::Failed to get summary of test results"
fi
@ -132,3 +77,8 @@ jobs:
with:
name: test-report
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
lib*.a
/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. 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. Unsafe code should be documented with Safety comments.
## Commit messages
@ -69,10 +70,6 @@ lines for non-utility modules include:
README: add help
```
```
travis: fix build
```
```
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" }
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" }
factor_benches = { optional = true, version = "0.0.0", package = "uu_factor_benches", path = "tests/benches/factor" }
#
# * pinned transitive dependencies
# Not needed for now. Keep as examples:
@ -338,17 +341,15 @@ filetime = "0.2"
glob = "0.3.0"
libc = "0.2"
nix = "0.20.0"
pretty_assertions = "0.7.2"
rand = "0.7"
regex = "1.0"
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
## min dep for tempfile = Rustc 1.40
tempfile = "= 3.1.0"
tempfile = "3.2.0"
time = "0.1"
unindent = "0.1"
uucore = { version=">=0.0.8", package="uucore", path="src/uucore", features=["entries"] }
walkdir = "2.2"
tempdir = "0.3"
[target.'cfg(unix)'.dev-dependencies]
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)
[![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)
[![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
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.
@ -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
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
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
```
## 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
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-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|
|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|
|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|

View file

@ -26,6 +26,7 @@ TARGETS = [
"x86_64-pc-windows-gnu",
"x86_64-pc-windows-msvc",
# Apple
"aarch64-apple-darwin",
"x86_64-apple-darwin",
"aarch64-apple-ios",
"x86_64-apple-ios",
@ -231,4 +232,4 @@ if __name__ == "__main__":
prev_table, _, _ = load_csv(CACHE_PATH)
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]
platform-info = "0.1"
clap = "2.33"
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }

View file

@ -11,12 +11,19 @@ extern crate uucore;
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 LONG_HELP: &str = "";
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());
println!("{}", uts.machine().trim());
0

View file

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

View file

@ -7,13 +7,14 @@
#[macro_use]
extern crate uucore;
use std::io::{stdin, Read};
use uucore::encoding::Format;
mod base_common;
pub mod base_common;
static SYNTAX: &str = "[OPTION]... [FILE]";
static SUMMARY: &str = "Base32 encode or decode FILE, or standard input, to standard output.";
static LONG_HELP: &str = "
static ABOUT: &str = "
With no FILE, or when FILE is -, read standard input.
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
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 {
base_common::execute(
args.collect_str(),
SYNTAX,
SUMMARY,
LONG_HELP,
Format::Base32,
)
let format = Format::Base32;
let usage = get_usage();
let name = executable!();
let config_result: Result<base_common::Config, String> =
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
// that was distributed with this source code.
use std::fs::File;
use std::io::{stdin, stdout, BufReader, Read, Write};
use std::path::Path;
use std::io::{stdout, Read, Write};
use uucore::encoding::{wrap_print, Data, Format};
use uucore::InvalidEncodingHandling;
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);
use std::fs::File;
use std::io::{BufReader, Stdin};
use std::path::Path;
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");
use clap::{App, Arg};
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
// Config.
pub struct Config {
pub decode: bool,
pub ignore_garbage: bool,
pub wrap_cols: Option<usize>,
pub to_read: Option<String>,
}
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,
format: Format,
line_wrap: Option<usize>,
ignore_garbage: bool,
decode: bool,
name: &str,
) {
let mut data = Data::new(input, format).ignore_garbage(ignore_garbage);
if let Some(wrap) = line_wrap {
@ -88,10 +148,14 @@ fn handle_input<R: Read>(
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");
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"
[dependencies]
clap = "2.33"
uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features = ["encoding"] }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
uu_base32 = { version=">=0.0.6", package="uu_base32", path="../base32"}
[[bin]]
name = "base64"

View file

@ -8,13 +8,14 @@
#[macro_use]
extern crate uucore;
use uu_base32::base_common;
use uucore::encoding::Format;
mod base_common;
use std::io::{stdin, Read};
static SYNTAX: &str = "[OPTION]... [FILE]";
static SUMMARY: &str = "Base64 encode or decode FILE, or standard input, to standard output.";
static LONG_HELP: &str = "
static ABOUT: &str = "
With no FILE, or when FILE is -, read standard input.
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
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 {
base_common::execute(
args.collect_str(),
SYNTAX,
SUMMARY,
LONG_HELP,
Format::Base64,
)
let format = Format::Base64;
let usage = get_usage();
let name = executable!();
let config_result: Result<base_common::Config, String> =
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

@ -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"
[dependencies]
clap = "2.33.2"
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }

View file

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

View file

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

View file

@ -3,14 +3,13 @@
// (c) Jordi Boggiano <j.boggiano@seld.be>
// (c) Evgeniy Klyuchikov <evgeniy.klyuchikov@gmail.com>
// (c) Joshua S. Miller <jsmiller@uchicago.edu>
// (c) Árni Dagur <arni@dagur.eu>
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
// spell-checker:ignore (ToDO) nonprint nonblank nonprinting
#[macro_use]
extern crate quick_error;
#[cfg(unix)]
extern crate unix_socket;
#[macro_use]
@ -18,11 +17,17 @@ extern crate uucore;
// last synced with: cat (GNU coreutils) 8.13
use clap::{App, Arg};
use quick_error::ResultExt;
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;
/// 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
#[cfg(unix)]
use std::net::Shutdown;
@ -30,6 +35,7 @@ use std::net::Shutdown;
use std::os::unix::fs::FileTypeExt;
#[cfg(unix)]
use unix_socket::UnixStream;
use uucore::InvalidEncodingHandling;
static NAME: &str = "cat";
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
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)]
enum NumberingMode {
None,
@ -44,39 +71,6 @@ enum NumberingMode {
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 {
/// Line numbering mode
number: NumberingMode,
@ -87,21 +81,56 @@ struct OutputOptions {
/// display TAB characters as `tab`
show_tabs: bool,
/// If `show_tabs == true`, this string will be printed in the
/// place of tabs
tab: String,
/// Can be set to show characters other than '\n' a the end of
/// each line, e.g. $
end_of_line: String,
/// Show end of lines
show_ends: bool,
/// use ^ and M- notation, except for LF (\\n) and TAB (\\t)
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
struct InputHandle {
reader: Box<dyn Read>,
struct InputHandle<R: Read> {
#[cfg(any(target_os = "linux", target_os = "android"))]
file_descriptor: RawFd,
reader: R,
is_interactive: bool,
}
@ -124,8 +153,6 @@ enum InputType {
Socket,
}
type CatResult<T> = Result<T, CatError>;
mod options {
pub static FILE: &str = "file";
pub static SHOW_ALL: &str = "show-all";
@ -140,7 +167,9 @@ mod options {
}
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!())
.name(NAME)
@ -243,30 +272,14 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
None => vec!["-".to_owned()],
};
let can_write_fast = !(show_tabs
|| show_nonprint
|| show_ends
|| squeeze_blank
|| number_mode != NumberingMode::None);
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 options = OutputOptions {
show_ends,
number: number_mode,
show_nonprint,
show_tabs,
squeeze_blank,
};
let success = cat_files(files, &options).is_ok();
if success {
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
///
/// # Arguments
@ -285,7 +368,8 @@ fn get_input_type(path: &str) -> CatResult<InputType> {
return Ok(InputType::StdIn);
}
match metadata(path).context(path)?.file_type() {
let ft = metadata(path)?.file_type();
match ft {
#[cfg(unix)]
ft if ft.is_block_device() => Ok(InputType::BlockDevice),
#[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_file() => Ok(InputType::File),
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
/// error
///
/// # Arguments
///
/// * `path` - `InputHandler` will wrap a reader from this file path
fn open(path: &str) -> CatResult<InputHandle> {
if path == "-" {
let stdin = stdin();
return Ok(InputHandle {
reader: Box::new(stdin) as Box<dyn Read>,
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,
})
/// Writes handle to stdout with no configuration. This allows a
/// simple memory copy.
fn write_fast<R: Read>(handle: &mut InputHandle<R>) -> CatResult<()> {
let stdout = io::stdout();
let mut stdout_lock = stdout.lock();
#[cfg(any(target_os = "linux", target_os = "android"))]
{
// If we're on Linux or Android, try to use the splice() system call
// for faster writing. If it works, we're done.
if !splice::write_fast_using_splice(handle, stdout_lock.as_raw_fd())? {
return Ok(());
}
}
}
/// Writes files to stdout with no configuration. This allows a
/// simple memory copy. 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_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;
}
// If we're not on Linux or Android, or the splice() call failed,
// fall back on slower writing.
let mut buf = [0; 1024 * 64];
while let Ok(n) = handle.reader.read(&mut buf) {
if n == 0 {
break;
}
stdout_lock.write_all(&buf[..n])?;
}
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)),
}
Ok(())
}
/// Outputs file contents to stdout in a line-by-line fashion,
/// propagating any errors that might occur.
fn write_file_lines(file: &str, options: &OutputOptions, state: &mut OutputState) -> CatResult<()> {
let mut handle = open(file)?;
fn write_lines<R: Read>(
handle: &mut InputHandle<R>,
options: &OutputOptions,
state: &mut OutputState,
) -> CatResult<()> {
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;
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)?;
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 {
writer.flush().context(file)?;
writer.flush()?;
}
}
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
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 {
write_tab_to_end(&in_buf[pos..], &mut writer)
} else {
@ -462,7 +468,7 @@ fn write_file_lines(file: &str, options: &OutputOptions, state: &mut OutputState
break;
}
// 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 {
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::path::Path;
use uucore::InvalidEncodingHandling;
static SYNTAX: &str =
"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;
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, "");
opts.optflag("c",
@ -94,7 +97,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
if recursive {
if bit_flag == FTS_PHYSICAL {
if derefer == 1 {
show_info!("-R --dereference requires -H or -L");
show_error!("-R --dereference requires -H or -L");
return 1;
}
derefer = 0;
@ -129,7 +132,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
dest_gid = meta.gid();
}
Err(e) => {
show_info!("failed to get attributes of '{}': {}", file, e);
show_error!("failed to get attributes of '{}': {}", file, e);
return 1;
}
}
@ -140,7 +143,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
dest_gid = g;
}
_ => {
show_info!("invalid group: {}", matches.free[0].as_str());
show_error!("invalid group: {}", matches.free[0].as_str());
return 1;
}
}
@ -232,8 +235,8 @@ impl Chgrper {
if let Some(p) = may_exist {
if p.parent().is_none() || self.is_bind_root(p) {
show_info!("it is dangerous to operate recursively on '/'");
show_info!("use --no-preserve-root to override this failsafe");
show_error!("it is dangerous to operate recursively on '/'");
show_error!("use --no-preserve-root to override this failsafe");
return 1;
}
}
@ -247,12 +250,12 @@ impl Chgrper {
self.verbosity.clone(),
) {
Ok(n) => {
show_info!("{}", n);
show_error!("{}", n);
0
}
Err(e) => {
if self.verbosity != Verbosity::Silent {
show_info!("{}", e);
show_error!("{}", e);
}
1
}
@ -272,7 +275,7 @@ impl Chgrper {
for entry in WalkDir::new(root).follow_links(follow).min_depth(1) {
let entry = unwrap!(entry, e, {
ret = 1;
show_info!("{}", e);
show_error!("{}", e);
continue;
});
let path = entry.path();
@ -286,14 +289,14 @@ impl Chgrper {
ret = match wrap_chgrp(path, &meta, self.dest_gid, follow, self.verbosity.clone()) {
Ok(n) => {
if n != "" {
show_info!("{}", n);
if !n.is_empty() {
show_error!("{}", n);
}
0
}
Err(e) => {
if self.verbosity != Verbosity::Silent {
show_info!("{}", e);
show_error!("{}", e);
}
1
}
@ -310,7 +313,7 @@ impl Chgrper {
unwrap!(path.metadata(), e, {
match self.verbosity {
Silent => (),
_ => show_info!("cannot access '{}': {}", path.display(), e),
_ => show_error!("cannot access '{}': {}", path.display(), e),
}
return None;
})
@ -318,7 +321,7 @@ impl Chgrper {
unwrap!(path.symlink_metadata(), e, {
match self.verbosity {
Silent => (),
_ => show_info!("cannot dereference '{}': {}", path.display(), e),
_ => show_error!("cannot dereference '{}': {}", path.display(), e),
}
return None;
})

View file

@ -15,8 +15,10 @@ use std::fs;
use std::os::unix::fs::{MetadataExt, PermissionsExt};
use std::path::Path;
use uucore::fs::display_permissions_unix;
use uucore::libc::mode_t;
#[cfg(not(windows))]
use uucore::mode;
use uucore::InvalidEncodingHandling;
use walkdir::WalkDir;
static VERSION: &str = env!("CARGO_PKG_VERSION");
@ -49,7 +51,9 @@ fn get_long_usage() -> String {
}
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),
// 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
// e.g. "chmod -v -xw -R FILE" -> "chmod -v xw -R FILE"
pub fn strip_minus_from_mode(args: &mut Vec<String>) -> bool {
for i in 0..args.len() {
if args[i].starts_with("-") {
if let Some(second) = args[i].chars().nth(1) {
for arg in args {
if arg.starts_with('-') {
if let Some(second) = arg.chars().nth(1) {
match second {
'r' | 'w' | 'x' | 'X' | 's' | 't' | 'u' | 'g' | 'o' | '0'..='7' => {
// 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;
}
_ => {}
@ -258,8 +262,10 @@ impl Chmoder {
);
}
return Ok(());
} else if err.kind() == std::io::ErrorKind::PermissionDenied {
show_error!("'{}': Permission denied", file.display());
} else {
show_error!("{}: '{}'", err, file.display());
show_error!("'{}': {}", file.display(), err);
}
return Err(1);
}
@ -303,7 +309,7 @@ impl Chmoder {
"mode of '{}' retained as {:04o} ({})",
file.display(),
fperm,
display_permissions_unix(fperm),
display_permissions_unix(fperm as mode_t, false),
);
}
Ok(())
@ -312,25 +318,25 @@ impl Chmoder {
show_error!("{}", err);
}
if self.verbose {
show_info!(
show_error!(
"failed to change mode of file '{}' from {:o} ({}) to {:o} ({})",
file.display(),
fperm,
display_permissions_unix(fperm),
display_permissions_unix(fperm as mode_t, false),
mode,
display_permissions_unix(mode)
display_permissions_unix(mode as mode_t, false)
);
}
Err(1)
} else {
if self.verbose || self.changes {
show_info!(
show_error!(
"mode of '{}' changed from {:o} ({}) to {:o} ({})",
file.display(),
fperm,
display_permissions_unix(fperm),
display_permissions_unix(fperm as mode_t, false),
mode,
display_permissions_unix(mode)
display_permissions_unix(mode as mode_t, false)
);
}
Ok(())

View file

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

View file

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

View file

@ -14,6 +14,7 @@ use clap::{App, Arg};
use std::fs::File;
use std::io::{self, stdin, BufReader, Read};
use std::path::Path;
use uucore::InvalidEncodingHandling;
// NOTE: CRC_TABLE_LEN *must* be <= 256 as we cast 0..CRC_TABLE_LEN to u8
const CRC_TABLE_LEN: usize = 256;
@ -180,7 +181,9 @@ mod options {
}
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!())
.name(NAME)

View file

@ -14,6 +14,7 @@ use std::cmp::Ordering;
use std::fs::File;
use std::io::{self, stdin, BufRead, BufReader, Stdin};
use std::path::Path;
use uucore::InvalidEncodingHandling;
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 {
let usage = get_usage();
let args = args
.collect_str(InvalidEncodingHandling::ConvertLossy)
.accept_any();
let matches = App::new(executable!())
.version(VERSION)

View file

@ -47,6 +47,7 @@ use std::os::windows::ffi::OsStrExt;
use std::path::{Path, PathBuf, StripPrefixError};
use std::str::FromStr;
use std::string::ToString;
use uucore::backup_control::{self, BackupMode};
use uucore::fs::resolve_relative_path;
use uucore::fs::{canonicalize, CanonicalizeMode};
use walkdir::WalkDir;
@ -132,7 +133,9 @@ macro_rules! prompt_yes(
pub type CopyResult<T> = Result<T, Error>;
pub type Source = PathBuf;
pub type SourceSlice = Path;
pub type Target = PathBuf;
pub type TargetSlice = Path;
/// Specifies whether when overwrite files
#[derive(Clone, Eq, PartialEq)]
@ -153,7 +156,8 @@ pub enum OverwriteMode {
NoClobber,
}
#[derive(Clone, Eq, PartialEq)]
/// Possible arguments for `--reflink`.
#[derive(Copy, Clone, Eq, PartialEq)]
pub enum ReflinkMode {
Always,
Auto,
@ -166,14 +170,6 @@ pub enum TargetType {
File,
}
#[derive(Clone, Eq, PartialEq)]
pub enum BackupMode {
ExistingBackup,
NoBackup,
NumberedBackup,
SimpleBackup,
}
pub enum CopyMode {
Link,
SymLink,
@ -198,7 +194,7 @@ pub enum Attribute {
#[allow(dead_code)]
pub struct Options {
attributes_only: bool,
backup: bool,
backup: BackupMode,
copy_contents: bool,
copy_mode: CopyMode,
dereference: bool,
@ -208,7 +204,6 @@ pub struct Options {
overwrite: OverwriteMode,
parents: bool,
strip_trailing_slashes: bool,
reflink: bool,
reflink_mode: ReflinkMode,
preserve_attributes: Vec<Attribute>,
recursive: bool,
@ -220,6 +215,7 @@ pub struct Options {
static VERSION: &str = env!("CARGO_PKG_VERSION");
static ABOUT: &str = "Copy SOURCE to DEST, or multiple SOURCE(s) to DIRECTORY.";
static LONG_HELP: &str = "";
static EXIT_OK: i32 = 0;
static EXIT_ERR: i32 = 1;
@ -236,6 +232,7 @@ fn get_usage() -> String {
static OPT_ARCHIVE: &str = "archive";
static OPT_ATTRIBUTES_ONLY: &str = "attributes-only";
static OPT_BACKUP: &str = "backup";
static OPT_BACKUP_NO_ARG: &str = "b";
static OPT_CLI_SYMBOLIC_LINKS: &str = "cli-symbolic-links";
static OPT_CONTEXT: &str = "context";
static OPT_COPY_CONTENTS: &str = "copy-contents";
@ -299,6 +296,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
let matches = App::new(executable!())
.version(VERSION)
.about(ABOUT)
.after_help(&*format!("{}\n{}", LONG_HELP, backup_control::BACKUP_CONTROL_LONG_HELP))
.usage(&usage[..])
.arg(Arg::with_name(OPT_TARGET_DIRECTORY)
.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 \
(contrast with --force). On Windows, current only works for writeable files."))
.arg(Arg::with_name(OPT_BACKUP)
.short("b")
.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)
.short("S")
.long(OPT_SUFFIX)
.takes_value(true)
.default_value("~")
.value_name("SUFFIX")
.help("override the usual backup suffix"))
.arg(Arg::with_name(OPT_UPDATE)
@ -461,6 +467,12 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.get_matches_from(args);
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
.values_of(OPT_PATHS)
.map(|v| v.map(ToString::to_string).collect())
@ -547,14 +559,17 @@ impl FromStr for 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)]
attr.push(Attribute::Mode);
attr.push(Attribute::Ownership);
attr.push(Attribute::Timestamps);
attr.push(Attribute::Context);
attr.push(Attribute::Xattr);
attr.push(Attribute::Links);
attr.insert(0, Mode);
attr
}
@ -580,7 +595,13 @@ impl Options {
|| matches.is_present(OPT_RECURSIVE_ALIAS)
|| 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
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_ARCHIVE),
one_file_system: matches.is_present(OPT_ONE_FILE_SYSTEM),
overwrite: OverwriteMode::from_matches(matches),
parents: matches.is_present(OPT_PARENTS),
backup_suffix: matches.value_of(OPT_SUFFIX).unwrap().to_string(),
update: matches.is_present(OPT_UPDATE),
verbose: matches.is_present(OPT_VERBOSE),
strip_trailing_slashes: matches.is_present(OPT_STRIP_TRAILING_SLASHES),
reflink: matches.is_present(OPT_REFLINK),
reflink_mode: {
if let Some(reflink) = matches.value_of(OPT_REFLINK) {
match reflink {
"always" => ReflinkMode::Always,
"auto" => ReflinkMode::Auto,
"never" => ReflinkMode::Never,
value => {
return Err(Error::InvalidArgument(format!(
"invalid argument '{}' for \'reflink\'",
@ -649,7 +668,9 @@ impl Options {
ReflinkMode::Never
}
},
backup,
backup: backup_mode,
backup_suffix: backup_suffix,
overwrite: overwrite,
no_target_dir,
preserve_attributes,
recursive,
@ -665,7 +686,7 @@ impl TargetType {
///
/// Treat target as a dir if we have multiple sources or the target
/// 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() {
TargetType::Directory
} else {
@ -714,7 +735,7 @@ fn parse_path_args(path_args: &[String], options: &Options) -> CopyResult<(Vec<S
fn preserve_hardlinks(
hard_links: &mut Vec<(String, u64)>,
source: &std::path::PathBuf,
source: &std::path::Path,
dest: std::path::PathBuf,
found_hard_link: &mut bool,
) -> CopyResult<()> {
@ -788,7 +809,7 @@ fn preserve_hardlinks(
/// Behavior depends on `options`, see [`Options`] for details.
///
/// [`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);
verify_target_type(target, &target_type)?;
@ -840,7 +861,7 @@ fn copy(sources: &[Source], target: &Target, options: &Options) -> CopyResult<()
fn construct_dest_path(
source_path: &Path,
target: &Target,
target: &TargetSlice,
target_type: &TargetType,
options: &Options,
) -> CopyResult<PathBuf> {
@ -870,8 +891,8 @@ fn construct_dest_path(
}
fn copy_source(
source: &Source,
target: &Target,
source: &SourceSlice,
target: &TargetSlice,
target_type: &TargetType,
options: &Options,
) -> CopyResult<()> {
@ -912,7 +933,7 @@ fn adjust_canonicalization(p: &Path) -> Cow<Path> {
///
/// Any errors encountered copying files in the tree will be logged but
/// 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 {
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))]
#[allow(clippy::unnecessary_wraps)] // needed for windows version
fn symlink_file(source: &Path, dest: &Path, context: &str) -> CopyResult<()> {
match std::os::unix::fs::symlink(source, dest).context(context) {
Ok(_) => Ok(()),
@ -1084,14 +1106,10 @@ fn context_for(src: &Path, dest: &Path) -> String {
format!("'{}' -> '{}'", src.display(), dest.display())
}
/// Implements a relatively naive backup that is not as full featured
/// as GNU cp. No CONTROL version control method argument is taken
/// for backups.
/// TODO: Add version control methods
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)?;
/// Implements a simple backup copy for the destination file.
/// TODO: for the backup, should this function be replaced by `copy_file(...)`?
fn backup_dest(dest: &Path, backup_path: &PathBuf) -> CopyResult<PathBuf> {
fs::copy(dest, &backup_path)?;
Ok(backup_path.into())
}
@ -1102,8 +1120,9 @@ fn handle_existing_dest(source: &Path, dest: &Path, options: &Options) -> CopyRe
options.overwrite.verify(dest)?;
if options.backup {
backup_file(dest, &options.backup_suffix)?;
let backup_path = backup_control::get_backup_path(options.backup, dest, &options.backup_suffix);
if let Some(backup_path) = backup_path {
backup_dest(dest, &backup_path)?;
}
match options.overwrite {
@ -1191,47 +1210,20 @@ fn copy_file(source: &Path, dest: &Path, options: &Options) -> CopyResult<()> {
Ok(())
}
///Copy the file from `source` to `dest` either using the normal `fs::copy` or the
///`FICLONE` ioctl if --reflink is specified and the filesystem supports it.
/// Copy the file from `source` to `dest` either using the normal `fs::copy` or a
/// copy-on-write scheme if --reflink is specified and the filesystem supports it.
fn copy_helper(source: &Path, dest: &Path, options: &Options) -> CopyResult<()> {
if options.reflink {
#[cfg(not(target_os = "linux"))]
return Err("--reflink is only supported on linux".to_string().into());
if options.reflink_mode != ReflinkMode::Never {
#[cfg(not(any(target_os = "linux", target_os = "macos")))]
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")]
{
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 => {}
}
}
copy_on_write_linux(source, dest, options.reflink_mode)?;
} else if options.no_dereference && fs::symlink_metadata(&source)?.file_type().is_symlink() {
// Here, we will copy the symlink itself (actually, just recreate it)
let link = fs::read_link(&source)?;
@ -1264,6 +1256,101 @@ fn copy_helper(source: &Path, dest: &Path, options: &Options) -> CopyResult<()>
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`
pub fn verify_target_type(target: &Path, target_type: &TargetType) -> CopyResult<()> {
match (target_type, target.is_dir()) {

View file

@ -17,6 +17,7 @@ mod splitname;
use crate::csplit_error::CsplitError;
use crate::splitname::SplitName;
use uucore::InvalidEncodingHandling;
static VERSION: &str = env!("CARGO_PKG_VERSION");
static SUMMARY: &str = "split a file into sections determined by context lines";
@ -123,12 +124,7 @@ where
// split the file based on patterns
for pattern in patterns.into_iter() {
let pattern_as_str = pattern.to_string();
#[allow(clippy::match_like_matches_macro)]
let is_skip = if let patterns::Pattern::SkipToMatch(_, _, _) = pattern {
true
} else {
false
};
let is_skip = matches!(pattern, patterns::Pattern::SkipToMatch(_, _, _));
match pattern {
patterns::Pattern::UpToLine(n, ex) => {
let mut up_to_line = n;
@ -711,7 +707,9 @@ mod tests {
pub fn uumain(args: impl uucore::Args) -> i32 {
let usage = get_usage();
let args = args.collect_str();
let args = args
.collect_str(InvalidEncodingHandling::Ignore)
.accept_any();
let matches = App::new(executable!())
.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"
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
memchr = "2"
bstr = "0.2"
[[bin]]
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]
extern crate uucore;
use bstr::io::BufReadExt;
use clap::{App, Arg};
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 self::searcher::Searcher;
use uucore::fs::is_stdout_interactive;
use uucore::ranges::Range;
use uucore::InvalidEncodingHandling;
mod buffer;
mod searcher;
static NAME: &str = "cut";
@ -125,6 +127,14 @@ enum Mode {
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> {
if complement {
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 {
use self::buffer::Bytes::Select;
use self::buffer::Bytes::Selected::*;
let newline_char = if opts.zero_terminated { b'\0' } else { b'\n' };
let mut buf_read = buffer::ByteReader::new(reader, newline_char);
let mut out = stdout();
let buf_in = BufReader::new(reader);
let mut out = stdout_writer();
let delim = opts
.out_delim
.as_ref()
.map_or("", String::as_str)
.as_bytes();
'newline: loop {
let mut cur_pos = 1;
let res = buf_in.for_byte_record(newline_char, |line| {
let mut print_delim = false;
for &Range { low, high } in ranges.iter() {
// skip up to low
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;
}
}
for &Range { low, high } in ranges {
if low > line.len() {
break;
}
if let Some(ref delim) = opts.out_delim {
if print_delim {
crash_if_err!(1, out.write_all(delim.as_bytes()));
}
if print_delim {
out.write_all(delim)?;
} else if opts.out_delim.is_some() {
print_delim = true;
}
// write out from low to high
loop {
match buf_read.select(high - cur_pos + 1, Some(&mut out)) {
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;
}
}
}
// change `low` from 1-indexed value to 0-index value
let low = low - 1;
let high = high.min(line.len());
out.write_all(&line[low..high])?;
}
buf_read.consume_line();
crash_if_err!(1, out.write_all(&[newline_char]));
}
out.write_all(&[newline_char])?;
Ok(true)
});
crash_if_err!(1, res);
0
}
@ -212,23 +185,11 @@ fn cut_fields_delimiter<R: Read>(
newline_char: u8,
out_delim: &str,
) -> i32 {
let mut buf_in = BufReader::new(reader);
let mut out = stdout();
let mut buffer = Vec::new();
let buf_in = BufReader::new(reader);
let mut out = stdout_writer();
let input_delim_len = delim.len();
'newline: loop {
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 result = buf_in.for_byte_record_with_terminator(newline_char, |line| {
let mut fields_pos = 1;
let mut low_idx = 0;
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 !only_delimited {
crash_if_err!(1, out.write_all(line));
out.write_all(line)?;
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 {
low_idx = match delim_search.nth(low - fields_pos - 1) {
Some((_, beyond_delim)) => beyond_delim,
Some(index) => index + input_delim_len,
None => break,
};
}
for _ in 0..=high - low {
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() {
Some((high_idx, next_low_idx)) => {
Some(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 = next_low_idx;
low_idx = high_idx + input_delim_len;
fields_pos = high + 1;
}
None => {
let segment = &line[low_idx..];
crash_if_err!(1, out.write_all(segment));
out.write_all(segment)?;
if line[line.len() - 1] == newline_char {
continue 'newline;
return Ok(true);
}
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
}
@ -303,23 +265,11 @@ fn cut_fields<R: Read>(reader: R, ranges: &[Range], opts: &FieldOptions) -> i32
);
}
let mut buf_in = BufReader::new(reader);
let mut out = stdout();
let mut buffer = Vec::new();
let buf_in = BufReader::new(reader);
let mut out = stdout_writer();
let delim_len = opts.delimiter.len();
'newline: loop {
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 result = buf_in.for_byte_record_with_terminator(newline_char, |line| {
let mut fields_pos = 1;
let mut low_idx = 0;
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 !opts.only_delimited {
crash_if_err!(1, out.write_all(line));
out.write_all(line)?;
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 {
low_idx = match delim_search.nth(low - fields_pos - 1) {
Some((_, beyond_delim)) => beyond_delim,
None => break,
};
}
if print_delim && low_idx >= opts.delimiter.as_bytes().len() {
low_idx -= opts.delimiter.as_bytes().len();
if let Some(delim_pos) = delim_search.nth(low - fields_pos - 1) {
low_idx = if print_delim {
delim_pos
} else {
delim_pos + delim_len
}
} else {
break;
}
}
match delim_search.nth(high - low) {
Some((high_idx, next_low_idx)) => {
Some(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 = next_low_idx;
low_idx = high_idx;
fields_pos = high + 1;
}
None => {
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 {
continue 'newline;
return Ok(true);
}
break;
}
}
}
crash_if_err!(1, out.write_all(&[newline_char]));
}
out.write_all(&[newline_char])?;
Ok(true)
});
crash_if_err!(1, result);
0
}
@ -406,7 +357,7 @@ fn cut_files(mut filenames: Vec<String>, mode: Mode) -> i32 {
continue;
}
if !path.metadata().is_ok() {
if path.metadata().is_err() {
show_error!("{}: No such file or directory", filename);
continue;
}
@ -443,7 +394,9 @@ mod options {
}
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!())
.name(NAME)
@ -487,7 +440,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.help("filter field columns from the input source")
.takes_value(true)
.allow_hyphen_values(true)
.value_name("LIST")
.value_name("LIST")
.display_order(4),
)
.arg(
@ -535,40 +488,36 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
matches.value_of(options::CHARACTERS),
matches.value_of(options::FIELDS),
) {
(Some(byte_ranges), None, None) => {
list_to_ranges(&byte_ranges[..], complement).map(|ranges| {
Mode::Bytes(
ranges,
Options {
out_delim: Some(
matches
.value_of(options::OUTPUT_DELIMITER)
.unwrap_or_default()
.to_owned(),
),
zero_terminated: matches.is_present(options::ZERO_TERMINATED),
},
)
})
}
(None, Some(char_ranges), None) => {
list_to_ranges(&char_ranges[..], complement).map(|ranges| {
Mode::Characters(
ranges,
Options {
out_delim: Some(
matches
.value_of(options::OUTPUT_DELIMITER)
.unwrap_or_default()
.to_owned(),
),
zero_terminated: matches.is_present(options::ZERO_TERMINATED),
},
)
})
}
(Some(byte_ranges), None, None) => list_to_ranges(byte_ranges, complement).map(|ranges| {
Mode::Bytes(
ranges,
Options {
out_delim: Some(
matches
.value_of(options::OUTPUT_DELIMITER)
.unwrap_or_default()
.to_owned(),
),
zero_terminated: matches.is_present(options::ZERO_TERMINATED),
},
)
}),
(None, Some(char_ranges), None) => list_to_ranges(char_ranges, complement).map(|ranges| {
Mode::Characters(
ranges,
Options {
out_delim: Some(
matches
.value_of(options::OUTPUT_DELIMITER)
.unwrap_or_default()
.to_owned(),
),
zero_terminated: matches.is_present(options::ZERO_TERMINATED),
},
)
}),
(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) {
Some(s) => {
if s.is_empty() {

View file

@ -5,7 +5,8 @@
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
#[derive(Clone)]
use memchr::memchr;
pub struct Searcher<'a> {
haystack: &'a [u8],
needle: &'a [u8],
@ -14,6 +15,7 @@ pub struct Searcher<'a> {
impl<'a> Searcher<'a> {
pub fn new(haystack: &'a [u8], needle: &'a [u8]) -> Searcher<'a> {
assert!(!needle.is_empty());
Searcher {
haystack,
needle,
@ -23,30 +25,81 @@ impl<'a> Searcher<'a> {
}
impl<'a> Iterator for Searcher<'a> {
type Item = (usize, usize);
type Item = usize;
fn next(&mut self) -> Option<(usize, usize)> {
if self.needle.len() == 1 {
for offset in self.position..self.haystack.len() {
if self.haystack[offset] == self.needle[0] {
self.position = offset + 1;
return Some((offset, offset + 1));
fn next(&mut self) -> Option<Self::Item> {
loop {
if let Some(match_idx) = memchr(self.needle[0], self.haystack) {
if self.needle.len() == 1
|| self.haystack[match_idx + 1..].starts_with(&self.needle[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 {
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)
.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);
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)
} else if let Some(fmt) = matches
.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) {
None => None,
Some(Err((input, _err))) => {
eprintln!("date: invalid date '{}'", input);
eprintln!("date: invalid date {}", input);
return 1;
}
Some(Ok(date)) => Some(date),
@ -297,11 +301,13 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
for date in dates {
match 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);
}
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")]
fn set_system_datetime(_date: DateTime<Utc>) -> i32 {
eprintln!("date: setting the date is not supported by macOS");
return 1;
1
}
#[cfg(all(unix, not(target_os = "macos")))]

View file

@ -16,14 +16,10 @@ path = "src/df.rs"
[dependencies]
clap = "2.33"
libc = "0.2"
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" }
[target.'cfg(target_os = "windows")'.dependencies]
winapi = { version = "0.3", features = ["errhandlingapi", "fileapi", "handleapi", "winerror"] }
[[bin]]
name = "df"
path = "src/main.rs"

View file

@ -6,22 +6,17 @@
// For the full copyright and license information, please view the LICENSE file
// that was distributed with this source code.
// spell-checker:ignore (ToDO) mountinfo mtab BLOCKSIZE getmntinfo 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 (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 statvfs subfs syncreads syncwrites sysfs wcslen
#[macro_use]
extern crate uucore;
#[cfg(unix)]
use uucore::fsext::statfs_fn;
use uucore::fsext::{read_fs_list, FsUsage, MountInfo};
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 std::cell::Cell;
use std::collections::HashMap;
@ -32,41 +27,11 @@ use std::ffi::CString;
#[cfg(unix)]
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")]
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)]
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 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_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_BLOCKSIZE: &str = "blocksize";
static OPT_DIRECT: &str = "direct";
@ -101,8 +58,6 @@ static OPT_TYPE: &str = "type";
static OPT_PRINT_TYPE: &str = "print-type";
static OPT_EXCLUDE_TYPE: &str = "exclude-type";
static MOUNT_OPT_BIND: &str = "bind";
/// Store names of file systems as a selector.
/// Note: `exclude` takes priority over `include`.
struct FsSelector {
@ -116,142 +71,21 @@ struct Options {
show_listed_fs: bool,
show_fs_type: bool,
show_inode_instead: bool,
print_grand_total: bool,
// block_size: usize,
human_readable_base: i64,
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)]
struct Filesystem {
mountinfo: MountInfo,
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 {
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 {
fn new() -> FsSelector {
FsSelector {
@ -286,7 +120,6 @@ impl Options {
show_listed_fs: false,
show_fs_type: false,
show_inode_instead: false,
print_grand_total: false,
// block_size: match env::var("BLOCKSIZE") {
// Ok(size) => size.parse().unwrap(),
// 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 {
// TODO: resolve uuid in `mountinfo.dev_name` if exists
fn new(mountinfo: MountInfo) -> Option<Filesystem> {
@ -550,7 +150,7 @@ impl Filesystem {
unsafe {
let path = CString::new(_stat_path).unwrap();
let mut statvfs = mem::zeroed();
if libc::statvfs(path.as_ptr(), &mut statvfs) < 0 {
if statfs_fn(path.as_ptr(), &mut statvfs) < 0 {
None
} else {
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> {
vmi.into_iter()
.filter_map(|mi| {
@ -871,9 +397,6 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
if matches.is_present(OPT_ALL) {
opt.show_all_fs = true;
}
if matches.is_present(OPT_TOTAL) {
opt.print_grand_total = true;
}
if matches.is_present(OPT_INODES) {
opt.show_inode_instead = true;
}
@ -921,6 +444,9 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
"Use%",
]
});
if cfg!(target_os = "macos") && !opt.show_inode_instead {
header.insert(header.len() - 1, "Capacity");
}
header.push("Mounted on");
for (idx, title) in header.iter().enumerate() {
@ -975,6 +501,11 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
"{0: >12} ",
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: <16}", fs.mountinfo.mount_dir);

View file

@ -53,7 +53,9 @@ pub fn guess_syntax() -> OutputFmt {
}
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)
.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 {
match guess_syntax() {
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;
}
fmt => out_format = fmt,
@ -128,7 +130,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
)
}
Err(e) => {
show_info!("{}: {}", matches.free[0], e);
show_error!("{}: {}", matches.free[0], e);
return 1;
}
}
@ -139,7 +141,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
0
}
Err(s) => {
show_info!("{}", s);
show_error!("{}", s);
1
}
}
@ -202,6 +204,8 @@ enum ParseState {
Pass,
}
use std::collections::HashMap;
use uucore::InvalidEncodingHandling;
fn parse<T>(lines: T, fmt: OutputFmt, fp: &str) -> Result<String, String>
where
T: IntoIterator,

View file

@ -10,36 +10,44 @@ extern crate uucore;
use clap::{App, Arg};
use std::path::Path;
use uucore::InvalidEncodingHandling;
static NAME: &str = "dirname";
static SYNTAX: &str = "[OPTION] NAME...";
static SUMMARY: &str = "strip last component from file name";
static ABOUT: &str = "strip last component from file name";
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 {
pub const ZERO: &str = "zero";
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 {
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!())
.name(NAME)
.usage(SYNTAX)
.about(SUMMARY)
.after_help(LONG_HELP)
.about(ABOUT)
.usage(&usage[..])
.after_help(&after_help[..])
.version(VERSION)
.arg(
Arg::with_name(options::ZERO)
.short(options::ZERO)
.long(options::ZERO)
.short("z")
.takes_value(false)
.help("separate output with NUL rather than newline"),
)
.arg(Arg::with_name(options::DIR).hidden(true).multiple(true))

View file

@ -15,6 +15,7 @@ edition = "2018"
path = "src/du.rs"
[dependencies]
clap = "2.33"
chrono = "0.4"
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" }
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::Local;
use clap::{App, Arg};
use std::collections::HashSet;
use std::env;
use std::fs;
use std::io::{stderr, Result, Write};
use std::io::{stderr, ErrorKind, Result, Write};
use std::iter;
#[cfg(not(windows))]
use std::os::unix::fs::MetadataExt;
@ -25,6 +26,7 @@ use std::os::windows::fs::MetadataExt;
use std::os::windows::io::AsRawHandle;
use std::path::PathBuf;
use std::time::{Duration, UNIX_EPOCH};
use uucore::InvalidEncodingHandling;
#[cfg(windows)]
use winapi::shared::minwindef::{DWORD, LPVOID};
#[cfg(windows)]
@ -36,6 +38,27 @@ use winapi::um::winbase::GetFileInformationByHandleEx;
#[cfg(windows)]
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 SUMMARY: &str = "estimate file space usage";
const LONG_HELP: &str = "
@ -219,14 +242,14 @@ fn unit_string_to_number(s: &str) -> Option<u64> {
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 {
Some(ref s) => unit_string_to_number(s),
None => None,
}
}
fn read_block_size(s: Option<String>) -> u64 {
fn read_block_size(s: Option<&str>) -> u64 {
match translate_to_pure_number(&s) {
Some(v) => v,
None => {
@ -235,7 +258,8 @@ fn read_block_size(s: Option<String>) -> u64 {
};
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;
}
}
@ -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),
}
@ -322,7 +360,7 @@ fn convert_size_human(size: u64, multiplier: u64, _block_size: u64) -> String {
}
}
if size == 0 {
return format!("0");
return "0".to_string();
}
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())
}
fn get_usage() -> String {
format!(
"{0} [OPTION]... [FILE]...
{0} [OPTION]... --files0-from=F",
executable!()
)
}
#[allow(clippy::cognitive_complexity)]
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!(
"[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 usage = get_usage();
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());
match (max_depth_str, max_depth) {
(Some(ref s), _) if summarize => {
@ -478,34 +581,35 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
}
let options = Options {
all: matches.opt_present("all"),
all: matches.is_present(options::ALL),
program_name: NAME.to_owned(),
max_depth,
total: matches.opt_present("total"),
separate_dirs: matches.opt_present("S"),
total: matches.is_present(options::TOTAL),
separate_dirs: matches.is_present(options::SEPARATE_DIRS),
};
let strs = if matches.free.is_empty() {
vec!["./".to_owned()]
} else {
matches.free.clone()
let strs = match matches.value_of(options::FILE) {
Some(_) => matches.values_of(options::FILE).unwrap().collect(),
None => {
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
} else {
1024
};
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
} else if matches.opt_present("b") {
} else if matches.is_present(options::BYTES) {
convert_size_b
} else if matches.opt_present("k") {
} else if matches.is_present(options::BLOCK_SIZE_1K) {
convert_size_k
} else if matches.opt_present("m") {
} else if matches.is_present(options::BLOCK_SIZE_1M) {
convert_size_m
} else {
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 time_format_str = match matches.opt_str("time-style") {
Some(s) => match &s[..] {
let time_format_str = match matches.value_of("time-style") {
Some(s) => match s {
"full-iso" => "%Y-%m-%d %H:%M:%S.%f %z",
"long-iso" => "%Y-%m-%d %H:%M",
"iso" => "%Y-%m-%d",
@ -535,7 +639,11 @@ Try '{} --help' for more information.",
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;
for path_str in strs {
@ -548,18 +656,20 @@ Try '{} --help' for more information.",
let (_, len) = iter.size_hint();
let len = len.unwrap();
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
} else {
// C's stat is such that each block is assume to be 512 bytes
// See: http://linux.die.net/man/2/stat
stat.blocks * 512
};
if matches.opt_present("time") {
if matches.is_present(options::TIME) {
let tm = {
let secs = {
match matches.opt_str("time") {
Some(s) => match &s[..] {
match matches.value_of(options::TIME) {
Some(s) => match s {
"accessed" => stat.accessed,
"created" => stat.created,
"modified" => stat.modified,
@ -632,8 +742,8 @@ mod test_du {
(Some("900KB".to_string()), Some(900 * 1000)),
(Some("BAD_STRING".to_string()), None),
];
for it in test_data.into_iter() {
assert_eq!(translate_to_pure_number(&it.0), it.1);
for it in test_data.iter() {
assert_eq!(translate_to_pure_number(&it.0.as_deref()), it.1);
}
}
@ -644,8 +754,8 @@ mod test_du {
(None, 1024),
(Some("BAD_STRING".to_string()), 1024),
];
for it in test_data.into_iter() {
assert_eq!(read_block_size(it.0.clone()), it.1);
for it in test_data.iter() {
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::iter::Peekable;
use std::str::Chars;
use uucore::InvalidEncodingHandling;
const NAME: &str = "echo";
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 {
let args = args
.collect_str(InvalidEncodingHandling::ConvertLossy)
.accept_any();
let matches = App::new(executable!())
.name(NAME)
// TrailingVarArg specifies the final positional argument is a VarArg

View file

@ -8,6 +8,8 @@
#[macro_use]
extern crate uucore;
use uucore::InvalidEncodingHandling;
mod syntax_tree;
mod tokens;
@ -15,7 +17,9 @@ static NAME: &str = "expr";
static VERSION: &str = env!("CARGO_PKG_VERSION");
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.
// 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)
}
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() {
Err(maybe_ast.err().unwrap())
} else {

View file

@ -17,10 +17,10 @@ use onig::{Regex, RegexOptions, Syntax};
use crate::tokens::Token;
type TokenStack = Vec<(usize, Token)>;
pub type OperandsList = Vec<Box<ASTNode>>;
pub type OperandsList = Vec<Box<AstNode>>;
#[derive(Debug)]
pub enum ASTNode {
pub enum AstNode {
Leaf {
token_idx: usize,
value: String,
@ -31,7 +31,7 @@ pub enum ASTNode {
operands: OperandsList,
},
}
impl ASTNode {
impl AstNode {
fn debug_dump(&self) {
self.debug_dump_impl(1);
}
@ -40,7 +40,7 @@ impl ASTNode {
print!("\t",);
}
match *self {
ASTNode::Leaf {
AstNode::Leaf {
ref token_idx,
ref value,
} => println!(
@ -49,7 +49,7 @@ impl ASTNode {
token_idx,
self.evaluate()
),
ASTNode::Node {
AstNode::Node {
ref token_idx,
ref op_type,
ref operands,
@ -67,23 +67,23 @@ impl ASTNode {
}
}
fn new_node(token_idx: usize, op_type: &str, operands: OperandsList) -> Box<ASTNode> {
Box::new(ASTNode::Node {
fn new_node(token_idx: usize, op_type: &str, operands: OperandsList) -> Box<AstNode> {
Box::new(AstNode::Node {
token_idx,
op_type: op_type.into(),
operands,
})
}
fn new_leaf(token_idx: usize, value: &str) -> Box<ASTNode> {
Box::new(ASTNode::Leaf {
fn new_leaf(token_idx: usize, value: &str) -> Box<AstNode> {
Box::new(AstNode::Leaf {
token_idx,
value: value.into(),
})
}
pub fn evaluate(&self) -> Result<String, String> {
match *self {
ASTNode::Leaf { ref value, .. } => Ok(value.clone()),
ASTNode::Node { ref op_type, .. } => match self.operand_values() {
AstNode::Leaf { ref value, .. } => Ok(value.clone()),
AstNode::Node { ref op_type, .. } => match self.operand_values() {
Err(reason) => Err(reason),
Ok(operand_values) => match op_type.as_ref() {
"+" => infix_operator_two_ints(
@ -153,7 +153,7 @@ impl ASTNode {
":" | "match" => operator_match(&operand_values),
"length" => Ok(prefix_operator_length(&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)),
},
@ -161,7 +161,7 @@ impl ASTNode {
}
}
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());
for operand in operands {
match operand.evaluate() {
@ -178,7 +178,7 @@ impl ASTNode {
pub fn tokens_to_ast(
maybe_tokens: Result<Vec<(usize, Token)>, String>,
) -> Result<Box<ASTNode>, String> {
) -> Result<Box<AstNode>, String> {
if maybe_tokens.is_err() {
Err(maybe_tokens.err().unwrap())
} 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;
if let Ok(debug_var) = env::var("EXPR_DEBUG_AST") {
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() {
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, .. })) => {
maybe_ast_node(token_idx, &value, 2, rpn)
@ -262,7 +262,7 @@ fn maybe_ast_node(
op_type: &str,
arity: usize,
rpn: &mut TokenStack,
) -> Result<Box<ASTNode>, String> {
) -> Result<Box<AstNode>, String> {
let mut operands = Vec::with_capacity(arity);
for _ in 0..arity {
match ast_from_rpn(rpn) {
@ -271,7 +271,7 @@ fn maybe_ast_node(
}
}
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(
@ -522,35 +522,23 @@ fn prefix_operator_index(values: &[String]) -> String {
"0".to_string()
}
fn prefix_operator_substr(values: &[String]) -> Result<String, String> {
fn prefix_operator_substr(values: &[String]) -> String {
assert!(values.len() == 3);
let subj = &values[0];
let mut idx = match values[1].parse::<i64>() {
Ok(i) => i,
Err(_) => return Err("expected integer as POS arg to 'substr'".to_string()),
let idx = match values[1]
.parse::<usize>()
.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,
Err(_) => return Err("expected integer as LENGTH arg to 'substr'".to_string()),
Err(_) => return String::new(),
};
if idx <= 0 || len <= 0 {
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)
subj.chars().skip(idx).take(len).collect()
}
fn bool_as_int(b: bool) -> i64 {

View file

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

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

View file

@ -13,17 +13,21 @@ use std::error::Error;
use std::io::{self, stdin, stdout, BufRead, Write};
mod factor;
pub(crate) use factor::*;
use clap::{App, Arg};
pub use factor::*;
mod miller_rabin;
pub mod numeric;
mod rho;
mod table;
pub mod table;
static SYNTAX: &str = "[OPTION] [NUMBER]...";
static SUMMARY: &str = "Print the prime factors of the given number(s).
If none are specified, read from standard input.";
static LONG_HELP: &str = "";
static VERSION: &str = env!("CARGO_PKG_VERSION");
static SUMMARY: &str = "Print the prime factors of the given NUMBER(s).
If none are specified, read from standard input.";
mod options {
pub static NUMBER: &str = "NUMBER";
}
fn print_factors_str(num_str: &str, w: &mut impl io::Write) -> Result<(), Box<dyn Error>> {
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 {
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 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();
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() {

View file

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

View file

@ -8,15 +8,13 @@
// spell-checker: ignore (ToDO) INVS
use std::num::Wrapping;
use crate::Factors;
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 {
if num == 1 {
if *num == 1 {
break;
}
@ -27,12 +25,14 @@ pub(crate) fn factor(mut num: u64, mut factors: Factors) -> (Factors, u64) {
// for a nice explanation.
let mut k = 0;
loop {
let Wrapping(x) = Wrapping(num) * Wrapping(inv);
let x = num.wrapping_mul(inv);
// While prime divides num
if x <= ceil {
num = x;
*num = x;
k += 1;
#[cfg(feature = "coz")]
coz::progress!("factor found");
} else {
if k > 0 {
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)
} else {
compute_demerits(
(args.opts.goal - tlen) as isize,
args.opts.goal as isize - tlen as isize,
stretch,
w.word_nchars as isize,
active.prev_rat,

View file

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

View file

@ -14,6 +14,7 @@ use clap::{App, Arg};
use std::fs::File;
use std::io::{stdin, BufRead, BufReader, Read};
use std::path::Path;
use uucore::InvalidEncodingHandling;
const TAB_WIDTH: usize = 8;
@ -31,7 +32,9 @@ mod options {
}
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 matches = App::new(executable!())
@ -66,7 +69,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.takes_value(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 spaces = matches.is_present(options::SPACES);

View file

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

View file

@ -16,7 +16,7 @@ path = "src/head.rs"
[dependencies]
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" }
[[bin]]

View file

@ -1,8 +1,8 @@
use clap::{App, Arg};
use std::convert::TryFrom;
use std::ffi::OsString;
use std::io::{ErrorKind, Read, Seek, SeekFrom, Write};
use uucore::{crash, executable, show_error};
use std::io::{self, ErrorKind, Read, Seek, SeekFrom, Write};
use uucore::{crash, executable, show_error, show_error_custom_description};
const EXIT_FAILURE: i32 = 1;
const EXIT_SUCCESS: i32 = 0;
@ -27,8 +27,12 @@ mod options {
pub const ZERO_NAME: &str = "ZERO";
pub const FILES_NAME: &str = "FILE";
}
mod lines;
mod parse;
mod split;
mod take;
use lines::zlines;
use take::take_all_but;
fn app<'a>() -> App<'a, 'a> {
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<()> {
if n == 0 {
return Ok(());
}
let mut readbuf = [0u8; BUF_SIZE];
let mut i = 0usize;
fn rbuf_n_bytes<R>(input: R, n: usize) -> std::io::Result<()>
where
R: Read,
{
// Read the first `n` bytes from the `input` reader.
let mut reader = input.take(n as u64);
// Write those bytes to `stdout`.
let stdout = std::io::stdout();
let mut stdout = stdout.lock();
loop {
let read = loop {
match input.read(&mut readbuf) {
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(());
}
}
io::copy(&mut reader, &mut stdout)?;
Ok(())
}
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(
input: &mut impl std::io::BufRead,
input: impl std::io::BufRead,
n: usize,
zero: bool,
) -> std::io::Result<()> {
if n == 0 {
//prints everything
return rbuf_n_bytes(input, std::usize::MAX);
if zero {
let stdout = std::io::stdout();
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];
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)
}
})
Ok(())
}
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;
for fname in &options.files {
let res = match fname.as_str() {
"-" => {
if options.verbose {
if (options.files.len() > 1 && !options.quiet) || options.verbose {
if !first {
println!();
}
@ -451,53 +424,49 @@ fn uu_head(options: &HeadOptions) {
name => {
let mut file = match std::fs::File::open(name) {
Ok(f) => f,
Err(err) => match err.kind() {
ErrorKind::NotFound => {
crash!(
EXIT_FAILURE,
"head: cannot open '{}' for reading: No such file or directory",
name
);
Err(err) => {
let prefix = format!("cannot open '{}' for reading", name);
match err.kind() {
ErrorKind::NotFound => {
show_error_custom_description!(prefix, "No such file or directory");
}
ErrorKind::PermissionDenied => {
show_error_custom_description!(prefix, "Permission denied");
}
_ => {
show_error_custom_description!(prefix, "{}", err);
}
}
ErrorKind::PermissionDenied => {
crash!(
EXIT_FAILURE,
"head: cannot open '{}' for reading: Permission denied",
name
);
}
_ => {
crash!(
EXIT_FAILURE,
"head: cannot open '{}' for reading: {}",
name,
err
);
}
},
error_count += 1;
continue;
}
};
if (options.files.len() > 1 && !options.quiet) || options.verbose {
if !first {
println!();
}
println!("==> {} <==", name)
}
head_file(&mut file, options)
}
};
if res.is_err() {
if fname.as_str() == "-" {
crash!(
EXIT_FAILURE,
"head: error reading standard input: Input/output error"
);
let name = if fname.as_str() == "-" {
"standard input"
} else {
crash!(
EXIT_FAILURE,
"head: error reading {}: Input/output error",
fname
);
}
fname
};
let prefix = format!("error reading {}", name);
show_error_custom_description!(prefix, "Input/output error");
error_count += 1;
}
first = false;
}
if error_count > 0 {
Err(error_count)
} else {
Ok(())
}
}
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);
}
};
uu_head(&args);
EXIT_SUCCESS
match uu_head(&args) {
Ok(_) => EXIT_SUCCESS,
Err(_) => EXIT_FAILURE,
}
}
#[cfg(test)]
@ -625,7 +595,7 @@ mod tests {
assert_eq!(arg_outputs("head"), Ok("head".to_owned()));
}
#[test]
#[cfg(linux)]
#[cfg(target_os = "linux")]
fn test_arg_iterate_bad_encoding() {
let invalid = unsafe { std::str::from_utf8_unchecked(b"\x80\x81") };
// 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;
use libc::c_long;
use uucore::InvalidEncodingHandling;
static SYNTAX: &str = "[options]";
static SUMMARY: &str = "";
@ -22,7 +23,10 @@ extern "C" {
}
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();
0
}

View file

@ -23,9 +23,11 @@ use std::fs;
use std::fs::File;
use std::os::unix::fs::MetadataExt;
use std::path::{Path, PathBuf};
use std::process::Command;
use std::result::Result;
const DEFAULT_MODE: u32 = 0o755;
const DEFAULT_STRIP_PROGRAM: &str = "strip";
#[allow(dead_code)]
pub struct Behavior {
@ -37,6 +39,9 @@ pub struct Behavior {
verbose: bool,
preserve_timestamps: bool,
compare: bool,
strip: bool,
strip_program: String,
create_leading: bool,
}
#[derive(Clone, Eq, PartialEq)]
@ -66,7 +71,7 @@ static OPT_BACKUP: &str = "backup";
static OPT_BACKUP_2: &str = "backup2";
static OPT_DIRECTORY: &str = "directory";
static OPT_IGNORED: &str = "ignored";
static OPT_CREATED: &str = "created";
static OPT_CREATE_LEADING: &str = "create-leading";
static OPT_GROUP: &str = "group";
static OPT_MODE: &str = "mode";
static OPT_OWNER: &str = "owner";
@ -129,9 +134,9 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.arg(
// TODO implement flag
Arg::with_name(OPT_CREATED)
Arg::with_name(OPT_CREATE_LEADING)
.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::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")
)
.arg(
// TODO implement flag
Arg::with_name(OPT_STRIP)
.short("s")
.long(OPT_STRIP)
.help("(unimplemented) strip symbol tables")
.help("strip symbol tables (no action Windows)")
)
.arg(
// TODO implement flag
Arg::with_name(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")
)
.arg(
@ -264,12 +267,6 @@ fn check_unimplemented<'a>(matches: &ArgMatches) -> Result<(), &'a str> {
Err("--backup")
} else if matches.is_present(OPT_BACKUP_2) {
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) {
Err("--suffix, -S")
} 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) {
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),
Err(err) => {
show_error!("Invalid mode string: {}", err);
@ -339,6 +336,13 @@ fn behavior(matches: &ArgMatches) -> Result<Behavior, i32> {
verbose: matches.is_present(OPT_VERBOSE),
preserve_timestamps: matches.is_present(OPT_PRESERVE_TIMESTAMPS),
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
// fs::create_dir_all and then only modify the target's dir mode.
if let Err(e) = fs::create_dir_all(path) {
show_info!("{}: {}", path.display(), e);
show_error!("{}: {}", path.display(), e);
all_successful = false;
continue;
}
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()
.map(PathBuf::from)
.collect::<Vec<_>>();
let target = Path::new(paths.last().unwrap());
if (target.is_file() || is_new_file_path(target)) && sources.len() == 1 {
copy_file_to_file(&sources[0], &target.to_path_buf(), &b)
} else {
if sources.len() > 1 || (target.exists() && target.is_dir()) {
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.
/// _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() {
show_error!("target '{}' is not a directory", target_dir.display());
return 1;
@ -434,7 +461,7 @@ fn copy_files_into_dir(files: &[PathBuf], target_dir: &PathBuf, b: &Behavior) ->
let mut all_successful = true;
for sourcepath in files.iter() {
if !sourcepath.exists() {
show_info!(
show_error!(
"cannot stat '{}': No such file or directory",
sourcepath.display()
);
@ -444,12 +471,12 @@ fn copy_files_into_dir(files: &[PathBuf], target_dir: &PathBuf, b: &Behavior) ->
}
if sourcepath.is_dir() {
show_info!("omitting directory '{}'", sourcepath.display());
show_error!("omitting directory '{}'", sourcepath.display());
all_successful = false;
continue;
}
let mut targetpath = target_dir.clone().to_path_buf();
let mut targetpath = target_dir.to_path_buf();
let filename = sourcepath.components().last().unwrap();
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.
/// _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() {
1
} 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.
///
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) {
return Ok(());
}
@ -521,6 +548,21 @@ fn copy(from: &PathBuf, to: &PathBuf, b: &Behavior) -> Result<(), ()> {
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() {
return Err(());
}
@ -537,7 +579,7 @@ fn copy(from: &PathBuf, to: &PathBuf, b: &Behavior) -> Result<(), ()> {
};
let gid = meta.gid();
match wrap_chown(
to.as_path(),
to,
&meta,
Some(owner_id),
Some(gid),
@ -546,10 +588,10 @@ fn copy(from: &PathBuf, to: &PathBuf, b: &Behavior) -> Result<(), ()> {
) {
Ok(n) => {
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,
_ => 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) => {
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 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(_) => {}
Err(e) => show_info!("{}", e),
Err(e) => show_error!("{}", e),
}
}
if b.verbose {
show_info!("'{}' -> '{}'", from.display(), to.display());
show_error!("'{}' -> '{}'", from.display(), to.display());
}
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_.
///
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) {
Ok(meta) => meta,
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<(), ()> {
use std::os::unix::fs::PermissionsExt;
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"
[dependencies]
clap = "2.33"
libc = "0.2.42"
uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["signals"] }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }

View file

@ -10,17 +10,26 @@
#[macro_use]
extern crate uucore;
use clap::{App, Arg};
use libc::{c_int, pid_t};
use std::io::Error;
use uucore::signals::ALL_SIGNALS;
use uucore::InvalidEncodingHandling;
static SYNTAX: &str = "[options] <pid> [...]";
static SUMMARY: &str = "";
static LONG_HELP: &str = "";
static VERSION: &str = env!("CARGO_PKG_VERSION");
static ABOUT: &str = "Send signal to processes or list information about signals.";
static EXIT_OK: i32 = 0;
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)]
pub enum Mode {
Kill,
@ -29,42 +38,73 @@ pub enum Mode {
}
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 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
} else if matches.opt_present("list") {
} else if matches.is_present(options::LIST) {
Mode::List
} else {
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 {
Mode::Kill => {
return kill(
&matches
.opt_str("signal")
.unwrap_or_else(|| obs_signal.unwrap_or_else(|| "9".to_owned())),
matches.free,
)
let sig = match (obs_signal, matches.value_of(options::SIGNAL)) {
(Some(s), Some(_)) => s, // -s takes precedence
(Some(s), None) => s,
(None, Some(s)) => s.to_owned(),
(None, None) => "TERM".to_owned(),
};
return kill(&sig, &pids_or_signals);
}
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>) {
@ -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 optional_signal_value = uucore::signals::signal_by_name_or_value(signalname);
let signal_value = match optional_signal_value {
Some(x) => x,
None => crash!(EXIT_ERR, "unknown signal name {}", signalname),
};
for pid in &pids {
for pid in pids {
match pid.parse::<usize>() {
Ok(x) => {
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"
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
clap = "2.33"
[[bin]]
name = "link"

View file

@ -8,13 +8,21 @@
#[macro_use]
extern crate uucore;
use clap::{App, Arg};
use std::fs::hard_link;
use std::io::Error;
use std::path::Path;
static SYNTAX: &str = "[OPTIONS] FILE1 FILE2";
static SUMMARY: &str = "Create a link named FILE2 to FILE1";
static LONG_HELP: &str = "";
static VERSION: &str = env!("CARGO_PKG_VERSION");
static ABOUT: &str = "Call the link function to create a link named FILE2 to an existing FILE1.";
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 {
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 {
let matches = app!(SYNTAX, SUMMARY, LONG_HELP).parse(args.collect_str());
if matches.free.len() != 2 {
crash!(1, "{}", msg_wrong_number_of_arguments!(2));
}
let usage = get_usage();
let matches = App::new(executable!())
.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 new = Path::new(&matches.free[1]);
let files: Vec<_> = matches
.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) {
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() {
show_error!("target '{}' is not a directory", target_dir.display());
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 {
match srcpath.as_os_str().to_str() {
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 absdst = canonicalize(dst, CanonicalizeMode::Normal)?;
let suffix_pos = abssrc
@ -390,7 +390,7 @@ fn relative_path<'a>(src: &PathBuf, dst: &PathBuf) -> Result<Cow<'a, Path>> {
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 source: Cow<'_, Path> = if settings.relative {
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();
p.push_str(suffix);
PathBuf::from(p)
}
fn numbered_backup_path(path: &PathBuf) -> PathBuf {
fn numbered_backup_path(path: &Path) -> PathBuf {
let mut i: u64 = 1;
loop {
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());
if test_path.exists() {
return numbered_backup_path(path);

View file

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

View file

@ -13,6 +13,9 @@
extern crate uucore;
use std::ffi::CStr;
use uucore::InvalidEncodingHandling;
use clap::App;
extern "C" {
// 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 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 {
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() {
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"
[dependencies]
locale = "0.2.2"
chrono = "0.4.19"
clap = "2.33"
lazy_static = "1.0.1"
unicode-width = "0.1.8"
number_prefix = "0.4"
term_grid = "0.1.5"
termsize = "0.1.6"
time = "0.1.40"
unicode-width = "0.1.5"
globset = "0.4.6"
uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["entries", "fs"] }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
lscolors = { version = "0.7.1", features = ["ansi_term"] }
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]
atty = "0.2"
lazy_static = "1.4.0"
[[bin]]
name = "ls"

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
use std::char::from_digit;
const SPECIAL_SHELL_CHARS: &str = "~`#$&*()\\|[]{};'\"<>?! ";
const SPECIAL_SHELL_CHARS: &str = "~`#$&*()|[]{};\\'\"<>?! ";
pub(super) enum QuotingStyle {
Shell {
@ -27,12 +27,10 @@ pub(super) enum Quotes {
// This implementation is heavily inspired by the std::char::EscapeDefault implementation
// in the Rust standard library. This custom implementation is needed because the
// characters \a, \b, \e, \f & \v are not recognized by Rust.
#[derive(Clone, Debug)]
struct EscapedChar {
state: EscapeState,
}
#[derive(Clone, Debug)]
enum EscapeState {
Done,
Char(char),
@ -41,14 +39,12 @@ enum EscapeState {
Octal(EscapeOctal),
}
#[derive(Clone, Debug)]
struct EscapeOctal {
c: char,
state: EscapeOctalState,
idx: usize,
}
#[derive(Clone, Debug)]
enum EscapeOctalState {
Done,
Backslash,
@ -135,7 +131,6 @@ impl EscapedChar {
'\x0B' => Backslash('v'),
'\x0C' => Backslash('f'),
'\r' => Backslash('r'),
'\\' => Backslash('\\'),
'\x00'..='\x1F' | '\x7F' => Octal(EscapeOctal::from(c)),
'\'' => match quotes {
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 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)
}
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
// because e.g. \b\n is escaped as $'\b\n' and not like $'b'$'n'
let mut in_dollar = false;
@ -254,7 +249,7 @@ fn shell_with_escape(name: String, quotes: Quotes) -> (String, bool) {
(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 {
QuotingStyle::Literal { 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())
.collect()
} else {
name
name.into()
}
}
QuotingStyle::C { quotes } => {
@ -359,7 +354,7 @@ mod tests {
fn check_names(name: &str, map: Vec<(&str, &str)>) {
assert_eq!(
map.iter()
.map(|(_, style)| escape_name(name.to_string(), &get_style(style)))
.map(|(_, style)| escape_name(name, &get_style(style)))
.collect::<Vec<String>>(),
map.iter()
.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
// no importance for file 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.
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 b_string = b.to_string_lossy();
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 let Some(parent) = path.parent() {
if parent != empty && !parent.exists() {
show_info!(
show_error!(
"cannot create directory '{}': No such file or directory",
path.display()
);
@ -125,7 +125,7 @@ fn mkdir(path: &Path, recursive: bool, mode: u16, verbose: bool) -> i32 {
fs::create_dir
};
if let Err(e) = create_dir(path) {
show_info!("{}: {}", path.display(), e.to_string());
show_error!("{}: {}", path.display(), e.to_string());
return 1;
}

View file

@ -11,6 +11,7 @@ extern crate uucore;
use clap::{App, Arg};
use libc::mkfifo;
use std::ffi::CString;
use uucore::InvalidEncodingHandling;
static NAME: &str = "mkfifo";
static VERSION: &str = env!("CARGO_PKG_VERSION");
@ -25,7 +26,9 @@ mod options {
}
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!())
.name(NAME)

View file

@ -16,7 +16,7 @@ name = "uu_mknod"
path = "src/mknod.rs"
[dependencies]
getopts = "0.2.18"
clap = "2.33"
libc = "^0.2.42"
uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["mode"] }
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
// file that was distributed with this source code.
// spell-checker:ignore (ToDO) parsemode makedev sysmacros makenod newmode perror IFBLK IFCHR IFIFO
mod parsemode;
// spell-checker:ignore (ToDO) parsemode makedev sysmacros perror IFBLK IFCHR IFIFO
#[macro_use]
extern crate uucore;
use std::ffi::CString;
use clap::{App, Arg, ArgMatches};
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 getopts::Options;
use std::ffi::CString;
use uucore::InvalidEncodingHandling;
static NAME: &str = "mknod";
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;
@ -31,168 +50,172 @@ fn makedev(maj: u64, min: u64) -> dev_t {
}
#[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")
}
#[cfg(unix)]
fn _makenod(path: CString, mode: mode_t, dev: dev_t) -> i32 {
unsafe { libc::mknod(path.as_ptr(), mode, dev) }
fn _makenod(file_name: &str, mode: mode_t, dev: dev_t) -> i32 {
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)]
pub fn uumain(args: impl uucore::Args) -> i32 {
let args = args.collect_str();
let mut opts = Options::new();
let args = args
.collect_str(InvalidEncodingHandling::Ignore)
.accept_any();
// Linux-specific options, not implemented
// 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(
"m",
"mode",
"set file permission bits to MODE, not a=rw - umask",
"MODE",
);
opts.optflag("", "help", "display this help and exit");
opts.optflag("", "version", "output version information and exit");
let matches = App::new(executable!())
.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..]) {
Ok(m) => m,
Err(f) => crash!(1, "{}\nTry '{} --help' for more information.", f, NAME),
let mode = match get_mode(&matches) {
Ok(mode) => mode,
Err(err) => {
show_error!("{}", err);
return 1;
}
};
if matches.opt_present("help") {
println!(
"Usage: {0} [OPTION]... NAME TYPE [MAJOR MINOR]
let file_name = matches.value_of("name").expect("Missing argument 'NAME'");
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
// Only check the first character, to allow mnemonic usage like
// 'mknod /dev/rst0 character 18 0'.
let ch = matches
.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
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.",
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;
}
if ch == 'p' {
if matches.is_present("major") || matches.is_present("minor") {
eprintln!("Fifos do not have major and minor device numbers.");
eprintln!("Try '{} --help' for more information.", NAME);
1
} else {
_makenod(file_name, S_IFIFO | mode, 0)
}
unsafe {
last_umask = libc::umask(0);
}
}
} else {
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;
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);
let dev = makedev(major, minor);
if ch == 'b' {
// block special file
ret = _makenod(c_str, S_IFBLK | newmode, dev);
} else {
_makenod(file_name, S_IFBLK | mode, dev)
} else if ch == 'c' || ch == 'u' {
// 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 {
libc::umask(last_umask);
}
}
if ret == -1 {
let c_str = CString::new(format!("{}: {}", NAME, matches.free[0]).as_str())
.expect("Failed to convert to CString");
unsafe {
libc::perror(c_str.as_ptr());
}
}
ret
}
fn get_mode(matches: &ArgMatches) -> Result<mode_t, String> {
match matches.value_of("mode") {
None => Ok(MODE_RW_UGO),
Some(str_mode) => uucore::mode::parse_mode(str_mode)
.map_err(|e| format!("invalid mode ({})", e))
.and_then(|mode| {
if mode > 0o777 {
Err("mode must specify only file permission bits".to_string())
} else {
Ok(mode)
}
}),
}
}
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;
pub fn parse_mode(mode: Option<String>) -> Result<mode_t, String> {
let fperm = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH;
if let Some(mode) = mode {
let arr: &[char] = &['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
let result = if mode.contains(arr) {
mode::parse_numeric(fperm as u32, mode.as_str())
} else {
mode::parse_symbolic(fperm as u32, mode.as_str(), true)
};
result.map(|mode| mode as mode_t)
pub const MODE_RW_UGO: mode_t = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH;
pub fn parse_mode(mode: &str) -> Result<mode_t, String> {
let arr: &[char] = &['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
let result = if mode.contains(arr) {
mode::parse_numeric(MODE_RW_UGO as u32, mode)
} else {
Ok(fperm)
}
mode::parse_symbolic(MODE_RW_UGO as u32, mode, true)
};
result.map(|mode| mode as mode_t)
}
#[cfg(test)]
@ -39,20 +36,19 @@ mod test {
#[test]
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!(
super::parse_mode(Some("+x".to_owned())).unwrap(),
super::parse_mode("+x").unwrap(),
if !is_wsl() { 0o777 } else { 0o776 }
);
assert_eq!(super::parse_mode(Some("a-w".to_owned())).unwrap(), 0o444);
assert_eq!(super::parse_mode(Some("g-r".to_owned())).unwrap(), 0o626);
assert_eq!(super::parse_mode("a-w").unwrap(), 0o444);
assert_eq!(super::parse_mode("g-r").unwrap(), 0o626);
}
#[test]
fn numeric_modes() {
assert_eq!(super::parse_mode(Some("644".to_owned())).unwrap(), 0o644);
assert_eq!(super::parse_mode(Some("+100".to_owned())).unwrap(), 0o766);
assert_eq!(super::parse_mode(Some("-4".to_owned())).unwrap(), 0o662);
assert_eq!(super::parse_mode(None).unwrap(), 0o666);
assert_eq!(super::parse_mode("644").unwrap(), 0o644);
assert_eq!(super::parse_mode("+100").unwrap(), 0o766);
assert_eq!(super::parse_mode("-4").unwrap(), 0o662);
}
}

View file

@ -15,14 +15,11 @@ use clap::{App, Arg};
use std::env;
use std::iter;
use std::mem::forget;
use std::path::{is_separator, PathBuf};
use rand::Rng;
use tempfile::Builder;
mod tempdir;
static ABOUT: &str = "create a temporary file or directory.";
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() {
show_info!(
show_error!(
"invalid template, {}; with --tmpdir, it may not be absolute",
template
);
@ -213,50 +210,48 @@ pub fn dry_exec(mut tmpdir: PathBuf, prefix: &str, rand: usize, suffix: &str) ->
0
}
fn exec(
tmpdir: PathBuf,
prefix: &str,
rand: usize,
suffix: &str,
make_dir: bool,
quiet: bool,
) -> i32 {
if make_dir {
match tempdir::new_in(&tmpdir, prefix, rand, suffix) {
Ok(ref f) => {
println!("{}", f);
return 0;
}
Err(e) => {
if !quiet {
show_info!("{}: {}", e, tmpdir.display());
fn exec(dir: PathBuf, prefix: &str, rand: usize, suffix: &str, make_dir: bool, quiet: bool) -> i32 {
let res = if make_dir {
let tmpdir = Builder::new()
.prefix(prefix)
.rand_bytes(rand)
.suffix(suffix)
.tempdir_in(&dir);
// `into_path` consumes the TempDir without removing it
tmpdir.map(|d| d.into_path().to_string_lossy().to_string())
} else {
let tmpfile = Builder::new()
.prefix(prefix)
.rand_bytes(rand)
.suffix(suffix)
.tempfile_in(&dir);
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;
}
}
}
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;
Err(x) => Err(x),
}
};
let tmpname = tmpfile.path().to_string_lossy().to_string();
println!("{}", tmpname);
// CAUTION: Not to call `drop` of tmpfile, which removes the tempfile,
// I call a dangerous function `forget`.
forget(tmpfile);
0
match res {
Ok(ref f) => {
println!("{}", f);
0
}
Err(e) => {
if !quiet {
show_error!("{}: {}", e, dir.display());
}
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;
#[cfg(all(unix, not(target_os = "fuchsia")))]
use nix::sys::termios::{self, LocalFlags, SetArg};
use uucore::InvalidEncodingHandling;
#[cfg(target_os = "redox")]
extern crate redox_termios;
@ -38,6 +39,9 @@ fn get_usage() -> String {
pub fn uumain(args: impl uucore::Args) -> i32 {
let usage = get_usage();
let args = args
.collect_str(InvalidEncodingHandling::ConvertLossy)
.accept_any();
let matches = App::new(executable!())
.version(VERSION)

View file

@ -20,6 +20,7 @@ use std::os::unix;
#[cfg(windows)]
use std::os::windows;
use std::path::{Path, PathBuf};
use uucore::backup_control::{self, BackupMode};
use fs_extra::dir::{move_dir, CopyOptions as DirCopyOptions};
@ -40,16 +41,9 @@ pub enum OverwriteMode {
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 VERSION: &str = env!("CARGO_PKG_VERSION");
static LONG_HELP: &str = "";
static OPT_BACKUP: &str = "backup";
static OPT_BACKUP_NO_ARG: &str = "b";
@ -80,20 +74,16 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
let matches = App::new(executable!())
.version(VERSION)
.about(ABOUT)
.after_help(&*format!("{}\n{}", LONG_HELP, backup_control::BACKUP_CONTROL_LONG_HELP))
.usage(&usage[..])
.arg(
Arg::with_name(OPT_BACKUP)
.long(OPT_BACKUP)
.help("make a backup of each existing destination file")
.takes_value(true)
.possible_value("simple")
.possible_value("never")
.possible_value("numbered")
.possible_value("t")
.possible_value("existing")
.possible_value("nil")
.possible_value("none")
.possible_value("off")
.require_equals(true)
.min_values(0)
.possible_values(backup_control::BACKUP_CONTROL_VALUES)
.value_name("CONTROL")
)
.arg(
@ -172,18 +162,17 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.unwrap_or_default();
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 {
show_error!(
"options --backup and --no-clobber are mutually exclusive\n\
Try '{} --help' for more information.",
executable!()
);
show_usage_error!("options --backup and --no-clobber are mutually exclusive");
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 {
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 {
if let Some(ref name) = b.target_dir {
return move_files_into_dir(files, &PathBuf::from(name), &b);
@ -295,7 +253,7 @@ fn exec(files: &[PathBuf], b: Behavior) -> i32 {
"cannot move {} to {}: {}",
source.display(),
target.display(),
e
e.to_string()
);
1
}
@ -335,7 +293,7 @@ fn exec(files: &[PathBuf], b: Behavior) -> i32 {
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() {
show_error!("target {} is not a directory", target_dir.display());
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) {
show_error!(
"mv: cannot move {} to {}: {}",
"cannot move {} to {}: {}",
sourcepath.display(),
targetpath.display(),
e
e.to_string()
);
all_successful = false;
}
}
if all_successful {
0
} 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;
if to.exists() {
@ -388,12 +347,7 @@ fn rename(from: &PathBuf, to: &PathBuf, b: &Behavior) -> io::Result<()> {
OverwriteMode::Force => {}
};
backup_path = match b.backup {
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)),
};
backup_path = backup_control::get_backup_path(b.backup, to, &b.suffix);
if let Some(ref backup_path) = 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
/// 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() {
// Get metadata without following symlinks
let metadata = from.symlink_metadata()?;
@ -452,7 +406,13 @@ fn rename_with_fallback(from: &PathBuf, to: &PathBuf) -> io::Result<()> {
..DirCopyOptions::new()
};
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 {
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
/// symlinks return an error.
#[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)?;
#[cfg(unix)]
{
@ -507,29 +467,7 @@ fn read_yes() -> bool {
}
}
fn simple_backup_path(path: &PathBuf, suffix: &str) -> PathBuf {
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 {
fn is_empty_dir(path: &Path) -> bool {
match fs::read_dir(path) {
Ok(contents) => contents.peekable().peek().is_none(),
Err(_e) => false,

View file

@ -16,6 +16,7 @@ use std::fs::File;
use std::io::{stdin, BufRead, BufReader, Read};
use std::iter::repeat;
use std::path::Path;
use uucore::InvalidEncodingHandling;
mod helper;
@ -84,7 +85,9 @@ pub mod options {
}
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!())
.name(NAME)

View file

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

View file

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

View file

@ -42,6 +42,7 @@ use crate::partialreader::*;
use crate::peekreader::*;
use crate::prn_char::format_ascii_dump;
use clap::{self, AppSettings, Arg, ArgMatches};
use uucore::InvalidEncodingHandling;
static VERSION: &str = env!("CARGO_PKG_VERSION");
const PEEK_BUFFER_SIZE: usize = 4; // utf-8 can be 4 bytes
@ -118,7 +119,7 @@ struct 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) {
None => ByteOrder::Native,
Some("little") => ByteOrder::Little,
@ -221,7 +222,9 @@ impl OdOptions {
/// parses and validates command line parameters, prepares data structures,
/// opens the input and calls `odfunc` to process the input.
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!())
.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 {
#[allow(clippy::match_like_matches_macro)]
// `matches!(...)` macro not stabilized until rust v1.42
match ch {
'A' | 'j' | 'N' | 'S' | 'w' => true,
_ => false,
}
matches!(ch, 'A' | 'j' | 'N' | 'S' | 'w')
}
/// 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 {
return Ok(CommandLineInputs::FileAndOffset((
input_strings[0].clone().to_owned(),
input_strings[0].to_string(),
n,
None,
)));
@ -106,7 +106,7 @@ pub fn parse_inputs_traditional(input_strings: Vec<&str>) -> Result<CommandLineI
Some(m),
))),
(_, Ok(m)) => Ok(CommandLineInputs::FileAndOffset((
input_strings[0].clone().to_owned(),
input_strings[0].to_string(),
m,
None,
))),
@ -118,7 +118,7 @@ pub fn parse_inputs_traditional(input_strings: Vec<&str>) -> Result<CommandLineI
let label = parse_offset_operand(&input_strings[2]);
match (offset, label) {
(Ok(n), Ok(m)) => Ok(CommandLineInputs::FileAndOffset((
input_strings[0].clone().to_owned(),
input_strings[0].to_string(),
n,
Some(m),
))),

View file

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

View file

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

View file

@ -15,69 +15,114 @@ use uucore::utmpx::{self, time, Utmpx};
use std::io::prelude::*;
use std::io::BufReader;
use std::io::Result as IOResult;
use std::fs::File;
use std::os::unix::fs::MetadataExt;
use clap::{App, Arg};
use std::path::PathBuf;
static SYNTAX: &str = "[OPTION]... [USER]...";
static SUMMARY: &str = "A lightweight 'finger' program; print user information.";
use uucore::InvalidEncodingHandling;
const BUFSIZE: usize = 1024;
pub fn uumain(args: impl uucore::Args) -> i32 {
let args = args.collect_str();
static VERSION: &str = env!("CARGO_PKG_VERSION");
static ABOUT: &str = "pinky - lightweight finger";
let long_help = &format!(
"
-l produce long format output for the specified USERs
-b omit the user's home directory and shell in long format
-h omit the user's project file in long format
-p omit the user's plan file in long format
-s do short format output, this is the default
-f omit the line of column headings in short format
-w omit the user's full name in short format
-i omit the user's full name and remote host in short format
-q omit the user's full name, remote host and idle time
in short format
--help display this help and exit
--version output version information and exit
mod options {
pub const LONG_FORMAT: &str = "long_format";
pub const OMIT_HOME_DIR: &str = "omit_home_dir";
pub const OMIT_PROJECT_FILE: &str = "omit_project_file";
pub const OMIT_PLAN_FILE: &str = "omit_plan_file";
pub const SHORT_FORMAT: &str = "short_format";
pub const OMIT_HEADINGS: &str = "omit_headings";
pub const OMIT_NAME: &str = "omit_name";
pub const OMIT_NAME_HOST: &str = "omit_name_host";
pub const OMIT_NAME_HOST_TIME: &str = "omit_name_host_time";
pub const USER: &str = "user";
}
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
);
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
// 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;
// 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.
let mut include_fullname = true;
// 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.
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
// 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.
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. */
let mut include_where = true;
if matches.opt_present("w") {
if matches.is_present(options::OMIT_NAME) {
include_fullname = false;
}
if matches.opt_present("i") {
if matches.is_present(options::OMIT_NAME_HOST) {
include_fullname = false;
include_where = false;
}
if matches.opt_present("q") {
if matches.is_present(options::OMIT_NAME_HOST_TIME) {
include_fullname = false;
include_idle = 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 {
include_idle,
include_heading,
@ -132,16 +172,12 @@ The utmp file will be {}",
include_plan,
include_home_and_shell,
include_where,
names: matches.free,
names: users,
};
if do_short_format {
if let Err(e) = pk.short_pinky() {
show_usage_error!("{}", e);
1
} else {
0
}
pk.short_pinky();
0
} else {
pk.long_pinky()
}
@ -250,17 +286,10 @@ impl Pinky {
print!(" {}", time_string(&ut));
if self.include_where && !ut.host().is_empty() {
let ut_host = ut.host();
let mut res = ut_host.splitn(2, ':');
let host = match res.next() {
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),
}
let mut s = ut.host();
if self.include_where && !s.is_empty() {
s = safe_unwrap!(ut.canon_host());
print!(" {}", s);
}
println!();
@ -282,7 +311,7 @@ impl Pinky {
println!();
}
fn short_pinky(&self) -> IOResult<()> {
fn short_pinky(&self) {
if self.include_heading {
self.print_heading();
}
@ -295,7 +324,6 @@ impl Pinky {
}
}
}
Ok(())
}
fn long_pinky(&self) -> i32 {

View file

@ -178,7 +178,9 @@ quick_error! {
}
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();
opts.opt(

View file

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

View file

@ -1,15 +1,15 @@
#![allow(dead_code)]
// spell-checker:ignore (change!) each's
// spell-checker:ignore (ToDO) LONGHELP FORMATSTRING templating parameterizing formatstr
use uucore::InvalidEncodingHandling;
mod cli;
mod memo;
mod tokenize;
static NAME: &str = "printf";
static VERSION: &str = env!("CARGO_PKG_VERSION");
static SHORT_USAGE: &str = "printf: usage: printf [-v var] format [arguments]";
static LONGHELP_LEAD: &str = "printf
USAGE: printf FORMATSTRING [ARGUMENT]...
@ -273,7 +273,9 @@ COPYRIGHT :
";
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];
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.iter().rev().map(|x| *x).collect();
let ret: Vec<u8> = ret_rev.into_iter().rev().collect();
ret
}
@ -102,70 +101,6 @@ pub fn arrnum_int_div_step(
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> {
let mut carry: u16 = u16::from(base_ten_int_term);
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.iter().rev().map(|x| *x).collect();
let ret: Vec<u8> = ret_rev.into_iter().rev().collect();
ret
}
pub fn base_conv_vec(src: &[u8], radix_src: u8, radix_dest: u8) -> Vec<u8> {
let mut result: Vec<u8> = Vec::new();
result.push(0);
let mut result = vec![0];
for i in src {
result = arrnum_int_mult(&result, radix_dest, radix_src);
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
#[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
// to implement this for arbitrary string input.
// until then, the below operates as an outline
// of how it would work.
let mut result: Vec<u8> = Vec::new();
result.push(0);
let mut factor: f64 = 1_f64;
let radix_src_float: f64 = f64::from(radix_src);
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
}
#[allow(unused_variables)]
pub fn base_conv_str(
src: &str,
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)
// 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(
inprefix: &InPrefix,
str_in: &str,
analysis: &FloatAnalysis,
last_dec_place: usize,
_str_in: &str,
_analysis: &FloatAnalysis,
_last_dec_place: usize,
capitalized: bool,
) -> FormatPrimitive {
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.
// for hex to hex, it's really just a matter of moving the
// decimal point and calculating the mantissa by its initial

View file

@ -22,12 +22,11 @@ fn get_len_fprim(fprim: &FormatPrimitive) -> usize {
len
}
pub struct Decf {
as_num: f64,
}
pub struct Decf;
impl Decf {
pub fn new() -> Decf {
Decf { as_num: 0.0 }
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::float_common::{get_primitive_dec, primitive_to_str_common, FloatAnalysis};
pub struct Floatf {
as_num: f64,
}
pub struct Floatf;
impl Floatf {
pub fn new() -> Floatf {
Floatf { as_num: 0.0 }
Floatf
}
}
impl Formatter for Floatf {

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