1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2025-07-29 03:57:44 +00:00

Merge branch 'master' of github.com:uutils/coreutils into hbina-tr-reimplement-expansion

This commit is contained in:
Hanif Bin Ariffin 2021-09-19 23:01:48 +08:00
commit 9ab4f3de2f
305 changed files with 9396 additions and 4464 deletions

View file

@ -1,21 +0,0 @@
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-2-release-amd64
setup_script:
- pkg install -y curl gmake
- curl https://sh.rustup.rs -sSf --output rustup.sh
- sh rustup.sh -y --profile=minimal
build_script:
- . $HOME/.cargo/env
- cargo build
test_script:
- . $HOME/.cargo/env
- cargo test -p uucore -p coreutils

View file

@ -14,7 +14,6 @@ env:
PROJECT_DESC: "Core universal (cross-platform) utilities" PROJECT_DESC: "Core universal (cross-platform) utilities"
PROJECT_AUTH: "uutils" PROJECT_AUTH: "uutils"
RUST_MIN_SRV: "1.47.0" ## MSRV v1.47.0 RUST_MIN_SRV: "1.47.0" ## MSRV v1.47.0
RUST_COV_SRV: "2021-05-06" ## (~v1.52.0) supported rust version for code coverage; (date required/used by 'coverage') ## !maint: refactor when code coverage support is included in the stable channel
on: [push, pull_request] on: [push, pull_request]
@ -102,11 +101,17 @@ jobs:
fail-fast: false fail-fast: false
matrix: matrix:
job: job:
- { os: ubuntu-latest , features: feat_os_unix } - { os: ubuntu-latest }
- { os: macos-latest , features: feat_os_macos } - { os: macos-latest , features: feat_os_macos }
- { os: windows-latest , features: feat_os_windows } - { os: windows-latest , features: feat_os_windows }
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Install/setup prerequisites
shell: bash
run: |
case '${{ matrix.job.os }}' in
macos-latest) brew install coreutils ;; # needed for show-utils.sh
esac
- name: Initialize workflow variables - name: Initialize workflow variables
id: vars id: vars
shell: bash shell: bash
@ -115,9 +120,14 @@ jobs:
outputs() { step_id="vars"; for var in "$@" ; do echo steps.${step_id}.outputs.${var}="${!var}"; echo ::set-output name=${var}::${!var}; done; } outputs() { step_id="vars"; for var in "$@" ; do echo steps.${step_id}.outputs.${var}="${!var}"; echo ::set-output name=${var}::${!var}; done; }
# target-specific options # target-specific options
# * CARGO_FEATURES_OPTION # * CARGO_FEATURES_OPTION
CARGO_FEATURES_OPTION='' ; CARGO_FEATURES_OPTION='--all-features' ;
if [ -n "${{ matrix.job.features }}" ]; then CARGO_FEATURES_OPTION='--features "${{ matrix.job.features }}"' ; fi if [ -n "${{ matrix.job.features }}" ]; then CARGO_FEATURES_OPTION='--features ${{ matrix.job.features }}' ; fi
outputs CARGO_FEATURES_OPTION outputs CARGO_FEATURES_OPTION
# * determine sub-crate utility list
UTILITY_LIST="$(./util/show-utils.sh ${CARGO_FEATURES_OPTION})"
echo UTILITY_LIST=${UTILITY_LIST}
CARGO_UTILITY_LIST_OPTIONS="$(for u in ${UTILITY_LIST}; do echo "-puu_${u}"; done;)"
outputs CARGO_UTILITY_LIST_OPTIONS
- name: Install `rust` toolchain - name: Install `rust` toolchain
uses: actions-rs/toolchain@v1 uses: actions-rs/toolchain@v1
with: with:
@ -130,7 +140,7 @@ jobs:
run: | run: |
## `clippy` lint testing ## `clippy` lint testing
# * convert any warnings to GHA UI annotations; ref: <https://help.github.com/en/actions/reference/workflow-commands-for-github-actions#setting-a-warning-message> # * convert any warnings to GHA UI annotations; ref: <https://help.github.com/en/actions/reference/workflow-commands-for-github-actions#setting-a-warning-message>
S=$(cargo +nightly clippy --all-targets ${{ matrix.job.cargo-options }} ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }} -- -D warnings 2>&1) && printf "%s\n" "$S" || { printf "%s\n" "$S" ; printf "%s" "$S" | sed -E -n -e '/^error:/{' -e "N; s/^error:[[:space:]]+(.*)\\n[[:space:]]+-->[[:space:]]+${PWD//\//\\/}\/(.*):([0-9]+):([0-9]+).*$/::error file=\2,line=\3,col=\4::ERROR: \`cargo clippy\`: \1 (file:'\2', line:\3)/p;" -e '}' ; exit 1 ; } S=$(cargo +nightly clippy --all-targets ${{ steps.vars.outputs.CARGO_UTILITY_LIST_OPTIONS }} ${{ matrix.job.cargo-options }} ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }} -- -D warnings 2>&1) && printf "%s\n" "$S" || { printf "%s\n" "$S" ; printf "%s" "$S" | sed -E -n -e '/^error:/{' -e "N; s/^error:[[:space:]]+(.*)\\n[[:space:]]+-->[[:space:]]+${PWD//\//\\/}\/(.*):([0-9]+):([0-9]+).*$/::error file=\2,line=\3,col=\4::ERROR: \`cargo clippy\`: \1 (file:'\2', line:\3)/p;" -e '}' ; exit 1 ; }
code_spellcheck: code_spellcheck:
name: Style/spelling name: Style/spelling
@ -521,6 +531,52 @@ jobs:
n_fails=$(echo "$output" | grep "^FAIL:\s" | wc --lines) n_fails=$(echo "$output" | grep "^FAIL:\s" | wc --lines)
if [ $n_fails -gt 0 ] ; then echo "::warning ::${n_fails}+ test failures" ; fi if [ $n_fails -gt 0 ] ; then echo "::warning ::${n_fails}+ test failures" ; fi
test_freebsd:
runs-on: macos-latest
name: Tests/FreeBSD test suite
env:
mem: 2048
steps:
- uses: actions/checkout@v2
- name: Prepare, build and test
id: test
uses: vmactions/freebsd-vm@v0.1.5
with:
usesh: true
prepare: pkg install -y curl gmake sudo
run: |
# Need to be run in the same block. Otherwise, we are back on the mac host.
set -e
pw adduser -n cuuser -d /root/ -g wheel -c "Coreutils user to build" -w random
chown -R cuuser:wheel /root/ /Users/runner/work/coreutils/
whoami
# Needs to be done in a sudo as we are changing users
sudo -i -u cuuser sh << EOF
set -e
whoami
curl https://sh.rustup.rs -sSf --output rustup.sh
sh rustup.sh -y --profile=minimal
## Info
# environment
echo "## environment"
echo "CI='${CI}'"
# tooling info display
echo "## tooling"
. $HOME/.cargo/env
cargo -V
rustc -V
env
# where the files are resynced
cd /Users/runner/work/coreutils/coreutils/
cargo build
cargo test --features feat_os_unix -p uucore -p coreutils
# Clean to avoid to rsync back the files
cargo clean
EOF
coverage: coverage:
name: Code Coverage name: Code Coverage
runs-on: ${{ matrix.job.os }} runs-on: ${{ matrix.job.os }}
@ -550,7 +606,7 @@ jobs:
## VARs setup ## VARs setup
outputs() { step_id="vars"; for var in "$@" ; do echo steps.${step_id}.outputs.${var}="${!var}"; echo ::set-output name=${var}::${!var}; done; } outputs() { step_id="vars"; for var in "$@" ; do echo steps.${step_id}.outputs.${var}="${!var}"; echo ::set-output name=${var}::${!var}; done; }
# toolchain # toolchain
TOOLCHAIN="nightly-${{ env.RUST_COV_SRV }}" ## default to "nightly" toolchain (required for certain required unstable compiler flags) ## !maint: refactor when stable channel has needed support TOOLCHAIN="nightly" ## default to "nightly" toolchain (required for certain required unstable compiler flags) ## !maint: refactor when stable channel has needed support
# * specify gnu-type TOOLCHAIN for windows; `grcov` requires gnu-style code coverage data files # * specify gnu-type TOOLCHAIN for windows; `grcov` requires gnu-style code coverage data files
case ${{ matrix.job.os }} in windows-*) TOOLCHAIN="$TOOLCHAIN-x86_64-pc-windows-gnu" ;; esac; case ${{ matrix.job.os }} in windows-*) TOOLCHAIN="$TOOLCHAIN-x86_64-pc-windows-gnu" ;; esac;
# * use requested TOOLCHAIN if specified # * use requested TOOLCHAIN if specified
@ -652,3 +708,35 @@ jobs:
flags: ${{ steps.vars.outputs.CODECOV_FLAGS }} flags: ${{ steps.vars.outputs.CODECOV_FLAGS }}
name: codecov-umbrella name: codecov-umbrella
fail_ci_if_error: false fail_ci_if_error: false
unused_deps:
name: Unused deps
runs-on: ${{ matrix.job.os }}
strategy:
fail-fast: false
matrix:
job:
- { os: ubuntu-latest , features: feat_os_unix }
- { os: macos-latest , features: feat_os_macos }
- { os: windows-latest , features: feat_os_windows }
steps:
- uses: actions/checkout@v2
- name: Install `rust` toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: nightly
default: true
profile: minimal
- name: Install `cargo-udeps`
uses: actions-rs/install@v0.1
with:
crate: cargo-udeps
version: latest
use-tool-cache: true
env:
RUSTUP_TOOLCHAIN: stable
- name: Confirms there isn't any unused deps
shell: bash
run: |
cargo +nightly udeps --all-targets &> udeps.log || cat udeps.log
grep "seem to have been used" udeps.log

View file

@ -11,7 +11,7 @@ repos:
- id: rust-clippy - id: rust-clippy
name: Rust clippy name: Rust clippy
description: Run cargo clippy on files included in the commit. description: Run cargo clippy on files included in the commit.
entry: cargo +nightly clippy --all-targets --all-features -- entry: cargo +nightly clippy --workspace --all-targets --all-features --
pass_filenames: false pass_filenames: false
types: [file, rust] types: [file, rust]
language: system language: system

View file

@ -1,3 +1,4 @@
AFAICT
arity arity
autogenerate autogenerate
autogenerated autogenerated
@ -8,6 +9,8 @@ bytewise
canonicalization canonicalization
canonicalize canonicalize
canonicalizing canonicalizing
codepoint
codepoints
colorizable colorizable
colorize colorize
coprime coprime
@ -35,10 +38,14 @@ falsey
fileio fileio
flamegraph flamegraph
fullblock fullblock
getfacl
gibi gibi
gibibytes gibibytes
glob glob
globbing globbing
hardcode
hardcoded
hardcoding
hardfloat hardfloat
hardlink hardlink
hardlinks hardlinks
@ -49,7 +56,9 @@ iflag
iflags iflags
kibi kibi
kibibytes kibibytes
libacl
lcase lcase
lossily
mebi mebi
mebibytes mebibytes
mergeable mergeable
@ -90,6 +99,7 @@ seedable
semver semver
semiprime semiprime
semiprimes semiprimes
setfacl
shortcode shortcode
shortcodes shortcodes
siginfo siginfo
@ -107,6 +117,7 @@ toolchain
truthy truthy
ucase ucase
unbuffered unbuffered
udeps
unescape unescape
unintuitive unintuitive
unprefixed unprefixed

View file

@ -8,6 +8,7 @@ csh
globstar globstar
inotify inotify
localtime localtime
mksh
mountinfo mountinfo
mountpoint mountpoint
mtab mtab
@ -91,6 +92,7 @@ rerast
rollup rollup
sed sed
selinuxenabled selinuxenabled
sestatus
wslpath wslpath
xargs xargs

View file

@ -9,12 +9,14 @@ aho-corasick
backtrace backtrace
blake2b_simd blake2b_simd
bstr bstr
bytecount
byteorder byteorder
chacha chacha
chrono chrono
conv conv
corasick corasick
crossterm crossterm
exacl
filetime filetime
formatteriteminfo formatteriteminfo
fsext fsext
@ -66,6 +68,7 @@ structs
substr substr
splitn splitn
trunc trunc
uninit
# * uutils # * uutils
basenc basenc
@ -106,12 +109,18 @@ whoami
# * vars/errno # * vars/errno
errno errno
EACCES
EBADF
EBUSY
EEXIST EEXIST
EINVAL
ENODATA ENODATA
ENOENT ENOENT
ENOSYS ENOSYS
EPERM ENOTEMPTY
EOPNOTSUPP EOPNOTSUPP
EPERM
EROFS
# * vars/fcntl # * vars/fcntl
F_GETFL F_GETFL
@ -162,6 +171,7 @@ blocksize
canonname canonname
chroot chroot
dlsym dlsym
execvp
fdatasync fdatasync
freeaddrinfo freeaddrinfo
getaddrinfo getaddrinfo
@ -268,6 +278,7 @@ ULONG
ULONGLONG ULONGLONG
UNLEN UNLEN
WCHAR WCHAR
WSADATA
errhandlingapi errhandlingapi
fileapi fileapi
handleapi handleapi
@ -313,3 +324,6 @@ uucore_procs
uumain uumain
uutil uutil
uutils uutils
# * function names
getcwd

View file

@ -1,2 +0,0 @@
{
}

311
Cargo.lock generated
View file

@ -8,6 +8,12 @@ version = "0.11.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3"
[[package]]
name = "ahash"
version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "739f4a8db6605981345c5654f3a85b056ce52f37a39d34da03f25bf2151ea16e"
[[package]] [[package]]
name = "aho-corasick" name = "aho-corasick"
version = "0.7.18" version = "0.7.18"
@ -104,9 +110,9 @@ dependencies = [
[[package]] [[package]]
name = "bitflags" name = "bitflags"
version = "1.3.1" version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2da1976d75adbe5fbc88130ecd119529cf1cc6a93ae1546d8696ee66f0d21af1" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]] [[package]]
name = "bitvec" name = "bitvec"
@ -167,6 +173,12 @@ dependencies = [
"utf8-width", "utf8-width",
] ]
[[package]]
name = "bytecount"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72feb31ffc86498dacdbd0fcebb56138e7177a8cc5cea4516031d15ae85a742e"
[[package]] [[package]]
name = "byteorder" name = "byteorder"
version = "1.4.3" version = "1.4.3"
@ -219,7 +231,7 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "853eda514c284c2287f4bf20ae614f8781f40a81d32ecda6e91449304dfe077c" checksum = "853eda514c284c2287f4bf20ae614f8781f40a81d32ecda6e91449304dfe077c"
dependencies = [ dependencies = [
"glob 0.3.0", "glob",
"libc", "libc",
"libloading", "libloading",
] ]
@ -235,7 +247,7 @@ dependencies = [
"bitflags", "bitflags",
"strsim", "strsim",
"term_size", "term_size",
"textwrap", "textwrap 0.11.0",
"unicode-width", "unicode-width",
"vec_map", "vec_map",
] ]
@ -279,7 +291,7 @@ dependencies = [
"clap", "clap",
"conv", "conv",
"filetime", "filetime",
"glob 0.3.0", "glob",
"lazy_static", "lazy_static",
"libc", "libc",
"nix 0.20.0", "nix 0.20.0",
@ -290,7 +302,7 @@ dependencies = [
"selinux", "selinux",
"sha1", "sha1",
"tempfile", "tempfile",
"textwrap", "textwrap 0.14.2",
"time", "time",
"unindent", "unindent",
"unix_socket", "unix_socket",
@ -363,6 +375,7 @@ dependencies = [
"uu_relpath", "uu_relpath",
"uu_rm", "uu_rm",
"uu_rmdir", "uu_rmdir",
"uu_runcon",
"uu_seq", "uu_seq",
"uu_shred", "uu_shred",
"uu_shuf", "uu_shuf",
@ -622,6 +635,17 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "derivative"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b"
dependencies = [
"proc-macro2",
"quote 1.0.9",
"syn",
]
[[package]] [[package]]
name = "diff" name = "diff"
version = "0.1.12" version = "0.1.12"
@ -637,6 +661,15 @@ dependencies = [
"generic-array", "generic-array",
] ]
[[package]]
name = "dlv-list"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68df3f2b690c1b86e65ef7830956aededf3cb0a16f898f79b9a6f421a7b6211b"
dependencies = [
"rand 0.8.4",
]
[[package]] [[package]]
name = "dns-lookup" name = "dns-lookup"
version = "1.0.5" version = "1.0.5"
@ -684,6 +717,21 @@ dependencies = [
"termcolor", "termcolor",
] ]
[[package]]
name = "exacl"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "769bbd173781e84865b957cf83449f0d2869f4c9d2f191cbbffffb3d9751ba2b"
dependencies = [
"bitflags",
"log",
"nix 0.21.0",
"num_enum",
"scopeguard",
"serde",
"uuid",
]
[[package]] [[package]]
name = "fake-simd" name = "fake-simd"
version = "0.1.2" version = "0.1.2"
@ -789,12 +837,6 @@ dependencies = [
"wasi 0.10.2+wasi-snapshot-preview1", "wasi 0.10.2+wasi-snapshot-preview1",
] ]
[[package]]
name = "glob"
version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8be18de09a56b60ed0edf84bc9df007e30040691af7acd1c41874faac5895bfb"
[[package]] [[package]]
name = "glob" name = "glob"
version = "0.3.0" version = "0.3.0"
@ -820,6 +862,15 @@ version = "1.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62aca2aba2d62b4a7f5b33f3712cb1b0692779a56fb510499d5c0aa594daeaf3" checksum = "62aca2aba2d62b4a7f5b33f3712cb1b0692779a56fb510499d5c0aa594daeaf3"
[[package]]
name = "hashbrown"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04"
dependencies = [
"ahash",
]
[[package]] [[package]]
name = "heck" name = "heck"
version = "0.3.3" version = "0.3.3"
@ -878,9 +929,9 @@ dependencies = [
[[package]] [[package]]
name = "ioctl-sys" name = "ioctl-sys"
version = "0.5.2" version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e2c4b26352496eaaa8ca7cfa9bd99e93419d3f7983dc6e99c2a35fe9e33504a" checksum = "1c429fffa658f288669529fc26565f728489a2e39bc7b24a428aaaf51355182e"
[[package]] [[package]]
name = "itertools" name = "itertools"
@ -937,9 +988,9 @@ dependencies = [
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.85" version = "0.2.101"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ccac4b00700875e6a07c6cde370d44d32fa01c5a65cdd2fca6858c479d28bb3" checksum = "3cb00336871be5ed2c8ed44b60ae9959dc5b9f08539422ed43f09e34ecaeba21"
[[package]] [[package]]
name = "libloading" name = "libloading"
@ -951,15 +1002,6 @@ dependencies = [
"winapi 0.3.9", "winapi 0.3.9",
] ]
[[package]]
name = "locale"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5fdbe492a9c0238da900a1165c42fc5067161ce292678a6fe80921f30fe307fd"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "lock_api" name = "lock_api"
version = "0.4.4" version = "0.4.4"
@ -1053,15 +1095,14 @@ dependencies = [
[[package]] [[package]]
name = "nix" name = "nix"
version = "0.13.1" version = "0.19.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4dbdc256eaac2e3bd236d93ad999d3479ef775c863dbda3068c4006a92eec51b" checksum = "b2ccba0cfe4fdf15982d1674c69b1fd80bad427d293849982668dfe454bd61f2"
dependencies = [ dependencies = [
"bitflags", "bitflags",
"cc", "cc",
"cfg-if 0.1.10", "cfg-if 1.0.0",
"libc", "libc",
"void",
] ]
[[package]] [[package]]
@ -1076,6 +1117,19 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "nix"
version = "0.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c3728fec49d363a50a8828a190b379a446cc5cf085c06259bbbeb34447e4ec7"
dependencies = [
"bitflags",
"cc",
"cfg-if 1.0.0",
"libc",
"memoffset",
]
[[package]] [[package]]
name = "nodrop" name = "nodrop"
version = "0.1.14" version = "0.1.14"
@ -1116,9 +1170,9 @@ dependencies = [
[[package]] [[package]]
name = "num-bigint" name = "num-bigint"
version = "0.4.0" version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e0d047c1062aa51e256408c560894e5251f08925980e53cf1aa5bd00eec6512" checksum = "74e768dff5fb39a41b3bcd30bb25cf989706c90d028d1ad71971987aa309d535"
dependencies = [ dependencies = [
"autocfg", "autocfg",
"num-integer", "num-integer",
@ -1154,6 +1208,28 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "num_enum"
version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9bd055fb730c4f8f4f57d45d35cd6b3f0980535b056dc7ff119cee6a66ed6f"
dependencies = [
"derivative",
"num_enum_derive",
]
[[package]]
name = "num_enum_derive"
version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "486ea01961c4a818096de679a8b740b26d9033146ac5291b1c98557658f8cdd9"
dependencies = [
"proc-macro-crate",
"proc-macro2",
"quote 1.0.9",
"syn",
]
[[package]] [[package]]
name = "number_prefix" name = "number_prefix"
version = "0.4.0" version = "0.4.0"
@ -1194,6 +1270,16 @@ dependencies = [
"pkg-config", "pkg-config",
] ]
[[package]]
name = "ordered-multimap"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c672c7ad9ec066e428c00eb917124a06f08db19e2584de982cc34b1f4c12485"
dependencies = [
"dlv-list",
"hashbrown",
]
[[package]] [[package]]
name = "ouroboros" name = "ouroboros"
version = "0.10.1" version = "0.10.1"
@ -1311,6 +1397,16 @@ dependencies = [
"output_vt100", "output_vt100",
] ]
[[package]]
name = "proc-macro-crate"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41fdbd1df62156fbc5945f4762632564d7d038153091c3fcf1067f6aef7cff92"
dependencies = [
"thiserror",
"toml",
]
[[package]] [[package]]
name = "proc-macro-error" name = "proc-macro-error"
version = "1.0.4" version = "1.0.4"
@ -1613,9 +1709,13 @@ dependencies = [
[[package]] [[package]]
name = "rust-ini" name = "rust-ini"
version = "0.13.0" version = "0.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e52c148ef37f8c375d49d5a73aa70713125b7f19095948a923f80afdeb22ec2" checksum = "63471c4aa97a1cf8332a5f97709a79a4234698de6a1f5087faf66f2dae810e22"
dependencies = [
"cfg-if 1.0.0",
"ordered-multimap",
]
[[package]] [[package]]
name = "rustc-hash" name = "rustc-hash"
@ -1646,9 +1746,9 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]] [[package]]
name = "selinux" name = "selinux"
version = "0.2.1" version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1aa2f705dd871c2eb90888bb2d44b13218b34f5c7318c3971df62f799d0143eb" checksum = "1cf704a543fe60d898f3253f1cc37655d0f0e9cdb68ef6230557e0e031b80608"
dependencies = [ dependencies = [
"bitflags", "bitflags",
"libc", "libc",
@ -1670,6 +1770,26 @@ dependencies = [
"walkdir", "walkdir",
] ]
[[package]]
name = "serde"
version = "1.0.130"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f12d06de37cf59146fbdecab66aa99f9fe4f78722e3607577a5375d66bd0c913"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.130"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7bc1a1ab1961464eae040d96713baa5a724a8152c1222492465b54322ec508b"
dependencies = [
"proc-macro2",
"quote 1.0.9",
"syn",
]
[[package]] [[package]]
name = "sha1" name = "sha1"
version = "0.6.0" version = "0.6.0"
@ -1752,6 +1872,12 @@ version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e" checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e"
[[package]]
name = "smawk"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f67ad224767faa3c7d8b6d91985b78e70a1324408abcb1cfcc2be4c06bc06043"
[[package]] [[package]]
name = "socket2" name = "socket2"
version = "0.3.19" version = "0.3.19"
@ -1783,15 +1909,15 @@ checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
[[package]] [[package]]
name = "strum" name = "strum"
version = "0.20.0" version = "0.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7318c509b5ba57f18533982607f24070a55d353e90d4cae30c467cdb2ad5ac5c" checksum = "aaf86bbcfd1fa9670b7a129f64fc0c9fcbbfe4f1bc4210e9e98fe71ffc12cde2"
[[package]] [[package]]
name = "strum_macros" name = "strum_macros"
version = "0.20.1" version = "0.21.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee8bc6b87a5112aeeab1f4a9f7ab634fe6cbefc4850006df31267f4cfb9e3149" checksum = "d06aaeeee809dbc59eb4556183dd927df67db1540de5be8d3ec0b6636358a5ec"
dependencies = [ dependencies = [
"heck", "heck",
"proc-macro2", "proc-macro2",
@ -1858,6 +1984,16 @@ dependencies = [
"winapi-util", "winapi-util",
] ]
[[package]]
name = "terminal_size"
version = "0.1.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "633c1a546cee861a1a6d0dc69ebeca693bf4296661ba7852b9d21d159e0506df"
dependencies = [
"libc",
"winapi 0.3.9",
]
[[package]] [[package]]
name = "termion" name = "termion"
version = "1.5.6" version = "1.5.6"
@ -1893,6 +2029,18 @@ dependencies = [
"unicode-width", "unicode-width",
] ]
[[package]]
name = "textwrap"
version = "0.14.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0066c8d12af8b5acd21e00547c3797fde4e8677254a7ee429176ccebbe93dd80"
dependencies = [
"smawk",
"terminal_size",
"unicode-linebreak",
"unicode-width",
]
[[package]] [[package]]
name = "thiserror" name = "thiserror"
version = "1.0.26" version = "1.0.26"
@ -1923,12 +2071,30 @@ dependencies = [
"winapi 0.3.9", "winapi 0.3.9",
] ]
[[package]]
name = "toml"
version = "0.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa"
dependencies = [
"serde",
]
[[package]] [[package]]
name = "typenum" name = "typenum"
version = "1.13.0" version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "879f6906492a7cd215bfa4cf595b600146ccfac0c79bcbd1f3000162af5e8b06" checksum = "879f6906492a7cd215bfa4cf595b600146ccfac0c79bcbd1f3000162af5e8b06"
[[package]]
name = "unicode-linebreak"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a52dcaab0c48d931f7cc8ef826fa51690a08e1ea55117ef26f89864f532383f"
dependencies = [
"regex",
]
[[package]] [[package]]
name = "unicode-segmentation" name = "unicode-segmentation"
version = "1.8.0" version = "1.8.0"
@ -1979,6 +2145,12 @@ dependencies = [
"log", "log",
] ]
[[package]]
name = "utf-8"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
[[package]] [[package]]
name = "utf8-width" name = "utf8-width"
version = "0.1.5" version = "0.1.5"
@ -2067,7 +2239,6 @@ dependencies = [
"clap", "clap",
"uucore", "uucore",
"uucore_procs", "uucore_procs",
"walkdir",
] ]
[[package]] [[package]]
@ -2086,10 +2257,8 @@ name = "uu_chown"
version = "0.0.7" version = "0.0.7"
dependencies = [ dependencies = [
"clap", "clap",
"glob 0.3.0",
"uucore", "uucore",
"uucore_procs", "uucore_procs",
"walkdir",
] ]
[[package]] [[package]]
@ -2126,10 +2295,12 @@ name = "uu_cp"
version = "0.0.7" version = "0.0.7"
dependencies = [ dependencies = [
"clap", "clap",
"exacl",
"filetime", "filetime",
"ioctl-sys", "ioctl-sys",
"libc", "libc",
"quick-error 1.2.3", "quick-error 1.2.3",
"selinux",
"uucore", "uucore",
"uucore_procs", "uucore_procs",
"walkdir", "walkdir",
@ -2142,7 +2313,6 @@ name = "uu_csplit"
version = "0.0.7" version = "0.0.7"
dependencies = [ dependencies = [
"clap", "clap",
"glob 0.2.11",
"regex", "regex",
"thiserror", "thiserror",
"uucore", "uucore",
@ -2202,7 +2372,7 @@ name = "uu_dircolors"
version = "0.0.7" version = "0.0.7"
dependencies = [ dependencies = [
"clap", "clap",
"glob 0.3.0", "glob",
"uucore", "uucore",
"uucore_procs", "uucore_procs",
] ]
@ -2334,6 +2504,7 @@ dependencies = [
"hex", "hex",
"libc", "libc",
"md5", "md5",
"memchr 2.4.0",
"regex", "regex",
"regex-syntax", "regex-syntax",
"sha1", "sha1",
@ -2455,7 +2626,6 @@ dependencies = [
"clap", "clap",
"globset", "globset",
"lazy_static", "lazy_static",
"locale",
"lscolors", "lscolors",
"number_prefix", "number_prefix",
"once_cell", "once_cell",
@ -2514,7 +2684,7 @@ dependencies = [
"atty", "atty",
"clap", "clap",
"crossterm", "crossterm",
"nix 0.13.1", "nix 0.19.1",
"redox_syscall", "redox_syscall",
"redox_termios", "redox_termios",
"unicode-segmentation", "unicode-segmentation",
@ -2539,7 +2709,7 @@ version = "0.0.7"
dependencies = [ dependencies = [
"clap", "clap",
"libc", "libc",
"nix 0.13.1", "nix 0.20.0",
"uucore", "uucore",
"uucore_procs", "uucore_procs",
] ]
@ -2639,7 +2809,6 @@ dependencies = [
"itertools 0.10.1", "itertools 0.10.1",
"quick-error 2.0.1", "quick-error 2.0.1",
"regex", "regex",
"time",
"uucore", "uucore",
"uucore_procs", "uucore_procs",
] ]
@ -2731,6 +2900,20 @@ name = "uu_rmdir"
version = "0.0.7" version = "0.0.7"
dependencies = [ dependencies = [
"clap", "clap",
"libc",
"uucore",
"uucore_procs",
]
[[package]]
name = "uu_runcon"
version = "0.0.7"
dependencies = [
"clap",
"fts-sys",
"libc",
"selinux",
"thiserror",
"uucore", "uucore",
"uucore_procs", "uucore_procs",
] ]
@ -2751,10 +2934,8 @@ name = "uu_shred"
version = "0.0.7" version = "0.0.7"
dependencies = [ dependencies = [
"clap", "clap",
"filetime",
"libc", "libc",
"rand 0.5.6", "rand 0.7.3",
"time",
"uucore", "uucore",
"uucore_procs", "uucore_procs",
] ]
@ -2863,6 +3044,8 @@ name = "uu_tac"
version = "0.0.7" version = "0.0.7"
dependencies = [ dependencies = [
"clap", "clap",
"memchr 2.4.0",
"regex",
"uucore", "uucore",
"uucore_procs", "uucore_procs",
] ]
@ -3008,7 +3191,6 @@ name = "uu_unlink"
version = "0.0.7" version = "0.0.7"
dependencies = [ dependencies = [
"clap", "clap",
"libc",
"uucore", "uucore",
"uucore_procs", "uucore_procs",
] ]
@ -3036,10 +3218,12 @@ dependencies = [
name = "uu_wc" name = "uu_wc"
version = "0.0.7" version = "0.0.7"
dependencies = [ dependencies = [
"bytecount",
"clap", "clap",
"libc", "libc",
"nix 0.20.0", "nix 0.20.0",
"thiserror", "unicode-width",
"utf-8",
"uucore", "uucore",
"uucore_procs", "uucore_procs",
] ]
@ -3058,6 +3242,7 @@ name = "uu_whoami"
version = "0.0.7" version = "0.0.7"
dependencies = [ dependencies = [
"clap", "clap",
"libc",
"uucore", "uucore",
"uucore_procs", "uucore_procs",
"winapi 0.3.9", "winapi 0.3.9",
@ -3068,6 +3253,7 @@ name = "uu_yes"
version = "0.0.7" version = "0.0.7"
dependencies = [ dependencies = [
"clap", "clap",
"nix 0.20.0",
"uucore", "uucore",
"uucore_procs", "uucore_procs",
] ]
@ -3084,11 +3270,12 @@ dependencies = [
"getopts", "getopts",
"lazy_static", "lazy_static",
"libc", "libc",
"nix 0.13.1", "nix 0.20.0",
"platform-info", "once_cell",
"termion", "termion",
"thiserror", "thiserror",
"time", "time",
"walkdir",
"wild", "wild",
"winapi 0.3.9", "winapi 0.3.9",
"z85", "z85",
@ -3103,6 +3290,12 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "uuid"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7"
[[package]] [[package]]
name = "vec_map" name = "vec_map"
version = "0.8.2" version = "0.8.2"
@ -3115,12 +3308,6 @@ version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe"
[[package]]
name = "void"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
[[package]] [[package]]
name = "walkdir" name = "walkdir"
version = "2.3.2" version = "2.3.2"
@ -3159,7 +3346,7 @@ version = "2.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "035793abb854745033f01a07647a79831eba29ec0be377205f2a25b0aa830020" checksum = "035793abb854745033f01a07647a79831eba29ec0be377205f2a25b0aa830020"
dependencies = [ dependencies = [
"glob 0.3.0", "glob",
] ]
[[package]] [[package]]

View file

@ -95,8 +95,10 @@ feat_common_core = [
"true", "true",
"truncate", "truncate",
"tsort", "tsort",
"touch",
"unexpand", "unexpand",
"uniq", "uniq",
"unlink",
"wc", "wc",
"yes", "yes",
] ]
@ -146,7 +148,12 @@ feat_os_unix_musl = [
# NOTE: # NOTE:
# The selinux(-sys) crate requires `libselinux` headers and shared library to be accessible in the C toolchain at compile time. # The selinux(-sys) crate requires `libselinux` headers and shared library to be accessible in the C toolchain at compile time.
# Running a uutils compiled with `feat_selinux` requires an SELinux enabled Kernel at run time. # Running a uutils compiled with `feat_selinux` requires an SELinux enabled Kernel at run time.
feat_selinux = ["id/selinux", "selinux", "feat_require_selinux"] feat_selinux = ["cp/selinux", "id/selinux", "selinux", "feat_require_selinux"]
# "feat_acl" == set of utilities providing support for acl (access control lists) if enabled with `--features feat_acl`.
# NOTE:
# On linux, the posix-acl/acl-sys crate requires `libacl` headers and shared library to be accessible in the C toolchain at compile time.
# On FreeBSD and macOS this is not required.
feat_acl = ["cp/feat_acl"]
## feature sets with requirements (restricting cross-platform availability) ## feature sets with requirements (restricting cross-platform availability)
# #
# ** NOTE: these `feat_require_...` sets should be minimized as much as possible to encourage cross-platform availability of utilities # ** NOTE: these `feat_require_...` sets should be minimized as much as possible to encourage cross-platform availability of utilities
@ -176,7 +183,6 @@ feat_require_unix = [
"timeout", "timeout",
"tty", "tty",
"uname", "uname",
"unlink",
] ]
# "feat_require_unix_utmpx" == set of utilities requiring unix utmp/utmpx support # "feat_require_unix_utmpx" == set of utilities requiring unix utmp/utmpx support
# * ref: <https://wiki.musl-libc.org/faq.html#Q:-Why-is-the-utmp/wtmp-functionality-only-implemented-as-stubs?> # * ref: <https://wiki.musl-libc.org/faq.html#Q:-Why-is-the-utmp/wtmp-functionality-only-implemented-as-stubs?>
@ -189,6 +195,7 @@ feat_require_unix_utmpx = [
# "feat_require_selinux" == set of utilities depending on SELinux. # "feat_require_selinux" == set of utilities depending on SELinux.
feat_require_selinux = [ feat_require_selinux = [
"chcon", "chcon",
"runcon",
] ]
## (alternate/newer/smaller platforms) feature sets ## (alternate/newer/smaller platforms) feature sets
# "feat_os_unix_fuchsia" == set of utilities which can be built/run on the "Fuchsia" OS (refs: <https://fuchsia.dev>; <https://en.wikipedia.org/wiki/Google_Fuchsia>) # "feat_os_unix_fuchsia" == set of utilities which can be built/run on the "Fuchsia" OS (refs: <https://fuchsia.dev>; <https://en.wikipedia.org/wiki/Google_Fuchsia>)
@ -239,9 +246,9 @@ test = [ "uu_test" ]
[dependencies] [dependencies]
clap = { version = "2.33", features = ["wrap_help"] } clap = { version = "2.33", features = ["wrap_help"] }
lazy_static = { version="1.3" } lazy_static = { version="1.3" }
textwrap = { version="=0.11.0", features=["term_size"] } # !maint: [2020-05-10; rivy] unstable crate using undocumented features; pinned currently, will review textwrap = { version="0.14", features=["terminal_size"] }
uucore = { version=">=0.0.9", package="uucore", path="src/uucore" } uucore = { version=">=0.0.9", package="uucore", path="src/uucore" }
selinux = { version="0.2.1", optional = true } selinux = { version="0.2.3", optional = true }
# * uutils # * uutils
uu_test = { optional=true, version="0.0.7", package="uu_test", path="src/uu/test" } uu_test = { optional=true, version="0.0.7", package="uu_test", path="src/uu/test" }
# #
@ -313,6 +320,7 @@ realpath = { optional=true, version="0.0.7", package="uu_realpath", path="src/uu
relpath = { optional=true, version="0.0.7", package="uu_relpath", path="src/uu/relpath" } relpath = { optional=true, version="0.0.7", package="uu_relpath", path="src/uu/relpath" }
rm = { optional=true, version="0.0.7", package="uu_rm", path="src/uu/rm" } rm = { optional=true, version="0.0.7", package="uu_rm", path="src/uu/rm" }
rmdir = { optional=true, version="0.0.7", package="uu_rmdir", path="src/uu/rmdir" } rmdir = { optional=true, version="0.0.7", package="uu_rmdir", path="src/uu/rmdir" }
runcon = { optional=true, version="0.0.7", package="uu_runcon", path="src/uu/runcon" }
seq = { optional=true, version="0.0.7", package="uu_seq", path="src/uu/seq" } seq = { optional=true, version="0.0.7", package="uu_seq", path="src/uu/seq" }
shred = { optional=true, version="0.0.7", package="uu_shred", path="src/uu/shred" } shred = { optional=true, version="0.0.7", package="uu_shred", path="src/uu/shred" }
shuf = { optional=true, version="0.0.7", package="uu_shuf", path="src/uu/shuf" } shuf = { optional=true, version="0.0.7", package="uu_shuf", path="src/uu/shuf" }
@ -358,7 +366,6 @@ conv = "0.3"
filetime = "0.2" filetime = "0.2"
glob = "0.3.0" glob = "0.3.0"
libc = "0.2" libc = "0.2"
nix = "0.20.0"
pretty_assertions = "0.7.2" pretty_assertions = "0.7.2"
rand = "0.7" rand = "0.7"
regex = "1.0" regex = "1.0"
@ -370,8 +377,12 @@ uucore = { version=">=0.0.9", package="uucore", path="src/uucore", features=["en
walkdir = "2.2" walkdir = "2.2"
atty = "0.2" atty = "0.2"
[target.'cfg(unix)'.dev-dependencies] [target.'cfg(target_os = "linux")'.dev-dependencies]
rlimit = "0.4.0" rlimit = "0.4.0"
[target.'cfg(unix)'.dev-dependencies]
nix = "0.20.0"
rust-users = { version="0.10", package="users" } rust-users = { version="0.10", package="users" }
unix_socket = "0.5.0" unix_socket = "0.5.0"

View file

@ -1,3 +1,29 @@
Documentation
-------------
The source of the documentation is available on:
https://uutils.github.io/coreutils-docs/coreutils/
The documentation is updated everyday on this repository:
https://github.com/uutils/coreutils-docs
Running GNU tests
-----------------
<!-- spell-checker:ignore gnulib -->
- Check out https://github.com/coreutils/coreutils next to your fork as gnu
- Check out https://github.com/coreutils/gnulib next to your fork as gnulib
- Rename the checkout of your fork to uutils
At the end you should have uutils, gnu and gnulib checked out next to each other.
- Run `cd uutils && ./util/build-gnu.sh && cd ..` to get everything ready (this may take a while)
- Finally, you can run `tests with bash uutils/util/run-gnu-test.sh <test>`. Instead of `<test>` insert the test you want to run, e.g. `tests/misc/wc-proc`.
Code Coverage Report Generation Code Coverage Report Generation
--------------------------------- ---------------------------------

View file

@ -157,7 +157,8 @@ UNIX_PROGS := \
who who
SELINUX_PROGS := \ SELINUX_PROGS := \
chcon chcon \
runcon
ifneq ($(OS),Windows_NT) ifneq ($(OS),Windows_NT)
PROGS := $(PROGS) $(UNIX_PROGS) PROGS := $(PROGS) $(UNIX_PROGS)
@ -216,6 +217,7 @@ TEST_PROGS := \
realpath \ realpath \
rm \ rm \
rmdir \ rmdir \
runcon \
seq \ seq \
sort \ sort \
split \ split \

View file

@ -365,8 +365,8 @@ To contribute to uutils, please see [CONTRIBUTING](CONTRIBUTING.md).
| Done | Semi-Done | To Do | | Done | Semi-Done | To Do |
|-----------|-----------|--------| |-----------|-----------|--------|
| arch | cp | runcon | | arch | cp | stty |
| base32 | date | stty | | base32 | date | |
| base64 | dd | | | base64 | dd | |
| basename | df | | | basename | df | |
| basenc | expr | | | basenc | expr | |
@ -426,6 +426,7 @@ To contribute to uutils, please see [CONTRIBUTING](CONTRIBUTING.md).
| relpath | | | | relpath | | |
| rm | | | | rm | | |
| rmdir | | | | rmdir | | |
| runcon | | |
| seq | | | | seq | | |
| shred | | | | shred | | |
| shuf | | | | shuf | | |

View file

@ -10,10 +10,12 @@ use clap::Arg;
use clap::Shell; use clap::Shell;
use std::cmp; use std::cmp;
use std::collections::hash_map::HashMap; use std::collections::hash_map::HashMap;
use std::ffi::OsStr;
use std::ffi::OsString; use std::ffi::OsString;
use std::io::{self, Write}; use std::io::{self, Write};
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::process; use std::process;
use uucore::display::Quotable;
const VERSION: &str = env!("CARGO_PKG_VERSION"); const VERSION: &str = env!("CARGO_PKG_VERSION");
@ -70,18 +72,27 @@ fn main() {
Some(OsString::from(*util)) Some(OsString::from(*util))
} else { } else {
// unmatched binary name => regard as multi-binary container and advance argument list // unmatched binary name => regard as multi-binary container and advance argument list
uucore::set_utility_is_second_arg();
args.next() args.next()
}; };
// 0th argument equals util name? // 0th argument equals util name?
if let Some(util_os) = util_name { if let Some(util_os) = util_name {
let util = util_os.as_os_str().to_string_lossy(); fn not_found(util: &OsStr) -> ! {
println!("{}: function/utility not found", util.maybe_quote());
process::exit(1);
}
let util = match util_os.to_str() {
Some(util) => util,
None => not_found(&util_os),
};
if util == "completion" { if util == "completion" {
gen_completions(args, utils); gen_completions(args, utils);
} }
match utils.get(&util[..]) { match utils.get(util) {
Some(&(uumain, _)) => { Some(&(uumain, _)) => {
process::exit(uumain((vec![util_os].into_iter()).chain(args))); process::exit(uumain((vec![util_os].into_iter()).chain(args)));
} }
@ -89,9 +100,12 @@ fn main() {
if util == "--help" || util == "-h" { if util == "--help" || util == "-h" {
// see if they want help on a specific util // see if they want help on a specific util
if let Some(util_os) = args.next() { if let Some(util_os) = args.next() {
let util = util_os.as_os_str().to_string_lossy(); let util = match util_os.to_str() {
Some(util) => util,
None => not_found(&util_os),
};
match utils.get(&util[..]) { match utils.get(util) {
Some(&(uumain, _)) => { Some(&(uumain, _)) => {
let code = uumain( let code = uumain(
(vec![util_os, OsString::from("--help")].into_iter()) (vec![util_os, OsString::from("--help")].into_iter())
@ -100,17 +114,13 @@ fn main() {
io::stdout().flush().expect("could not flush stdout"); io::stdout().flush().expect("could not flush stdout");
process::exit(code); process::exit(code);
} }
None => { None => not_found(&util_os),
println!("{}: function/utility not found", util);
process::exit(1);
}
} }
} }
usage(&utils, binary_as_util); usage(&utils, binary_as_util);
process::exit(0); process::exit(0);
} else { } else {
println!("{}: function/utility not found", util); not_found(&util_os);
process::exit(1);
} }
} }
} }

View file

@ -27,7 +27,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
} }
pub fn uu_app() -> App<'static, 'static> { pub fn uu_app() -> App<'static, 'static> {
App::new(executable!()) App::new(uucore::util_name())
.version(crate_version!()) .version(crate_version!())
.about(ABOUT) .about(ABOUT)
.after_help(SUMMARY) .after_help(SUMMARY)

View file

@ -22,3 +22,7 @@ uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_p
[[bin]] [[bin]]
name = "base32" name = "base32"
path = "src/main.rs" path = "src/main.rs"
[package.metadata.cargo-udeps.ignore]
# Necessary for "make all"
normal = ["uucore_procs"]

View file

@ -28,14 +28,14 @@ static VERSION: &str = env!("CARGO_PKG_VERSION");
static BASE_CMD_PARSE_ERROR: i32 = 1; static BASE_CMD_PARSE_ERROR: i32 = 1;
fn get_usage() -> String { fn usage() -> String {
format!("{0} [OPTION]... [FILE]", executable!()) format!("{0} [OPTION]... [FILE]", uucore::execution_phrase())
} }
pub fn uumain(args: impl uucore::Args) -> i32 { pub fn uumain(args: impl uucore::Args) -> i32 {
let format = Format::Base32; let format = Format::Base32;
let usage = get_usage(); let usage = usage();
let name = executable!(); let name = uucore::util_name();
let config_result: Result<base_common::Config, String> = let config_result: Result<base_common::Config, String> =
base_common::parse_base_cmd_args(args, name, VERSION, ABOUT, &usage); base_common::parse_base_cmd_args(args, name, VERSION, ABOUT, &usage);
@ -59,5 +59,5 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
} }
pub fn uu_app() -> App<'static, 'static> { pub fn uu_app() -> App<'static, 'static> {
base_common::base_app(executable!(), VERSION, ABOUT) base_common::base_app(uucore::util_name(), VERSION, ABOUT)
} }

View file

@ -9,6 +9,7 @@
use std::io::{stdout, Read, Write}; use std::io::{stdout, Read, Write};
use uucore::display::Quotable;
use uucore::encoding::{wrap_print, Data, Format}; use uucore::encoding::{wrap_print, Data, Format};
use uucore::InvalidEncodingHandling; use uucore::InvalidEncodingHandling;
@ -40,8 +41,9 @@ impl Config {
let name = values.next().unwrap(); let name = values.next().unwrap();
if let Some(extra_op) = values.next() { if let Some(extra_op) = values.next() {
return Err(format!( return Err(format!(
"extra operand '{}'\nTry '{} --help' for more information.", "extra operand {}\nTry '{} --help' for more information.",
extra_op, app_name extra_op.quote(),
app_name
)); ));
} }
@ -49,7 +51,7 @@ impl Config {
None None
} else { } else {
if !Path::exists(Path::new(name)) { if !Path::exists(Path::new(name)) {
return Err(format!("{}: No such file or directory", name)); return Err(format!("{}: No such file or directory", name.maybe_quote()));
} }
Some(name.to_owned()) Some(name.to_owned())
} }
@ -61,7 +63,7 @@ impl Config {
.value_of(options::WRAP) .value_of(options::WRAP)
.map(|num| { .map(|num| {
num.parse::<usize>() num.parse::<usize>()
.map_err(|_| format!("invalid wrap size: '{}'", num)) .map_err(|_| format!("invalid wrap size: {}", num.quote()))
}) })
.transpose()?; .transpose()?;
@ -122,7 +124,7 @@ pub fn base_app<'a>(name: &str, version: &'a str, about: &'a str) -> App<'static
pub fn get_input<'a>(config: &Config, stdin_ref: &'a Stdin) -> Box<dyn Read + 'a> { pub fn get_input<'a>(config: &Config, stdin_ref: &'a Stdin) -> Box<dyn Read + 'a> {
match &config.to_read { match &config.to_read {
Some(name) => { Some(name) => {
let file_buf = safe_unwrap!(File::open(Path::new(name))); let file_buf = crash_if_err!(1, File::open(Path::new(name)));
Box::new(BufReader::new(file_buf)) // as Box<dyn Read> Box::new(BufReader::new(file_buf)) // as Box<dyn Read>
} }
None => { None => {

View file

@ -23,3 +23,7 @@ uu_base32 = { version=">=0.0.6", package="uu_base32", path="../base32"}
[[bin]] [[bin]]
name = "base64" name = "base64"
path = "src/main.rs" path = "src/main.rs"
[package.metadata.cargo-udeps.ignore]
# Necessary for "make all"
normal = ["uucore_procs"]

View file

@ -29,14 +29,14 @@ static VERSION: &str = env!("CARGO_PKG_VERSION");
static BASE_CMD_PARSE_ERROR: i32 = 1; static BASE_CMD_PARSE_ERROR: i32 = 1;
fn get_usage() -> String { fn usage() -> String {
format!("{0} [OPTION]... [FILE]", executable!()) format!("{0} [OPTION]... [FILE]", uucore::execution_phrase())
} }
pub fn uumain(args: impl uucore::Args) -> i32 { pub fn uumain(args: impl uucore::Args) -> i32 {
let format = Format::Base64; let format = Format::Base64;
let usage = get_usage(); let usage = usage();
let name = executable!(); let name = uucore::util_name();
let config_result: Result<base_common::Config, String> = let config_result: Result<base_common::Config, String> =
base_common::parse_base_cmd_args(args, name, VERSION, ABOUT, &usage); base_common::parse_base_cmd_args(args, name, VERSION, ABOUT, &usage);
let config = config_result.unwrap_or_else(|s| crash!(BASE_CMD_PARSE_ERROR, "{}", s)); let config = config_result.unwrap_or_else(|s| crash!(BASE_CMD_PARSE_ERROR, "{}", s));

View file

@ -22,3 +22,7 @@ uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_p
[[bin]] [[bin]]
name = "basename" name = "basename"
path = "src/main.rs" path = "src/main.rs"
[package.metadata.cargo-udeps.ignore]
# Necessary for "make all"
normal = ["uucore_procs"]

View file

@ -17,11 +17,11 @@ use uucore::InvalidEncodingHandling;
static SUMMARY: &str = "Print NAME with any leading directory components removed static SUMMARY: &str = "Print NAME with any leading directory components removed
If specified, also remove a trailing SUFFIX"; If specified, also remove a trailing SUFFIX";
fn get_usage() -> String { fn usage() -> String {
format!( format!(
"{0} NAME [SUFFIX] "{0} NAME [SUFFIX]
{0} OPTION... NAME...", {0} OPTION... NAME...",
executable!() uucore::execution_phrase()
) )
} }
@ -36,7 +36,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
let args = args let args = args
.collect_str(InvalidEncodingHandling::ConvertLossy) .collect_str(InvalidEncodingHandling::ConvertLossy)
.accept_any(); .accept_any();
let usage = get_usage(); let usage = usage();
// //
// Argument parsing // Argument parsing
// //
@ -47,7 +47,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
crash!( crash!(
1, 1,
"{1}\nTry '{0} --help' for more information.", "{1}\nTry '{0} --help' for more information.",
executable!(), uucore::execution_phrase(),
"missing operand" "missing operand"
); );
} }
@ -61,7 +61,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
crash!( crash!(
1, 1,
"extra operand '{1}'\nTry '{0} --help' for more information.", "extra operand '{1}'\nTry '{0} --help' for more information.",
executable!(), uucore::execution_phrase(),
matches.values_of(options::NAME).unwrap().nth(2).unwrap() matches.values_of(options::NAME).unwrap().nth(2).unwrap()
); );
} }
@ -93,7 +93,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
} }
pub fn uu_app() -> App<'static, 'static> { pub fn uu_app() -> App<'static, 'static> {
App::new(executable!()) App::new(uucore::util_name())
.version(crate_version!()) .version(crate_version!())
.about(SUMMARY) .about(SUMMARY)
.arg( .arg(
@ -119,9 +119,15 @@ pub fn uu_app() -> App<'static, 'static> {
} }
fn basename(fullname: &str, suffix: &str) -> String { fn basename(fullname: &str, suffix: &str) -> String {
// Remove all platform-specific path separators from the end // Remove all platform-specific path separators from the end.
let path = fullname.trim_end_matches(is_separator); let path = fullname.trim_end_matches(is_separator);
// If the path contained *only* suffix characters (for example, if
// `fullname` were "///" and `suffix` were "/"), then `path` would
// be left with the empty string. In that case, we set `path` to be
// the original `fullname` to avoid returning the empty path.
let path = if path.is_empty() { fullname } else { path };
// Convert to path buffer and get last path component // Convert to path buffer and get last path component
let pb = PathBuf::from(path); let pb = PathBuf::from(path);
match pb.components().last() { match pb.components().last() {

View file

@ -23,3 +23,7 @@ uu_base32 = { version=">=0.0.6", package="uu_base32", path="../base32"}
[[bin]] [[bin]]
name = "basenc" name = "basenc"
path = "src/main.rs" path = "src/main.rs"
[package.metadata.cargo-udeps.ignore]
# Necessary for "make all"
normal = ["uucore_procs"]

View file

@ -42,12 +42,12 @@ const ENCODINGS: &[(&str, Format)] = &[
("base2m", Format::Base2Msbf), ("base2m", Format::Base2Msbf),
]; ];
fn get_usage() -> String { fn usage() -> String {
format!("{0} [OPTION]... [FILE]", executable!()) format!("{0} [OPTION]... [FILE]", uucore::execution_phrase())
} }
pub fn uu_app() -> App<'static, 'static> { pub fn uu_app() -> App<'static, 'static> {
let mut app = base_common::base_app(executable!(), crate_version!(), ABOUT); let mut app = base_common::base_app(uucore::util_name(), crate_version!(), ABOUT);
for encoding in ENCODINGS { for encoding in ENCODINGS {
app = app.arg(Arg::with_name(encoding.0).long(encoding.0)); app = app.arg(Arg::with_name(encoding.0).long(encoding.0));
} }
@ -55,7 +55,7 @@ pub fn uu_app() -> App<'static, 'static> {
} }
fn parse_cmd_args(args: impl uucore::Args) -> (Config, Format) { fn parse_cmd_args(args: impl uucore::Args) -> (Config, Format) {
let usage = get_usage(); let usage = usage();
let matches = uu_app().usage(&usage[..]).get_matches_from( let matches = uu_app().usage(&usage[..]).get_matches_from(
args.collect_str(InvalidEncodingHandling::ConvertLossy) args.collect_str(InvalidEncodingHandling::ConvertLossy)
.accept_any(), .accept_any(),
@ -75,7 +75,7 @@ fn parse_cmd_args(args: impl uucore::Args) -> (Config, Format) {
} }
pub fn uumain(args: impl uucore::Args) -> i32 { pub fn uumain(args: impl uucore::Args) -> i32 {
let name = executable!(); let name = uucore::util_name();
let (config, format) = parse_cmd_args(args); let (config, format) = parse_cmd_args(args);
// Create a reference to stdin so we can return a locked stdin from // Create a reference to stdin so we can return a locked stdin from
// parse_base_cmd_args // parse_base_cmd_args

View file

@ -18,7 +18,7 @@ path = "src/cat.rs"
clap = { version = "2.33", features = ["wrap_help"] } clap = { version = "2.33", features = ["wrap_help"] }
thiserror = "1.0" thiserror = "1.0"
atty = "0.2" atty = "0.2"
uucore = { version=">=0.0.9", package="uucore", path="../../uucore", features=["fs"] } uucore = { version=">=0.0.9", package="uucore", path="../../uucore", features=["fs", "pipes"] }
uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_procs" } uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_procs" }
[target.'cfg(unix)'.dependencies] [target.'cfg(unix)'.dependencies]

View file

@ -20,6 +20,7 @@ use clap::{crate_version, App, Arg};
use std::fs::{metadata, File}; use std::fs::{metadata, File};
use std::io::{self, Read, Write}; use std::io::{self, Read, Write};
use thiserror::Error; use thiserror::Error;
use uucore::display::Quotable;
use uucore::error::UResult; use uucore::error::UResult;
#[cfg(unix)] #[cfg(unix)]
@ -28,8 +29,6 @@ use std::os::unix::io::AsRawFd;
/// Linux splice support /// Linux splice support
#[cfg(any(target_os = "linux", target_os = "android"))] #[cfg(any(target_os = "linux", target_os = "android"))]
mod splice; mod splice;
#[cfg(any(target_os = "linux", target_os = "android"))]
use std::os::unix::io::RawFd;
/// Unix domain socket support /// Unix domain socket support
#[cfg(unix)] #[cfg(unix)]
@ -136,10 +135,18 @@ struct OutputState {
one_blank_kept: bool, one_blank_kept: bool,
} }
#[cfg(unix)]
trait FdReadable: Read + AsRawFd {}
#[cfg(not(unix))]
trait FdReadable: Read {}
#[cfg(unix)]
impl<T> FdReadable for T where T: Read + AsRawFd {}
#[cfg(not(unix))]
impl<T> FdReadable for T where T: Read {}
/// Represents an open file handle, stream, or other device /// Represents an open file handle, stream, or other device
struct InputHandle<R: Read> { struct InputHandle<R: FdReadable> {
#[cfg(any(target_os = "linux", target_os = "android"))]
file_descriptor: RawFd,
reader: R, reader: R,
is_interactive: bool, is_interactive: bool,
} }
@ -234,7 +241,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
} }
pub fn uu_app() -> App<'static, 'static> { pub fn uu_app() -> App<'static, 'static> {
App::new(executable!()) App::new(uucore::util_name())
.name(NAME) .name(NAME)
.version(crate_version!()) .version(crate_version!())
.usage(SYNTAX) .usage(SYNTAX)
@ -296,7 +303,7 @@ pub fn uu_app() -> App<'static, 'static> {
) )
} }
fn cat_handle<R: Read>( fn cat_handle<R: FdReadable>(
handle: &mut InputHandle<R>, handle: &mut InputHandle<R>,
options: &OutputOptions, options: &OutputOptions,
state: &mut OutputState, state: &mut OutputState,
@ -318,8 +325,6 @@ fn cat_path(
if path == "-" { if path == "-" {
let stdin = io::stdin(); let stdin = io::stdin();
let mut handle = InputHandle { let mut handle = InputHandle {
#[cfg(any(target_os = "linux", target_os = "android"))]
file_descriptor: stdin.as_raw_fd(),
reader: stdin, reader: stdin,
is_interactive: atty::is(atty::Stream::Stdin), is_interactive: atty::is(atty::Stream::Stdin),
}; };
@ -332,8 +337,6 @@ fn cat_path(
let socket = UnixStream::connect(path)?; let socket = UnixStream::connect(path)?;
socket.shutdown(Shutdown::Write)?; socket.shutdown(Shutdown::Write)?;
let mut handle = InputHandle { let mut handle = InputHandle {
#[cfg(any(target_os = "linux", target_os = "android"))]
file_descriptor: socket.as_raw_fd(),
reader: socket, reader: socket,
is_interactive: false, is_interactive: false,
}; };
@ -346,8 +349,6 @@ fn cat_path(
return Err(CatError::OutputIsInput); return Err(CatError::OutputIsInput);
} }
let mut handle = InputHandle { let mut handle = InputHandle {
#[cfg(any(target_os = "linux", target_os = "android"))]
file_descriptor: file.as_raw_fd(),
reader: file, reader: file,
is_interactive: false, is_interactive: false,
}; };
@ -386,7 +387,7 @@ fn cat_files(files: Vec<String>, options: &OutputOptions) -> UResult<()> {
for path in &files { for path in &files {
if let Err(err) = cat_path(path, options, &mut state, &out_info) { if let Err(err) = cat_path(path, options, &mut state, &out_info) {
error_messages.push(format!("{}: {}", path, err)); error_messages.push(format!("{}: {}", path.maybe_quote(), err));
} }
} }
if state.skipped_carriage_return { if state.skipped_carriage_return {
@ -396,7 +397,7 @@ fn cat_files(files: Vec<String>, options: &OutputOptions) -> UResult<()> {
Ok(()) Ok(())
} else { } else {
// each next line is expected to display "cat: …" // each next line is expected to display "cat: …"
let line_joiner = format!("\n{}: ", executable!()); let line_joiner = format!("\n{}: ", uucore::util_name());
Err(uucore::error::USimpleError::new( Err(uucore::error::USimpleError::new(
error_messages.len() as i32, error_messages.len() as i32,
@ -436,14 +437,14 @@ fn get_input_type(path: &str) -> CatResult<InputType> {
/// Writes handle to stdout with no configuration. This allows a /// Writes handle to stdout with no configuration. This allows a
/// simple memory copy. /// simple memory copy.
fn write_fast<R: Read>(handle: &mut InputHandle<R>) -> CatResult<()> { fn write_fast<R: FdReadable>(handle: &mut InputHandle<R>) -> CatResult<()> {
let stdout = io::stdout(); let stdout = io::stdout();
let mut stdout_lock = stdout.lock(); let mut stdout_lock = stdout.lock();
#[cfg(any(target_os = "linux", target_os = "android"))] #[cfg(any(target_os = "linux", target_os = "android"))]
{ {
// If we're on Linux or Android, try to use the splice() system call // If we're on Linux or Android, try to use the splice() system call
// for faster writing. If it works, we're done. // for faster writing. If it works, we're done.
if !splice::write_fast_using_splice(handle, stdout_lock.as_raw_fd())? { if !splice::write_fast_using_splice(handle, &stdout_lock)? {
return Ok(()); return Ok(());
} }
} }
@ -461,7 +462,7 @@ fn write_fast<R: Read>(handle: &mut InputHandle<R>) -> CatResult<()> {
/// Outputs file contents to stdout in a line-by-line fashion, /// Outputs file contents to stdout in a line-by-line fashion,
/// propagating any errors that might occur. /// propagating any errors that might occur.
fn write_lines<R: Read>( fn write_lines<R: FdReadable>(
handle: &mut InputHandle<R>, handle: &mut InputHandle<R>,
options: &OutputOptions, options: &OutputOptions,
state: &mut OutputState, state: &mut OutputState,

View file

@ -1,10 +1,11 @@
use super::{CatResult, InputHandle}; use super::{CatResult, FdReadable, InputHandle};
use nix::fcntl::{splice, SpliceFFlags}; use nix::unistd;
use nix::unistd::{self, pipe}; use std::os::unix::io::{AsRawFd, RawFd};
use std::io::Read;
use std::os::unix::io::RawFd;
use uucore::pipes::{pipe, splice, splice_exact};
const SPLICE_SIZE: usize = 1024 * 128;
const BUF_SIZE: usize = 1024 * 16; const BUF_SIZE: usize = 1024 * 16;
/// This function is called from `write_fast()` on Linux and Android. The /// This function is called from `write_fast()` on Linux and Android. The
@ -15,38 +16,25 @@ const BUF_SIZE: usize = 1024 * 16;
/// The `bool` in the result value indicates if we need to fall back to normal /// 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. /// copying or not. False means we don't have to.
#[inline] #[inline]
pub(super) fn write_fast_using_splice<R: Read>( pub(super) fn write_fast_using_splice<R: FdReadable>(
handle: &mut InputHandle<R>, handle: &mut InputHandle<R>,
write_fd: RawFd, write_fd: &impl AsRawFd,
) -> CatResult<bool> { ) -> CatResult<bool> {
let (pipe_rd, pipe_wr) = match pipe() { let (pipe_rd, pipe_wr) = pipe()?;
Ok(r) => r,
Err(_) => {
// It is very rare that creating a pipe fails, but it can happen.
return Ok(true);
}
};
loop { loop {
match splice( match splice(&handle.reader, &pipe_wr, SPLICE_SIZE) {
handle.file_descriptor,
None,
pipe_wr,
None,
BUF_SIZE,
SpliceFFlags::empty(),
) {
Ok(n) => { Ok(n) => {
if n == 0 { if n == 0 {
return Ok(false); return Ok(false);
} }
if splice_exact(pipe_rd, write_fd, n).is_err() { if splice_exact(&pipe_rd, write_fd, n).is_err() {
// If the first splice manages to copy to the intermediate // If the first splice manages to copy to the intermediate
// pipe, but the second splice to stdout fails for some reason // pipe, but the second splice to stdout fails for some reason
// we can recover by copying the data that we have from the // we can recover by copying the data that we have from the
// intermediate pipe to stdout using normal read/write. Then // intermediate pipe to stdout using normal read/write. Then
// we tell the caller to fall back. // we tell the caller to fall back.
copy_exact(pipe_rd, write_fd, n)?; copy_exact(pipe_rd.as_raw_fd(), write_fd.as_raw_fd(), n)?;
return Ok(true); return Ok(true);
} }
} }
@ -57,35 +45,23 @@ pub(super) fn write_fast_using_splice<R: Read>(
} }
} }
/// Splice wrapper which handles short writes. /// Move exactly `num_bytes` bytes from `read_fd` to `write_fd`.
#[inline] ///
fn splice_exact(read_fd: RawFd, write_fd: RawFd, num_bytes: usize) -> nix::Result<()> { /// Panics if not enough bytes can be read.
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<()> { fn copy_exact(read_fd: RawFd, write_fd: RawFd, num_bytes: usize) -> nix::Result<()> {
let mut left = num_bytes; let mut left = num_bytes;
let mut buf = [0; BUF_SIZE]; let mut buf = [0; BUF_SIZE];
loop { while left > 0 {
let read = unistd::read(read_fd, &mut buf[..left])?; let read = unistd::read(read_fd, &mut buf)?;
let written = unistd::write(write_fd, &buf[..read])?; assert_ne!(read, 0, "unexpected end of pipe");
left -= written; let mut written = 0;
if left == 0 { while written < read {
break; match unistd::write(write_fd, &buf[written..read])? {
0 => panic!(),
n => written += n,
} }
} }
left -= read;
}
Ok(()) Ok(())
} }

View file

@ -2,7 +2,7 @@
#![allow(clippy::upper_case_acronyms)] #![allow(clippy::upper_case_acronyms)]
use uucore::{executable, show_error, show_usage_error, show_warning}; use uucore::{display::Quotable, show_error, show_usage_error, show_warning};
use clap::{App, Arg}; use clap::{App, Arg};
use selinux::{OpaqueSecurityContext, SecurityContext}; use selinux::{OpaqueSecurityContext, SecurityContext};
@ -56,7 +56,7 @@ fn get_usage() -> String {
"{0} [OPTION]... CONTEXT FILE... \n \ "{0} [OPTION]... CONTEXT FILE... \n \
{0} [OPTION]... [-u USER] [-r ROLE] [-l RANGE] [-t TYPE] FILE... \n \ {0} [OPTION]... [-u USER] [-r ROLE] [-l RANGE] [-t TYPE] FILE... \n \
{0} [OPTION]... --reference=RFILE FILE...", {0} [OPTION]... --reference=RFILE FILE...",
executable!() uucore::execution_phrase()
) )
} }
@ -111,13 +111,13 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
Ok(context) => context, Ok(context) => context,
Err(_r) => { Err(_r) => {
show_error!("Invalid security context '{}'.", context.to_string_lossy()); show_error!("Invalid security context {}.", context.quote());
return libc::EXIT_FAILURE; return libc::EXIT_FAILURE;
} }
}; };
if SecurityContext::from_c_str(&c_context, false).check() == Some(false) { if SecurityContext::from_c_str(&c_context, false).check() == Some(false) {
show_error!("Invalid security context '{}'.", context.to_string_lossy()); show_error!("Invalid security context {}.", context.quote());
return libc::EXIT_FAILURE; return libc::EXIT_FAILURE;
} }
@ -152,7 +152,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
} }
pub fn uu_app() -> App<'static, 'static> { pub fn uu_app() -> App<'static, 'static> {
App::new(executable!()) App::new(uucore::util_name())
.version(VERSION) .version(VERSION)
.about(ABOUT) .about(ABOUT)
.arg( .arg(
@ -281,7 +281,6 @@ pub fn uu_app() -> App<'static, 'static> {
#[derive(Debug)] #[derive(Debug)]
struct Options { struct Options {
verbose: bool, verbose: bool,
dereference: bool,
preserve_root: bool, preserve_root: bool,
recursive_mode: RecursiveMode, recursive_mode: RecursiveMode,
affect_symlink_referent: bool, affect_symlink_referent: bool,
@ -331,9 +330,6 @@ fn parse_command_line(config: clap::App, args: impl uucore::Args) -> Result<Opti
(RecursiveMode::NotRecursive, !no_dereference) (RecursiveMode::NotRecursive, !no_dereference)
}; };
// By default, dereference.
let dereference = !matches.is_present(options::dereference::NO_DEREFERENCE);
// By default, do not preserve root. // By default, do not preserve root.
let preserve_root = matches.is_present(options::preserve_root::PRESERVE_ROOT); let preserve_root = matches.is_present(options::preserve_root::PRESERVE_ROOT);
@ -369,7 +365,6 @@ fn parse_command_line(config: clap::App, args: impl uucore::Args) -> Result<Opti
Ok(Options { Ok(Options {
verbose, verbose,
dereference,
preserve_root, preserve_root,
recursive_mode, recursive_mode,
affect_symlink_referent, affect_symlink_referent,
@ -563,8 +558,8 @@ fn process_file(
if options.verbose { if options.verbose {
println!( println!(
"{}: Changing security context of: {}", "{}: Changing security context of: {}",
executable!(), uucore::util_name(),
file_full_name.to_string_lossy() file_full_name.quote()
); );
} }
@ -699,9 +694,9 @@ fn root_dev_ino_warn(dir_name: &Path) {
); );
} else { } else {
show_warning!( show_warning!(
"It is dangerous to operate recursively on '{}' (same as '/'). \ "It is dangerous to operate recursively on {} (same as '/'). \
Use --{} to override this failsafe.", Use --{} to override this failsafe.",
dir_name.to_string_lossy(), dir_name.quote(),
options::preserve_root::NO_PRESERVE_ROOT, options::preserve_root::NO_PRESERVE_ROOT,
); );
} }
@ -726,8 +721,8 @@ fn emit_cycle_warning(file_name: &Path) {
"Circular directory structure.\n\ "Circular directory structure.\n\
This almost certainly means that you have a corrupted file system.\n\ This almost certainly means that you have a corrupted file system.\n\
NOTIFY YOUR SYSTEM MANAGER.\n\ NOTIFY YOUR SYSTEM MANAGER.\n\
The following directory is part of the cycle '{}'.", The following directory is part of the cycle {}.",
file_name.display() file_name.quote()
) )
} }

View file

@ -2,6 +2,8 @@ use std::ffi::OsString;
use std::fmt::Write; use std::fmt::Write;
use std::io; use std::io;
use uucore::display::Quotable;
pub(crate) type Result<T> = std::result::Result<T, Error>; pub(crate) type Result<T> = std::result::Result<T, Error>;
#[derive(thiserror::Error, Debug)] #[derive(thiserror::Error, Debug)]
@ -30,7 +32,7 @@ pub(crate) enum Error {
source: io::Error, source: io::Error,
}, },
#[error("{operation} failed on '{}'", .operand1.to_string_lossy())] #[error("{operation} failed on {}", .operand1.quote())]
Io1 { Io1 {
operation: &'static str, operation: &'static str,
operand1: OsString, operand1: OsString,

View file

@ -18,7 +18,6 @@ path = "src/chgrp.rs"
clap = { version = "2.33", features = ["wrap_help"] } clap = { version = "2.33", features = ["wrap_help"] }
uucore = { version=">=0.0.9", package="uucore", path="../../uucore", features=["entries", "fs", "perms"] } uucore = { version=">=0.0.9", package="uucore", path="../../uucore", features=["entries", "fs", "perms"] }
uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_procs" } uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_procs" }
walkdir = "2.2"
[[bin]] [[bin]]
name = "chgrp" name = "chgrp"

View file

@ -5,170 +5,35 @@
// For the full copyright and license information, please view the LICENSE // For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code. // file that was distributed with this source code.
// spell-checker:ignore (ToDO) COMFOLLOW Chgrper RFILE RFILE's derefer dgid nonblank nonprint nonprinting // spell-checker:ignore (ToDO) COMFOLLOW Chowner RFILE RFILE's derefer dgid nonblank nonprint nonprinting
#[macro_use] #[macro_use]
extern crate uucore; extern crate uucore;
use uucore::display::Quotable;
pub use uucore::entries; pub use uucore::entries;
use uucore::fs::resolve_relative_path; use uucore::error::{FromIo, UResult, USimpleError};
use uucore::libc::gid_t; use uucore::perms::{chown_base, options, IfFrom};
use uucore::perms::{wrap_chgrp, Verbosity};
use clap::{App, Arg}; use clap::{App, Arg, ArgMatches};
extern crate walkdir;
use walkdir::WalkDir;
use std::fs; use std::fs;
use std::fs::Metadata;
use std::os::unix::fs::MetadataExt; use std::os::unix::fs::MetadataExt;
use std::path::Path;
use uucore::InvalidEncodingHandling;
static ABOUT: &str = "Change the group of each FILE to GROUP."; static ABOUT: &str = "Change the group of each FILE to GROUP.";
static VERSION: &str = env!("CARGO_PKG_VERSION"); static VERSION: &str = env!("CARGO_PKG_VERSION");
pub mod options {
pub mod verbosity {
pub static CHANGES: &str = "changes";
pub static QUIET: &str = "quiet";
pub static SILENT: &str = "silent";
pub static VERBOSE: &str = "verbose";
}
pub mod preserve_root {
pub static PRESERVE: &str = "preserve-root";
pub static NO_PRESERVE: &str = "no-preserve-root";
}
pub mod dereference {
pub static DEREFERENCE: &str = "dereference";
pub static NO_DEREFERENCE: &str = "no-dereference";
}
pub static RECURSIVE: &str = "recursive";
pub mod traverse {
pub static TRAVERSE: &str = "H";
pub static NO_TRAVERSE: &str = "P";
pub static EVERY: &str = "L";
}
pub static REFERENCE: &str = "reference";
pub static ARG_GROUP: &str = "GROUP";
pub static ARG_FILES: &str = "FILE";
}
const FTS_COMFOLLOW: u8 = 1;
const FTS_PHYSICAL: u8 = 1 << 1;
const FTS_LOGICAL: u8 = 1 << 2;
fn get_usage() -> String { fn get_usage() -> String {
format!( format!(
"{0} [OPTION]... GROUP FILE...\n {0} [OPTION]... --reference=RFILE FILE...", "{0} [OPTION]... GROUP FILE...\n {0} [OPTION]... --reference=RFILE FILE...",
executable!() uucore::execution_phrase()
) )
} }
pub fn uumain(args: impl uucore::Args) -> i32 { fn parse_gid_and_uid(matches: &ArgMatches) -> UResult<(Option<u32>, Option<u32>, IfFrom)> {
let args = args
.collect_str(InvalidEncodingHandling::ConvertLossy)
.accept_any();
let usage = get_usage();
let mut app = uu_app().usage(&usage[..]);
// we change the positional args based on whether
// --reference was used.
let mut reference = false;
let mut help = false;
// stop processing options on --
for arg in args.iter().take_while(|s| *s != "--") {
if arg.starts_with("--reference=") || arg == "--reference" {
reference = true;
} else if arg == "--help" {
// we stop processing once we see --help,
// as it doesn't matter if we've seen reference or not
help = true;
break;
}
}
if help || !reference {
// add both positional arguments
app = app.arg(
Arg::with_name(options::ARG_GROUP)
.value_name(options::ARG_GROUP)
.required(true)
.takes_value(true)
.multiple(false),
)
}
app = app.arg(
Arg::with_name(options::ARG_FILES)
.value_name(options::ARG_FILES)
.multiple(true)
.takes_value(true)
.required(true)
.min_values(1),
);
let matches = app.get_matches_from(args);
/* Get the list of files */
let files: Vec<String> = matches
.values_of(options::ARG_FILES)
.map(|v| v.map(ToString::to_string).collect())
.unwrap_or_default();
let preserve_root = matches.is_present(options::preserve_root::PRESERVE);
let mut derefer = if matches.is_present(options::dereference::DEREFERENCE) {
1
} else if matches.is_present(options::dereference::NO_DEREFERENCE) {
0
} else {
-1
};
let mut bit_flag = if matches.is_present(options::traverse::TRAVERSE) {
FTS_COMFOLLOW | FTS_PHYSICAL
} else if matches.is_present(options::traverse::EVERY) {
FTS_LOGICAL
} else {
FTS_PHYSICAL
};
let recursive = matches.is_present(options::RECURSIVE);
if recursive {
if bit_flag == FTS_PHYSICAL {
if derefer == 1 {
show_error!("-R --dereference requires -H or -L");
return 1;
}
derefer = 0;
}
} else {
bit_flag = FTS_PHYSICAL;
}
let verbosity = if matches.is_present(options::verbosity::CHANGES) {
Verbosity::Changes
} else if matches.is_present(options::verbosity::SILENT)
|| matches.is_present(options::verbosity::QUIET)
{
Verbosity::Silent
} else if matches.is_present(options::verbosity::VERBOSE) {
Verbosity::Verbose
} else {
Verbosity::Normal
};
let dest_gid = if let Some(file) = matches.value_of(options::REFERENCE) { let dest_gid = if let Some(file) = matches.value_of(options::REFERENCE) {
match fs::metadata(&file) { fs::metadata(&file)
Ok(meta) => Some(meta.gid()), .map(|meta| Some(meta.gid()))
Err(e) => { .map_err_context(|| format!("failed to get attributes of {}", file.quote()))?
show_error!("failed to get attributes of '{}': {}", file, e);
return 1;
}
}
} else { } else {
let group = matches.value_of(options::ARG_GROUP).unwrap_or_default(); let group = matches.value_of(options::ARG_GROUP).unwrap_or_default();
if group.is_empty() { if group.is_empty() {
@ -177,27 +42,32 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
match entries::grp2gid(group) { match entries::grp2gid(group) {
Ok(g) => Some(g), Ok(g) => Some(g),
_ => { _ => {
show_error!("invalid group: {}", group); return Err(USimpleError::new(
return 1; 1,
format!("invalid group: {}", group.quote()),
))
} }
} }
} }
}; };
Ok((dest_gid, None, IfFrom::All))
}
let executor = Chgrper { #[uucore_procs::gen_uumain]
bit_flag, pub fn uumain(args: impl uucore::Args) -> UResult<()> {
dest_gid, let usage = get_usage();
verbosity,
recursive, chown_base(
dereference: derefer != 0, uu_app().usage(&usage[..]),
preserve_root, args,
files, options::ARG_GROUP,
}; parse_gid_and_uid,
executor.exec() true,
)
} }
pub fn uu_app() -> App<'static, 'static> { pub fn uu_app() -> App<'static, 'static> {
App::new(executable!()) App::new(uucore::util_name())
.version(VERSION) .version(VERSION)
.about(ABOUT) .about(ABOUT)
.arg( .arg(
@ -275,172 +145,3 @@ pub fn uu_app() -> App<'static, 'static> {
.help("traverse every symbolic link to a directory encountered"), .help("traverse every symbolic link to a directory encountered"),
) )
} }
struct Chgrper {
dest_gid: Option<gid_t>,
bit_flag: u8,
verbosity: Verbosity,
files: Vec<String>,
recursive: bool,
preserve_root: bool,
dereference: bool,
}
macro_rules! unwrap {
($m:expr, $e:ident, $err:block) => {
match $m {
Ok(meta) => meta,
Err($e) => $err,
}
};
}
impl Chgrper {
fn exec(&self) -> i32 {
let mut ret = 0;
for f in &self.files {
ret |= self.traverse(f);
}
ret
}
#[cfg(windows)]
fn is_bind_root<P: AsRef<Path>>(&self, root: P) -> bool {
// TODO: is there an equivalent on Windows?
false
}
#[cfg(unix)]
fn is_bind_root<P: AsRef<Path>>(&self, path: P) -> bool {
if let (Ok(given), Ok(root)) = (fs::metadata(path), fs::metadata("/")) {
given.dev() == root.dev() && given.ino() == root.ino()
} else {
// FIXME: not totally sure if it's okay to just ignore an error here
false
}
}
fn traverse<P: AsRef<Path>>(&self, root: P) -> i32 {
let follow_arg = self.dereference || self.bit_flag != FTS_PHYSICAL;
let path = root.as_ref();
let meta = match self.obtain_meta(path, follow_arg) {
Some(m) => m,
_ => return 1,
};
// Prohibit only if:
// (--preserve-root and -R present) &&
// (
// (argument is not symlink && resolved to be '/') ||
// (argument is symlink && should follow argument && resolved to be '/')
// )
if self.recursive && self.preserve_root {
let may_exist = if follow_arg {
path.canonicalize().ok()
} else {
let real = resolve_relative_path(path);
if real.is_dir() {
Some(real.canonicalize().expect("failed to get real path"))
} else {
Some(real.into_owned())
}
};
if let Some(p) = may_exist {
if p.parent().is_none() || self.is_bind_root(p) {
show_error!("it is dangerous to operate recursively on '/'");
show_error!("use --no-preserve-root to override this failsafe");
return 1;
}
}
}
let ret = match wrap_chgrp(
path,
&meta,
self.dest_gid,
follow_arg,
self.verbosity.clone(),
) {
Ok(n) => {
if !n.is_empty() {
show_error!("{}", n);
}
0
}
Err(e) => {
if self.verbosity != Verbosity::Silent {
show_error!("{}", e);
}
1
}
};
if !self.recursive {
ret
} else {
ret | self.dive_into(&root)
}
}
fn dive_into<P: AsRef<Path>>(&self, root: P) -> i32 {
let mut ret = 0;
let root = root.as_ref();
let follow = self.dereference || self.bit_flag & FTS_LOGICAL != 0;
for entry in WalkDir::new(root).follow_links(follow).min_depth(1) {
let entry = unwrap!(entry, e, {
ret = 1;
show_error!("{}", e);
continue;
});
let path = entry.path();
let meta = match self.obtain_meta(path, follow) {
Some(m) => m,
_ => {
ret = 1;
continue;
}
};
ret = match wrap_chgrp(path, &meta, self.dest_gid, follow, self.verbosity.clone()) {
Ok(n) => {
if !n.is_empty() {
show_error!("{}", n);
}
0
}
Err(e) => {
if self.verbosity != Verbosity::Silent {
show_error!("{}", e);
}
1
}
}
}
ret
}
fn obtain_meta<P: AsRef<Path>>(&self, path: P, follow: bool) -> Option<Metadata> {
use self::Verbosity::*;
let path = path.as_ref();
let meta = if follow {
unwrap!(path.metadata(), e, {
match self.verbosity {
Silent => (),
_ => show_error!("cannot access '{}': {}", path.display(), e),
}
return None;
})
} else {
unwrap!(path.symlink_metadata(), e, {
match self.verbosity {
Silent => (),
_ => show_error!("cannot dereference '{}': {}", path.display(), e),
}
return None;
})
};
Some(meta)
}
}

View file

@ -14,6 +14,7 @@ use clap::{crate_version, App, Arg};
use std::fs; use std::fs;
use std::os::unix::fs::{MetadataExt, PermissionsExt}; use std::os::unix::fs::{MetadataExt, PermissionsExt};
use std::path::Path; use std::path::Path;
use uucore::display::Quotable;
use uucore::fs::display_permissions_unix; use uucore::fs::display_permissions_unix;
use uucore::libc::mode_t; use uucore::libc::mode_t;
#[cfg(not(windows))] #[cfg(not(windows))]
@ -36,12 +37,12 @@ mod options {
pub const FILE: &str = "FILE"; pub const FILE: &str = "FILE";
} }
fn get_usage() -> String { fn usage() -> String {
format!( format!(
"{0} [OPTION]... MODE[,MODE]... FILE... "{0} [OPTION]... MODE[,MODE]... FILE...
or: {0} [OPTION]... OCTAL-MODE FILE... or: {0} [OPTION]... OCTAL-MODE FILE...
or: {0} [OPTION]... --reference=RFILE FILE...", or: {0} [OPTION]... --reference=RFILE FILE...",
executable!() uucore::execution_phrase()
) )
} }
@ -58,7 +59,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
// a possible MODE prefix '-' needs to be removed (e.g. "chmod -x FILE"). // a possible MODE prefix '-' needs to be removed (e.g. "chmod -x FILE").
let mode_had_minus_prefix = strip_minus_from_mode(&mut args); let mode_had_minus_prefix = strip_minus_from_mode(&mut args);
let usage = get_usage(); let usage = usage();
let after_help = get_long_usage(); let after_help = get_long_usage();
let matches = uu_app() let matches = uu_app()
@ -75,7 +76,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.value_of(options::REFERENCE) .value_of(options::REFERENCE)
.and_then(|fref| match fs::metadata(fref) { .and_then(|fref| match fs::metadata(fref) {
Ok(meta) => Some(meta.mode()), Ok(meta) => Some(meta.mode()),
Err(err) => crash!(1, "cannot stat attributes of '{}': {}", fref, err), Err(err) => crash!(1, "cannot stat attributes of {}: {}", fref.quote(), err),
}); });
let modes = matches.value_of(options::MODE).unwrap(); // should always be Some because required let modes = matches.value_of(options::MODE).unwrap(); // should always be Some because required
let cmode = if mode_had_minus_prefix { let cmode = if mode_had_minus_prefix {
@ -98,6 +99,10 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
Some(cmode) Some(cmode)
}; };
if files.is_empty() {
crash!(1, "missing operand");
}
let chmoder = Chmoder { let chmoder = Chmoder {
changes, changes,
quiet, quiet,
@ -116,7 +121,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
} }
pub fn uu_app() -> App<'static, 'static> { pub fn uu_app() -> App<'static, 'static> {
App::new(executable!()) App::new(uucore::util_name())
.version(crate_version!()) .version(crate_version!())
.about(ABOUT) .about(ABOUT)
.arg( .arg(
@ -180,6 +185,9 @@ pub fn uu_app() -> App<'static, 'static> {
// e.g. "chmod -v -xw -R FILE" -> "chmod -v xw -R FILE" // e.g. "chmod -v -xw -R FILE" -> "chmod -v xw -R FILE"
pub fn strip_minus_from_mode(args: &mut Vec<String>) -> bool { pub fn strip_minus_from_mode(args: &mut Vec<String>) -> bool {
for arg in args { for arg in args {
if arg == "--" {
break;
}
if arg.starts_with('-') { if arg.starts_with('-') {
if let Some(second) = arg.chars().nth(1) { if let Some(second) = arg.chars().nth(1) {
match second { match second {
@ -216,21 +224,24 @@ impl Chmoder {
if !file.exists() { if !file.exists() {
if is_symlink(file) { if is_symlink(file) {
println!( println!(
"failed to change mode of '{}' from 0000 (---------) to 0000 (---------)", "failed to change mode of {} from 0000 (---------) to 0000 (---------)",
filename filename.quote()
); );
if !self.quiet { if !self.quiet {
show_error!("cannot operate on dangling symlink '{}'", filename); show_error!("cannot operate on dangling symlink {}", filename.quote());
} }
} else { } else if !self.quiet {
show_error!("cannot access '{}': No such file or directory", filename); show_error!(
"cannot access {}: No such file or directory",
filename.quote()
);
} }
return Err(1); return Err(1);
} }
if self.recursive && self.preserve_root && filename == "/" { if self.recursive && self.preserve_root && filename == "/" {
show_error!( show_error!(
"it is dangerous to operate recursively on '{}'\nuse --no-preserve-root to override this failsafe", "it is dangerous to operate recursively on {}\nuse --no-preserve-root to override this failsafe",
filename filename.quote()
); );
return Err(1); return Err(1);
} }
@ -253,23 +264,27 @@ impl Chmoder {
// instead it just sets the readonly attribute on the file // instead it just sets the readonly attribute on the file
Err(0) Err(0)
} }
#[cfg(any(unix, target_os = "redox"))] #[cfg(unix)]
fn chmod_file(&self, file: &Path) -> Result<(), i32> { fn chmod_file(&self, file: &Path) -> Result<(), i32> {
let mut fperm = match fs::metadata(file) { use uucore::mode::get_umask;
let fperm = match fs::metadata(file) {
Ok(meta) => meta.mode() & 0o7777, Ok(meta) => meta.mode() & 0o7777,
Err(err) => { Err(err) => {
if is_symlink(file) { if is_symlink(file) {
if self.verbose { if self.verbose {
println!( println!(
"neither symbolic link '{}' nor referent has been changed", "neither symbolic link {} nor referent has been changed",
file.display() file.quote()
); );
} }
return Ok(()); return Ok(());
} else if err.kind() == std::io::ErrorKind::PermissionDenied { } else if err.kind() == std::io::ErrorKind::PermissionDenied {
show_error!("'{}': Permission denied", file.display()); // These two filenames would normally be conditionally
// quoted, but GNU's tests expect them to always be quoted
show_error!("{}: Permission denied", file.quote());
} else { } else {
show_error!("'{}': {}", file.display(), err); show_error!("{}: {}", file.quote(), err);
} }
return Err(1); return Err(1);
} }
@ -278,18 +293,30 @@ impl Chmoder {
Some(mode) => self.change_file(fperm, mode, file)?, Some(mode) => self.change_file(fperm, mode, file)?,
None => { None => {
let cmode_unwrapped = self.cmode.clone().unwrap(); let cmode_unwrapped = self.cmode.clone().unwrap();
let mut new_mode = fperm;
let mut naively_expected_new_mode = new_mode;
for mode in cmode_unwrapped.split(',') { for mode in cmode_unwrapped.split(',') {
// cmode is guaranteed to be Some in this case // cmode is guaranteed to be Some in this case
let arr: &[char] = &['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']; let arr: &[char] = &['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
let result = if mode.contains(arr) { let result = if mode.contains(arr) {
mode::parse_numeric(fperm, mode) mode::parse_numeric(new_mode, mode, file.is_dir()).map(|v| (v, v))
} else { } else {
mode::parse_symbolic(fperm, mode, file.is_dir()) mode::parse_symbolic(new_mode, mode, get_umask(), file.is_dir()).map(|m| {
// calculate the new mode as if umask was 0
let naive_mode = mode::parse_symbolic(
naively_expected_new_mode,
mode,
0,
file.is_dir(),
)
.unwrap(); // we know that mode must be valid, so this cannot fail
(m, naive_mode)
})
}; };
match result { match result {
Ok(mode) => { Ok((mode, naive_mode)) => {
self.change_file(fperm, mode, file)?; new_mode = mode;
fperm = mode; naively_expected_new_mode = naive_mode;
} }
Err(f) => { Err(f) => {
if !self.quiet { if !self.quiet {
@ -299,6 +326,17 @@ impl Chmoder {
} }
} }
} }
self.change_file(fperm, new_mode, file)?;
// if a permission would have been removed if umask was 0, but it wasn't because umask was not 0, print an error and fail
if (new_mode & !naively_expected_new_mode) != 0 {
show_error!(
"{}: new permissions are {}, not {}",
file.maybe_quote(),
display_permissions_unix(new_mode as mode_t, false),
display_permissions_unix(naively_expected_new_mode as mode_t, false)
);
return Err(1);
}
} }
} }
@ -310,8 +348,8 @@ impl Chmoder {
if fperm == mode { if fperm == mode {
if self.verbose && !self.changes { if self.verbose && !self.changes {
println!( println!(
"mode of '{}' retained as {:04o} ({})", "mode of {} retained as {:04o} ({})",
file.display(), file.quote(),
fperm, fperm,
display_permissions_unix(fperm as mode_t, false), display_permissions_unix(fperm as mode_t, false),
); );
@ -322,9 +360,9 @@ impl Chmoder {
show_error!("{}", err); show_error!("{}", err);
} }
if self.verbose { if self.verbose {
show_error!( println!(
"failed to change mode of file '{}' from {:o} ({}) to {:o} ({})", "failed to change mode of file {} from {:04o} ({}) to {:04o} ({})",
file.display(), file.quote(),
fperm, fperm,
display_permissions_unix(fperm as mode_t, false), display_permissions_unix(fperm as mode_t, false),
mode, mode,
@ -334,9 +372,9 @@ impl Chmoder {
Err(1) Err(1)
} else { } else {
if self.verbose || self.changes { if self.verbose || self.changes {
show_error!( println!(
"mode of '{}' changed from {:o} ({}) to {:o} ({})", "mode of {} changed from {:04o} ({}) to {:04o} ({})",
file.display(), file.quote(),
fperm, fperm,
display_permissions_unix(fperm as mode_t, false), display_permissions_unix(fperm as mode_t, false),
mode, mode,

View file

@ -16,10 +16,8 @@ path = "src/chown.rs"
[dependencies] [dependencies]
clap = { version = "2.33", features = ["wrap_help"] } clap = { version = "2.33", features = ["wrap_help"] }
glob = "0.3.0"
uucore = { version=">=0.0.9", package="uucore", path="../../uucore", features=["entries", "fs", "perms"] } uucore = { version=">=0.0.9", package="uucore", path="../../uucore", features=["entries", "fs", "perms"] }
uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_procs" } uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_procs" }
walkdir = "2.2"
[[bin]] [[bin]]
name = "chown" name = "chown"

View file

@ -5,130 +5,33 @@
// For the full copyright and license information, please view the LICENSE // For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code. // file that was distributed with this source code.
// spell-checker:ignore (ToDO) COMFOLLOW Chowner Passwd RFILE RFILE's derefer dgid duid // spell-checker:ignore (ToDO) COMFOLLOW Passwd RFILE RFILE's derefer dgid duid groupname
#[macro_use] #[macro_use]
extern crate uucore; extern crate uucore;
use uucore::display::Quotable;
pub use uucore::entries::{self, Group, Locate, Passwd}; pub use uucore::entries::{self, Group, Locate, Passwd};
use uucore::fs::resolve_relative_path; use uucore::perms::{chown_base, options, IfFrom};
use uucore::libc::{gid_t, uid_t};
use uucore::perms::{wrap_chown, Verbosity};
use uucore::error::{FromIo, UResult, USimpleError}; use uucore::error::{FromIo, UResult, USimpleError};
use clap::{crate_version, App, Arg}; use clap::{crate_version, App, Arg, ArgMatches};
use walkdir::WalkDir; use std::fs;
use std::fs::{self, Metadata};
use std::os::unix::fs::MetadataExt; 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 ABOUT: &str = "change file owner and group";
pub mod options {
pub mod verbosity {
pub static CHANGES: &str = "changes";
pub static QUIET: &str = "quiet";
pub static SILENT: &str = "silent";
pub static VERBOSE: &str = "verbose";
}
pub mod preserve_root {
pub static PRESERVE: &str = "preserve-root";
pub static NO_PRESERVE: &str = "no-preserve-root";
}
pub mod dereference {
pub static DEREFERENCE: &str = "dereference";
pub static NO_DEREFERENCE: &str = "no-dereference";
}
pub static FROM: &str = "from";
pub static RECURSIVE: &str = "recursive";
pub mod traverse {
pub static TRAVERSE: &str = "H";
pub static NO_TRAVERSE: &str = "P";
pub static EVERY: &str = "L";
}
pub static REFERENCE: &str = "reference";
}
static ARG_OWNER: &str = "owner";
static ARG_FILES: &str = "files";
const FTS_COMFOLLOW: u8 = 1;
const FTS_PHYSICAL: u8 = 1 << 1;
const FTS_LOGICAL: u8 = 1 << 2;
fn get_usage() -> String { fn get_usage() -> String {
format!( format!(
"{0} [OPTION]... [OWNER][:[GROUP]] FILE...\n{0} [OPTION]... --reference=RFILE FILE...", "{0} [OPTION]... [OWNER][:[GROUP]] FILE...\n{0} [OPTION]... --reference=RFILE FILE...",
executable!() uucore::execution_phrase()
) )
} }
#[uucore_procs::gen_uumain] fn parse_gid_uid_and_filter(matches: &ArgMatches) -> UResult<(Option<u32>, Option<u32>, IfFrom)> {
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let args = args
.collect_str(InvalidEncodingHandling::Ignore)
.accept_any();
let usage = get_usage();
let matches = uu_app().usage(&usage[..]).get_matches_from(args);
/* First arg is the owner/group */
let owner = matches.value_of(ARG_OWNER).unwrap();
/* Then the list of files */
let files: Vec<String> = matches
.values_of(ARG_FILES)
.map(|v| v.map(ToString::to_string).collect())
.unwrap_or_default();
let preserve_root = matches.is_present(options::preserve_root::PRESERVE);
let mut derefer = if matches.is_present(options::dereference::NO_DEREFERENCE) {
1
} else {
0
};
let mut bit_flag = if matches.is_present(options::traverse::TRAVERSE) {
FTS_COMFOLLOW | FTS_PHYSICAL
} else if matches.is_present(options::traverse::EVERY) {
FTS_LOGICAL
} else {
FTS_PHYSICAL
};
let recursive = matches.is_present(options::RECURSIVE);
if recursive {
if bit_flag == FTS_PHYSICAL {
if derefer == 1 {
return Err(USimpleError::new(1, "-R --dereference requires -H or -L"));
}
derefer = 0;
}
} else {
bit_flag = FTS_PHYSICAL;
}
let verbosity = if matches.is_present(options::verbosity::CHANGES) {
Verbosity::Changes
} else if matches.is_present(options::verbosity::SILENT)
|| matches.is_present(options::verbosity::QUIET)
{
Verbosity::Silent
} else if matches.is_present(options::verbosity::VERBOSE) {
Verbosity::Verbose
} else {
Verbosity::Normal
};
let filter = if let Some(spec) = matches.value_of(options::FROM) { let filter = if let Some(spec) = matches.value_of(options::FROM) {
match parse_spec(spec)? { match parse_spec(spec, ':')? {
(Some(uid), None) => IfFrom::User(uid), (Some(uid), None) => IfFrom::User(uid),
(None, Some(gid)) => IfFrom::Group(gid), (None, Some(gid)) => IfFrom::Group(gid),
(Some(uid), Some(gid)) => IfFrom::UserGroup(uid, gid), (Some(uid), Some(gid)) => IfFrom::UserGroup(uid, gid),
@ -142,30 +45,32 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let dest_gid: Option<u32>; let dest_gid: Option<u32>;
if let Some(file) = matches.value_of(options::REFERENCE) { if let Some(file) = matches.value_of(options::REFERENCE) {
let meta = fs::metadata(&file) let meta = fs::metadata(&file)
.map_err_context(|| format!("failed to get attributes of '{}'", file))?; .map_err_context(|| format!("failed to get attributes of {}", file.quote()))?;
dest_gid = Some(meta.gid()); dest_gid = Some(meta.gid());
dest_uid = Some(meta.uid()); dest_uid = Some(meta.uid());
} else { } else {
let (u, g) = parse_spec(owner)?; let (u, g) = parse_spec(matches.value_of(options::ARG_OWNER).unwrap(), ':')?;
dest_uid = u; dest_uid = u;
dest_gid = g; dest_gid = g;
} }
let executor = Chowner { Ok((dest_gid, dest_uid, filter))
bit_flag, }
dest_uid,
dest_gid, #[uucore_procs::gen_uumain]
verbosity, pub fn uumain(args: impl uucore::Args) -> UResult<()> {
recursive, let usage = get_usage();
dereference: derefer != 0,
filter, chown_base(
preserve_root, uu_app().usage(&usage[..]),
files, args,
}; options::ARG_OWNER,
executor.exec() parse_gid_uid_and_filter,
false,
)
} }
pub fn uu_app() -> App<'static, 'static> { pub fn uu_app() -> App<'static, 'static> {
App::new(executable!()) App::new(uucore::util_name())
.version(crate_version!()) .version(crate_version!())
.about(ABOUT) .about(ABOUT)
.arg( .arg(
@ -174,22 +79,31 @@ pub fn uu_app() -> App<'static, 'static> {
.long(options::verbosity::CHANGES) .long(options::verbosity::CHANGES)
.help("like verbose but report only when a change is made"), .help("like verbose but report only when a change is made"),
) )
.arg(Arg::with_name(options::dereference::DEREFERENCE).long(options::dereference::DEREFERENCE).help( .arg(
"affect the referent of each symbolic link (this is the default), rather than the symbolic link itself", Arg::with_name(options::dereference::DEREFERENCE)
)) .long(options::dereference::DEREFERENCE)
.help(
"affect the referent of each symbolic link (this is the default), \
rather than the symbolic link itself",
),
)
.arg( .arg(
Arg::with_name(options::dereference::NO_DEREFERENCE) Arg::with_name(options::dereference::NO_DEREFERENCE)
.short("h") .short("h")
.long(options::dereference::NO_DEREFERENCE) .long(options::dereference::NO_DEREFERENCE)
.help( .help(
"affect symbolic links instead of any referenced file (useful only on systems that can change the ownership of a symlink)", "affect symbolic links instead of any referenced file \
(useful only on systems that can change the ownership of a symlink)",
), ),
) )
.arg( .arg(
Arg::with_name(options::FROM) Arg::with_name(options::FROM)
.long(options::FROM) .long(options::FROM)
.help( .help(
"change the owner and/or group of each file only if its current owner and/or group match those specified here. Either may be omitted, in which case a match is not required for the omitted attribute", "change the owner and/or group of each file only if its \
current owner and/or group match those specified here. \
Either may be omitted, in which case a match is not required \
for the omitted attribute",
) )
.value_name("CURRENT_OWNER:CURRENT_GROUP"), .value_name("CURRENT_OWNER:CURRENT_GROUP"),
) )
@ -221,7 +135,11 @@ pub fn uu_app() -> App<'static, 'static> {
.value_name("RFILE") .value_name("RFILE")
.min_values(1), .min_values(1),
) )
.arg(Arg::with_name(options::verbosity::SILENT).short("f").long(options::verbosity::SILENT)) .arg(
Arg::with_name(options::verbosity::SILENT)
.short("f")
.long(options::verbosity::SILENT),
)
.arg( .arg(
Arg::with_name(options::traverse::TRAVERSE) Arg::with_name(options::traverse::TRAVERSE)
.short(options::traverse::TRAVERSE) .short(options::traverse::TRAVERSE)
@ -243,41 +161,55 @@ pub fn uu_app() -> App<'static, 'static> {
.arg( .arg(
Arg::with_name(options::verbosity::VERBOSE) Arg::with_name(options::verbosity::VERBOSE)
.long(options::verbosity::VERBOSE) .long(options::verbosity::VERBOSE)
.short("v")
.help("output a diagnostic for every file processed"), .help("output a diagnostic for every file processed"),
) )
.arg(
Arg::with_name(ARG_OWNER)
.multiple(false)
.takes_value(true)
.required(true),
)
.arg(
Arg::with_name(ARG_FILES)
.multiple(true)
.takes_value(true)
.required(true)
.min_values(1),
)
} }
fn parse_spec(spec: &str) -> UResult<(Option<u32>, Option<u32>)> { /// Parse the username and groupname
let args = spec.split_terminator(':').collect::<Vec<_>>(); ///
let usr_only = args.len() == 1 && !args[0].is_empty(); /// In theory, it should be username:groupname
let grp_only = args.len() == 2 && args[0].is_empty(); /// but ...
let usr_grp = args.len() == 2 && !args[0].is_empty() && !args[1].is_empty(); /// it can user.name:groupname
let uid = if usr_only || usr_grp { /// or username.groupname
Some( ///
Passwd::locate(args[0]) /// # Arguments
.map_err(|_| USimpleError::new(1, format!("invalid user: '{}'", spec)))? ///
.uid(), /// * `spec` - The input from the user
) /// * `sep` - Should be ':' or '.'
fn parse_spec(spec: &str, sep: char) -> UResult<(Option<u32>, Option<u32>)> {
assert!(['.', ':'].contains(&sep));
let mut args = spec.splitn(2, sep);
let user = args.next().unwrap_or("");
let group = args.next().unwrap_or("");
let uid = if !user.is_empty() {
Some(match Passwd::locate(user) {
Ok(u) => u.uid(), // We have been able to get the uid
Err(_) =>
// we have NOT been able to find the uid
// but we could be in the case where we have user.group
{
if spec.contains('.') && !spec.contains(':') && sep == ':' {
// but the input contains a '.' but not a ':'
// we might have something like username.groupname
// So, try to parse it this way
return parse_spec(spec, '.');
} else {
return Err(USimpleError::new(
1,
format!("invalid user: {}", spec.quote()),
));
}
}
})
} else { } else {
None None
}; };
let gid = if grp_only || usr_grp { let gid = if !group.is_empty() {
Some( Some(
Group::locate(args[1]) Group::locate(group)
.map_err(|_| USimpleError::new(1, format!("invalid group: '{}'", spec)))? .map_err(|_| USimpleError::new(1, format!("invalid group: {}", spec.quote())))?
.gid(), .gid(),
) )
} else { } else {
@ -286,203 +218,16 @@ fn parse_spec(spec: &str) -> UResult<(Option<u32>, Option<u32>)> {
Ok((uid, gid)) Ok((uid, gid))
} }
enum IfFrom {
All,
User(u32),
Group(u32),
UserGroup(u32, u32),
}
struct Chowner {
dest_uid: Option<u32>,
dest_gid: Option<u32>,
bit_flag: u8,
verbosity: Verbosity,
filter: IfFrom,
files: Vec<String>,
recursive: bool,
preserve_root: bool,
dereference: bool,
}
macro_rules! unwrap {
($m:expr, $e:ident, $err:block) => {
match $m {
Ok(meta) => meta,
Err($e) => $err,
}
};
}
impl Chowner {
fn exec(&self) -> UResult<()> {
let mut ret = 0;
for f in &self.files {
ret |= self.traverse(f);
}
if ret != 0 {
return Err(ret.into());
}
Ok(())
}
fn traverse<P: AsRef<Path>>(&self, root: P) -> i32 {
let follow_arg = self.dereference || self.bit_flag != FTS_PHYSICAL;
let path = root.as_ref();
let meta = match self.obtain_meta(path, follow_arg) {
Some(m) => m,
_ => return 1,
};
// Prohibit only if:
// (--preserve-root and -R present) &&
// (
// (argument is not symlink && resolved to be '/') ||
// (argument is symlink && should follow argument && resolved to be '/')
// )
if self.recursive && self.preserve_root {
let may_exist = if follow_arg {
path.canonicalize().ok()
} else {
let real = resolve_relative_path(path);
if real.is_dir() {
Some(real.canonicalize().expect("failed to get real path"))
} else {
Some(real.into_owned())
}
};
if let Some(p) = may_exist {
if p.parent().is_none() {
show_error!("it is dangerous to operate recursively on '/'");
show_error!("use --no-preserve-root to override this failsafe");
return 1;
}
}
}
let ret = if self.matched(meta.uid(), meta.gid()) {
match wrap_chown(
path,
&meta,
self.dest_uid,
self.dest_gid,
follow_arg,
self.verbosity.clone(),
) {
Ok(n) => {
if !n.is_empty() {
show_error!("{}", n);
}
0
}
Err(e) => {
if self.verbosity != Verbosity::Silent {
show_error!("{}", e);
}
1
}
}
} else {
0
};
if !self.recursive {
ret
} else {
ret | self.dive_into(&root)
}
}
fn dive_into<P: AsRef<Path>>(&self, root: P) -> i32 {
let mut ret = 0;
let root = root.as_ref();
let follow = self.dereference || self.bit_flag & FTS_LOGICAL != 0;
for entry in WalkDir::new(root).follow_links(follow).min_depth(1) {
let entry = unwrap!(entry, e, {
ret = 1;
show_error!("{}", e);
continue;
});
let path = entry.path();
let meta = match self.obtain_meta(path, follow) {
Some(m) => m,
_ => {
ret = 1;
continue;
}
};
if !self.matched(meta.uid(), meta.gid()) {
continue;
}
ret = match wrap_chown(
path,
&meta,
self.dest_uid,
self.dest_gid,
follow,
self.verbosity.clone(),
) {
Ok(n) => {
if !n.is_empty() {
show_error!("{}", n);
}
0
}
Err(e) => {
if self.verbosity != Verbosity::Silent {
show_error!("{}", e);
}
1
}
}
}
ret
}
fn obtain_meta<P: AsRef<Path>>(&self, path: P, follow: bool) -> Option<Metadata> {
use self::Verbosity::*;
let path = path.as_ref();
let meta = if follow {
unwrap!(path.metadata(), e, {
match self.verbosity {
Silent => (),
_ => show_error!("cannot access '{}': {}", path.display(), e),
}
return None;
})
} else {
unwrap!(path.symlink_metadata(), e, {
match self.verbosity {
Silent => (),
_ => show_error!("cannot dereference '{}': {}", path.display(), e),
}
return None;
})
};
Some(meta)
}
#[inline]
fn matched(&self, uid: uid_t, gid: gid_t) -> bool {
match self.filter {
IfFrom::All => true,
IfFrom::User(u) => u == uid,
IfFrom::Group(g) => g == gid,
IfFrom::UserGroup(u, g) => u == uid && g == gid,
}
}
}
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::*; use super::*;
#[test] #[test]
fn test_parse_spec() { fn test_parse_spec() {
assert!(matches!(parse_spec(":"), Ok((None, None)))); assert!(matches!(parse_spec(":", ':'), Ok((None, None))));
assert!(format!("{}", parse_spec("::").err().unwrap()).starts_with("invalid group: ")); assert!(matches!(parse_spec(".", ':'), Ok((None, None))));
assert!(matches!(parse_spec(".", '.'), Ok((None, None))));
assert!(format!("{}", parse_spec("::", ':').err().unwrap()).starts_with("invalid group: "));
assert!(format!("{}", parse_spec("..", ':').err().unwrap()).starts_with("invalid group: "));
} }
} }

View file

@ -15,10 +15,10 @@ use std::ffi::CString;
use std::io::Error; use std::io::Error;
use std::path::Path; use std::path::Path;
use std::process::Command; use std::process::Command;
use uucore::display::Quotable;
use uucore::libc::{self, chroot, setgid, setgroups, setuid}; use uucore::libc::{self, chroot, setgid, setgroups, setuid};
use uucore::{entries, InvalidEncodingHandling}; use uucore::{entries, InvalidEncodingHandling};
static NAME: &str = "chroot";
static ABOUT: &str = "Run COMMAND with root directory set to NEWROOT."; static ABOUT: &str = "Run COMMAND with root directory set to NEWROOT.";
static SYNTAX: &str = "[OPTION]... NEWROOT [COMMAND [ARG]...]"; static SYNTAX: &str = "[OPTION]... NEWROOT [COMMAND [ARG]...]";
@ -47,15 +47,15 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
None => crash!( None => crash!(
1, 1,
"Missing operand: NEWROOT\nTry '{} --help' for more information.", "Missing operand: NEWROOT\nTry '{} --help' for more information.",
NAME uucore::execution_phrase()
), ),
}; };
if !newroot.is_dir() { if !newroot.is_dir() {
crash!( crash!(
1, 1,
"cannot change root directory to `{}`: no such directory", "cannot change root directory to {}: no such directory",
newroot.display() newroot.quote()
); );
} }
@ -92,7 +92,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
} }
pub fn uu_app() -> App<'static, 'static> { pub fn uu_app() -> App<'static, 'static> {
App::new(executable!()) App::new(uucore::util_name())
.version(crate_version!()) .version(crate_version!())
.about(ABOUT) .about(ABOUT)
.usage(SYNTAX) .usage(SYNTAX)
@ -150,7 +150,7 @@ fn set_context(root: &Path, options: &clap::ArgMatches) {
Some(u) => { Some(u) => {
let s: Vec<&str> = u.split(':').collect(); let s: Vec<&str> = u.split(':').collect();
if s.len() != 2 || s.iter().any(|&spec| spec.is_empty()) { if s.len() != 2 || s.iter().any(|&spec| spec.is_empty()) {
crash!(1, "invalid userspec: `{}`", u) crash!(1, "invalid userspec: {}", u.quote())
}; };
s s
} }
@ -171,7 +171,6 @@ fn set_context(root: &Path, options: &clap::ArgMatches) {
} }
fn enter_chroot(root: &Path) { fn enter_chroot(root: &Path) {
let root_str = root.display();
std::env::set_current_dir(root).unwrap(); std::env::set_current_dir(root).unwrap();
let err = unsafe { let err = unsafe {
chroot(CString::new(".").unwrap().as_bytes_with_nul().as_ptr() as *const libc::c_char) chroot(CString::new(".").unwrap().as_bytes_with_nul().as_ptr() as *const libc::c_char)
@ -180,7 +179,7 @@ fn enter_chroot(root: &Path) {
crash!( crash!(
1, 1,
"cannot chroot to {}: {}", "cannot chroot to {}: {}",
root_str, root.quote(),
Error::last_os_error() Error::last_os_error()
) )
}; };
@ -190,7 +189,7 @@ fn set_main_group(group: &str) {
if !group.is_empty() { if !group.is_empty() {
let group_id = match entries::grp2gid(group) { let group_id = match entries::grp2gid(group) {
Ok(g) => g, Ok(g) => g,
_ => crash!(1, "no such group: {}", group), _ => crash!(1, "no such group: {}", group.maybe_quote()),
}; };
let err = unsafe { setgid(group_id) }; let err = unsafe { setgid(group_id) };
if err != 0 { if err != 0 {
@ -235,7 +234,12 @@ fn set_user(user: &str) {
let user_id = entries::usr2uid(user).unwrap(); let user_id = entries::usr2uid(user).unwrap();
let err = unsafe { setuid(user_id as libc::uid_t) }; let err = unsafe { setuid(user_id as libc::uid_t) };
if err != 0 { if err != 0 {
crash!(1, "cannot set user to {}: {}", user, Error::last_os_error()) crash!(
1,
"cannot set user to {}: {}",
user.maybe_quote(),
Error::last_os_error()
)
} }
} }
} }

View file

@ -23,3 +23,7 @@ uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_p
[[bin]] [[bin]]
name = "cksum" name = "cksum"
path = "src/main.rs" path = "src/main.rs"
[package.metadata.cargo-udeps.ignore]
# Necessary for "make all"
normal = ["uucore_procs"]

View file

@ -14,6 +14,7 @@ use clap::{crate_version, App, Arg};
use std::fs::File; use std::fs::File;
use std::io::{self, stdin, BufReader, Read}; use std::io::{self, stdin, BufReader, Read};
use std::path::Path; use std::path::Path;
use uucore::display::Quotable;
use uucore::InvalidEncodingHandling; use uucore::InvalidEncodingHandling;
// NOTE: CRC_TABLE_LEN *must* be <= 256 as we cast 0..CRC_TABLE_LEN to u8 // NOTE: CRC_TABLE_LEN *must* be <= 256 as we cast 0..CRC_TABLE_LEN to u8
@ -191,7 +192,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
match cksum("-") { match cksum("-") {
Ok((crc, size)) => println!("{} {}", crc, size), Ok((crc, size)) => println!("{} {}", crc, size),
Err(err) => { Err(err) => {
show_error!("{}", err); show_error!("-: {}", err);
return 2; return 2;
} }
} }
@ -203,7 +204,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
match cksum(fname.as_ref()) { match cksum(fname.as_ref()) {
Ok((crc, size)) => println!("{} {} {}", crc, size, fname), Ok((crc, size)) => println!("{} {} {}", crc, size, fname),
Err(err) => { Err(err) => {
show_error!("'{}' {}", fname, err); show_error!("{}: {}", fname.maybe_quote(), err);
exit_code = 2; exit_code = 2;
} }
} }
@ -213,7 +214,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
} }
pub fn uu_app() -> App<'static, 'static> { pub fn uu_app() -> App<'static, 'static> {
App::new(executable!()) App::new(uucore::util_name())
.name(NAME) .name(NAME)
.version(crate_version!()) .version(crate_version!())
.about(SUMMARY) .about(SUMMARY)

View file

@ -23,3 +23,7 @@ uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_p
[[bin]] [[bin]]
name = "comm" name = "comm"
path = "src/main.rs" path = "src/main.rs"
[package.metadata.cargo-udeps.ignore]
# Necessary for "make all"
normal = ["uucore_procs"]

View file

@ -7,9 +7,6 @@
// spell-checker:ignore (ToDO) delim mkdelim // spell-checker:ignore (ToDO) delim mkdelim
#[macro_use]
extern crate uucore;
use std::cmp::Ordering; use std::cmp::Ordering;
use std::fs::File; use std::fs::File;
use std::io::{self, stdin, BufRead, BufReader, Stdin}; use std::io::{self, stdin, BufRead, BufReader, Stdin};
@ -31,8 +28,8 @@ mod options {
pub const FILE_2: &str = "FILE2"; pub const FILE_2: &str = "FILE2";
} }
fn get_usage() -> String { fn usage() -> String {
format!("{} [OPTION]... FILE1 FILE2", executable!()) format!("{} [OPTION]... FILE1 FILE2", uucore::execution_phrase())
} }
fn mkdelim(col: usize, opts: &ArgMatches) -> String { fn mkdelim(col: usize, opts: &ArgMatches) -> String {
@ -132,7 +129,7 @@ fn open_file(name: &str) -> io::Result<LineReader> {
} }
pub fn uumain(args: impl uucore::Args) -> i32 { pub fn uumain(args: impl uucore::Args) -> i32 {
let usage = get_usage(); let usage = usage();
let args = args let args = args
.collect_str(InvalidEncodingHandling::ConvertLossy) .collect_str(InvalidEncodingHandling::ConvertLossy)
.accept_any(); .accept_any();
@ -148,7 +145,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
} }
pub fn uu_app() -> App<'static, 'static> { pub fn uu_app() -> App<'static, 'static> {
App::new(executable!()) App::new(uucore::util_name())
.version(crate_version!()) .version(crate_version!())
.about(ABOUT) .about(ABOUT)
.after_help(LONG_HELP) .after_help(LONG_HELP)

View file

@ -23,19 +23,29 @@ clap = { version = "2.33", features = ["wrap_help"] }
filetime = "0.2" filetime = "0.2"
libc = "0.2.85" libc = "0.2.85"
quick-error = "1.2.3" quick-error = "1.2.3"
uucore = { version=">=0.0.9", package="uucore", path="../../uucore", features=["fs"] } selinux = { version="0.2.3", optional=true }
uucore = { version=">=0.0.9", package="uucore", path="../../uucore", features=["fs", "perms", "mode"] }
uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_procs" } uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_procs" }
walkdir = "2.2" walkdir = "2.2"
[target.'cfg(target_os = "linux")'.dependencies] [target.'cfg(target_os = "linux")'.dependencies]
ioctl-sys = "0.5.2" ioctl-sys = "0.6"
[target.'cfg(target_os = "windows")'.dependencies] [target.'cfg(target_os = "windows")'.dependencies]
winapi = { version="0.3", features=["fileapi"] } winapi = { version="0.3", features=["fileapi"] }
[target.'cfg(unix)'.dependencies] [target.'cfg(unix)'.dependencies]
xattr="0.2.1" xattr="0.2.1"
exacl= { version = "0.6.0", optional=true }
[[bin]] [[bin]]
name = "cp" name = "cp"
path = "src/main.rs" path = "src/main.rs"
[features]
feat_selinux = ["selinux"]
feat_acl = ["exacl"]
[package.metadata.cargo-udeps.ignore]
# Necessary for "make all"
normal = ["uucore_procs"]

View file

@ -18,6 +18,7 @@ extern crate quick_error;
#[macro_use] #[macro_use]
extern crate uucore; extern crate uucore;
use uucore::display::Quotable;
#[cfg(windows)] #[cfg(windows)]
use winapi::um::fileapi::CreateFileW; use winapi::um::fileapi::CreateFileW;
#[cfg(windows)] #[cfg(windows)]
@ -48,7 +49,7 @@ use std::path::{Path, PathBuf, StripPrefixError};
use std::str::FromStr; use std::str::FromStr;
use std::string::ToString; use std::string::ToString;
use uucore::backup_control::{self, BackupMode}; use uucore::backup_control::{self, BackupMode};
use uucore::fs::{canonicalize, CanonicalizeMode}; use uucore::fs::{canonicalize, MissingHandling, ResolveMode};
use walkdir::WalkDir; use walkdir::WalkDir;
#[cfg(unix)] #[cfg(unix)]
@ -67,6 +68,7 @@ quick_error! {
IoErrContext(err: io::Error, path: String) { IoErrContext(err: io::Error, path: String) {
display("{}: {}", path, err) display("{}: {}", path, err)
context(path: &'a str, err: io::Error) -> (err, path.to_owned()) context(path: &'a str, err: io::Error) -> (err, path.to_owned())
context(context: String, err: io::Error) -> (err, context)
cause(err) cause(err)
} }
@ -99,7 +101,7 @@ quick_error! {
NotImplemented(opt: String) { display("Option '{}' not yet implemented.", opt) } NotImplemented(opt: String) { display("Option '{}' not yet implemented.", opt) }
/// Invalid arguments to backup /// Invalid arguments to backup
Backup(description: String) { display("{}\nTry 'cp --help' for more information.", description) } Backup(description: String) { display("{}\nTry '{} --help' for more information.", description, uucore::execution_phrase()) }
} }
} }
@ -180,12 +182,15 @@ pub enum CopyMode {
AttrOnly, AttrOnly,
} }
#[derive(Clone, Eq, PartialEq)] // The ordering here determines the order in which attributes are (re-)applied.
// In particular, Ownership must be changed first to avoid interfering with mode change.
#[derive(Clone, Eq, PartialEq, Debug, PartialOrd, Ord)]
pub enum Attribute { pub enum Attribute {
#[cfg(unix)] #[cfg(unix)]
Mode,
Ownership, Ownership,
Mode,
Timestamps, Timestamps,
#[cfg(feature = "feat_selinux")]
Context, Context,
Links, Links,
Xattr, Xattr,
@ -218,12 +223,12 @@ static LONG_HELP: &str = "";
static EXIT_OK: i32 = 0; static EXIT_OK: i32 = 0;
static EXIT_ERR: i32 = 1; static EXIT_ERR: i32 = 1;
fn get_usage() -> String { fn usage() -> String {
format!( format!(
"{0} [OPTION]... [-T] SOURCE DEST "{0} [OPTION]... [-T] SOURCE DEST
{0} [OPTION]... SOURCE... DIRECTORY {0} [OPTION]... SOURCE... DIRECTORY
{0} [OPTION]... -t DIRECTORY SOURCE...", {0} [OPTION]... -t DIRECTORY SOURCE...",
executable!() uucore::execution_phrase()
) )
} }
@ -231,8 +236,6 @@ fn get_usage() -> String {
mod options { mod options {
pub const ARCHIVE: &str = "archive"; pub const ARCHIVE: &str = "archive";
pub const ATTRIBUTES_ONLY: &str = "attributes-only"; pub const ATTRIBUTES_ONLY: &str = "attributes-only";
pub const BACKUP: &str = "backup";
pub const BACKUP_NO_ARG: &str = "b";
pub const CLI_SYMBOLIC_LINKS: &str = "cli-symbolic-links"; pub const CLI_SYMBOLIC_LINKS: &str = "cli-symbolic-links";
pub const CONTEXT: &str = "context"; pub const CONTEXT: &str = "context";
pub const COPY_CONTENTS: &str = "copy-contents"; pub const COPY_CONTENTS: &str = "copy-contents";
@ -242,7 +245,7 @@ mod options {
pub const LINK: &str = "link"; pub const LINK: &str = "link";
pub const NO_CLOBBER: &str = "no-clobber"; pub const NO_CLOBBER: &str = "no-clobber";
pub const NO_DEREFERENCE: &str = "no-dereference"; pub const NO_DEREFERENCE: &str = "no-dereference";
pub const NO_DEREFERENCE_PRESERVE_LINKS: &str = "no-dereference-preserve-linkgs"; pub const NO_DEREFERENCE_PRESERVE_LINKS: &str = "no-dereference-preserve-links";
pub const NO_PRESERVE: &str = "no-preserve"; pub const NO_PRESERVE: &str = "no-preserve";
pub const NO_TARGET_DIRECTORY: &str = "no-target-directory"; pub const NO_TARGET_DIRECTORY: &str = "no-target-directory";
pub const ONE_FILE_SYSTEM: &str = "one-file-system"; pub const ONE_FILE_SYSTEM: &str = "one-file-system";
@ -257,7 +260,6 @@ mod options {
pub const REMOVE_DESTINATION: &str = "remove-destination"; pub const REMOVE_DESTINATION: &str = "remove-destination";
pub const SPARSE: &str = "sparse"; pub const SPARSE: &str = "sparse";
pub const STRIP_TRAILING_SLASHES: &str = "strip-trailing-slashes"; pub const STRIP_TRAILING_SLASHES: &str = "strip-trailing-slashes";
pub const SUFFIX: &str = "suffix";
pub const SYMBOLIC_LINK: &str = "symbolic-link"; pub const SYMBOLIC_LINK: &str = "symbolic-link";
pub const TARGET_DIRECTORY: &str = "target-directory"; pub const TARGET_DIRECTORY: &str = "target-directory";
pub const UPDATE: &str = "update"; pub const UPDATE: &str = "update";
@ -269,6 +271,7 @@ static PRESERVABLE_ATTRIBUTES: &[&str] = &[
"mode", "mode",
"ownership", "ownership",
"timestamps", "timestamps",
#[cfg(feature = "feat_selinux")]
"context", "context",
"links", "links",
"xattr", "xattr",
@ -276,24 +279,18 @@ static PRESERVABLE_ATTRIBUTES: &[&str] = &[
]; ];
#[cfg(not(unix))] #[cfg(not(unix))]
static PRESERVABLE_ATTRIBUTES: &[&str] = &[ static PRESERVABLE_ATTRIBUTES: &[&str] =
"ownership", &["mode", "timestamps", "context", "links", "xattr", "all"];
"timestamps",
"context",
"links",
"xattr",
"all",
];
static DEFAULT_ATTRIBUTES: &[Attribute] = &[ static DEFAULT_ATTRIBUTES: &[Attribute] = &[
#[cfg(unix)]
Attribute::Mode, Attribute::Mode,
#[cfg(unix)]
Attribute::Ownership, Attribute::Ownership,
Attribute::Timestamps, Attribute::Timestamps,
]; ];
pub fn uu_app() -> App<'static, 'static> { pub fn uu_app() -> App<'static, 'static> {
App::new(executable!()) App::new(uucore::util_name())
.version(crate_version!()) .version(crate_version!())
.about(ABOUT) .about(ABOUT)
.arg(Arg::with_name(options::TARGET_DIRECTORY) .arg(Arg::with_name(options::TARGET_DIRECTORY)
@ -355,24 +352,9 @@ pub fn uu_app() -> App<'static, 'static> {
.conflicts_with(options::FORCE) .conflicts_with(options::FORCE)
.help("remove each existing destination file before attempting to open it \ .help("remove each existing destination file before attempting to open it \
(contrast with --force). On Windows, current only works for writeable files.")) (contrast with --force). On Windows, current only works for writeable files."))
.arg(Arg::with_name(options::BACKUP) .arg(backup_control::arguments::backup())
.long(options::BACKUP) .arg(backup_control::arguments::backup_no_args())
.help("make a backup of each existing destination file") .arg(backup_control::arguments::suffix())
.takes_value(true)
.require_equals(true)
.min_values(0)
.value_name("CONTROL")
)
.arg(Arg::with_name(options::BACKUP_NO_ARG)
.short(options::BACKUP_NO_ARG)
.help("like --backup but does not accept an argument")
)
.arg(Arg::with_name(options::SUFFIX)
.short("S")
.long(options::SUFFIX)
.takes_value(true)
.value_name("SUFFIX")
.help("override the usual backup suffix"))
.arg(Arg::with_name(options::UPDATE) .arg(Arg::with_name(options::UPDATE)
.short("u") .short("u")
.long(options::UPDATE) .long(options::UPDATE)
@ -399,13 +381,13 @@ pub fn uu_app() -> App<'static, 'static> {
.conflicts_with_all(&[options::PRESERVE_DEFAULT_ATTRIBUTES, options::NO_PRESERVE]) .conflicts_with_all(&[options::PRESERVE_DEFAULT_ATTRIBUTES, options::NO_PRESERVE])
// -d sets this option // -d sets this option
// --archive sets this option // --archive sets this option
.help("Preserve the specified attributes (default: mode (unix only), ownership, timestamps), \ .help("Preserve the specified attributes (default: mode, ownership (unix only), timestamps), \
if possible additional attributes: context, links, xattr, all")) if possible additional attributes: context, links, xattr, all"))
.arg(Arg::with_name(options::PRESERVE_DEFAULT_ATTRIBUTES) .arg(Arg::with_name(options::PRESERVE_DEFAULT_ATTRIBUTES)
.short("-p") .short("-p")
.long(options::PRESERVE_DEFAULT_ATTRIBUTES) .long(options::PRESERVE_DEFAULT_ATTRIBUTES)
.conflicts_with_all(&[options::PRESERVE, options::NO_PRESERVE, options::ARCHIVE]) .conflicts_with_all(&[options::PRESERVE, options::NO_PRESERVE, options::ARCHIVE])
.help("same as --preserve=mode(unix only),ownership,timestamps")) .help("same as --preserve=mode,ownership(unix only),timestamps"))
.arg(Arg::with_name(options::NO_PRESERVE) .arg(Arg::with_name(options::NO_PRESERVE)
.long(options::NO_PRESERVE) .long(options::NO_PRESERVE)
.takes_value(true) .takes_value(true)
@ -465,7 +447,7 @@ pub fn uu_app() -> App<'static, 'static> {
} }
pub fn uumain(args: impl uucore::Args) -> i32 { pub fn uumain(args: impl uucore::Args) -> i32 {
let usage = get_usage(); let usage = usage();
let matches = uu_app() let matches = uu_app()
.after_help(&*format!( .after_help(&*format!(
"{}\n{}", "{}\n{}",
@ -550,17 +532,18 @@ impl FromStr for Attribute {
fn from_str(value: &str) -> CopyResult<Attribute> { fn from_str(value: &str) -> CopyResult<Attribute> {
Ok(match &*value.to_lowercase() { Ok(match &*value.to_lowercase() {
#[cfg(unix)]
"mode" => Attribute::Mode, "mode" => Attribute::Mode,
#[cfg(unix)]
"ownership" => Attribute::Ownership, "ownership" => Attribute::Ownership,
"timestamps" => Attribute::Timestamps, "timestamps" => Attribute::Timestamps,
#[cfg(feature = "feat_selinux")]
"context" => Attribute::Context, "context" => Attribute::Context,
"links" => Attribute::Links, "links" => Attribute::Links,
"xattr" => Attribute::Xattr, "xattr" => Attribute::Xattr,
_ => { _ => {
return Err(Error::InvalidArgument(format!( return Err(Error::InvalidArgument(format!(
"invalid attribute '{}'", "invalid attribute {}",
value value.quote()
))); )));
} }
}) })
@ -570,14 +553,16 @@ impl FromStr for Attribute {
fn add_all_attributes() -> Vec<Attribute> { fn add_all_attributes() -> Vec<Attribute> {
use Attribute::*; use Attribute::*;
#[cfg(target_os = "windows")] let attr = vec![
let attr = vec![Ownership, Timestamps, Context, Xattr, Links];
#[cfg(not(target_os = "windows"))]
let mut attr = vec![Ownership, Timestamps, Context, Xattr, Links];
#[cfg(unix)] #[cfg(unix)]
attr.insert(0, Mode); Ownership,
Mode,
Timestamps,
#[cfg(feature = "feat_selinux")]
Context,
Links,
Xattr,
];
attr attr
} }
@ -604,20 +589,12 @@ impl Options {
|| matches.is_present(options::RECURSIVE_ALIAS) || matches.is_present(options::RECURSIVE_ALIAS)
|| matches.is_present(options::ARCHIVE); || matches.is_present(options::ARCHIVE);
let backup_mode = backup_control::determine_backup_mode( let backup_mode = match backup_control::determine_backup_mode(matches) {
matches.is_present(options::BACKUP_NO_ARG), Err(e) => return Err(Error::Backup(format!("{}", e))),
matches.is_present(options::BACKUP),
matches.value_of(options::BACKUP),
);
let backup_mode = match backup_mode {
Err(err) => {
return Err(Error::Backup(err));
}
Ok(mode) => mode, Ok(mode) => mode,
}; };
let backup_suffix = let backup_suffix = backup_control::determine_backup_suffix(matches);
backup_control::determine_backup_suffix(matches.value_of(options::SUFFIX));
let overwrite = OverwriteMode::from_matches(matches); let overwrite = OverwriteMode::from_matches(matches);
@ -628,7 +605,7 @@ impl Options {
.map(ToString::to_string); .map(ToString::to_string);
// Parse attributes to preserve // Parse attributes to preserve
let preserve_attributes: Vec<Attribute> = if matches.is_present(options::PRESERVE) { let mut preserve_attributes: Vec<Attribute> = if matches.is_present(options::PRESERVE) {
match matches.values_of(options::PRESERVE) { match matches.values_of(options::PRESERVE) {
None => DEFAULT_ATTRIBUTES.to_vec(), None => DEFAULT_ATTRIBUTES.to_vec(),
Some(attribute_strs) => { Some(attribute_strs) => {
@ -655,6 +632,11 @@ impl Options {
vec![] vec![]
}; };
// Make sure ownership is changed before other attributes,
// as chown clears some of the permission and therefore could undo previous changes
// if not executed first.
preserve_attributes.sort_unstable();
let options = Options { let options = Options {
attributes_only: matches.is_present(options::ATTRIBUTES_ONLY), attributes_only: matches.is_present(options::ATTRIBUTES_ONLY),
copy_contents: matches.is_present(options::COPY_CONTENTS), copy_contents: matches.is_present(options::COPY_CONTENTS),
@ -678,8 +660,8 @@ impl Options {
"never" => ReflinkMode::Never, "never" => ReflinkMode::Never,
value => { value => {
return Err(Error::InvalidArgument(format!( return Err(Error::InvalidArgument(format!(
"invalid argument '{}' for \'reflink\'", "invalid argument {} for \'reflink\'",
value value.quote()
))); )));
} }
} }
@ -851,7 +833,7 @@ fn copy(sources: &[Source], target: &TargetSlice, options: &Options) -> CopyResu
let mut seen_sources = HashSet::with_capacity(sources.len()); let mut seen_sources = HashSet::with_capacity(sources.len());
for source in sources { for source in sources {
if seen_sources.contains(source) { if seen_sources.contains(source) {
show_warning!("source '{}' specified more than once", source.display()); show_warning!("source {} specified more than once", source.quote());
} else { } else {
let mut found_hard_link = false; let mut found_hard_link = false;
if preserve_hard_links { if preserve_hard_links {
@ -892,8 +874,8 @@ fn construct_dest_path(
) -> CopyResult<PathBuf> { ) -> CopyResult<PathBuf> {
if options.no_target_dir && target.is_dir() { if options.no_target_dir && target.is_dir() {
return Err(format!( return Err(format!(
"cannot overwrite directory '{}' with non-directory", "cannot overwrite directory {} with non-directory",
target.display() target.quote()
) )
.into()); .into());
} }
@ -960,7 +942,7 @@ fn adjust_canonicalization(p: &Path) -> Cow<Path> {
/// will not cause a short-circuit. /// will not cause a short-circuit.
fn copy_directory(root: &Path, target: &TargetSlice, options: &Options) -> CopyResult<()> { fn copy_directory(root: &Path, target: &TargetSlice, options: &Options) -> CopyResult<()> {
if !options.recursive { if !options.recursive {
return Err(format!("omitting directory '{}'", root.display()).into()); return Err(format!("omitting directory {}", root.quote()).into());
} }
// if no-dereference is enabled and this is a symlink, copy it as a file // if no-dereference is enabled and this is a symlink, copy it as a file
@ -1060,12 +1042,12 @@ impl OverwriteMode {
match *self { match *self {
OverwriteMode::NoClobber => Err(Error::NotAllFilesCopied), OverwriteMode::NoClobber => Err(Error::NotAllFilesCopied),
OverwriteMode::Interactive(_) => { OverwriteMode::Interactive(_) => {
if prompt_yes!("{}: overwrite {}? ", executable!(), path.display()) { if prompt_yes!("{}: overwrite {}? ", uucore::util_name(), path.quote()) {
Ok(()) Ok(())
} else { } else {
Err(Error::Skipped(format!( Err(Error::Skipped(format!(
"Not overwriting {} at user request", "Not overwriting {} at user request",
path.display() path.quote()
))) )))
} }
} }
@ -1075,27 +1057,66 @@ impl OverwriteMode {
} }
fn copy_attribute(source: &Path, dest: &Path, attribute: &Attribute) -> CopyResult<()> { fn copy_attribute(source: &Path, dest: &Path, attribute: &Attribute) -> CopyResult<()> {
let context = &*format!("'{}' -> '{}'", source.display().to_string(), dest.display()); let context = &*format!("{} -> {}", source.quote(), dest.quote());
let source_metadata = fs::symlink_metadata(source).context(context)?;
match *attribute { match *attribute {
#[cfg(unix)]
Attribute::Mode => { Attribute::Mode => {
let mode = fs::metadata(source).context(context)?.permissions().mode(); fs::set_permissions(dest, source_metadata.permissions()).context(context)?;
let mut dest_metadata = fs::metadata(source).context(context)?.permissions(); // FIXME: Implement this for windows as well
dest_metadata.set_mode(mode); #[cfg(feature = "feat_acl")]
exacl::getfacl(source, None)
.and_then(|acl| exacl::setfacl(&[dest], &acl, None))
.map_err(|err| Error::Error(err.to_string()))?;
} }
#[cfg(unix)]
Attribute::Ownership => { Attribute::Ownership => {
let metadata = fs::metadata(source).context(context)?; use std::os::unix::prelude::MetadataExt;
fs::set_permissions(dest, metadata.permissions()).context(context)?; use uucore::perms::wrap_chown;
use uucore::perms::Verbosity;
use uucore::perms::VerbosityLevel;
let dest_uid = source_metadata.uid();
let dest_gid = source_metadata.gid();
wrap_chown(
dest,
&dest.symlink_metadata().context(context)?,
Some(dest_uid),
Some(dest_gid),
false,
Verbosity {
groups_only: false,
level: VerbosityLevel::Normal,
},
)
.map_err(Error::Error)?;
} }
Attribute::Timestamps => { Attribute::Timestamps => {
let metadata = fs::metadata(source)?;
filetime::set_file_times( filetime::set_file_times(
Path::new(dest), Path::new(dest),
FileTime::from_last_access_time(&metadata), FileTime::from_last_access_time(&source_metadata),
FileTime::from_last_modification_time(&metadata), FileTime::from_last_modification_time(&source_metadata),
)?; )?;
} }
Attribute::Context => {} #[cfg(feature = "feat_selinux")]
Attribute::Context => {
let context = selinux::SecurityContext::of_path(source, false, false).map_err(|e| {
format!(
"failed to get security context of {}: {}",
source.display(),
e
)
})?;
if let Some(context) = context {
context.set_for_path(dest, false, false).map_err(|e| {
format!(
"failed to set security context for {}: {}",
dest.display(),
e
)
})?;
}
}
Attribute::Links => {} Attribute::Links => {}
Attribute::Xattr => { Attribute::Xattr => {
#[cfg(unix)] #[cfg(unix)]
@ -1132,7 +1153,7 @@ fn symlink_file(source: &Path, dest: &Path, context: &str) -> CopyResult<()> {
} }
fn context_for(src: &Path, dest: &Path) -> String { fn context_for(src: &Path, dest: &Path) -> String {
format!("'{}' -> '{}'", src.display(), dest.display()) format!("{} -> {}", src.quote(), dest.quote())
} }
/// Implements a simple backup copy for the destination file. /// Implements a simple backup copy for the destination file.
@ -1169,8 +1190,8 @@ fn handle_existing_dest(source: &Path, dest: &Path, options: &Options) -> CopyRe
Ok(()) Ok(())
} }
/// Copy the a file from `source` to `dest`. No path manipulation is /// Copy the a file from `source` to `dest`. `source` will be dereferenced if
/// done on either `source` or `dest`, the are used as provided. /// `options.dereference` is set to true. `dest` will always be dereferenced.
/// ///
/// Behavior when copying to existing files is contingent on the /// Behavior when copying to existing files is contingent on the
/// `options.overwrite` mode. If a file is skipped, the return type /// `options.overwrite` mode. If a file is skipped, the return type
@ -1187,41 +1208,66 @@ fn copy_file(source: &Path, dest: &Path, options: &Options) -> CopyResult<()> {
println!("{}", context_for(source, dest)); println!("{}", context_for(source, dest));
} }
#[allow(unused)] // Calculate the context upfront before canonicalizing the path
let context = context_for(source, dest);
let context = context.as_str();
// canonicalize dest and source so that later steps can work with the paths directly
let dest = canonicalize(dest, MissingHandling::Missing, ResolveMode::Physical).unwrap();
let source = if options.dereference {
canonicalize(source, MissingHandling::Missing, ResolveMode::Physical).unwrap()
} else {
source.to_owned()
};
let dest_permissions = if dest.exists() {
dest.symlink_metadata().context(context)?.permissions()
} else {
#[allow(unused_mut)]
let mut permissions = source.symlink_metadata().context(context)?.permissions();
#[cfg(unix)]
{ {
// TODO: implement --preserve flag use uucore::mode::get_umask;
let mut preserve_context = false;
for attribute in &options.preserve_attributes { let mut mode = permissions.mode();
if *attribute == Attribute::Context {
preserve_context = true; // remove sticky bit, suid and gid bit
} const SPECIAL_PERMS_MASK: u32 = 0o7000;
} mode &= !SPECIAL_PERMS_MASK;
// apply umask
mode &= !get_umask();
permissions.set_mode(mode);
} }
permissions
};
match options.copy_mode { match options.copy_mode {
CopyMode::Link => { CopyMode::Link => {
fs::hard_link(source, dest).context(&*context_for(source, dest))?; fs::hard_link(&source, &dest).context(context)?;
} }
CopyMode::Copy => { CopyMode::Copy => {
copy_helper(source, dest, options)?; copy_helper(&source, &dest, options, context)?;
} }
CopyMode::SymLink => { CopyMode::SymLink => {
symlink_file(source, dest, &*context_for(source, dest))?; symlink_file(&source, &dest, context)?;
} }
CopyMode::Sparse => return Err(Error::NotImplemented(options::SPARSE.to_string())), CopyMode::Sparse => return Err(Error::NotImplemented(options::SPARSE.to_string())),
CopyMode::Update => { CopyMode::Update => {
if dest.exists() { if dest.exists() {
let src_metadata = fs::metadata(source)?; let src_metadata = fs::symlink_metadata(&source)?;
let dest_metadata = fs::metadata(dest)?; let dest_metadata = fs::symlink_metadata(&dest)?;
let src_time = src_metadata.modified()?; let src_time = src_metadata.modified()?;
let dest_time = dest_metadata.modified()?; let dest_time = dest_metadata.modified()?;
if src_time <= dest_time { if src_time <= dest_time {
return Ok(()); return Ok(());
} else { } else {
copy_helper(source, dest, options)?; copy_helper(&source, &dest, options, context)?;
} }
} else { } else {
copy_helper(source, dest, options)?; copy_helper(&source, &dest, options, context)?;
} }
} }
CopyMode::AttrOnly => { CopyMode::AttrOnly => {
@ -1229,53 +1275,51 @@ fn copy_file(source: &Path, dest: &Path, options: &Options) -> CopyResult<()> {
.write(true) .write(true)
.truncate(false) .truncate(false)
.create(true) .create(true)
.open(dest) .open(&dest)
.unwrap(); .unwrap();
} }
}; };
// TODO: implement something similar to gnu's lchown
if fs::symlink_metadata(&dest)
.map(|meta| !meta.file_type().is_symlink())
.unwrap_or(false)
{
fs::set_permissions(&dest, dest_permissions).unwrap();
}
for attribute in &options.preserve_attributes { for attribute in &options.preserve_attributes {
copy_attribute(source, dest, attribute)?; copy_attribute(&source, &dest, attribute)?;
} }
Ok(()) Ok(())
} }
/// Copy the file from `source` to `dest` either using the normal `fs::copy` or a /// 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. /// copy-on-write scheme if --reflink is specified and the filesystem supports it.
fn copy_helper(source: &Path, dest: &Path, options: &Options) -> CopyResult<()> { fn copy_helper(source: &Path, dest: &Path, options: &Options, context: &str) -> CopyResult<()> {
if options.parents { if options.parents {
let parent = dest.parent().unwrap_or(dest); let parent = dest.parent().unwrap_or(dest);
fs::create_dir_all(parent)?; fs::create_dir_all(parent)?;
} }
let is_symlink = fs::symlink_metadata(&source)?.file_type().is_symlink(); let is_symlink = fs::symlink_metadata(&source)?.file_type().is_symlink();
if source.to_string_lossy() == "/dev/null" { if source.as_os_str() == "/dev/null" {
/* workaround a limitation of fs::copy /* workaround a limitation of fs::copy
* https://github.com/rust-lang/rust/issues/79390 * https://github.com/rust-lang/rust/issues/79390
*/ */
File::create(dest)?; File::create(dest)?;
} else if !options.dereference && is_symlink { } else if is_symlink {
copy_link(source, dest)?; copy_link(source, dest)?;
} else if options.reflink_mode != ReflinkMode::Never { } else if options.reflink_mode != ReflinkMode::Never {
#[cfg(not(any(target_os = "linux", target_os = "macos")))] #[cfg(not(any(target_os = "linux", target_os = "macos")))]
return Err("--reflink is only supported on linux and macOS" return Err("--reflink is only supported on linux and macOS"
.to_string() .to_string()
.into()); .into());
#[cfg(any(target_os = "linux", target_os = "macos"))]
if is_symlink {
assert!(options.dereference);
let real_path = std::fs::read_link(source)?;
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
copy_on_write_macos(&real_path, dest, options.reflink_mode)?; copy_on_write_macos(source, dest, options.reflink_mode, context)?;
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
copy_on_write_linux(&real_path, dest, options.reflink_mode)?; copy_on_write_linux(source, dest, options.reflink_mode, context)?;
} else { } else {
#[cfg(target_os = "macos")] fs::copy(source, dest).context(context)?;
copy_on_write_macos(source, dest, options.reflink_mode)?;
#[cfg(target_os = "linux")]
copy_on_write_linux(source, dest, options.reflink_mode)?;
}
} else {
fs::copy(source, dest).context(&*context_for(source, dest))?;
} }
Ok(()) Ok(())
@ -1289,8 +1333,8 @@ fn copy_link(source: &Path, dest: &Path) -> CopyResult<()> {
Some(name) => dest.join(name).into(), Some(name) => dest.join(name).into(),
None => crash!( None => crash!(
EXIT_ERR, EXIT_ERR,
"cannot stat '{}': No such file or directory", "cannot stat {}: No such file or directory",
source.display() source.quote()
), ),
} }
} else { } else {
@ -1306,16 +1350,21 @@ fn copy_link(source: &Path, dest: &Path) -> CopyResult<()> {
/// Copies `source` to `dest` using copy-on-write if possible. /// Copies `source` to `dest` using copy-on-write if possible.
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
fn copy_on_write_linux(source: &Path, dest: &Path, mode: ReflinkMode) -> CopyResult<()> { fn copy_on_write_linux(
source: &Path,
dest: &Path,
mode: ReflinkMode,
context: &str,
) -> CopyResult<()> {
debug_assert!(mode != ReflinkMode::Never); debug_assert!(mode != ReflinkMode::Never);
let src_file = File::open(source).context(&*context_for(source, dest))?; let src_file = File::open(source).context(context)?;
let dst_file = OpenOptions::new() let dst_file = OpenOptions::new()
.write(true) .write(true)
.truncate(false) .truncate(false)
.create(true) .create(true)
.open(dest) .open(dest)
.context(&*context_for(source, dest))?; .context(context)?;
match mode { match mode {
ReflinkMode::Always => unsafe { ReflinkMode::Always => unsafe {
let result = ficlone(dst_file.as_raw_fd(), src_file.as_raw_fd() as *const i32); let result = ficlone(dst_file.as_raw_fd(), src_file.as_raw_fd() as *const i32);
@ -1334,7 +1383,7 @@ fn copy_on_write_linux(source: &Path, dest: &Path, mode: ReflinkMode) -> CopyRes
ReflinkMode::Auto => unsafe { ReflinkMode::Auto => unsafe {
let result = ficlone(dst_file.as_raw_fd(), src_file.as_raw_fd() as *const i32); let result = ficlone(dst_file.as_raw_fd(), src_file.as_raw_fd() as *const i32);
if result != 0 { if result != 0 {
fs::copy(source, dest).context(&*context_for(source, dest))?; fs::copy(source, dest).context(context)?;
} }
Ok(()) Ok(())
}, },
@ -1344,7 +1393,12 @@ fn copy_on_write_linux(source: &Path, dest: &Path, mode: ReflinkMode) -> CopyRes
/// Copies `source` to `dest` using copy-on-write if possible. /// Copies `source` to `dest` using copy-on-write if possible.
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
fn copy_on_write_macos(source: &Path, dest: &Path, mode: ReflinkMode) -> CopyResult<()> { fn copy_on_write_macos(
source: &Path,
dest: &Path,
mode: ReflinkMode,
context: &str,
) -> CopyResult<()> {
debug_assert!(mode != ReflinkMode::Never); debug_assert!(mode != ReflinkMode::Never);
// Extract paths in a form suitable to be passed to a syscall. // Extract paths in a form suitable to be passed to a syscall.
@ -1389,7 +1443,7 @@ fn copy_on_write_macos(source: &Path, dest: &Path, mode: ReflinkMode) -> CopyRes
format!("failed to clone {:?} from {:?}: {}", source, dest, error).into(), format!("failed to clone {:?} from {:?}: {}", source, dest, error).into(),
) )
} }
ReflinkMode::Auto => fs::copy(source, dest).context(&*context_for(source, dest))?, ReflinkMode::Auto => fs::copy(source, dest).context(context)?,
ReflinkMode::Never => unreachable!(), ReflinkMode::Never => unreachable!(),
}; };
} }
@ -1401,11 +1455,11 @@ fn copy_on_write_macos(source: &Path, dest: &Path, mode: ReflinkMode) -> CopyRes
pub fn verify_target_type(target: &Path, target_type: &TargetType) -> CopyResult<()> { pub fn verify_target_type(target: &Path, target_type: &TargetType) -> CopyResult<()> {
match (target_type, target.is_dir()) { match (target_type, target.is_dir()) {
(&TargetType::Directory, false) => { (&TargetType::Directory, false) => {
Err(format!("target: '{}' is not a directory", target.display()).into()) Err(format!("target: {} is not a directory", target.quote()).into())
} }
(&TargetType::File, true) => Err(format!( (&TargetType::File, true) => Err(format!(
"cannot overwrite directory '{}' with non-directory", "cannot overwrite directory {} with non-directory",
target.display() target.quote()
) )
.into()), .into()),
_ => Ok(()), _ => Ok(()),
@ -1430,8 +1484,8 @@ pub fn localize_to_target(root: &Path, source: &Path, target: &Path) -> CopyResu
pub fn paths_refer_to_same_file(p1: &Path, p2: &Path) -> io::Result<bool> { pub fn paths_refer_to_same_file(p1: &Path, p2: &Path) -> io::Result<bool> {
// We have to take symlinks and relative paths into account. // We have to take symlinks and relative paths into account.
let pathbuf1 = canonicalize(p1, CanonicalizeMode::Normal)?; let pathbuf1 = canonicalize(p1, MissingHandling::Normal, ResolveMode::Logical)?;
let pathbuf2 = canonicalize(p2, CanonicalizeMode::Normal)?; let pathbuf2 = canonicalize(p2, MissingHandling::Normal, ResolveMode::Logical)?;
Ok(pathbuf1 == pathbuf2) Ok(pathbuf1 == pathbuf2)
} }

View file

@ -18,10 +18,13 @@ path = "src/csplit.rs"
clap = { version = "2.33", features = ["wrap_help"] } clap = { version = "2.33", features = ["wrap_help"] }
thiserror = "1.0" thiserror = "1.0"
regex = "1.0.0" regex = "1.0.0"
glob = "0.2.11"
uucore = { version=">=0.0.9", package="uucore", path="../../uucore", features=["entries", "fs"] } uucore = { version=">=0.0.9", package="uucore", path="../../uucore", features=["entries", "fs"] }
uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_procs" } uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_procs" }
[[bin]] [[bin]]
name = "csplit" name = "csplit"
path = "src/main.rs" path = "src/main.rs"
[package.metadata.cargo-udeps.ignore]
# Necessary for "make all"
normal = ["uucore_procs"]

View file

@ -10,6 +10,7 @@ use std::{
fs::{remove_file, File}, fs::{remove_file, File},
io::{BufRead, BufWriter, Write}, io::{BufRead, BufWriter, Write},
}; };
use uucore::display::Quotable;
mod csplit_error; mod csplit_error;
mod patterns; mod patterns;
@ -34,8 +35,11 @@ mod options {
pub const PATTERN: &str = "pattern"; pub const PATTERN: &str = "pattern";
} }
fn get_usage() -> String { fn usage() -> String {
format!("{0} [OPTION]... FILE PATTERN...", executable!()) format!(
"{0} [OPTION]... FILE PATTERN...",
uucore::execution_phrase()
)
} }
/// Command line options for csplit. /// Command line options for csplit.
@ -565,7 +569,7 @@ mod tests {
assert_eq!(input_splitter.add_line_to_buffer(0, line), None); assert_eq!(input_splitter.add_line_to_buffer(0, line), None);
assert_eq!(input_splitter.buffer_len(), 1); assert_eq!(input_splitter.buffer_len(), 1);
} }
item @ _ => panic!("wrong item: {:?}", item), item => panic!("wrong item: {:?}", item),
}; };
match input_splitter.next() { match input_splitter.next() {
@ -574,7 +578,7 @@ mod tests {
assert_eq!(input_splitter.add_line_to_buffer(1, line), None); assert_eq!(input_splitter.add_line_to_buffer(1, line), None);
assert_eq!(input_splitter.buffer_len(), 2); assert_eq!(input_splitter.buffer_len(), 2);
} }
item @ _ => panic!("wrong item: {:?}", item), item => panic!("wrong item: {:?}", item),
}; };
match input_splitter.next() { match input_splitter.next() {
@ -586,7 +590,7 @@ mod tests {
); );
assert_eq!(input_splitter.buffer_len(), 2); assert_eq!(input_splitter.buffer_len(), 2);
} }
item @ _ => panic!("wrong item: {:?}", item), item => panic!("wrong item: {:?}", item),
}; };
input_splitter.rewind_buffer(); input_splitter.rewind_buffer();
@ -596,7 +600,7 @@ mod tests {
assert_eq!(line, String::from("bbb")); assert_eq!(line, String::from("bbb"));
assert_eq!(input_splitter.buffer_len(), 1); assert_eq!(input_splitter.buffer_len(), 1);
} }
item @ _ => panic!("wrong item: {:?}", item), item => panic!("wrong item: {:?}", item),
}; };
match input_splitter.next() { match input_splitter.next() {
@ -604,7 +608,7 @@ mod tests {
assert_eq!(line, String::from("ccc")); assert_eq!(line, String::from("ccc"));
assert_eq!(input_splitter.buffer_len(), 0); assert_eq!(input_splitter.buffer_len(), 0);
} }
item @ _ => panic!("wrong item: {:?}", item), item => panic!("wrong item: {:?}", item),
}; };
match input_splitter.next() { match input_splitter.next() {
@ -612,7 +616,7 @@ mod tests {
assert_eq!(line, String::from("ddd")); assert_eq!(line, String::from("ddd"));
assert_eq!(input_splitter.buffer_len(), 0); assert_eq!(input_splitter.buffer_len(), 0);
} }
item @ _ => panic!("wrong item: {:?}", item), item => panic!("wrong item: {:?}", item),
}; };
assert!(input_splitter.next().is_none()); assert!(input_splitter.next().is_none());
@ -637,7 +641,7 @@ mod tests {
assert_eq!(input_splitter.add_line_to_buffer(0, line), None); assert_eq!(input_splitter.add_line_to_buffer(0, line), None);
assert_eq!(input_splitter.buffer_len(), 1); assert_eq!(input_splitter.buffer_len(), 1);
} }
item @ _ => panic!("wrong item: {:?}", item), item => panic!("wrong item: {:?}", item),
}; };
match input_splitter.next() { match input_splitter.next() {
@ -646,7 +650,7 @@ mod tests {
assert_eq!(input_splitter.add_line_to_buffer(1, line), None); assert_eq!(input_splitter.add_line_to_buffer(1, line), None);
assert_eq!(input_splitter.buffer_len(), 2); assert_eq!(input_splitter.buffer_len(), 2);
} }
item @ _ => panic!("wrong item: {:?}", item), item => panic!("wrong item: {:?}", item),
}; };
match input_splitter.next() { match input_splitter.next() {
@ -655,7 +659,7 @@ mod tests {
assert_eq!(input_splitter.add_line_to_buffer(2, line), None); assert_eq!(input_splitter.add_line_to_buffer(2, line), None);
assert_eq!(input_splitter.buffer_len(), 3); assert_eq!(input_splitter.buffer_len(), 3);
} }
item @ _ => panic!("wrong item: {:?}", item), item => panic!("wrong item: {:?}", item),
}; };
input_splitter.rewind_buffer(); input_splitter.rewind_buffer();
@ -666,7 +670,7 @@ mod tests {
assert_eq!(input_splitter.add_line_to_buffer(0, line), None); assert_eq!(input_splitter.add_line_to_buffer(0, line), None);
assert_eq!(input_splitter.buffer_len(), 3); assert_eq!(input_splitter.buffer_len(), 3);
} }
item @ _ => panic!("wrong item: {:?}", item), item => panic!("wrong item: {:?}", item),
}; };
match input_splitter.next() { match input_splitter.next() {
@ -674,7 +678,7 @@ mod tests {
assert_eq!(line, String::from("aaa")); assert_eq!(line, String::from("aaa"));
assert_eq!(input_splitter.buffer_len(), 2); assert_eq!(input_splitter.buffer_len(), 2);
} }
item @ _ => panic!("wrong item: {:?}", item), item => panic!("wrong item: {:?}", item),
}; };
match input_splitter.next() { match input_splitter.next() {
@ -682,7 +686,7 @@ mod tests {
assert_eq!(line, String::from("bbb")); assert_eq!(line, String::from("bbb"));
assert_eq!(input_splitter.buffer_len(), 1); assert_eq!(input_splitter.buffer_len(), 1);
} }
item @ _ => panic!("wrong item: {:?}", item), item => panic!("wrong item: {:?}", item),
}; };
match input_splitter.next() { match input_splitter.next() {
@ -690,7 +694,7 @@ mod tests {
assert_eq!(line, String::from("ccc")); assert_eq!(line, String::from("ccc"));
assert_eq!(input_splitter.buffer_len(), 0); assert_eq!(input_splitter.buffer_len(), 0);
} }
item @ _ => panic!("wrong item: {:?}", item), item => panic!("wrong item: {:?}", item),
}; };
match input_splitter.next() { match input_splitter.next() {
@ -698,7 +702,7 @@ mod tests {
assert_eq!(line, String::from("ddd")); assert_eq!(line, String::from("ddd"));
assert_eq!(input_splitter.buffer_len(), 0); assert_eq!(input_splitter.buffer_len(), 0);
} }
item @ _ => panic!("wrong item: {:?}", item), item => panic!("wrong item: {:?}", item),
}; };
assert!(input_splitter.next().is_none()); assert!(input_splitter.next().is_none());
@ -706,7 +710,7 @@ mod tests {
} }
pub fn uumain(args: impl uucore::Args) -> i32 { pub fn uumain(args: impl uucore::Args) -> i32 {
let usage = get_usage(); let usage = usage();
let args = args let args = args
.collect_str(InvalidEncodingHandling::Ignore) .collect_str(InvalidEncodingHandling::Ignore)
.accept_any(); .accept_any();
@ -722,16 +726,16 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.unwrap() .unwrap()
.map(str::to_string) .map(str::to_string)
.collect(); .collect();
let patterns = return_if_err!(1, patterns::get_patterns(&patterns[..])); let patterns = crash_if_err!(1, patterns::get_patterns(&patterns[..]));
let options = CsplitOptions::new(&matches); let options = CsplitOptions::new(&matches);
if file_name == "-" { if file_name == "-" {
let stdin = io::stdin(); let stdin = io::stdin();
crash_if_err!(1, csplit(&options, patterns, stdin.lock())); crash_if_err!(1, csplit(&options, patterns, stdin.lock()));
} else { } else {
let file = return_if_err!(1, File::open(file_name)); let file = crash_if_err!(1, File::open(file_name));
let file_metadata = return_if_err!(1, file.metadata()); let file_metadata = crash_if_err!(1, file.metadata());
if !file_metadata.is_file() { if !file_metadata.is_file() {
crash!(1, "'{}' is not a regular file", file_name); crash!(1, "{} is not a regular file", file_name.quote());
} }
crash_if_err!(1, csplit(&options, patterns, BufReader::new(file))); crash_if_err!(1, csplit(&options, patterns, BufReader::new(file)));
}; };
@ -739,7 +743,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
} }
pub fn uu_app() -> App<'static, 'static> { pub fn uu_app() -> App<'static, 'static> {
App::new(executable!()) App::new(uucore::util_name())
.version(crate_version!()) .version(crate_version!())
.about(SUMMARY) .about(SUMMARY)
.arg( .arg(

View file

@ -1,26 +1,28 @@
use std::io; use std::io;
use thiserror::Error; use thiserror::Error;
use uucore::display::Quotable;
/// Errors thrown by the csplit command /// Errors thrown by the csplit command
#[derive(Debug, Error)] #[derive(Debug, Error)]
pub enum CsplitError { pub enum CsplitError {
#[error("IO error: {}", _0)] #[error("IO error: {}", _0)]
IoError(io::Error), IoError(io::Error),
#[error("'{}': line number out of range", _0)] #[error("{}: line number out of range", ._0.quote())]
LineOutOfRange(String), LineOutOfRange(String),
#[error("'{}': line number out of range on repetition {}", _0, _1)] #[error("{}: line number out of range on repetition {}", ._0.quote(), _1)]
LineOutOfRangeOnRepetition(String, usize), LineOutOfRangeOnRepetition(String, usize),
#[error("'{}': match not found", _0)] #[error("{}: match not found", ._0.quote())]
MatchNotFound(String), MatchNotFound(String),
#[error("'{}': match not found on repetition {}", _0, _1)] #[error("{}: match not found on repetition {}", ._0.quote(), _1)]
MatchNotFoundOnRepetition(String, usize), MatchNotFoundOnRepetition(String, usize),
#[error("line number must be greater than zero")] #[error("line number must be greater than zero")]
LineNumberIsZero, LineNumberIsZero,
#[error("line number '{}' is smaller than preceding line number, {}", _0, _1)] #[error("line number '{}' is smaller than preceding line number, {}", _0, _1)]
LineNumberSmallerThanPrevious(usize, usize), LineNumberSmallerThanPrevious(usize, usize),
#[error("invalid pattern: {}", _0)] #[error("{}: invalid pattern", ._0.quote())]
InvalidPattern(String), InvalidPattern(String),
#[error("invalid number: '{}'", _0)] #[error("invalid number: {}", ._0.quote())]
InvalidNumber(String), InvalidNumber(String),
#[error("incorrect conversion specification in suffix")] #[error("incorrect conversion specification in suffix")]
SuffixFormatIncorrect, SuffixFormatIncorrect,

View file

@ -25,3 +25,7 @@ atty = "0.2"
[[bin]] [[bin]]
name = "cut" name = "cut"
path = "src/main.rs" path = "src/main.rs"
[package.metadata.cargo-udeps.ignore]
# Necessary for "make all"
normal = ["uucore_procs"]

View file

@ -15,6 +15,7 @@ use clap::{crate_version, App, Arg};
use std::fs::File; use std::fs::File;
use std::io::{stdin, stdout, BufReader, BufWriter, Read, Write}; use std::io::{stdin, stdout, BufReader, BufWriter, Read, Write};
use std::path::Path; use std::path::Path;
use uucore::display::Quotable;
use self::searcher::Searcher; use self::searcher::Searcher;
use uucore::ranges::Range; use uucore::ranges::Range;
@ -351,19 +352,19 @@ fn cut_files(mut filenames: Vec<String>, mode: Mode) -> i32 {
let path = Path::new(&filename[..]); let path = Path::new(&filename[..]);
if path.is_dir() { if path.is_dir() {
show_error!("{}: Is a directory", filename); show_error!("{}: Is a directory", filename.maybe_quote());
continue; continue;
} }
if path.metadata().is_err() { if path.metadata().is_err() {
show_error!("{}: No such file or directory", filename); show_error!("{}: No such file or directory", filename.maybe_quote());
continue; continue;
} }
let file = match File::open(&path) { let file = match File::open(&path) {
Ok(f) => f, Ok(f) => f,
Err(e) => { Err(e) => {
show_error!("opening '{}': {}", &filename[..], e); show_error!("opening {}: {}", filename.quote(), e);
continue; continue;
} }
}; };
@ -548,7 +549,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
} }
pub fn uu_app() -> App<'static, 'static> { pub fn uu_app() -> App<'static, 'static> {
App::new(executable!()) App::new(uucore::util_name())
.name(NAME) .name(NAME)
.version(crate_version!()) .version(crate_version!())
.usage(SYNTAX) .usage(SYNTAX)

View file

@ -29,3 +29,7 @@ winapi = { version = "0.3", features = ["minwinbase", "sysinfoapi", "minwindef"]
[[bin]] [[bin]]
name = "date" name = "date"
path = "src/main.rs" path = "src/main.rs"
[package.metadata.cargo-udeps.ignore]
# Necessary for "make all"
normal = ["uucore_procs"]

View file

@ -8,9 +8,6 @@
// spell-checker:ignore (chrono) Datelike Timelike ; (format) DATEFILE MMDDhhmm ; (vars) datetime datetimes // spell-checker:ignore (chrono) Datelike Timelike ; (format) DATEFILE MMDDhhmm ; (vars) datetime datetimes
#[macro_use]
extern crate uucore;
use chrono::{DateTime, FixedOffset, Local, Offset, Utc}; use chrono::{DateTime, FixedOffset, Local, Offset, Utc};
#[cfg(windows)] #[cfg(windows)]
use chrono::{Datelike, Timelike}; use chrono::{Datelike, Timelike};
@ -20,6 +17,8 @@ use libc::{clock_settime, timespec, CLOCK_REALTIME};
use std::fs::File; use std::fs::File;
use std::io::{BufRead, BufReader}; use std::io::{BufRead, BufReader};
use std::path::PathBuf; use std::path::PathBuf;
use uucore::display::Quotable;
use uucore::show_error;
#[cfg(windows)] #[cfg(windows)]
use winapi::{ use winapi::{
shared::minwindef::WORD, shared::minwindef::WORD,
@ -148,7 +147,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
let format = if let Some(form) = matches.value_of(OPT_FORMAT) { let format = if let Some(form) = matches.value_of(OPT_FORMAT) {
if !form.starts_with('+') { if !form.starts_with('+') {
eprintln!("date: invalid date '{}'", form); show_error!("invalid date {}", form.quote());
return 1; return 1;
} }
let form = form[1..].to_string(); let form = form[1..].to_string();
@ -177,7 +176,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
let set_to = match matches.value_of(OPT_SET).map(parse_date) { let set_to = match matches.value_of(OPT_SET).map(parse_date) {
None => None, None => None,
Some(Err((input, _err))) => { Some(Err((input, _err))) => {
eprintln!("date: invalid date '{}'", input); show_error!("invalid date {}", input.quote());
return 1; return 1;
} }
Some(Ok(date)) => Some(date), Some(Ok(date)) => Some(date),
@ -243,7 +242,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
println!("{}", formatted); println!("{}", formatted);
} }
Err((input, _err)) => { Err((input, _err)) => {
println!("date: invalid date '{}'", input); show_error!("invalid date {}", input.quote());
} }
} }
} }
@ -253,7 +252,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
} }
pub fn uu_app() -> App<'static, 'static> { pub fn uu_app() -> App<'static, 'static> {
App::new(executable!()) App::new(uucore::util_name())
.version(crate_version!()) .version(crate_version!())
.about(ABOUT) .about(ABOUT)
.arg( .arg(
@ -355,13 +354,13 @@ fn set_system_datetime(_date: DateTime<Utc>) -> i32 {
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
fn set_system_datetime(_date: DateTime<Utc>) -> i32 { fn set_system_datetime(_date: DateTime<Utc>) -> i32 {
eprintln!("date: setting the date is not supported by macOS"); show_error!("setting the date is not supported by macOS");
1 1
} }
#[cfg(target_os = "redox")] #[cfg(target_os = "redox")]
fn set_system_datetime(_date: DateTime<Utc>) -> i32 { fn set_system_datetime(_date: DateTime<Utc>) -> i32 {
eprintln!("date: setting the date is not supported by Redox"); show_error!("setting the date is not supported by Redox");
1 1
} }
@ -381,7 +380,7 @@ fn set_system_datetime(date: DateTime<Utc>) -> i32 {
if result != 0 { if result != 0 {
let error = std::io::Error::last_os_error(); let error = std::io::Error::last_os_error();
eprintln!("date: cannot set date: {}", error); show_error!("cannot set date: {}", error);
error.raw_os_error().unwrap() error.raw_os_error().unwrap()
} else { } else {
0 0
@ -411,7 +410,7 @@ fn set_system_datetime(date: DateTime<Utc>) -> i32 {
if result == 0 { if result == 0 {
let error = std::io::Error::last_os_error(); let error = std::io::Error::last_os_error();
eprintln!("date: cannot set date: {}", error); show_error!("cannot set date: {}", error);
error.raw_os_error().unwrap() error.raw_os_error().unwrap()
} else { } else {
0 0

View file

@ -31,3 +31,7 @@ signal-hook = "0.3.9"
[[bin]] [[bin]]
name = "dd" name = "dd"
path = "src/main.rs" path = "src/main.rs"
[package.metadata.cargo-udeps.ignore]
# Necessary for "make all"
normal = ["uucore_procs"]

View file

@ -7,8 +7,6 @@
// spell-checker:ignore fname, tname, fpath, specfile, testfile, unspec, ifile, ofile, outfile, fullblock, urand, fileio, atoe, atoibm, behaviour, bmax, bremain, btotal, cflags, creat, ctable, ctty, datastructures, doesnt, etoa, fileout, fname, gnudd, iconvflags, nocache, noctty, noerror, nofollow, nolinks, nonblock, oconvflags, outfile, parseargs, rlen, rmax, rposition, rremain, rsofar, rstat, sigusr, sigval, wlen, wstat // spell-checker:ignore fname, tname, fpath, specfile, testfile, unspec, ifile, ofile, outfile, fullblock, urand, fileio, atoe, atoibm, behaviour, bmax, bremain, btotal, cflags, creat, ctable, ctty, datastructures, doesnt, etoa, fileout, fname, gnudd, iconvflags, nocache, noctty, noerror, nofollow, nolinks, nonblock, oconvflags, outfile, parseargs, rlen, rmax, rposition, rremain, rsofar, rstat, sigusr, sigval, wlen, wstat
#[macro_use]
extern crate uucore;
use uucore::InvalidEncodingHandling; use uucore::InvalidEncodingHandling;
#[cfg(test)] #[cfg(test)]
@ -275,13 +273,19 @@ impl<R: Read> Input<R> {
} }
} }
trait OutputTrait: Sized + Write {
fn new(matches: &Matches) -> Result<Self, Box<dyn Error>>;
fn fsync(&mut self) -> io::Result<()>;
fn fdatasync(&mut self) -> io::Result<()>;
}
struct Output<W: Write> { struct Output<W: Write> {
dst: W, dst: W,
obs: usize, obs: usize,
cflags: OConvFlags, cflags: OConvFlags,
} }
impl Output<io::Stdout> { impl OutputTrait for Output<io::Stdout> {
fn new(matches: &Matches) -> Result<Self, Box<dyn Error>> { fn new(matches: &Matches) -> Result<Self, Box<dyn Error>> {
let obs = parseargs::parse_obs(matches)?; let obs = parseargs::parse_obs(matches)?;
let cflags = parseargs::parse_conv_flag_output(matches)?; let cflags = parseargs::parse_conv_flag_output(matches)?;
@ -300,6 +304,100 @@ impl Output<io::Stdout> {
} }
} }
impl<W: Write> Output<W>
where
Self: OutputTrait,
{
fn write_blocks(&mut self, buf: Vec<u8>) -> io::Result<WriteStat> {
let mut writes_complete = 0;
let mut writes_partial = 0;
let mut bytes_total = 0;
for chunk in buf.chunks(self.obs) {
match self.write(chunk)? {
wlen if wlen < chunk.len() => {
writes_partial += 1;
bytes_total += wlen;
}
wlen => {
writes_complete += 1;
bytes_total += wlen;
}
}
}
Ok(WriteStat {
writes_complete,
writes_partial,
bytes_total: bytes_total.try_into().unwrap_or(0u128),
})
}
fn dd_out<R: Read>(mut self, mut i: Input<R>) -> Result<(), Box<dyn Error>> {
let mut rstat = ReadStat {
reads_complete: 0,
reads_partial: 0,
records_truncated: 0,
};
let mut wstat = WriteStat {
writes_complete: 0,
writes_partial: 0,
bytes_total: 0,
};
let start = time::Instant::now();
let bsize = calc_bsize(i.ibs, self.obs);
let prog_tx = {
let (tx, rx) = mpsc::channel();
thread::spawn(gen_prog_updater(rx, i.print_level));
tx
};
while below_count_limit(&i.count, &rstat, &wstat) {
// Read/Write
let loop_bsize = calc_loop_bsize(&i.count, &rstat, &wstat, i.ibs, bsize);
match read_helper(&mut i, loop_bsize)? {
(
ReadStat {
reads_complete: 0,
reads_partial: 0,
..
},
_,
) => break,
(rstat_update, buf) => {
let wstat_update = self.write_blocks(buf)?;
rstat += rstat_update;
wstat += wstat_update;
}
};
// Update Prog
prog_tx.send(ProgUpdate {
read_stat: rstat,
write_stat: wstat,
duration: start.elapsed(),
})?;
}
if self.cflags.fsync {
self.fsync()?;
} else if self.cflags.fdatasync {
self.fdatasync()?;
}
match i.print_level {
Some(StatusLevel::Noxfer) | Some(StatusLevel::None) => {}
_ => print_transfer_stats(&ProgUpdate {
read_stat: rstat,
write_stat: wstat,
duration: start.elapsed(),
}),
}
Ok(())
}
}
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
fn make_linux_oflags(oflags: &OFlags) -> Option<libc::c_int> { fn make_linux_oflags(oflags: &OFlags) -> Option<libc::c_int> {
let mut flag = 0; let mut flag = 0;
@ -340,7 +438,7 @@ fn make_linux_oflags(oflags: &OFlags) -> Option<libc::c_int> {
} }
} }
impl Output<File> { impl OutputTrait for Output<File> {
fn new(matches: &Matches) -> Result<Self, Box<dyn Error>> { fn new(matches: &Matches) -> Result<Self, Box<dyn Error>> {
fn open_dst(path: &Path, cflags: &OConvFlags, oflags: &OFlags) -> Result<File, io::Error> { fn open_dst(path: &Path, cflags: &OConvFlags, oflags: &OFlags) -> Result<File, io::Error> {
let mut opts = OpenOptions::new(); let mut opts = OpenOptions::new();
@ -430,62 +528,6 @@ impl Write for Output<io::Stdout> {
} }
} }
impl Output<io::Stdout> {
/// Write all data in the given buffer in writes of size obs.
fn write_blocks(&mut self, buf: Vec<u8>) -> io::Result<WriteStat> {
let mut writes_complete = 0;
let mut writes_partial = 0;
let mut bytes_total = 0;
for chunk in buf.chunks(self.obs) {
match self.write(chunk)? {
wlen if wlen < chunk.len() => {
writes_partial += 1;
bytes_total += wlen;
}
wlen => {
writes_complete += 1;
bytes_total += wlen;
}
}
}
Ok(WriteStat {
writes_complete,
writes_partial,
bytes_total: bytes_total.try_into().unwrap_or(0u128),
})
}
}
impl Output<File> {
/// Write all data in the given buffer in writes of size obs.
fn write_blocks(&mut self, buf: Vec<u8>) -> io::Result<WriteStat> {
let mut writes_complete = 0;
let mut writes_partial = 0;
let mut bytes_total = 0;
for chunk in buf.chunks(self.obs) {
match self.write(chunk)? {
wlen if wlen < chunk.len() => {
writes_partial += 1;
bytes_total += wlen;
}
wlen => {
writes_complete += 1;
bytes_total += wlen;
}
}
}
Ok(WriteStat {
writes_complete,
writes_partial,
bytes_total: bytes_total.try_into().unwrap_or(0u128),
})
}
}
/// Splits the content of buf into cbs-length blocks /// Splits the content of buf into cbs-length blocks
/// Appends padding as specified by conv=block and cbs=N /// Appends padding as specified by conv=block and cbs=N
/// Expects ascii encoded data /// Expects ascii encoded data
@ -827,140 +869,6 @@ fn below_count_limit(count: &Option<CountType>, rstat: &ReadStat, wstat: &WriteS
} }
} }
/// Perform the copy/convert operations. Stdout version
/// Note: The body of this function should be kept identical to dd_fileout. This is definitely a problem from a maintenance perspective
/// and should be addressed (TODO). The problem exists because some of dd's functionality depends on whether the output is a file or stdout.
fn dd_stdout<R: Read>(mut i: Input<R>, mut o: Output<io::Stdout>) -> Result<(), Box<dyn Error>> {
let mut rstat = ReadStat {
reads_complete: 0,
reads_partial: 0,
records_truncated: 0,
};
let mut wstat = WriteStat {
writes_complete: 0,
writes_partial: 0,
bytes_total: 0,
};
let start = time::Instant::now();
let bsize = calc_bsize(i.ibs, o.obs);
let prog_tx = {
let (tx, rx) = mpsc::channel();
thread::spawn(gen_prog_updater(rx, i.print_level));
tx
};
while below_count_limit(&i.count, &rstat, &wstat) {
// Read/Write
let loop_bsize = calc_loop_bsize(&i.count, &rstat, &wstat, i.ibs, bsize);
match read_helper(&mut i, loop_bsize)? {
(
ReadStat {
reads_complete: 0,
reads_partial: 0,
..
},
_,
) => break,
(rstat_update, buf) => {
let wstat_update = o.write_blocks(buf)?;
rstat += rstat_update;
wstat += wstat_update;
}
};
// Update Prog
prog_tx.send(ProgUpdate {
read_stat: rstat,
write_stat: wstat,
duration: start.elapsed(),
})?;
}
if o.cflags.fsync {
o.fsync()?;
} else if o.cflags.fdatasync {
o.fdatasync()?;
}
match i.print_level {
Some(StatusLevel::Noxfer) | Some(StatusLevel::None) => {}
_ => print_transfer_stats(&ProgUpdate {
read_stat: rstat,
write_stat: wstat,
duration: start.elapsed(),
}),
}
Ok(())
}
/// Perform the copy/convert operations. File backed output version
/// Note: The body of this function should be kept identical to dd_stdout. This is definitely a problem from a maintenance perspective
/// and should be addressed (TODO). The problem exists because some of dd's functionality depends on whether the output is a file or stdout.
fn dd_fileout<R: Read>(mut i: Input<R>, mut o: Output<File>) -> Result<(), Box<dyn Error>> {
let mut rstat = ReadStat {
reads_complete: 0,
reads_partial: 0,
records_truncated: 0,
};
let mut wstat = WriteStat {
writes_complete: 0,
writes_partial: 0,
bytes_total: 0,
};
let start = time::Instant::now();
let bsize = calc_bsize(i.ibs, o.obs);
let prog_tx = {
let (tx, rx) = mpsc::channel();
thread::spawn(gen_prog_updater(rx, i.print_level));
tx
};
while below_count_limit(&i.count, &rstat, &wstat) {
// Read/Write
let loop_bsize = calc_loop_bsize(&i.count, &rstat, &wstat, i.ibs, bsize);
match read_helper(&mut i, loop_bsize)? {
(
ReadStat {
reads_complete: 0,
reads_partial: 0,
..
},
_,
) => break,
(rstat_update, buf) => {
let wstat_update = o.write_blocks(buf)?;
rstat += rstat_update;
wstat += wstat_update;
}
};
// Update Prog
prog_tx.send(ProgUpdate {
read_stat: rstat,
write_stat: wstat,
duration: start.elapsed(),
})?;
}
if o.cflags.fsync {
o.fsync()?;
} else if o.cflags.fdatasync {
o.fdatasync()?;
}
match i.print_level {
Some(StatusLevel::Noxfer) | Some(StatusLevel::None) => {}
_ => print_transfer_stats(&ProgUpdate {
read_stat: rstat,
write_stat: wstat,
duration: start.elapsed(),
}),
}
Ok(())
}
fn append_dashes_if_not_present(mut acc: Vec<String>, mut s: String) -> Vec<String> { fn append_dashes_if_not_present(mut acc: Vec<String>, mut s: String) -> Vec<String> {
if !s.starts_with("--") && !s.starts_with('-') { if !s.starts_with("--") && !s.starts_with('-') {
s.insert_str(0, "--"); s.insert_str(0, "--");
@ -1009,7 +917,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
let (i, o) = let (i, o) =
unpack_or_rtn!(Input::<File>::new(&matches), Output::<File>::new(&matches)); unpack_or_rtn!(Input::<File>::new(&matches), Output::<File>::new(&matches));
dd_fileout(i, o) o.dd_out(i)
} }
(false, true) => { (false, true) => {
let (i, o) = unpack_or_rtn!( let (i, o) = unpack_or_rtn!(
@ -1017,7 +925,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
Output::<File>::new(&matches) Output::<File>::new(&matches)
); );
dd_fileout(i, o) o.dd_out(i)
} }
(true, false) => { (true, false) => {
let (i, o) = unpack_or_rtn!( let (i, o) = unpack_or_rtn!(
@ -1025,7 +933,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
Output::<io::Stdout>::new(&matches) Output::<io::Stdout>::new(&matches)
); );
dd_stdout(i, o) o.dd_out(i)
} }
(false, false) => { (false, false) => {
let (i, o) = unpack_or_rtn!( let (i, o) = unpack_or_rtn!(
@ -1033,7 +941,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
Output::<io::Stdout>::new(&matches) Output::<io::Stdout>::new(&matches)
); );
dd_stdout(i, o) o.dd_out(i)
} }
}; };
match result { match result {
@ -1046,7 +954,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
} }
pub fn uu_app() -> clap::App<'static, 'static> { pub fn uu_app() -> clap::App<'static, 'static> {
clap::App::new(executable!()) clap::App::new(uucore::util_name())
.version(crate_version!()) .version(crate_version!())
.about(ABOUT) .about(ABOUT)
.arg( .arg(

View file

@ -153,7 +153,7 @@ fn all_valid_ascii_ebcdic_ascii_roundtrip_conv_test() {
cflags: OConvFlags::default(), cflags: OConvFlags::default(),
}; };
dd_fileout(i, o).unwrap(); o.dd_out(i).unwrap();
// EBCDIC->ASCII // EBCDIC->ASCII
let test_name = "all-valid-ebcdic-to-ascii"; let test_name = "all-valid-ebcdic-to-ascii";
@ -175,7 +175,7 @@ fn all_valid_ascii_ebcdic_ascii_roundtrip_conv_test() {
cflags: OConvFlags::default(), cflags: OConvFlags::default(),
}; };
dd_fileout(i, o).unwrap(); o.dd_out(i).unwrap();
// Final Comparison // Final Comparison
let res = File::open(&tmp_fname_ea).unwrap(); let res = File::open(&tmp_fname_ea).unwrap();

View file

@ -67,7 +67,7 @@ macro_rules! make_spec_test (
#[test] #[test]
fn $test_id() fn $test_id()
{ {
dd_fileout($i,$o).unwrap(); $o.dd_out($i).unwrap();
let res = File::open($tmp_fname).unwrap(); let res = File::open($tmp_fname).unwrap();
// Check test file isn't empty (unless spec file is too) // Check test file isn't empty (unless spec file is too)

View file

@ -10,7 +10,7 @@ fn unimplemented_flags_should_error_non_linux() {
let mut succeeded = Vec::new(); let mut succeeded = Vec::new();
// The following flags are only implemented in linux // The following flags are only implemented in linux
for flag in vec![ for &flag in &[
"direct", "direct",
"directory", "directory",
"dsync", "dsync",
@ -27,13 +27,11 @@ fn unimplemented_flags_should_error_non_linux() {
]; ];
let matches = uu_app().get_matches_from_safe(args).unwrap(); let matches = uu_app().get_matches_from_safe(args).unwrap();
match parse_iflags(&matches) { if parse_iflags(&matches).is_ok() {
Ok(_) => succeeded.push(format!("iflag={}", flag)), succeeded.push(format!("iflag={}", flag));
Err(_) => { /* expected behaviour :-) */ }
} }
match parse_oflags(&matches) { if parse_oflags(&matches).is_ok() {
Ok(_) => succeeded.push(format!("oflag={}", flag)), succeeded.push(format!("oflag={}", flag));
Err(_) => { /* expected behaviour :-) */ }
} }
} }
@ -50,7 +48,7 @@ fn unimplemented_flags_should_error() {
let mut succeeded = Vec::new(); let mut succeeded = Vec::new();
// The following flags are not implemented // The following flags are not implemented
for flag in vec!["cio", "nocache", "nolinks", "text", "binary"] { for &flag in &["cio", "nocache", "nolinks", "text", "binary"] {
let args = vec![ let args = vec![
String::from("dd"), String::from("dd"),
format!("--iflag={}", flag), format!("--iflag={}", flag),
@ -58,13 +56,11 @@ fn unimplemented_flags_should_error() {
]; ];
let matches = uu_app().get_matches_from_safe(args).unwrap(); let matches = uu_app().get_matches_from_safe(args).unwrap();
match parse_iflags(&matches) { if parse_iflags(&matches).is_ok() {
Ok(_) => succeeded.push(format!("iflag={}", flag)), succeeded.push(format!("iflag={}", flag))
Err(_) => { /* expected behaviour :-) */ }
} }
match parse_oflags(&matches) { if parse_oflags(&matches).is_ok() {
Ok(_) => succeeded.push(format!("oflag={}", flag)), succeeded.push(format!("oflag={}", flag))
Err(_) => { /* expected behaviour :-) */ }
} }
} }
@ -356,7 +352,7 @@ fn parse_icf_token_ibm() {
assert_eq!(exp.len(), act.len()); assert_eq!(exp.len(), act.len());
for cf in &exp { for cf in &exp {
assert!(exp.contains(&cf)); assert!(exp.contains(cf));
} }
} }
@ -373,7 +369,7 @@ fn parse_icf_tokens_elu() {
assert_eq!(exp.len(), act.len()); assert_eq!(exp.len(), act.len());
for cf in &exp { for cf in &exp {
assert!(exp.contains(&cf)); assert!(exp.contains(cf));
} }
} }
@ -405,7 +401,7 @@ fn parse_icf_tokens_remaining() {
assert_eq!(exp.len(), act.len()); assert_eq!(exp.len(), act.len());
for cf in &exp { for cf in &exp {
assert!(exp.contains(&cf)); assert!(exp.contains(cf));
} }
} }
@ -429,7 +425,7 @@ fn parse_iflag_tokens() {
assert_eq!(exp.len(), act.len()); assert_eq!(exp.len(), act.len());
for cf in &exp { for cf in &exp {
assert!(exp.contains(&cf)); assert!(exp.contains(cf));
} }
} }
@ -453,7 +449,7 @@ fn parse_oflag_tokens() {
assert_eq!(exp.len(), act.len()); assert_eq!(exp.len(), act.len());
for cf in &exp { for cf in &exp {
assert!(exp.contains(&cf)); assert!(exp.contains(cf));
} }
} }
@ -481,7 +477,7 @@ fn parse_iflag_tokens_linux() {
assert_eq!(exp.len(), act.len()); assert_eq!(exp.len(), act.len());
for cf in &exp { for cf in &exp {
assert!(exp.contains(&cf)); assert!(exp.contains(cf));
} }
} }
@ -509,7 +505,7 @@ fn parse_oflag_tokens_linux() {
assert_eq!(exp.len(), act.len()); assert_eq!(exp.len(), act.len());
for cf in &exp { for cf in &exp {
assert!(exp.contains(&cf)); assert!(exp.contains(cf));
} }
} }

View file

@ -28,9 +28,6 @@ use std::fmt::Display;
#[cfg(unix)] #[cfg(unix)]
use std::mem; use std::mem;
#[cfg(target_os = "freebsd")]
use uucore::libc::{c_char, fsid_t, uid_t};
#[cfg(windows)] #[cfg(windows)]
use std::path::Path; use std::path::Path;
@ -79,8 +76,8 @@ struct Filesystem {
usage: FsUsage, usage: FsUsage,
} }
fn get_usage() -> String { fn usage() -> String {
format!("{0} [OPTION]... [FILE]...", executable!()) format!("{0} [OPTION]... [FILE]...", uucore::execution_phrase())
} }
impl FsSelector { impl FsSelector {
@ -284,7 +281,7 @@ impl UError for DfError {
#[uucore_procs::gen_uumain] #[uucore_procs::gen_uumain]
pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let usage = get_usage(); let usage = usage();
let matches = uu_app().usage(&usage[..]).get_matches_from(args); let matches = uu_app().usage(&usage[..]).get_matches_from(args);
let paths: Vec<String> = matches let paths: Vec<String> = matches
@ -295,7 +292,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
#[cfg(windows)] #[cfg(windows)]
{ {
if matches.is_present(OPT_INODES) { if matches.is_present(OPT_INODES) {
println!("{}: doesn't support -i option", executable!()); println!("{}: doesn't support -i option", uucore::util_name());
return Ok(()); return Ok(());
} }
} }
@ -427,7 +424,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
} }
pub fn uu_app() -> App<'static, 'static> { pub fn uu_app() -> App<'static, 'static> {
App::new(executable!()) App::new(uucore::util_name())
.version(crate_version!()) .version(crate_version!())
.about(ABOUT) .about(ABOUT)
.arg( .arg(

View file

@ -23,3 +23,7 @@ uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_p
[[bin]] [[bin]]
name = "dircolors" name = "dircolors"
path = "src/main.rs" path = "src/main.rs"
[package.metadata.cargo-udeps.ignore]
# Necessary for "make all"
normal = ["uucore_procs"]

View file

@ -17,6 +17,7 @@ use std::fs::File;
use std::io::{BufRead, BufReader}; use std::io::{BufRead, BufReader};
use clap::{crate_version, App, Arg}; use clap::{crate_version, App, Arg};
use uucore::display::Quotable;
mod options { mod options {
pub const BOURNE_SHELL: &str = "bourne-shell"; pub const BOURNE_SHELL: &str = "bourne-shell";
@ -62,8 +63,8 @@ pub fn guess_syntax() -> OutputFmt {
} }
} }
fn get_usage() -> String { fn usage() -> String {
format!("{0} {1}", executable!(), SYNTAX) format!("{0} {1}", uucore::execution_phrase(), SYNTAX)
} }
pub fn uumain(args: impl uucore::Args) -> i32 { pub fn uumain(args: impl uucore::Args) -> i32 {
@ -71,7 +72,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.collect_str(InvalidEncodingHandling::Ignore) .collect_str(InvalidEncodingHandling::Ignore)
.accept_any(); .accept_any();
let usage = get_usage(); let usage = usage();
let matches = uu_app().usage(&usage[..]).get_matches_from(&args); let matches = uu_app().usage(&usage[..]).get_matches_from(&args);
@ -94,9 +95,9 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
if matches.is_present(options::PRINT_DATABASE) { if matches.is_present(options::PRINT_DATABASE) {
if !files.is_empty() { if !files.is_empty() {
show_usage_error!( show_usage_error!(
"extra operand '{}'\nfile operands cannot be combined with \ "extra operand {}\nfile operands cannot be combined with \
--print-database (-p)", --print-database (-p)",
files[0] files[0].quote()
); );
return 1; return 1;
} }
@ -126,7 +127,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
result = parse(INTERNAL_DB.lines(), out_format, "") result = parse(INTERNAL_DB.lines(), out_format, "")
} else { } else {
if files.len() > 1 { if files.len() > 1 {
show_usage_error!("extra operand '{}'", files[1]); show_usage_error!("extra operand {}", files[1].quote());
return 1; return 1;
} }
match File::open(files[0]) { match File::open(files[0]) {
@ -135,7 +136,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
result = parse(fin.lines().filter_map(Result::ok), out_format, files[0]) result = parse(fin.lines().filter_map(Result::ok), out_format, files[0])
} }
Err(e) => { Err(e) => {
show_error!("{}: {}", files[0], e); show_error!("{}: {}", files[0].maybe_quote(), e);
return 1; return 1;
} }
} }
@ -153,7 +154,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
} }
pub fn uu_app() -> App<'static, 'static> { pub fn uu_app() -> App<'static, 'static> {
App::new(executable!()) App::new(uucore::util_name())
.version(crate_version!()) .version(crate_version!())
.about(SUMMARY) .about(SUMMARY)
.after_help(LONG_HELP) .after_help(LONG_HELP)
@ -314,7 +315,8 @@ where
if val.is_empty() { if val.is_empty() {
return Err(format!( return Err(format!(
"{}:{}: invalid line; missing second token", "{}:{}: invalid line; missing second token",
fp, num fp.maybe_quote(),
num
)); ));
} }
let lower = key.to_lowercase(); let lower = key.to_lowercase();
@ -341,7 +343,12 @@ where
} else if let Some(s) = table.get(lower.as_str()) { } else if let Some(s) = table.get(lower.as_str()) {
result.push_str(format!("{}={}:", s, val).as_str()); result.push_str(format!("{}={}:", s, val).as_str());
} else { } else {
return Err(format!("{}:{}: unrecognized keyword {}", fp, num, key)); return Err(format!(
"{}:{}: unrecognized keyword {}",
fp.maybe_quote(),
num,
key
));
} }
} }
} }

View file

@ -10,6 +10,7 @@ extern crate uucore;
use clap::{crate_version, App, Arg}; use clap::{crate_version, App, Arg};
use std::path::Path; use std::path::Path;
use uucore::display::print_verbatim;
use uucore::error::{UResult, UUsageError}; use uucore::error::{UResult, UUsageError};
use uucore::InvalidEncodingHandling; use uucore::InvalidEncodingHandling;
@ -20,8 +21,8 @@ mod options {
pub const DIR: &str = "dir"; pub const DIR: &str = "dir";
} }
fn get_usage() -> String { fn usage() -> String {
format!("{0} [OPTION] NAME...", executable!()) format!("{0} [OPTION] NAME...", uucore::execution_phrase())
} }
fn get_long_usage() -> String { fn get_long_usage() -> String {
@ -37,7 +38,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
.collect_str(InvalidEncodingHandling::ConvertLossy) .collect_str(InvalidEncodingHandling::ConvertLossy)
.accept_any(); .accept_any();
let usage = get_usage(); let usage = usage();
let after_help = get_long_usage(); let after_help = get_long_usage();
let matches = uu_app() let matches = uu_app()
@ -65,7 +66,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
if d.components().next() == None { if d.components().next() == None {
print!(".") print!(".")
} else { } else {
print!("{}", d.to_string_lossy()); print_verbatim(d).unwrap();
} }
} }
None => { None => {
@ -86,7 +87,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
} }
pub fn uu_app() -> App<'static, 'static> { pub fn uu_app() -> App<'static, 'static> {
App::new(executable!()) App::new(uucore::util_name())
.about(ABOUT) .about(ABOUT)
.version(crate_version!()) .version(crate_version!())
.arg( .arg(

View file

@ -32,6 +32,7 @@ use std::path::PathBuf;
use std::str::FromStr; use std::str::FromStr;
use std::time::{Duration, UNIX_EPOCH}; use std::time::{Duration, UNIX_EPOCH};
use std::{error::Error, fmt::Display}; use std::{error::Error, fmt::Display};
use uucore::display::{print_verbatim, Quotable};
use uucore::error::{UError, UResult}; use uucore::error::{UError, UResult};
use uucore::parse_size::{parse_size, ParseSizeError}; use uucore::parse_size::{parse_size, ParseSizeError};
use uucore::InvalidEncodingHandling; use uucore::InvalidEncodingHandling;
@ -70,7 +71,6 @@ mod options {
pub const FILE: &str = "FILE"; pub const FILE: &str = "FILE";
} }
const NAME: &str = "du";
const SUMMARY: &str = "estimate file space usage"; const SUMMARY: &str = "estimate file space usage";
const LONG_HELP: &str = " const LONG_HELP: &str = "
Display values are in units of the first available SIZE from --block-size, Display values are in units of the first available SIZE from --block-size,
@ -87,7 +87,7 @@ const UNITS: [(char, u32); 6] = [('E', 6), ('P', 5), ('T', 4), ('G', 3), ('M', 2
struct Options { struct Options {
all: bool, all: bool,
program_name: String, util_name: String,
max_depth: Option<usize>, max_depth: Option<usize>,
total: bool, total: bool,
separate_dirs: bool, separate_dirs: bool,
@ -294,9 +294,9 @@ fn du(
Err(e) => { Err(e) => {
safe_writeln!( safe_writeln!(
stderr(), stderr(),
"{}: cannot read directory '{}': {}", "{}: cannot read directory {}: {}",
options.program_name, options.util_name,
my_stat.path.display(), my_stat.path.quote(),
e e
); );
return Box::new(iter::once(my_stat)); return Box::new(iter::once(my_stat));
@ -335,11 +335,11 @@ fn du(
} }
Err(error) => match error.kind() { Err(error) => match error.kind() {
ErrorKind::PermissionDenied => { ErrorKind::PermissionDenied => {
let description = format!("cannot access '{}'", entry.path().display()); let description = format!("cannot access {}", entry.path().quote());
let error_message = "Permission denied"; let error_message = "Permission denied";
show_error_custom_description!(description, "{}", error_message) show_error_custom_description!(description, "{}", error_message)
} }
_ => show_error!("cannot access '{}': {}", entry.path().display(), error), _ => show_error!("cannot access {}: {}", entry.path().quote(), error),
}, },
}, },
Err(error) => show_error!("{}", error), Err(error) => show_error!("{}", error),
@ -393,11 +393,11 @@ fn convert_size_other(size: u64, _multiplier: u64, block_size: u64) -> String {
format!("{}", ((size as f64) / (block_size as f64)).ceil()) format!("{}", ((size as f64) / (block_size as f64)).ceil())
} }
fn get_usage() -> String { fn usage() -> String {
format!( format!(
"{0} [OPTION]... [FILE]... "{0} [OPTION]... [FILE]...
{0} [OPTION]... --files0-from=F", {0} [OPTION]... --files0-from=F",
executable!() uucore::execution_phrase()
) )
} }
@ -412,25 +412,30 @@ enum DuError {
impl Display for DuError { impl Display for DuError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self { match self {
DuError::InvalidMaxDepthArg(s) => write!(f, "invalid maximum depth '{}'", s), DuError::InvalidMaxDepthArg(s) => write!(f, "invalid maximum depth {}", s.quote()),
DuError::SummarizeDepthConflict(s) => { DuError::SummarizeDepthConflict(s) => {
write!(f, "summarizing conflicts with --max-depth={}", s) write!(
f,
"summarizing conflicts with --max-depth={}",
s.maybe_quote()
)
} }
DuError::InvalidTimeStyleArg(s) => write!( DuError::InvalidTimeStyleArg(s) => write!(
f, f,
"invalid argument '{}' for 'time style' "invalid argument {} for 'time style'
Valid arguments are: Valid arguments are:
- 'full-iso' - 'full-iso'
- 'long-iso' - 'long-iso'
- 'iso' - 'iso'
Try '{} --help' for more information.", Try '{} --help' for more information.",
s, NAME s.quote(),
uucore::execution_phrase()
), ),
DuError::InvalidTimeArg(s) => write!( DuError::InvalidTimeArg(s) => write!(
f, f,
"Invalid argument '{}' for --time. "Invalid argument {} for --time.
'birth' and 'creation' arguments are not supported on this platform.", 'birth' and 'creation' arguments are not supported on this platform.",
s s.quote()
), ),
} }
} }
@ -456,7 +461,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
.collect_str(InvalidEncodingHandling::Ignore) .collect_str(InvalidEncodingHandling::Ignore)
.accept_any(); .accept_any();
let usage = get_usage(); let usage = usage();
let matches = uu_app().usage(&usage[..]).get_matches_from(args); let matches = uu_app().usage(&usage[..]).get_matches_from(args);
@ -466,7 +471,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let options = Options { let options = Options {
all: matches.is_present(options::ALL), all: matches.is_present(options::ALL),
program_name: NAME.to_owned(), util_name: uucore::util_name().to_owned(),
max_depth, max_depth,
total: matches.is_present(options::TOTAL), total: matches.is_present(options::TOTAL),
separate_dirs: matches.is_present(options::SEPARATE_DIRS), separate_dirs: matches.is_present(options::SEPARATE_DIRS),
@ -566,21 +571,14 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
}; };
if !summarize || index == len - 1 { if !summarize || index == len - 1 {
let time_str = tm.format(time_format_str).to_string(); let time_str = tm.format(time_format_str).to_string();
print!( print!("{}\t{}\t", convert_size(size), time_str);
"{}\t{}\t{}{}", print_verbatim(stat.path).unwrap();
convert_size(size), print!("{}", line_separator);
time_str,
stat.path.display(),
line_separator
);
} }
} else if !summarize || index == len - 1 { } else if !summarize || index == len - 1 {
print!( print!("{}\t", convert_size(size));
"{}\t{}{}", print_verbatim(stat.path).unwrap();
convert_size(size), print!("{}", line_separator);
stat.path.display(),
line_separator
);
} }
if options.total && index == (len - 1) { if options.total && index == (len - 1) {
// The last element will be the total size of the the path under // The last element will be the total size of the the path under
@ -590,7 +588,11 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
} }
} }
Err(_) => { Err(_) => {
show_error!("{}: {}", path_string, "No such file or directory"); show_error!(
"{}: {}",
path_string.maybe_quote(),
"No such file or directory"
);
} }
} }
} }
@ -625,7 +627,7 @@ fn parse_depth(max_depth_str: Option<&str>, summarize: bool) -> UResult<Option<u
} }
pub fn uu_app() -> App<'static, 'static> { pub fn uu_app() -> App<'static, 'static> {
App::new(executable!()) App::new(uucore::util_name())
.version(crate_version!()) .version(crate_version!())
.about(SUMMARY) .about(SUMMARY)
.after_help(LONG_HELP) .after_help(LONG_HELP)
@ -837,8 +839,8 @@ fn format_error_message(error: ParseSizeError, s: &str, option: &str) -> String
// GNU's du echos affected flag, -B or --block-size (-t or --threshold), depending user's selection // GNU's du echos affected flag, -B or --block-size (-t or --threshold), depending user's selection
// GNU's du does distinguish between "invalid (suffix in) argument" // GNU's du does distinguish between "invalid (suffix in) argument"
match error { match error {
ParseSizeError::ParseFailure(_) => format!("invalid --{} argument '{}'", option, s), ParseSizeError::ParseFailure(_) => format!("invalid --{} argument {}", option, s.quote()),
ParseSizeError::SizeTooBig(_) => format!("--{} argument '{}' too large", option, s), ParseSizeError::SizeTooBig(_) => format!("--{} argument {} too large", option, s.quote()),
} }
} }

View file

@ -132,7 +132,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
} }
pub fn uu_app() -> App<'static, 'static> { pub fn uu_app() -> App<'static, 'static> {
App::new(executable!()) App::new(uucore::util_name())
.name(NAME) .name(NAME)
// TrailingVarArg specifies the final positional argument is a VarArg // TrailingVarArg specifies the final positional argument is a VarArg
// and it doesn't attempts the parse any further args. // and it doesn't attempts the parse any further args.

View file

@ -17,7 +17,7 @@ path = "src/env.rs"
[dependencies] [dependencies]
clap = { version = "2.33", features = ["wrap_help"] } clap = { version = "2.33", features = ["wrap_help"] }
libc = "0.2.42" libc = "0.2.42"
rust-ini = "0.13.0" rust-ini = "0.17.0"
uucore = { version=">=0.0.9", package="uucore", path="../../uucore" } uucore = { version=">=0.0.9", package="uucore", path="../../uucore" }
uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_procs" } uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_procs" }

43
src/uu/env/src/env.rs vendored
View file

@ -12,6 +12,9 @@
#[macro_use] #[macro_use]
extern crate clap; extern crate clap;
#[macro_use]
extern crate uucore;
use clap::{App, AppSettings, Arg}; use clap::{App, AppSettings, Arg};
use ini::Ini; use ini::Ini;
use std::borrow::Cow; use std::borrow::Cow;
@ -19,6 +22,8 @@ use std::env;
use std::io::{self, Write}; use std::io::{self, Write};
use std::iter::Iterator; use std::iter::Iterator;
use std::process::Command; use std::process::Command;
use uucore::display::Quotable;
use uucore::error::{UResult, USimpleError};
const USAGE: &str = "env [OPTION]... [-] [NAME=VALUE]... [COMMAND [ARG]...]"; const USAGE: &str = "env [OPTION]... [-] [NAME=VALUE]... [COMMAND [ARG]...]";
const AFTER_HELP: &str = "\ const AFTER_HELP: &str = "\
@ -61,8 +66,14 @@ fn parse_name_value_opt<'a>(opts: &mut Options<'a>, opt: &'a str) -> Result<bool
fn parse_program_opt<'a>(opts: &mut Options<'a>, opt: &'a str) -> Result<(), i32> { fn parse_program_opt<'a>(opts: &mut Options<'a>, opt: &'a str) -> Result<(), i32> {
if opts.null { if opts.null {
eprintln!("{}: cannot specify --null (-0) with command", crate_name!()); eprintln!(
eprintln!("Type \"{} --help\" for detailed information", crate_name!()); "{}: cannot specify --null (-0) with command",
uucore::util_name()
);
eprintln!(
"Type \"{} --help\" for detailed information",
uucore::execution_phrase()
);
Err(1) Err(1)
} else { } else {
opts.program.push(opt); opts.program.push(opt);
@ -70,7 +81,7 @@ fn parse_program_opt<'a>(opts: &mut Options<'a>, opt: &'a str) -> Result<(), i32
} }
} }
fn load_config_file(opts: &mut Options) -> Result<(), i32> { fn load_config_file(opts: &mut Options) -> UResult<()> {
// NOTE: config files are parsed using an INI parser b/c it's available and compatible with ".env"-style files // NOTE: config files are parsed using an INI parser b/c it's available and compatible with ".env"-style files
// ... * but support for actual INI files, although working, is not intended, nor claimed // ... * but support for actual INI files, although working, is not intended, nor claimed
for &file in &opts.files { for &file in &opts.files {
@ -83,13 +94,13 @@ fn load_config_file(opts: &mut Options) -> Result<(), i32> {
}; };
let conf = conf.map_err(|error| { let conf = conf.map_err(|error| {
eprintln!("env: error: \"{}\": {}", file, error); show_error!("{}: {}", file.maybe_quote(), error);
1 1
})?; })?;
for (_, prop) in &conf { for (_, prop) in &conf {
// ignore all INI section lines (treat them as comments) // ignore all INI section lines (treat them as comments)
for (key, value) in prop { for (key, value) in prop.iter() {
env::set_var(key, value); env::set_var(key, value);
} }
} }
@ -157,7 +168,7 @@ pub fn uu_app() -> App<'static, 'static> {
.help("remove variable from the environment")) .help("remove variable from the environment"))
} }
fn run_env(args: impl uucore::Args) -> Result<(), i32> { fn run_env(args: impl uucore::Args) -> UResult<()> {
let app = uu_app(); let app = uu_app();
let matches = app.get_matches_from(args); let matches = app.get_matches_from(args);
@ -188,8 +199,10 @@ fn run_env(args: impl uucore::Args) -> Result<(), i32> {
match env::set_current_dir(d) { match env::set_current_dir(d) {
Ok(()) => d, Ok(()) => d,
Err(error) => { Err(error) => {
eprintln!("env: cannot change directory to \"{}\": {}", d, error); return Err(USimpleError::new(
return Err(125); 125,
format!("cannot change directory to \"{}\": {}", d, error),
));
} }
}; };
} }
@ -253,9 +266,9 @@ fn run_env(args: impl uucore::Args) -> Result<(), i32> {
// FIXME: this should just use execvp() (no fork()) on Unix-like systems // FIXME: this should just use execvp() (no fork()) on Unix-like systems
match Command::new(&*prog).args(args).status() { match Command::new(&*prog).args(args).status() {
Ok(exit) if !exit.success() => return Err(exit.code().unwrap()), Ok(exit) if !exit.success() => return Err(exit.code().unwrap().into()),
Err(ref err) if err.kind() == io::ErrorKind::NotFound => return Err(127), Err(ref err) if err.kind() == io::ErrorKind::NotFound => return Err(127.into()),
Err(_) => return Err(126), Err(_) => return Err(126.into()),
Ok(_) => (), Ok(_) => (),
} }
} else { } else {
@ -266,9 +279,7 @@ fn run_env(args: impl uucore::Args) -> Result<(), i32> {
Ok(()) Ok(())
} }
pub fn uumain(args: impl uucore::Args) -> i32 { #[uucore_procs::gen_uumain]
match run_env(args) { pub fn uumain(args: impl uucore::Args) -> UResult<()> {
Ok(()) => 0, run_env(args)
Err(code) => code,
}
} }

View file

@ -23,3 +23,7 @@ uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_p
[[bin]] [[bin]]
name = "expand" name = "expand"
path = "src/main.rs" path = "src/main.rs"
[package.metadata.cargo-udeps.ignore]
# Necessary for "make all"
normal = ["uucore_procs"]

View file

@ -17,6 +17,7 @@ use std::fs::File;
use std::io::{stdin, stdout, BufRead, BufReader, BufWriter, Read, Write}; use std::io::{stdin, stdout, BufRead, BufReader, BufWriter, Read, Write};
use std::str::from_utf8; use std::str::from_utf8;
use unicode_width::UnicodeWidthChar; use unicode_width::UnicodeWidthChar;
use uucore::display::Quotable;
static ABOUT: &str = "Convert tabs in each FILE to spaces, writing to standard output. static ABOUT: &str = "Convert tabs in each FILE to spaces, writing to standard output.
With no FILE, or when FILE is -, read standard input."; With no FILE, or when FILE is -, read standard input.";
@ -32,8 +33,8 @@ static LONG_HELP: &str = "";
static DEFAULT_TABSTOP: usize = 8; static DEFAULT_TABSTOP: usize = 8;
fn get_usage() -> String { fn usage() -> String {
format!("{0} [OPTION]... [FILE]...", executable!()) format!("{0} [OPTION]... [FILE]...", uucore::execution_phrase())
} }
/// The mode to use when replacing tabs beyond the last one specified in /// The mode to use when replacing tabs beyond the last one specified in
@ -170,7 +171,7 @@ impl Options {
} }
pub fn uumain(args: impl uucore::Args) -> i32 { pub fn uumain(args: impl uucore::Args) -> i32 {
let usage = get_usage(); let usage = usage();
let matches = uu_app().usage(&usage[..]).get_matches_from(args); let matches = uu_app().usage(&usage[..]).get_matches_from(args);
expand(Options::new(&matches)); expand(Options::new(&matches));
@ -178,7 +179,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
} }
pub fn uu_app() -> App<'static, 'static> { pub fn uu_app() -> App<'static, 'static> {
App::new(executable!()) App::new(uucore::util_name())
.version(crate_version!()) .version(crate_version!())
.about(ABOUT) .about(ABOUT)
.after_help(LONG_HELP) .after_help(LONG_HELP)
@ -216,7 +217,7 @@ fn open(path: String) -> BufReader<Box<dyn Read + 'static>> {
} else { } else {
file_buf = match File::open(&path[..]) { file_buf = match File::open(&path[..]) {
Ok(a) => a, Ok(a) => a,
Err(e) => crash!(1, "{}: {}\n", &path[..], e), Err(e) => crash!(1, "{}: {}\n", path.maybe_quote(), e),
}; };
BufReader::new(Box::new(file_buf) as Box<dyn Read>) BufReader::new(Box::new(file_buf) as Box<dyn Read>)
} }
@ -329,12 +330,15 @@ fn expand(options: Options) {
// now dump out either spaces if we're expanding, or a literal tab if we're not // now dump out either spaces if we're expanding, or a literal tab if we're not
if init || !options.iflag { if init || !options.iflag {
if nts <= options.tspaces.len() { if nts <= options.tspaces.len() {
safe_unwrap!(output.write_all(options.tspaces[..nts].as_bytes())); crash_if_err!(
1,
output.write_all(options.tspaces[..nts].as_bytes())
);
} else { } else {
safe_unwrap!(output.write_all(" ".repeat(nts).as_bytes())); crash_if_err!(1, output.write_all(" ".repeat(nts).as_bytes()));
}; };
} else { } else {
safe_unwrap!(output.write_all(&buf[byte..byte + nbytes])); crash_if_err!(1, output.write_all(&buf[byte..byte + nbytes]));
} }
} }
_ => { _ => {
@ -352,14 +356,14 @@ fn expand(options: Options) {
init = false; init = false;
} }
safe_unwrap!(output.write_all(&buf[byte..byte + nbytes])); crash_if_err!(1, output.write_all(&buf[byte..byte + nbytes]));
} }
} }
byte += nbytes; // advance the pointer byte += nbytes; // advance the pointer
} }
safe_unwrap!(output.flush()); crash_if_err!(1, output.flush());
buf.truncate(0); // clear the buffer buf.truncate(0); // clear the buffer
} }
} }

View file

@ -26,3 +26,7 @@ uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_p
[[bin]] [[bin]]
name = "expr" name = "expr"
path = "src/main.rs" path = "src/main.rs"
[package.metadata.cargo-udeps.ignore]
# Necessary for "make all"
normal = ["uucore_procs"]

View file

@ -18,7 +18,7 @@ const VERSION: &str = "version";
const HELP: &str = "help"; const HELP: &str = "help";
pub fn uu_app() -> App<'static, 'static> { pub fn uu_app() -> App<'static, 'static> {
App::new(executable!()) App::new(uucore::util_name())
.arg(Arg::with_name(VERSION).long(VERSION)) .arg(Arg::with_name(VERSION).long(VERSION))
.arg(Arg::with_name(HELP).long(HELP)) .arg(Arg::with_name(HELP).long(HELP))
} }
@ -140,5 +140,5 @@ Environment variables:
} }
fn print_version() { fn print_version() {
println!("{} {}", executable!(), crate_version!()); println!("{} {}", uucore::util_name(), crate_version!());
} }

View file

@ -34,3 +34,7 @@ path = "src/main.rs"
[lib] [lib]
path = "src/cli.rs" path = "src/cli.rs"
[package.metadata.cargo-udeps.ignore]
# Necessary for "make all"
normal = ["uucore_procs"]

View file

@ -16,6 +16,7 @@ use std::io::{self, stdin, stdout, BufRead, Write};
mod factor; mod factor;
use clap::{crate_version, App, Arg}; use clap::{crate_version, App, Arg};
pub use factor::*; pub use factor::*;
use uucore::display::Quotable;
mod miller_rabin; mod miller_rabin;
pub mod numeric; pub mod numeric;
@ -52,7 +53,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
if let Some(values) = matches.values_of(options::NUMBER) { if let Some(values) = matches.values_of(options::NUMBER) {
for number in values { for number in values {
if let Err(e) = print_factors_str(number, &mut w, &mut factors_buffer) { if let Err(e) = print_factors_str(number, &mut w, &mut factors_buffer) {
show_warning!("{}: {}", number, e); show_warning!("{}: {}", number.maybe_quote(), e);
} }
} }
} else { } else {
@ -61,7 +62,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
for line in stdin.lock().lines() { for line in stdin.lock().lines() {
for number in line.unwrap().split_whitespace() { for number in line.unwrap().split_whitespace() {
if let Err(e) = print_factors_str(number, &mut w, &mut factors_buffer) { if let Err(e) = print_factors_str(number, &mut w, &mut factors_buffer) {
show_warning!("{}: {}", number, e); show_warning!("{}: {}", number.maybe_quote(), e);
} }
} }
} }
@ -75,7 +76,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
} }
pub fn uu_app() -> App<'static, 'static> { pub fn uu_app() -> App<'static, 'static> {
App::new(executable!()) App::new(uucore::util_name())
.version(crate_version!()) .version(crate_version!())
.about(SUMMARY) .about(SUMMARY)
.arg(Arg::with_name(options::NUMBER).multiple(true)) .arg(Arg::with_name(options::NUMBER).multiple(true))

View file

@ -86,7 +86,7 @@ mod tests {
let mut n_c: [u64; CHUNK_SIZE] = rng.gen(); let mut n_c: [u64; CHUNK_SIZE] = rng.gen();
let mut f_c: [Factors; CHUNK_SIZE] = rng.gen(); let mut f_c: [Factors; CHUNK_SIZE] = rng.gen();
let mut n_i = n_c.clone(); let mut n_i = n_c;
let mut f_i = f_c.clone(); let mut f_i = f_c.clone();
for (n, f) in n_i.iter_mut().zip(f_i.iter_mut()) { for (n, f) in n_i.iter_mut().zip(f_i.iter_mut()) {
factor(n, f); factor(n, f);

View file

@ -9,7 +9,7 @@
extern crate uucore; extern crate uucore;
use clap::App; use clap::App;
use uucore::{error::UResult, executable}; use uucore::error::UResult;
#[uucore_procs::gen_uumain] #[uucore_procs::gen_uumain]
pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uumain(args: impl uucore::Args) -> UResult<()> {
@ -18,5 +18,5 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
} }
pub fn uu_app() -> App<'static, 'static> { pub fn uu_app() -> App<'static, 'static> {
App::new(executable!()) App::new(uucore::util_name())
} }

View file

@ -24,3 +24,7 @@ uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_p
[[bin]] [[bin]]
name = "fmt" name = "fmt"
path = "src/main.rs" path = "src/main.rs"
[package.metadata.cargo-udeps.ignore]
# Necessary for "make all"
normal = ["uucore_procs"]

View file

@ -15,6 +15,7 @@ use std::cmp;
use std::fs::File; use std::fs::File;
use std::io::{stdin, stdout, Write}; use std::io::{stdin, stdout, Write};
use std::io::{BufReader, BufWriter, Read}; use std::io::{BufReader, BufWriter, Read};
use uucore::display::Quotable;
use self::linebreak::break_lines; use self::linebreak::break_lines;
use self::parasplit::ParagraphStream; use self::parasplit::ParagraphStream;
@ -50,8 +51,8 @@ static OPT_TAB_WIDTH: &str = "tab-width";
static ARG_FILES: &str = "files"; static ARG_FILES: &str = "files";
fn get_usage() -> String { fn usage() -> String {
format!("{} [OPTION]... [FILE]...", executable!()) format!("{} [OPTION]... [FILE]...", uucore::execution_phrase())
} }
pub type FileOrStdReader = BufReader<Box<dyn Read + 'static>>; pub type FileOrStdReader = BufReader<Box<dyn Read + 'static>>;
@ -75,7 +76,7 @@ pub struct FmtOptions {
#[allow(clippy::cognitive_complexity)] #[allow(clippy::cognitive_complexity)]
pub fn uumain(args: impl uucore::Args) -> i32 { pub fn uumain(args: impl uucore::Args) -> i32 {
let usage = get_usage(); let usage = usage();
let matches = uu_app().usage(&usage[..]).get_matches_from(args); let matches = uu_app().usage(&usage[..]).get_matches_from(args);
@ -132,7 +133,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
fmt_opts.width = match s.parse::<usize>() { fmt_opts.width = match s.parse::<usize>() {
Ok(t) => t, Ok(t) => t,
Err(e) => { Err(e) => {
crash!(1, "Invalid WIDTH specification: `{}': {}", s, e); crash!(1, "Invalid WIDTH specification: {}: {}", s.quote(), e);
} }
}; };
if fmt_opts.width > MAX_WIDTH { if fmt_opts.width > MAX_WIDTH {
@ -149,7 +150,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
fmt_opts.goal = match s.parse::<usize>() { fmt_opts.goal = match s.parse::<usize>() {
Ok(t) => t, Ok(t) => t,
Err(e) => { Err(e) => {
crash!(1, "Invalid GOAL specification: `{}': {}", s, e); crash!(1, "Invalid GOAL specification: {}: {}", s.quote(), e);
} }
}; };
if !matches.is_present(OPT_WIDTH) { if !matches.is_present(OPT_WIDTH) {
@ -163,7 +164,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
fmt_opts.tabwidth = match s.parse::<usize>() { fmt_opts.tabwidth = match s.parse::<usize>() {
Ok(t) => t, Ok(t) => t,
Err(e) => { Err(e) => {
crash!(1, "Invalid TABWIDTH specification: `{}': {}", s, e); crash!(1, "Invalid TABWIDTH specification: {}: {}", s.quote(), e);
} }
}; };
}; };
@ -187,7 +188,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
_ => match File::open(i) { _ => match File::open(i) {
Ok(f) => BufReader::new(Box::new(f) as Box<dyn Read + 'static>), Ok(f) => BufReader::new(Box::new(f) as Box<dyn Read + 'static>),
Err(e) => { Err(e) => {
show_warning!("{}: {}", i, e); show_warning!("{}: {}", i.maybe_quote(), e);
continue; continue;
} }
}, },
@ -211,7 +212,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
} }
pub fn uu_app() -> App<'static, 'static> { pub fn uu_app() -> App<'static, 'static> {
App::new(executable!()) App::new(uucore::util_name())
.version(crate_version!()) .version(crate_version!())
.about(ABOUT) .about(ABOUT)
.arg( .arg(

View file

@ -22,3 +22,7 @@ uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_p
[[bin]] [[bin]]
name = "fold" name = "fold"
path = "src/main.rs" path = "src/main.rs"
[package.metadata.cargo-udeps.ignore]
# Necessary for "make all"
normal = ["uucore_procs"]

View file

@ -64,7 +64,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
} }
pub fn uu_app() -> App<'static, 'static> { pub fn uu_app() -> App<'static, 'static> {
App::new(executable!()) App::new(uucore::util_name())
.name(NAME) .name(NAME)
.version(crate_version!()) .version(crate_version!())
.usage(SYNTAX) .usage(SYNTAX)
@ -119,7 +119,7 @@ fn fold(filenames: Vec<String>, bytes: bool, spaces: bool, width: usize) {
stdin_buf = stdin(); stdin_buf = stdin();
&mut stdin_buf as &mut dyn Read &mut stdin_buf as &mut dyn Read
} else { } else {
file_buf = safe_unwrap!(File::open(Path::new(filename))); file_buf = crash_if_err!(1, File::open(Path::new(filename)));
&mut file_buf as &mut dyn Read &mut file_buf as &mut dyn Read
}); });

View file

@ -17,7 +17,10 @@
#[macro_use] #[macro_use]
extern crate uucore; extern crate uucore;
use uucore::entries::{get_groups_gnu, gid2grp, Locate, Passwd}; use uucore::{
display::Quotable,
entries::{get_groups_gnu, gid2grp, Locate, Passwd},
};
use clap::{crate_version, App, Arg}; use clap::{crate_version, App, Arg};
@ -28,12 +31,12 @@ static ABOUT: &str = "Print group memberships for each USERNAME or, \
if no USERNAME is specified, for\nthe current process \ if no USERNAME is specified, for\nthe current process \
(which may differ if the groups database has changed)."; (which may differ if the groups database has changed).";
fn get_usage() -> String { fn usage() -> String {
format!("{0} [OPTION]... [USERNAME]...", executable!()) format!("{0} [OPTION]... [USERNAME]...", uucore::execution_phrase())
} }
pub fn uumain(args: impl uucore::Args) -> i32 { pub fn uumain(args: impl uucore::Args) -> i32 {
let usage = get_usage(); let usage = usage();
let matches = uu_app().usage(&usage[..]).get_matches_from(args); let matches = uu_app().usage(&usage[..]).get_matches_from(args);
@ -77,7 +80,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.join(" ") .join(" ")
); );
} else { } else {
show_error!("'{}': no such user", user); show_error!("{}: no such user", user.quote());
exit_code = 1; exit_code = 1;
} }
} }
@ -85,7 +88,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
} }
pub fn uu_app() -> App<'static, 'static> { pub fn uu_app() -> App<'static, 'static> {
App::new(executable!()) App::new(uucore::util_name())
.version(crate_version!()) .version(crate_version!())
.about(ABOUT) .about(ABOUT)
.arg( .arg(

View file

@ -19,6 +19,7 @@ digest = "0.6.1"
clap = { version = "2.33", features = ["wrap_help"] } clap = { version = "2.33", features = ["wrap_help"] }
hex = "0.2.0" hex = "0.2.0"
libc = "0.2.42" libc = "0.2.42"
memchr = "2"
md5 = "0.3.5" md5 = "0.3.5"
regex = "1.0.1" regex = "1.0.1"
regex-syntax = "0.6.7" regex-syntax = "0.6.7"
@ -32,3 +33,7 @@ uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_p
[[bin]] [[bin]]
name = "hashsum" name = "hashsum"
path = "src/main.rs" path = "src/main.rs"
[package.metadata.cargo-udeps.ignore]
# Necessary for "make all"
normal = ["uucore_procs"]

View file

@ -1,10 +1,22 @@
// spell-checker:ignore memmem
//! Implementations of digest functions, like md5 and sha1.
//!
//! The [`Digest`] trait represents the interface for providing inputs
//! to these digest functions and accessing the resulting hash. The
//! [`DigestWriter`] struct provides a wrapper around [`Digest`] that
//! implements the [`Write`] trait, for use in situations where calling
//! [`write`] would be useful.
extern crate digest; extern crate digest;
extern crate md5; extern crate md5;
extern crate sha1; extern crate sha1;
extern crate sha2; extern crate sha2;
extern crate sha3; extern crate sha3;
use std::io::Write;
use hex::ToHex; use hex::ToHex;
#[cfg(windows)]
use memchr::memmem;
use crate::digest::digest::{ExtendableOutput, Input, XofReader}; use crate::digest::digest::{ExtendableOutput, Input, XofReader};
@ -158,3 +170,76 @@ impl_digest_sha!(sha3::Sha3_384, 384);
impl_digest_sha!(sha3::Sha3_512, 512); impl_digest_sha!(sha3::Sha3_512, 512);
impl_digest_shake!(sha3::Shake128); impl_digest_shake!(sha3::Shake128);
impl_digest_shake!(sha3::Shake256); impl_digest_shake!(sha3::Shake256);
/// A struct that writes to a digest.
///
/// This struct wraps a [`Digest`] and provides a [`Write`]
/// implementation that passes input bytes directly to the
/// [`Digest::input`].
///
/// On Windows, if `binary` is `false`, then the [`write`]
/// implementation replaces instances of "\r\n" with "\n" before passing
/// the input bytes to the [`digest`].
pub struct DigestWriter<'a> {
digest: &'a mut Box<dyn Digest>,
/// Whether to write to the digest in binary mode or text mode on Windows.
///
/// If this is `false`, then instances of "\r\n" are replaced with
/// "\n" before passing input bytes to the [`digest`].
#[allow(dead_code)]
binary: bool,
// TODO This is dead code only on non-Windows operating systems. It
// might be better to use a `#[cfg(windows)]` guard here.
}
impl<'a> DigestWriter<'a> {
pub fn new(digest: &'a mut Box<dyn Digest>, binary: bool) -> DigestWriter {
DigestWriter { digest, binary }
}
}
impl<'a> Write for DigestWriter<'a> {
#[cfg(not(windows))]
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
self.digest.input(buf);
Ok(buf.len())
}
#[cfg(windows)]
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
if self.binary {
self.digest.input(buf);
return Ok(buf.len());
}
// In Windows text mode, replace each occurrence of "\r\n"
// with "\n".
//
// Find all occurrences of "\r\n", inputting the slice just
// before the "\n" in the previous instance of "\r\n" and
// the beginning of this "\r\n".
//
// FIXME This fails if one call to `write()` ends with the
// "\r" and the next call to `write()` begins with the "\n".
let n = buf.len();
let mut i_prev = 0;
for i in memmem::find_iter(buf, b"\r\n") {
self.digest.input(&buf[i_prev..i]);
i_prev = i + 1;
}
self.digest.input(&buf[i_prev..n]);
// Even though we dropped a "\r" for each "\r\n" we found, we
// still report the number of bytes written as `n`. This is
// because the meaning of the returned number is supposed to be
// the number of bytes consumed by the writer, so that if the
// calling code were calling `write()` in a loop, it would know
// where the next contiguous slice of the buffer starts.
Ok(n)
}
fn flush(&mut self) -> std::io::Result<()> {
Ok(())
}
}

View file

@ -18,6 +18,7 @@ extern crate uucore;
mod digest; mod digest;
use self::digest::Digest; use self::digest::Digest;
use self::digest::DigestWriter;
use clap::{App, Arg, ArgMatches}; use clap::{App, Arg, ArgMatches};
use hex::ToHex; use hex::ToHex;
@ -33,6 +34,7 @@ use std::io::{self, stdin, BufRead, BufReader, Read};
use std::iter; use std::iter;
use std::num::ParseIntError; use std::num::ParseIntError;
use std::path::Path; use std::path::Path;
use uucore::display::Quotable;
const NAME: &str = "hashsum"; const NAME: &str = "hashsum";
@ -342,7 +344,7 @@ pub fn uu_app_common() -> App<'static, 'static> {
const TEXT_HELP: &str = "read in text mode"; const TEXT_HELP: &str = "read in text mode";
#[cfg(not(windows))] #[cfg(not(windows))]
const TEXT_HELP: &str = "read in text mode (default)"; const TEXT_HELP: &str = "read in text mode (default)";
App::new(executable!()) App::new(uucore::util_name())
.version(crate_version!()) .version(crate_version!())
.about("Compute and check message digests.") .about("Compute and check message digests.")
.arg( .arg(
@ -468,25 +470,42 @@ where
stdin_buf = stdin(); stdin_buf = stdin();
Box::new(stdin_buf) as Box<dyn Read> Box::new(stdin_buf) as Box<dyn Read>
} else { } else {
file_buf = safe_unwrap!(File::open(filename)); file_buf = crash_if_err!(1, File::open(filename));
Box::new(file_buf) as Box<dyn Read> Box::new(file_buf) as Box<dyn Read>
}); });
if options.check { if options.check {
// Set up Regexes for line validation and parsing // Set up Regexes for line validation and parsing
//
// First, we compute the number of bytes we expect to be in
// the digest string. If the algorithm has a variable number
// of output bits, then we use the `+` modifier in the
// regular expression, otherwise we use the `{n}` modifier,
// where `n` is the number of bytes.
let bytes = options.digest.output_bits() / 4; let bytes = options.digest.output_bits() / 4;
let gnu_re = safe_unwrap!(Regex::new(&format!( let modifier = if bytes > 0 {
r"^(?P<digest>[a-fA-F0-9]{{{}}}) (?P<binary>[ \*])(?P<fileName>.*)", format!("{{{}}}", bytes)
bytes } else {
))); "+".to_string()
let bsd_re = safe_unwrap!(Regex::new(&format!( };
r"^{algorithm} \((?P<fileName>.*)\) = (?P<digest>[a-fA-F0-9]{{{digest_size}}})", let gnu_re = crash_if_err!(
1,
Regex::new(&format!(
r"^(?P<digest>[a-fA-F0-9]{}) (?P<binary>[ \*])(?P<fileName>.*)",
modifier,
))
);
let bsd_re = crash_if_err!(
1,
Regex::new(&format!(
r"^{algorithm} \((?P<fileName>.*)\) = (?P<digest>[a-fA-F0-9]{digest_size})",
algorithm = options.algoname, algorithm = options.algoname,
digest_size = bytes digest_size = modifier,
))); ))
);
let buffer = file; let buffer = file;
for (i, line) in buffer.lines().enumerate() { for (i, line) in buffer.lines().enumerate() {
let line = safe_unwrap!(line); let line = crash_if_err!(1, line);
let (ck_filename, sum, binary_check) = match gnu_re.captures(&line) { let (ck_filename, sum, binary_check) = match gnu_re.captures(&line) {
Some(caps) => ( Some(caps) => (
caps.name("fileName").unwrap().as_str(), caps.name("fileName").unwrap().as_str(),
@ -507,7 +526,7 @@ where
if options.warn { if options.warn {
show_warning!( show_warning!(
"{}: {}: improperly formatted {} checksum line", "{}: {}: improperly formatted {} checksum line",
filename.display(), filename.maybe_quote(),
i + 1, i + 1,
options.algoname options.algoname
); );
@ -516,15 +535,27 @@ where
} }
}, },
}; };
let f = safe_unwrap!(File::open(ck_filename)); let f = crash_if_err!(1, File::open(ck_filename));
let mut ckf = BufReader::new(Box::new(f) as Box<dyn Read>); let mut ckf = BufReader::new(Box::new(f) as Box<dyn Read>);
let real_sum = safe_unwrap!(digest_reader( let real_sum = crash_if_err!(
&mut *options.digest, 1,
digest_reader(
&mut options.digest,
&mut ckf, &mut ckf,
binary_check, binary_check,
options.output_bits options.output_bits
)) )
)
.to_ascii_lowercase(); .to_ascii_lowercase();
// FIXME: Filenames with newlines should be treated specially.
// GNU appears to replace newlines by \n and backslashes by
// \\ and prepend a backslash (to the hash or filename) if it did
// this escaping.
// Different sorts of output (checking vs outputting hashes) may
// handle this differently. Compare carefully to GNU.
// If you can, try to preserve invalid unicode using OsStr(ing)Ext
// and display it using uucore::display::print_verbatim(). This is
// easier (and more important) on Unix than on Windows.
if sum == real_sum { if sum == real_sum {
if !options.quiet { if !options.quiet {
println!("{}: OK", ck_filename); println!("{}: OK", ck_filename);
@ -537,12 +568,15 @@ where
} }
} }
} else { } else {
let sum = safe_unwrap!(digest_reader( let sum = crash_if_err!(
&mut *options.digest, 1,
digest_reader(
&mut options.digest,
&mut file, &mut file,
options.binary, options.binary,
options.output_bits options.output_bits
)); )
);
if options.tag { if options.tag {
println!("{} ({}) = {}", options.algoname, filename.display(), sum); println!("{} ({}) = {}", options.algoname, filename.display(), sum);
} else { } else {
@ -564,55 +598,21 @@ where
Ok(()) Ok(())
} }
fn digest_reader<'a, T: Read>( fn digest_reader<T: Read>(
digest: &mut (dyn Digest + 'a), digest: &mut Box<dyn Digest>,
reader: &mut BufReader<T>, reader: &mut BufReader<T>,
binary: bool, binary: bool,
output_bits: usize, output_bits: usize,
) -> io::Result<String> { ) -> io::Result<String> {
digest.reset(); digest.reset();
// Digest file, do not hold too much in memory at any given moment // Read bytes from `reader` and write those bytes to `digest`.
let windows = cfg!(windows); //
let mut buffer = Vec::with_capacity(524_288); // If `binary` is `false` and the operating system is Windows, then
let mut vec = Vec::with_capacity(524_288); // `DigestWriter` replaces "\r\n" with "\n" before it writes the
let mut looking_for_newline = false; // bytes into `digest`. Otherwise, it just inserts the bytes as-is.
loop { let mut digest_writer = DigestWriter::new(digest, binary);
match reader.read_to_end(&mut buffer) { std::io::copy(reader, &mut digest_writer)?;
Ok(0) => {
break;
}
Ok(nread) => {
if windows && !binary {
// Windows text mode returns '\n' when reading '\r\n'
for &b in buffer.iter().take(nread) {
if looking_for_newline {
if b != b'\n' {
vec.push(b'\r');
}
if b != b'\r' {
vec.push(b);
looking_for_newline = false;
}
} else if b != b'\r' {
vec.push(b);
} else {
looking_for_newline = true;
}
}
digest.input(&vec);
vec.clear();
} else {
digest.input(&buffer[..nread]);
}
}
Err(e) => return Err(e),
}
}
if windows && looking_for_newline {
vec.push(b'\r');
digest.input(&vec);
}
if digest.output_bits() > 0 { if digest.output_bits() > 0 {
Ok(digest.result_str()) Ok(digest.result_str())

View file

@ -22,3 +22,7 @@ uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_p
[[bin]] [[bin]]
name = "head" name = "head"
path = "src/main.rs" path = "src/main.rs"
[package.metadata.cargo-udeps.ignore]
# Necessary for "make all"
normal = ["uucore_procs"]

View file

@ -9,7 +9,8 @@ use clap::{crate_version, App, Arg};
use std::convert::TryFrom; use std::convert::TryFrom;
use std::ffi::OsString; use std::ffi::OsString;
use std::io::{self, ErrorKind, Read, Seek, SeekFrom, Write}; use std::io::{self, ErrorKind, Read, Seek, SeekFrom, Write};
use uucore::{crash, executable, show_error, show_error_custom_description}; use uucore::display::Quotable;
use uucore::{crash, show_error_custom_description};
const EXIT_FAILURE: i32 = 1; const EXIT_FAILURE: i32 = 1;
const EXIT_SUCCESS: i32 = 0; const EXIT_SUCCESS: i32 = 0;
@ -41,7 +42,7 @@ use lines::zlines;
use take::take_all_but; use take::take_all_but;
pub fn uu_app() -> App<'static, 'static> { pub fn uu_app() -> App<'static, 'static> {
App::new(executable!()) App::new(uucore::util_name())
.version(crate_version!()) .version(crate_version!())
.about(ABOUT) .about(ABOUT)
.usage(USAGE) .usage(USAGE)
@ -127,10 +128,10 @@ fn arg_iterate<'a>(
match parse::parse_obsolete(s) { match parse::parse_obsolete(s) {
Some(Ok(iter)) => Ok(Box::new(vec![first].into_iter().chain(iter).chain(args))), Some(Ok(iter)) => Ok(Box::new(vec![first].into_iter().chain(iter).chain(args))),
Some(Err(e)) => match e { Some(Err(e)) => match e {
parse::ParseError::Syntax => Err(format!("bad argument format: '{}'", s)), parse::ParseError::Syntax => Err(format!("bad argument format: {}", s.quote())),
parse::ParseError::Overflow => Err(format!( parse::ParseError::Overflow => Err(format!(
"invalid argument: '{}' Value too large for defined datatype", "invalid argument: {} Value too large for defined datatype",
s s.quote()
)), )),
}, },
None => Ok(Box::new(vec![first, second].into_iter().chain(args))), None => Ok(Box::new(vec![first, second].into_iter().chain(args))),
@ -418,7 +419,7 @@ fn uu_head(options: &HeadOptions) -> Result<(), u32> {
let mut file = match std::fs::File::open(name) { let mut file = match std::fs::File::open(name) {
Ok(f) => f, Ok(f) => f,
Err(err) => { Err(err) => {
let prefix = format!("cannot open '{}' for reading", name); let prefix = format!("cannot open {} for reading", name.quote());
match err.kind() { match err.kind() {
ErrorKind::NotFound => { ErrorKind::NotFound => {
show_error_custom_description!(prefix, "No such file or directory"); show_error_custom_description!(prefix, "No such file or directory");
@ -483,7 +484,7 @@ mod tests {
fn options(args: &str) -> Result<HeadOptions, String> { fn options(args: &str) -> Result<HeadOptions, String> {
let combined = "head ".to_owned() + args; let combined = "head ".to_owned() + args;
let args = combined.split_whitespace(); let args = combined.split_whitespace();
HeadOptions::get_from(args.map(|s| OsString::from(s))) HeadOptions::get_from(args.map(OsString::from))
} }
#[test] #[test]
fn test_args_modes() { fn test_args_modes() {
@ -522,6 +523,7 @@ mod tests {
assert!(options("-c IsThisJustFantasy").is_err()); assert!(options("-c IsThisJustFantasy").is_err());
} }
#[test] #[test]
#[allow(clippy::bool_comparison)]
fn test_options_correct_defaults() { fn test_options_correct_defaults() {
let opts = HeadOptions::new(); let opts = HeadOptions::new();
let opts2: HeadOptions = Default::default(); let opts2: HeadOptions = Default::default();
@ -552,7 +554,7 @@ mod tests {
assert!(parse_mode("1T", Modes::Bytes).is_err()); assert!(parse_mode("1T", Modes::Bytes).is_err());
} }
fn arg_outputs(src: &str) -> Result<String, String> { fn arg_outputs(src: &str) -> Result<String, String> {
let split = src.split_whitespace().map(|x| OsString::from(x)); let split = src.split_whitespace().map(OsString::from);
match arg_iterate(split) { match arg_iterate(split) {
Ok(args) => { Ok(args) => {
let vec = args let vec = args

View file

@ -29,7 +29,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
} }
pub fn uu_app() -> App<'static, 'static> { pub fn uu_app() -> App<'static, 'static> {
App::new(executable!()) App::new(uucore::util_name())
.version(crate_version!()) .version(crate_version!())
.usage(SYNTAX) .usage(SYNTAX)
} }

View file

@ -10,18 +10,13 @@
#[macro_use] #[macro_use]
extern crate uucore; extern crate uucore;
use clap::{crate_version, App, Arg, ArgMatches};
use std::collections::hash_set::HashSet; use std::collections::hash_set::HashSet;
use std::net::ToSocketAddrs; use std::net::ToSocketAddrs;
use std::str; use std::str;
#[cfg(windows)]
use uucore::error::UUsageError;
use uucore::error::{UResult, USimpleError};
#[cfg(windows)] use clap::{crate_version, App, Arg, ArgMatches};
use winapi::shared::minwindef::MAKEWORD;
#[cfg(windows)] use uucore::error::{FromIo, UResult};
use winapi::um::winsock2::{WSACleanup, WSAStartup};
static ABOUT: &str = "Display or set the system's host name."; static ABOUT: &str = "Display or set the system's host name.";
@ -31,93 +26,110 @@ static OPT_FQDN: &str = "fqdn";
static OPT_SHORT: &str = "short"; static OPT_SHORT: &str = "short";
static OPT_HOST: &str = "host"; static OPT_HOST: &str = "host";
#[cfg(windows)]
mod wsa {
use std::io;
use winapi::shared::minwindef::MAKEWORD;
use winapi::um::winsock2::{WSACleanup, WSAStartup, WSADATA};
pub(super) struct WsaHandle(());
pub(super) fn start() -> io::Result<WsaHandle> {
let err = unsafe {
let mut data = std::mem::MaybeUninit::<WSADATA>::uninit();
WSAStartup(MAKEWORD(2, 2), data.as_mut_ptr())
};
if err != 0 {
Err(io::Error::from_raw_os_error(err))
} else {
Ok(WsaHandle(()))
}
}
impl Drop for WsaHandle {
fn drop(&mut self) {
unsafe {
// This possibly returns an error but we can't handle it
let _err = WSACleanup();
}
}
}
}
fn usage() -> String {
format!("{0} [OPTION]... [HOSTNAME]", uucore::execution_phrase())
}
#[uucore_procs::gen_uumain] #[uucore_procs::gen_uumain]
pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uumain(args: impl uucore::Args) -> UResult<()> {
#![allow(clippy::let_and_return)] let usage = usage();
#[cfg(windows)]
unsafe {
#[allow(deprecated)]
let mut data = std::mem::uninitialized();
if WSAStartup(MAKEWORD(2, 2), &mut data as *mut _) != 0 {
return Err(UUsageError::new(
1,
"Failed to start Winsock 2.2".to_string(),
));
}
}
let result = execute(args);
#[cfg(windows)]
unsafe {
WSACleanup();
}
result
}
fn get_usage() -> String {
format!("{0} [OPTION]... [HOSTNAME]", executable!())
}
fn execute(args: impl uucore::Args) -> UResult<()> {
let usage = get_usage();
let matches = uu_app().usage(&usage[..]).get_matches_from(args); let matches = uu_app().usage(&usage[..]).get_matches_from(args);
match matches.value_of(OPT_HOST) { #[cfg(windows)]
let _handle = wsa::start().map_err_context(|| "failed to start Winsock".to_owned())?;
match matches.value_of_os(OPT_HOST) {
None => display_hostname(&matches), None => display_hostname(&matches),
Some(host) => { Some(host) => hostname::set(host).map_err_context(|| "failed to set hostname".to_owned()),
if let Err(err) = hostname::set(host) {
return Err(USimpleError::new(1, format!("{}", err)));
} else {
Ok(())
}
}
} }
} }
pub fn uu_app() -> App<'static, 'static> { pub fn uu_app() -> App<'static, 'static> {
App::new(executable!()) App::new(uucore::util_name())
.version(crate_version!()) .version(crate_version!())
.about(ABOUT) .about(ABOUT)
.arg( .arg(
Arg::with_name(OPT_DOMAIN) Arg::with_name(OPT_DOMAIN)
.short("d") .short("d")
.long("domain") .long("domain")
.overrides_with_all(&[OPT_DOMAIN, OPT_IP_ADDRESS, OPT_FQDN, OPT_SHORT])
.help("Display the name of the DNS domain if possible"), .help("Display the name of the DNS domain if possible"),
) )
.arg( .arg(
Arg::with_name(OPT_IP_ADDRESS) Arg::with_name(OPT_IP_ADDRESS)
.short("i") .short("i")
.long("ip-address") .long("ip-address")
.overrides_with_all(&[OPT_DOMAIN, OPT_IP_ADDRESS, OPT_FQDN, OPT_SHORT])
.help("Display the network address(es) of the host"), .help("Display the network address(es) of the host"),
) )
// TODO: support --long
.arg( .arg(
Arg::with_name(OPT_FQDN) Arg::with_name(OPT_FQDN)
.short("f") .short("f")
.long("fqdn") .long("fqdn")
.overrides_with_all(&[OPT_DOMAIN, OPT_IP_ADDRESS, OPT_FQDN, OPT_SHORT])
.help("Display the FQDN (Fully Qualified Domain Name) (default)"), .help("Display the FQDN (Fully Qualified Domain Name) (default)"),
) )
.arg(Arg::with_name(OPT_SHORT).short("s").long("short").help( .arg(
"Display the short hostname (the portion before the first dot) if \ Arg::with_name(OPT_SHORT)
possible", .short("s")
)) .long("short")
.overrides_with_all(&[OPT_DOMAIN, OPT_IP_ADDRESS, OPT_FQDN, OPT_SHORT])
.help("Display the short hostname (the portion before the first dot) if possible"),
)
.arg(Arg::with_name(OPT_HOST)) .arg(Arg::with_name(OPT_HOST))
} }
fn display_hostname(matches: &ArgMatches) -> UResult<()> { fn display_hostname(matches: &ArgMatches) -> UResult<()> {
let hostname = hostname::get().unwrap().into_string().unwrap(); let hostname = hostname::get()
.map_err_context(|| "failed to get hostname".to_owned())?
.to_string_lossy()
.into_owned();
if matches.is_present(OPT_IP_ADDRESS) { if matches.is_present(OPT_IP_ADDRESS) {
// XXX: to_socket_addrs needs hostname:port so append a dummy port and remove it later. // XXX: to_socket_addrs needs hostname:port so append a dummy port and remove it later.
// This was originally supposed to use std::net::lookup_host, but that seems to be // This was originally supposed to use std::net::lookup_host, but that seems to be
// deprecated. Perhaps we should use the dns-lookup crate? // deprecated. Perhaps we should use the dns-lookup crate?
let hostname = hostname + ":1"; let hostname = hostname + ":1";
match hostname.to_socket_addrs() { let addresses = hostname
Ok(addresses) => { .to_socket_addrs()
.map_err_context(|| "failed to resolve socket addresses".to_owned())?;
let mut hashset = HashSet::new(); let mut hashset = HashSet::new();
let mut output = String::new(); let mut output = String::new();
for addr in addresses { for addr in addresses {
// XXX: not sure why this is necessary... // XXX: not sure why this is necessary...
if !hashset.contains(&addr) { if !hashset.contains(&addr) {
let mut ip = format!("{}", addr); let mut ip = addr.to_string();
if ip.ends_with(":1") { if ip.ends_with(":1") {
let len = ip.len(); let len = ip.len();
ip.truncate(len - 2); ip.truncate(len - 2);
@ -133,11 +145,6 @@ fn display_hostname(matches: &ArgMatches) -> UResult<()> {
} }
Ok(()) Ok(())
}
Err(f) => {
return Err(USimpleError::new(1, format!("{}", f)));
}
}
} else { } else {
if matches.is_present(OPT_SHORT) || matches.is_present(OPT_DOMAIN) { if matches.is_present(OPT_SHORT) || matches.is_present(OPT_DOMAIN) {
let mut it = hostname.char_indices().filter(|&ci| ci.1 == '.'); let mut it = hostname.char_indices().filter(|&ci| ci.1 == '.');

View file

@ -41,6 +41,7 @@ extern crate uucore;
use clap::{crate_version, App, Arg}; use clap::{crate_version, App, Arg};
use std::ffi::CStr; use std::ffi::CStr;
use uucore::display::Quotable;
use uucore::entries::{self, Group, Locate, Passwd}; use uucore::entries::{self, Group, Locate, Passwd};
use uucore::error::UResult; use uucore::error::UResult;
use uucore::error::{set_exit_code, USimpleError}; use uucore::error::{set_exit_code, USimpleError};
@ -76,8 +77,8 @@ mod options {
pub const ARG_USERS: &str = "USER"; pub const ARG_USERS: &str = "USER";
} }
fn get_usage() -> String { fn usage() -> String {
format!("{0} [OPTION]... [USER]...", executable!()) format!("{0} [OPTION]... [USER]...", uucore::execution_phrase())
} }
fn get_description() -> String { fn get_description() -> String {
@ -127,7 +128,7 @@ struct State {
#[uucore_procs::gen_uumain] #[uucore_procs::gen_uumain]
pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let usage = get_usage(); let usage = usage();
let after_help = get_description(); let after_help = get_description();
let matches = uu_app() let matches = uu_app()
@ -230,7 +231,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
match Passwd::locate(users[i].as_str()) { match Passwd::locate(users[i].as_str()) {
Ok(p) => Some(p), Ok(p) => Some(p),
Err(_) => { Err(_) => {
show_error!("'{}': no such user", users[i]); show_error!("{}: no such user", users[i].quote());
set_exit_code(1); set_exit_code(1);
if i + 1 >= users.len() { if i + 1 >= users.len() {
break; break;
@ -347,7 +348,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
} }
pub fn uu_app() -> App<'static, 'static> { pub fn uu_app() -> App<'static, 'static> {
App::new(executable!()) App::new(uucore::util_name())
.version(crate_version!()) .version(crate_version!())
.about(ABOUT) .about(ABOUT)
.arg( .arg(

View file

@ -16,9 +16,11 @@ use clap::{crate_version, App, Arg, ArgMatches};
use file_diff::diff; use file_diff::diff;
use filetime::{set_file_times, FileTime}; use filetime::{set_file_times, FileTime};
use uucore::backup_control::{self, BackupMode}; use uucore::backup_control::{self, BackupMode};
use uucore::display::Quotable;
use uucore::entries::{grp2gid, usr2uid}; use uucore::entries::{grp2gid, usr2uid};
use uucore::error::{FromIo, UError, UIoError, UResult, USimpleError}; use uucore::error::{FromIo, UError, UIoError, UResult};
use uucore::perms::{wrap_chgrp, wrap_chown, Verbosity}; use uucore::mode::get_umask;
use uucore::perms::{wrap_chown, Verbosity, VerbosityLevel};
use libc::{getegid, geteuid}; use libc::{getegid, geteuid};
use std::error::Error; use std::error::Error;
@ -86,46 +88,38 @@ impl Display for InstallError {
use InstallError as IE; use InstallError as IE;
match self { match self {
IE::Unimplemented(opt) => write!(f, "Unimplemented feature: {}", opt), IE::Unimplemented(opt) => write!(f, "Unimplemented feature: {}", opt),
IE::DirNeedsArg() => write!( IE::DirNeedsArg() => {
write!(
f, f,
"{} with -d requires at least one argument.", "{} with -d requires at least one argument.",
executable!() uucore::util_name()
), )
IE::CreateDirFailed(dir, e) => {
Display::fmt(&uio_error!(e, "failed to create {}", dir.display()), f)
} }
IE::ChmodFailed(file) => write!(f, "failed to chmod {}", file.display()), IE::CreateDirFailed(dir, e) => {
Display::fmt(&uio_error!(e, "failed to create {}", dir.quote()), f)
}
IE::ChmodFailed(file) => write!(f, "failed to chmod {}", file.quote()),
IE::InvalidTarget(target) => write!( IE::InvalidTarget(target) => write!(
f, f,
"invalid target {}: No such file or directory", "invalid target {}: No such file or directory",
target.display() target.quote()
), ),
IE::TargetDirIsntDir(target) => { IE::TargetDirIsntDir(target) => {
write!(f, "target '{}' is not a directory", target.display()) write!(f, "target {} is not a directory", target.quote())
} }
IE::BackupFailed(from, to, e) => Display::fmt( IE::BackupFailed(from, to, e) => Display::fmt(
&uio_error!( &uio_error!(e, "cannot backup {} to {}", from.quote(), to.quote()),
e,
"cannot backup '{}' to '{}'",
from.display(),
to.display()
),
f, f,
), ),
IE::InstallFailed(from, to, e) => Display::fmt( IE::InstallFailed(from, to, e) => Display::fmt(
&uio_error!( &uio_error!(e, "cannot install {} to {}", from.quote(), to.quote()),
e,
"cannot install '{}' to '{}'",
from.display(),
to.display()
),
f, f,
), ),
IE::StripProgramFailed(msg) => write!(f, "strip program failed: {}", msg), IE::StripProgramFailed(msg) => write!(f, "strip program failed: {}", msg),
IE::MetadataFailed(e) => Display::fmt(&uio_error!(e, ""), f), IE::MetadataFailed(e) => Display::fmt(&uio_error!(e, ""), f),
IE::NoSuchUser(user) => write!(f, "no such user: {}", user), IE::NoSuchUser(user) => write!(f, "no such user: {}", user.maybe_quote()),
IE::NoSuchGroup(group) => write!(f, "no such group: {}", group), IE::NoSuchGroup(group) => write!(f, "no such group: {}", group.maybe_quote()),
IE::OmittingDirectory(dir) => write!(f, "omitting directory '{}'", dir.display()), IE::OmittingDirectory(dir) => write!(f, "omitting directory {}", dir.quote()),
} }
} }
} }
@ -152,8 +146,6 @@ static ABOUT: &str = "Copy SOURCE to DEST or multiple SOURCE(s) to the existing
DIRECTORY, while setting permission modes and owner/group"; DIRECTORY, while setting permission modes and owner/group";
static OPT_COMPARE: &str = "compare"; static OPT_COMPARE: &str = "compare";
static OPT_BACKUP: &str = "backup";
static OPT_BACKUP_NO_ARG: &str = "backup2";
static OPT_DIRECTORY: &str = "directory"; static OPT_DIRECTORY: &str = "directory";
static OPT_IGNORED: &str = "ignored"; static OPT_IGNORED: &str = "ignored";
static OPT_CREATE_LEADING: &str = "create-leading"; static OPT_CREATE_LEADING: &str = "create-leading";
@ -163,7 +155,6 @@ static OPT_OWNER: &str = "owner";
static OPT_PRESERVE_TIMESTAMPS: &str = "preserve-timestamps"; static OPT_PRESERVE_TIMESTAMPS: &str = "preserve-timestamps";
static OPT_STRIP: &str = "strip"; static OPT_STRIP: &str = "strip";
static OPT_STRIP_PROGRAM: &str = "strip-program"; static OPT_STRIP_PROGRAM: &str = "strip-program";
static OPT_SUFFIX: &str = "suffix";
static OPT_TARGET_DIRECTORY: &str = "target-directory"; static OPT_TARGET_DIRECTORY: &str = "target-directory";
static OPT_NO_TARGET_DIRECTORY: &str = "no-target-directory"; static OPT_NO_TARGET_DIRECTORY: &str = "no-target-directory";
static OPT_VERBOSE: &str = "verbose"; static OPT_VERBOSE: &str = "verbose";
@ -172,8 +163,8 @@ static OPT_CONTEXT: &str = "context";
static ARG_FILES: &str = "files"; static ARG_FILES: &str = "files";
fn get_usage() -> String { fn usage() -> String {
format!("{0} [OPTION]... [FILE]...", executable!()) format!("{0} [OPTION]... [FILE]...", uucore::execution_phrase())
} }
/// Main install utility function, called from main.rs. /// Main install utility function, called from main.rs.
@ -182,7 +173,7 @@ fn get_usage() -> String {
/// ///
#[uucore_procs::gen_uumain] #[uucore_procs::gen_uumain]
pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let usage = get_usage(); let usage = usage();
let matches = uu_app().usage(&usage[..]).get_matches_from(args); let matches = uu_app().usage(&usage[..]).get_matches_from(args);
@ -202,23 +193,14 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
} }
pub fn uu_app() -> App<'static, 'static> { pub fn uu_app() -> App<'static, 'static> {
App::new(executable!()) App::new(uucore::util_name())
.version(crate_version!()) .version(crate_version!())
.about(ABOUT) .about(ABOUT)
.arg( .arg(
Arg::with_name(OPT_BACKUP) backup_control::arguments::backup()
.long(OPT_BACKUP)
.help("make a backup of each existing destination file")
.takes_value(true)
.require_equals(true)
.min_values(0)
.value_name("CONTROL")
) )
.arg( .arg(
// TODO implement flag backup_control::arguments::backup_no_args()
Arg::with_name(OPT_BACKUP_NO_ARG)
.short("b")
.help("like --backup but does not accept an argument")
) )
.arg( .arg(
Arg::with_name(OPT_IGNORED) Arg::with_name(OPT_IGNORED)
@ -287,14 +269,7 @@ pub fn uu_app() -> App<'static, 'static> {
.value_name("PROGRAM") .value_name("PROGRAM")
) )
.arg( .arg(
// TODO implement flag backup_control::arguments::suffix()
Arg::with_name(OPT_SUFFIX)
.short("S")
.long(OPT_SUFFIX)
.help("override the usual backup suffix")
.value_name("SUFFIX")
.takes_value(true)
.min_values(1)
) )
.arg( .arg(
// TODO implement flag // TODO implement flag
@ -376,7 +351,7 @@ fn behavior(matches: &ArgMatches) -> UResult<Behavior> {
let specified_mode: Option<u32> = if matches.is_present(OPT_MODE) { let specified_mode: Option<u32> = if matches.is_present(OPT_MODE) {
let x = matches.value_of(OPT_MODE).ok_or(1)?; let x = matches.value_of(OPT_MODE).ok_or(1)?;
Some(mode::parse(x, considering_dir).map_err(|err| { Some(mode::parse(x, considering_dir, get_umask()).map_err(|err| {
show_error!("Invalid mode string: {}", err); show_error!("Invalid mode string: {}", err);
1 1
})?) })?)
@ -384,23 +359,14 @@ fn behavior(matches: &ArgMatches) -> UResult<Behavior> {
None None
}; };
let backup_mode = backup_control::determine_backup_mode( let backup_mode = backup_control::determine_backup_mode(matches)?;
matches.is_present(OPT_BACKUP_NO_ARG),
matches.is_present(OPT_BACKUP),
matches.value_of(OPT_BACKUP),
);
let backup_mode = match backup_mode {
Err(err) => return Err(USimpleError::new(1, err)),
Ok(mode) => mode,
};
let target_dir = matches.value_of(OPT_TARGET_DIRECTORY).map(|d| d.to_owned()); let target_dir = matches.value_of(OPT_TARGET_DIRECTORY).map(|d| d.to_owned());
Ok(Behavior { Ok(Behavior {
main_function, main_function,
specified_mode, specified_mode,
backup_mode, backup_mode,
suffix: backup_control::determine_backup_suffix(matches.value_of(OPT_SUFFIX)), suffix: backup_control::determine_backup_suffix(matches),
owner: matches.value_of(OPT_OWNER).unwrap_or("").to_string(), owner: matches.value_of(OPT_OWNER).unwrap_or("").to_string(),
group: matches.value_of(OPT_GROUP).unwrap_or("").to_string(), group: matches.value_of(OPT_GROUP).unwrap_or("").to_string(),
verbose: matches.is_present(OPT_VERBOSE), verbose: matches.is_present(OPT_VERBOSE),
@ -441,14 +407,14 @@ fn directory(paths: Vec<String>, b: Behavior) -> UResult<()> {
// the default mode. Hence it is safe to use fs::create_dir_all // the default mode. Hence it is safe to use fs::create_dir_all
// and then only modify the target's dir mode. // and then only modify the target's dir mode.
if let Err(e) = if let Err(e) =
fs::create_dir_all(path).map_err_context(|| format!("{}", path.display())) fs::create_dir_all(path).map_err_context(|| path.maybe_quote().to_string())
{ {
show!(e); show!(e);
continue; continue;
} }
if b.verbose { if b.verbose {
println!("creating directory '{}'", path.display()); println!("creating directory {}", path.quote());
} }
} }
@ -470,7 +436,7 @@ fn directory(paths: Vec<String>, b: Behavior) -> UResult<()> {
fn is_new_file_path(path: &Path) -> bool { fn is_new_file_path(path: &Path) -> bool {
!path.exists() !path.exists()
&& (path.parent().map(Path::is_dir).unwrap_or(true) && (path.parent().map(Path::is_dir).unwrap_or(true)
|| path.parent().unwrap().to_string_lossy().is_empty()) // In case of a simple file || path.parent().unwrap().as_os_str().is_empty()) // In case of a simple file
} }
/// Perform an install, given a list of paths and behavior. /// Perform an install, given a list of paths and behavior.
@ -524,11 +490,10 @@ fn copy_files_into_dir(files: &[PathBuf], target_dir: &Path, b: &Behavior) -> UR
return Err(InstallError::TargetDirIsntDir(target_dir.to_path_buf()).into()); return Err(InstallError::TargetDirIsntDir(target_dir.to_path_buf()).into());
} }
for sourcepath in files.iter() { for sourcepath in files.iter() {
if !sourcepath.exists() { if let Err(err) = sourcepath
let err = UIoError::new( .metadata()
std::io::ErrorKind::NotFound, .map_err_context(|| format!("cannot stat {}", sourcepath.quote()))
format!("cannot stat '{}'", sourcepath.display()), {
);
show!(err); show!(err);
continue; continue;
} }
@ -591,7 +556,7 @@ fn copy(from: &Path, to: &Path, b: &Behavior) -> UResult<()> {
} }
} }
if from.to_string_lossy() == "/dev/null" { if from.as_os_str() == "/dev/null" {
/* workaround a limitation of fs::copy /* workaround a limitation of fs::copy
* https://github.com/rust-lang/rust/issues/79390 * https://github.com/rust-lang/rust/issues/79390
*/ */
@ -639,7 +604,10 @@ fn copy(from: &Path, to: &Path, b: &Behavior) -> UResult<()> {
Some(owner_id), Some(owner_id),
Some(gid), Some(gid),
false, false,
Verbosity::Normal, Verbosity {
groups_only: false,
level: VerbosityLevel::Normal,
},
) { ) {
Ok(n) => { Ok(n) => {
if !n.is_empty() { if !n.is_empty() {
@ -660,7 +628,17 @@ fn copy(from: &Path, to: &Path, b: &Behavior) -> UResult<()> {
Ok(g) => g, Ok(g) => g,
_ => return Err(InstallError::NoSuchGroup(b.group.clone()).into()), _ => return Err(InstallError::NoSuchGroup(b.group.clone()).into()),
}; };
match wrap_chgrp(to, &meta, Some(group_id), false, Verbosity::Normal) { match wrap_chown(
to,
&meta,
Some(group_id),
None,
false,
Verbosity {
groups_only: true,
level: VerbosityLevel::Normal,
},
) {
Ok(n) => { Ok(n) => {
if !n.is_empty() { if !n.is_empty() {
show_error!("{}", n); show_error!("{}", n);
@ -686,9 +664,9 @@ fn copy(from: &Path, to: &Path, b: &Behavior) -> UResult<()> {
} }
if b.verbose { if b.verbose {
print!("'{}' -> '{}'", from.display(), to.display()); print!("{} -> {}", from.quote(), to.quote());
match backup_path { match backup_path {
Some(path) => println!(" (backup: '{}')", path.display()), Some(path) => println!(" (backup: {})", path.quote()),
None => println!(), None => println!(),
} }
} }

View file

@ -4,14 +4,14 @@ use std::path::Path;
use uucore::mode; use uucore::mode;
/// Takes a user-supplied string and tries to parse to u16 mode bitmask. /// Takes a user-supplied string and tries to parse to u16 mode bitmask.
pub fn parse(mode_string: &str, considering_dir: bool) -> Result<u32, String> { pub fn parse(mode_string: &str, considering_dir: bool, umask: u32) -> Result<u32, String> {
let numbers: &[char] = &['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']; let numbers: &[char] = &['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
// Passing 000 as the existing permissions seems to mirror GNU behavior. // Passing 000 as the existing permissions seems to mirror GNU behavior.
if mode_string.contains(numbers) { if mode_string.contains(numbers) {
mode::parse_numeric(0, mode_string) mode::parse_numeric(0, mode_string, considering_dir)
} else { } else {
mode::parse_symbolic(0, mode_string, considering_dir) mode::parse_symbolic(0, mode_string, umask, considering_dir)
} }
} }
@ -22,8 +22,9 @@ pub fn parse(mode_string: &str, considering_dir: bool) -> Result<u32, String> {
#[cfg(any(unix, target_os = "redox"))] #[cfg(any(unix, target_os = "redox"))]
pub fn chmod(path: &Path, mode: u32) -> Result<(), ()> { pub fn chmod(path: &Path, mode: u32) -> Result<(), ()> {
use std::os::unix::fs::PermissionsExt; use std::os::unix::fs::PermissionsExt;
use uucore::display::Quotable;
fs::set_permissions(path, fs::Permissions::from_mode(mode)).map_err(|err| { fs::set_permissions(path, fs::Permissions::from_mode(mode)).map_err(|err| {
show_error!("{}: chmod failed with error {}", path.display(), err); show_error!("{}: chmod failed with error {}", path.maybe_quote(), err);
}) })
} }

View file

@ -22,3 +22,7 @@ uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_p
[[bin]] [[bin]]
name = "join" name = "join"
path = "src/main.rs" path = "src/main.rs"
[package.metadata.cargo-udeps.ignore]
# Necessary for "make all"
normal = ["uucore_procs"]

View file

@ -11,9 +11,10 @@
extern crate uucore; extern crate uucore;
use clap::{crate_version, App, Arg}; use clap::{crate_version, App, Arg};
use std::cmp::{min, Ordering}; use std::cmp::Ordering;
use std::fs::File; use std::fs::File;
use std::io::{stdin, BufRead, BufReader, Lines, Stdin}; use std::io::{stdin, BufRead, BufReader, Lines, Stdin};
use uucore::display::Quotable;
static NAME: &str = "join"; static NAME: &str = "join";
@ -102,17 +103,12 @@ impl<'a> Repr<'a> {
} }
/// Print each field except the one at the index. /// Print each field except the one at the index.
fn print_fields(&self, line: &Line, index: usize, max_fields: Option<usize>) { fn print_fields(&self, line: &Line, index: usize) {
for i in 0..min(max_fields.unwrap_or(usize::max_value()), line.fields.len()) { for i in 0..line.fields.len() {
if i != index { if i != index {
print!("{}{}", self.separator, line.fields[i]); print!("{}{}", self.separator, line.fields[i]);
} }
} }
if let Some(n) = max_fields {
for _ in line.fields.len()..n {
print!("{}", self.separator)
}
}
} }
/// Print each field or the empty filler if the field is not set. /// Print each field or the empty filler if the field is not set.
@ -186,18 +182,18 @@ impl Spec {
return Spec::Key; return Spec::Key;
} }
crash!(1, "invalid field specifier: '{}'", format); crash!(1, "invalid field specifier: {}", format.quote());
} }
Some('1') => FileNum::File1, Some('1') => FileNum::File1,
Some('2') => FileNum::File2, Some('2') => FileNum::File2,
_ => crash!(1, "invalid file number in field spec: '{}'", format), _ => crash!(1, "invalid file number in field spec: {}", format.quote()),
}; };
if let Some('.') = chars.next() { if let Some('.') = chars.next() {
return Spec::Field(file_num, parse_field_number(chars.as_str())); return Spec::Field(file_num, parse_field_number(chars.as_str()));
} }
crash!(1, "invalid field specifier: '{}'", format); crash!(1, "invalid field specifier: {}", format.quote());
} }
} }
@ -233,7 +229,6 @@ struct State<'a> {
print_unpaired: bool, print_unpaired: bool,
lines: Lines<Box<dyn BufRead + 'a>>, lines: Lines<Box<dyn BufRead + 'a>>,
seq: Vec<Line>, seq: Vec<Line>,
max_fields: Option<usize>,
line_num: usize, line_num: usize,
has_failed: bool, has_failed: bool,
} }
@ -251,7 +246,7 @@ impl<'a> State<'a> {
} else { } else {
match File::open(name) { match File::open(name) {
Ok(file) => Box::new(BufReader::new(file)) as Box<dyn BufRead>, Ok(file) => Box::new(BufReader::new(file)) as Box<dyn BufRead>,
Err(err) => crash!(1, "{}: {}", name, err), Err(err) => crash!(1, "{}: {}", name.maybe_quote(), err),
} }
}; };
@ -262,7 +257,6 @@ impl<'a> State<'a> {
print_unpaired, print_unpaired,
lines: f.lines(), lines: f.lines(),
seq: Vec::new(), seq: Vec::new(),
max_fields: None,
line_num: 0, line_num: 0,
has_failed: false, has_failed: false,
} }
@ -329,8 +323,8 @@ impl<'a> State<'a> {
}); });
} else { } else {
repr.print_field(key); repr.print_field(key);
repr.print_fields(line1, self.key, self.max_fields); repr.print_fields(line1, self.key);
repr.print_fields(line2, other.key, other.max_fields); repr.print_fields(line2, other.key);
} }
println!(); println!();
@ -361,15 +355,16 @@ impl<'a> State<'a> {
!self.seq.is_empty() !self.seq.is_empty()
} }
fn initialize(&mut self, read_sep: Sep, autoformat: bool) { fn initialize(&mut self, read_sep: Sep, autoformat: bool) -> usize {
if let Some(line) = self.read_line(read_sep) { if let Some(line) = self.read_line(read_sep) {
if autoformat {
self.max_fields = Some(line.fields.len());
}
self.seq.push(line); self.seq.push(line);
if autoformat {
return self.seq[0].fields.len();
} }
} }
0
}
fn finalize(&mut self, input: &Input, repr: &Repr) { fn finalize(&mut self, input: &Input, repr: &Repr) {
if self.has_line() && self.print_unpaired { if self.has_line() && self.print_unpaired {
@ -399,7 +394,11 @@ impl<'a> State<'a> {
let diff = input.compare(self.get_current_key(), line.get_field(self.key)); let diff = input.compare(self.get_current_key(), line.get_field(self.key));
if diff == Ordering::Greater { if diff == Ordering::Greater {
eprintln!("{}:{}: is not sorted", self.file_name, self.line_num); eprintln!(
"{}:{}: is not sorted",
self.file_name.maybe_quote(),
self.line_num
);
// This is fatal if the check is enabled. // This is fatal if the check is enabled.
if input.check_order == CheckOrder::Enabled { if input.check_order == CheckOrder::Enabled {
@ -431,7 +430,7 @@ impl<'a> State<'a> {
}); });
} else { } else {
repr.print_field(line.get_field(self.key)); repr.print_field(line.get_field(self.key));
repr.print_fields(line, self.key, self.max_fields); repr.print_fields(line, self.key);
} }
println!(); println!();
@ -512,7 +511,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
crash!(1, "both files cannot be standard input"); crash!(1, "both files cannot be standard input");
} }
exec(file1, file2, &settings) exec(file1, file2, settings)
} }
pub fn uu_app() -> App<'static, 'static> { pub fn uu_app() -> App<'static, 'static> {
@ -622,7 +621,7 @@ FILENUM is 1 or 2, corresponding to FILE1 or FILE2",
) )
} }
fn exec(file1: &str, file2: &str, settings: &Settings) -> i32 { fn exec(file1: &str, file2: &str, settings: Settings) -> i32 {
let stdin = stdin(); let stdin = stdin();
let mut state1 = State::new( let mut state1 = State::new(
@ -647,18 +646,34 @@ fn exec(file1: &str, file2: &str, settings: &Settings) -> i32 {
settings.check_order, settings.check_order,
); );
let format = if settings.autoformat {
let mut format = vec![Spec::Key];
let mut initialize = |state: &mut State| {
let max_fields = state.initialize(settings.separator, settings.autoformat);
for i in 0..max_fields {
if i != state.key {
format.push(Spec::Field(state.file_num, i));
}
}
};
initialize(&mut state1);
initialize(&mut state2);
format
} else {
state1.initialize(settings.separator, settings.autoformat);
state2.initialize(settings.separator, settings.autoformat);
settings.format
};
let repr = Repr::new( let repr = Repr::new(
match settings.separator { match settings.separator {
Sep::Char(sep) => sep, Sep::Char(sep) => sep,
_ => ' ', _ => ' ',
}, },
&settings.format, &format,
&settings.empty, &settings.empty,
); );
state1.initialize(settings.separator, settings.autoformat);
state2.initialize(settings.separator, settings.autoformat);
if settings.headers { if settings.headers {
state1.print_headers(&state2, &repr); state1.print_headers(&state2, &repr);
state1.reset_read_line(&input); state1.reset_read_line(&input);
@ -717,7 +732,7 @@ fn get_field_number(keys: Option<usize>, key: Option<usize>) -> usize {
fn parse_field_number(value: &str) -> usize { fn parse_field_number(value: &str) -> usize {
match value.parse::<usize>() { match value.parse::<usize>() {
Ok(result) if result > 0 => result - 1, Ok(result) if result > 0 => result - 1,
_ => crash!(1, "invalid field number: '{}'", value), _ => crash!(1, "invalid field number: {}", value.quote()),
} }
} }
@ -725,7 +740,7 @@ fn parse_file_number(value: &str) -> FileNum {
match value { match value {
"1" => FileNum::File1, "1" => FileNum::File1,
"2" => FileNum::File2, "2" => FileNum::File2,
value => crash!(1, "invalid file number: '{}'", value), value => crash!(1, "invalid file number: {}", value.quote()),
} }
} }

View file

@ -13,8 +13,9 @@ extern crate uucore;
use clap::{crate_version, App, Arg}; use clap::{crate_version, App, Arg};
use libc::{c_int, pid_t}; use libc::{c_int, pid_t};
use std::io::Error; use std::io::Error;
use uucore::display::Quotable;
use uucore::error::{UResult, USimpleError}; use uucore::error::{UResult, USimpleError};
use uucore::signals::ALL_SIGNALS; use uucore::signals::{signal_by_name_or_value, ALL_SIGNALS};
use uucore::InvalidEncodingHandling; use uucore::InvalidEncodingHandling;
static ABOUT: &str = "Send signal to processes or list information about signals."; static ABOUT: &str = "Send signal to processes or list information about signals.";
@ -36,12 +37,12 @@ pub enum Mode {
#[uucore_procs::gen_uumain] #[uucore_procs::gen_uumain]
pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let args = args let mut args = args
.collect_str(InvalidEncodingHandling::Ignore) .collect_str(InvalidEncodingHandling::Ignore)
.accept_any(); .accept_any();
let (args, obs_signal) = handle_obsolete(args); let obs_signal = handle_obsolete(&mut args);
let usage = format!("{} [OPTIONS]... PID...", executable!()); let usage = format!("{} [OPTIONS]... PID...", uucore::execution_phrase());
let matches = uu_app().usage(&usage[..]).get_matches_from(args); let matches = uu_app().usage(&usage[..]).get_matches_from(args);
let mode = if matches.is_present(options::TABLE) || matches.is_present(options::TABLE_OLD) { let mode = if matches.is_present(options::TABLE) || matches.is_present(options::TABLE_OLD) {
@ -59,13 +60,15 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
match mode { match mode {
Mode::Kill => { Mode::Kill => {
let sig = match (obs_signal, matches.value_of(options::SIGNAL)) { let sig = if let Some(signal) = obs_signal {
(Some(s), Some(_)) => s, // -s takes precedence signal
(Some(s), None) => s, } else if let Some(signal) = matches.value_of(options::SIGNAL) {
(None, Some(s)) => s.to_owned(), parse_signal_value(signal)?
(None, None) => "TERM".to_owned(), } else {
15_usize //SIGTERM
}; };
kill(&sig, &pids_or_signals) let pids = parse_pids(&pids_or_signals)?;
kill(sig, &pids)
} }
Mode::Table => { Mode::Table => {
table(); table();
@ -76,7 +79,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
} }
pub fn uu_app() -> App<'static, 'static> { pub fn uu_app() -> App<'static, 'static> {
App::new(executable!()) App::new(uucore::util_name())
.version(crate_version!()) .version(crate_version!())
.about(ABOUT) .about(ABOUT)
.arg( .arg(
@ -108,26 +111,22 @@ pub fn uu_app() -> App<'static, 'static> {
) )
} }
fn handle_obsolete(mut args: Vec<String>) -> (Vec<String>, Option<String>) { fn handle_obsolete(args: &mut Vec<String>) -> Option<usize> {
let mut i = 0; // Sanity check
while i < args.len() { if args.len() > 2 {
// this is safe because slice is valid when it is referenced // Old signal can only be in the first argument position
let slice = &args[i].clone(); let slice = args[1].as_str();
if slice.starts_with('-') && slice.chars().nth(1).map_or(false, |c| c.is_digit(10)) { if let Some(signal) = slice.strip_prefix('-') {
let val = &slice[1..]; // Check if it is a valid signal
match val.parse() { let opt_signal = signal_by_name_or_value(signal);
Ok(num) => { if opt_signal.is_some() {
if uucore::signals::is_signal(num) { // remove the signal before return
args.remove(i); args.remove(1);
return (args, Some(val.to_owned())); return opt_signal;
} }
} }
Err(_) => break, /* getopts will error out for us */
} }
} None
i += 1;
}
(args, None)
} }
fn table() { fn table() {
@ -154,7 +153,7 @@ fn print_signal(signal_name_or_value: &str) -> UResult<()> {
} }
Err(USimpleError::new( Err(USimpleError::new(
1, 1,
format!("unknown signal name {}", signal_name_or_value), format!("unknown signal name {}", signal_name_or_value.quote()),
)) ))
} }
@ -183,31 +182,32 @@ fn list(arg: Option<String>) -> UResult<()> {
} }
} }
fn kill(signalname: &str, pids: &[String]) -> UResult<()> { fn parse_signal_value(signal_name: &str) -> UResult<usize> {
let optional_signal_value = uucore::signals::signal_by_name_or_value(signalname); let optional_signal_value = signal_by_name_or_value(signal_name);
let signal_value = match optional_signal_value { match optional_signal_value {
Some(x) => x, Some(x) => Ok(x),
None => { None => Err(USimpleError::new(
return Err(USimpleError::new(
1, 1,
format!("unknown signal name {}", signalname), format!("unknown signal name {}", signal_name.quote()),
)); )),
} }
}; }
for pid in pids {
match pid.parse::<usize>() { fn parse_pids(pids: &[String]) -> UResult<Vec<usize>> {
Ok(x) => { pids.iter()
if unsafe { libc::kill(x as pid_t, signal_value as c_int) } != 0 { .map(|x| {
x.parse::<usize>().map_err(|e| {
USimpleError::new(1, format!("failed to parse argument {}: {}", x.quote(), e))
})
})
.collect()
}
fn kill(signal_value: usize, pids: &[usize]) -> UResult<()> {
for &pid in pids {
if unsafe { libc::kill(pid as pid_t, signal_value as c_int) } != 0 {
show!(USimpleError::new(1, format!("{}", Error::last_os_error()))); show!(USimpleError::new(1, format!("{}", Error::last_os_error())));
} }
} }
Err(e) => {
return Err(USimpleError::new(
1,
format!("failed to parse argument {}: {}", pid, e),
));
}
};
}
Ok(()) Ok(())
} }

View file

@ -23,3 +23,7 @@ clap = { version = "2.33", features = ["wrap_help"] }
[[bin]] [[bin]]
name = "link" name = "link"
path = "src/main.rs" path = "src/main.rs"
[package.metadata.cargo-udeps.ignore]
# Necessary for "make all"
normal = ["uucore_procs"]

View file

@ -19,8 +19,8 @@ pub mod options {
pub static FILES: &str = "FILES"; pub static FILES: &str = "FILES";
} }
fn get_usage() -> String { fn usage() -> String {
format!("{0} FILE1 FILE2", executable!()) format!("{0} FILE1 FILE2", uucore::execution_phrase())
} }
pub fn normalize_error_message(e: Error) -> String { pub fn normalize_error_message(e: Error) -> String {
@ -31,7 +31,7 @@ pub fn normalize_error_message(e: Error) -> String {
} }
pub fn uumain(args: impl uucore::Args) -> i32 { pub fn uumain(args: impl uucore::Args) -> i32 {
let usage = get_usage(); let usage = usage();
let matches = uu_app().usage(&usage[..]).get_matches_from(args); let matches = uu_app().usage(&usage[..]).get_matches_from(args);
let files: Vec<_> = matches let files: Vec<_> = matches
@ -51,7 +51,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
} }
pub fn uu_app() -> App<'static, 'static> { pub fn uu_app() -> App<'static, 'static> {
App::new(executable!()) App::new(uucore::util_name())
.version(crate_version!()) .version(crate_version!())
.about(ABOUT) .about(ABOUT)
.arg( .arg(

View file

@ -11,11 +11,12 @@
extern crate uucore; extern crate uucore;
use clap::{crate_version, App, Arg}; use clap::{crate_version, App, Arg};
use uucore::display::Quotable;
use uucore::error::{UError, UResult}; use uucore::error::{UError, UResult};
use std::borrow::Cow; use std::borrow::Cow;
use std::error::Error; use std::error::Error;
use std::ffi::OsStr; use std::ffi::{OsStr, OsString};
use std::fmt::Display; use std::fmt::Display;
use std::fs; use std::fs;
@ -26,7 +27,7 @@ use std::os::unix::fs::symlink;
use std::os::windows::fs::{symlink_dir, symlink_file}; use std::os::windows::fs::{symlink_dir, symlink_file};
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use uucore::backup_control::{self, BackupMode}; use uucore::backup_control::{self, BackupMode};
use uucore::fs::{canonicalize, CanonicalizeMode}; use uucore::fs::{canonicalize, MissingHandling, ResolveMode};
pub struct Settings { pub struct Settings {
overwrite: OverwriteMode, overwrite: OverwriteMode,
@ -49,30 +50,28 @@ pub enum OverwriteMode {
#[derive(Debug)] #[derive(Debug)]
enum LnError { enum LnError {
TargetIsDirectory(String), TargetIsDirectory(PathBuf),
SomeLinksFailed, SomeLinksFailed,
FailedToLink(String), FailedToLink(String),
MissingDestination(String), MissingDestination(PathBuf),
ExtraOperand(String), ExtraOperand(OsString),
InvalidBackupMode(String),
} }
impl Display for LnError { impl Display for LnError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self { match self {
Self::TargetIsDirectory(s) => write!(f, "target '{}' is not a directory", s), Self::TargetIsDirectory(s) => write!(f, "target {} is not a directory", s.quote()),
Self::FailedToLink(s) => write!(f, "failed to link '{}'", s), Self::FailedToLink(e) => write!(f, "failed to link: {}", e),
Self::SomeLinksFailed => write!(f, "some links failed to create"), Self::SomeLinksFailed => write!(f, "some links failed to create"),
Self::MissingDestination(s) => { Self::MissingDestination(s) => {
write!(f, "missing destination file operand after '{}'", s) write!(f, "missing destination file operand after {}", s.quote())
} }
Self::ExtraOperand(s) => write!( Self::ExtraOperand(s) => write!(
f, f,
"extra operand '{}'\nTry '{} --help' for more information.", "extra operand {}\nTry '{} --help' for more information.",
s, s.quote(),
executable!() uucore::execution_phrase()
), ),
Self::InvalidBackupMode(s) => write!(f, "{}", s),
} }
} }
} }
@ -87,22 +86,21 @@ impl UError for LnError {
Self::FailedToLink(_) => 1, Self::FailedToLink(_) => 1,
Self::MissingDestination(_) => 1, Self::MissingDestination(_) => 1,
Self::ExtraOperand(_) => 1, Self::ExtraOperand(_) => 1,
Self::InvalidBackupMode(_) => 1,
} }
} }
} }
fn get_usage() -> String { fn usage() -> String {
format!( format!(
"{0} [OPTION]... [-T] TARGET LINK_NAME (1st form) "{0} [OPTION]... [-T] TARGET LINK_NAME (1st form)
{0} [OPTION]... TARGET (2nd form) {0} [OPTION]... TARGET (2nd form)
{0} [OPTION]... TARGET... DIRECTORY (3rd form) {0} [OPTION]... TARGET... DIRECTORY (3rd form)
{0} [OPTION]... -t DIRECTORY TARGET... (4th form)", {0} [OPTION]... -t DIRECTORY TARGET... (4th form)",
executable!() uucore::execution_phrase()
) )
} }
fn get_long_usage() -> String { fn long_usage() -> String {
String::from( String::from(
" In the 1st form, create a link to TARGET with the name LINK_NAME. " In the 1st form, create a link to TARGET with the name LINK_NAME.
In the 2nd form, create a link to TARGET in the current directory. In the 2nd form, create a link to TARGET in the current directory.
@ -119,13 +117,10 @@ fn get_long_usage() -> String {
static ABOUT: &str = "change file owner and group"; static ABOUT: &str = "change file owner and group";
mod options { mod options {
pub const BACKUP_NO_ARG: &str = "b";
pub const BACKUP: &str = "backup";
pub const FORCE: &str = "force"; pub const FORCE: &str = "force";
pub const INTERACTIVE: &str = "interactive"; pub const INTERACTIVE: &str = "interactive";
pub const NO_DEREFERENCE: &str = "no-dereference"; pub const NO_DEREFERENCE: &str = "no-dereference";
pub const SYMBOLIC: &str = "symbolic"; pub const SYMBOLIC: &str = "symbolic";
pub const SUFFIX: &str = "suffix";
pub const TARGET_DIRECTORY: &str = "target-directory"; pub const TARGET_DIRECTORY: &str = "target-directory";
pub const NO_TARGET_DIRECTORY: &str = "no-target-directory"; pub const NO_TARGET_DIRECTORY: &str = "no-target-directory";
pub const RELATIVE: &str = "relative"; pub const RELATIVE: &str = "relative";
@ -136,8 +131,8 @@ static ARG_FILES: &str = "files";
#[uucore_procs::gen_uumain] #[uucore_procs::gen_uumain]
pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let usage = get_usage(); let usage = usage();
let long_usage = get_long_usage(); let long_usage = long_usage();
let matches = uu_app() let matches = uu_app()
.usage(&usage[..]) .usage(&usage[..])
@ -164,19 +159,8 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
OverwriteMode::NoClobber OverwriteMode::NoClobber
}; };
let backup_mode = backup_control::determine_backup_mode( let backup_mode = backup_control::determine_backup_mode(&matches)?;
matches.is_present(options::BACKUP_NO_ARG), let backup_suffix = backup_control::determine_backup_suffix(&matches);
matches.is_present(options::BACKUP),
matches.value_of(options::BACKUP),
);
let backup_mode = match backup_mode {
Err(err) => {
return Err(LnError::InvalidBackupMode(err).into());
}
Ok(mode) => mode,
};
let backup_suffix = backup_control::determine_backup_suffix(matches.value_of(options::SUFFIX));
let settings = Settings { let settings = Settings {
overwrite: overwrite_mode, overwrite: overwrite_mode,
@ -196,23 +180,11 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
} }
pub fn uu_app() -> App<'static, 'static> { pub fn uu_app() -> App<'static, 'static> {
App::new(executable!()) App::new(uucore::util_name())
.version(crate_version!()) .version(crate_version!())
.about(ABOUT) .about(ABOUT)
.arg( .arg(backup_control::arguments::backup())
Arg::with_name(options::BACKUP) .arg(backup_control::arguments::backup_no_args())
.long(options::BACKUP)
.help("make a backup of each existing destination file")
.takes_value(true)
.require_equals(true)
.min_values(0)
.value_name("CONTROL"),
)
.arg(
Arg::with_name(options::BACKUP_NO_ARG)
.short(options::BACKUP_NO_ARG)
.help("like --backup but does not accept an argument"),
)
// TODO: opts.arg( // TODO: opts.arg(
// Arg::with_name(("d", "directory", "allow users with appropriate privileges to attempt \ // Arg::with_name(("d", "directory", "allow users with appropriate privileges to attempt \
// to make hard links to directories"); // to make hard links to directories");
@ -250,14 +222,7 @@ pub fn uu_app() -> App<'static, 'static> {
// override added for https://github.com/uutils/coreutils/issues/2359 // override added for https://github.com/uutils/coreutils/issues/2359
.overrides_with(options::SYMBOLIC), .overrides_with(options::SYMBOLIC),
) )
.arg( .arg(backup_control::arguments::suffix())
Arg::with_name(options::SUFFIX)
.short("S")
.long(options::SUFFIX)
.help("override the usual backup suffix")
.value_name("SUFFIX")
.takes_value(true),
)
.arg( .arg(
Arg::with_name(options::TARGET_DIRECTORY) Arg::with_name(options::TARGET_DIRECTORY)
.short("t") .short("t")
@ -315,10 +280,10 @@ fn exec(files: &[PathBuf], settings: &Settings) -> UResult<()> {
// 1st form. Now there should be only two operands, but if -T is // 1st form. Now there should be only two operands, but if -T is
// specified we may have a wrong number of operands. // specified we may have a wrong number of operands.
if files.len() == 1 { if files.len() == 1 {
return Err(LnError::MissingDestination(files[0].to_string_lossy().into()).into()); return Err(LnError::MissingDestination(files[0].clone()).into());
} }
if files.len() > 2 { if files.len() > 2 {
return Err(LnError::ExtraOperand(files[2].display().to_string()).into()); return Err(LnError::ExtraOperand(files[2].clone().into()).into());
} }
assert!(!files.is_empty()); assert!(!files.is_empty());
@ -330,7 +295,7 @@ fn exec(files: &[PathBuf], settings: &Settings) -> UResult<()> {
fn link_files_in_dir(files: &[PathBuf], target_dir: &Path, settings: &Settings) -> UResult<()> { fn link_files_in_dir(files: &[PathBuf], target_dir: &Path, settings: &Settings) -> UResult<()> {
if !target_dir.is_dir() { if !target_dir.is_dir() {
return Err(LnError::TargetIsDirectory(target_dir.display().to_string()).into()); return Err(LnError::TargetIsDirectory(target_dir.to_owned()).into());
} }
let mut all_successful = true; let mut all_successful = true;
@ -342,7 +307,7 @@ fn link_files_in_dir(files: &[PathBuf], target_dir: &Path, settings: &Settings)
if is_symlink(target_dir) { if is_symlink(target_dir) {
if target_dir.is_file() { if target_dir.is_file() {
if let Err(e) = fs::remove_file(target_dir) { if let Err(e) = fs::remove_file(target_dir) {
show_error!("Could not update {}: {}", target_dir.display(), e) show_error!("Could not update {}: {}", target_dir.quote(), e)
}; };
} }
if target_dir.is_dir() { if target_dir.is_dir() {
@ -350,7 +315,7 @@ fn link_files_in_dir(files: &[PathBuf], target_dir: &Path, settings: &Settings)
// considered as a dir // considered as a dir
// See test_ln::test_symlink_no_deref_dir // See test_ln::test_symlink_no_deref_dir
if let Err(e) = fs::remove_dir(target_dir) { if let Err(e) = fs::remove_dir(target_dir) {
show_error!("Could not update {}: {}", target_dir.display(), e) show_error!("Could not update {}: {}", target_dir.quote(), e)
}; };
} }
} }
@ -368,10 +333,7 @@ fn link_files_in_dir(files: &[PathBuf], target_dir: &Path, settings: &Settings)
} }
} }
None => { None => {
show_error!( show_error!("cannot stat {}: No such file or directory", srcpath.quote());
"cannot stat '{}': No such file or directory",
srcpath.display()
);
all_successful = false; all_successful = false;
continue; continue;
} }
@ -380,9 +342,9 @@ fn link_files_in_dir(files: &[PathBuf], target_dir: &Path, settings: &Settings)
if let Err(e) = link(srcpath, &targetpath, settings) { if let Err(e) = link(srcpath, &targetpath, settings) {
show_error!( show_error!(
"cannot link '{}' to '{}': {}", "cannot link {} to {}: {}",
targetpath.display(), targetpath.quote(),
srcpath.display(), srcpath.quote(),
e e
); );
all_successful = false; all_successful = false;
@ -396,8 +358,12 @@ fn link_files_in_dir(files: &[PathBuf], target_dir: &Path, settings: &Settings)
} }
fn relative_path<'a>(src: &Path, dst: &Path) -> Result<Cow<'a, Path>> { fn relative_path<'a>(src: &Path, dst: &Path) -> Result<Cow<'a, Path>> {
let src_abs = canonicalize(src, CanonicalizeMode::Normal)?; let src_abs = canonicalize(src, MissingHandling::Normal, ResolveMode::Logical)?;
let mut dst_abs = canonicalize(dst.parent().unwrap(), CanonicalizeMode::Normal)?; let mut dst_abs = canonicalize(
dst.parent().unwrap(),
MissingHandling::Normal,
ResolveMode::Logical,
)?;
dst_abs.push(dst.components().last().unwrap()); dst_abs.push(dst.components().last().unwrap());
let suffix_pos = src_abs let suffix_pos = src_abs
.components() .components()
@ -431,7 +397,7 @@ fn link(src: &Path, dst: &Path, settings: &Settings) -> Result<()> {
match settings.overwrite { match settings.overwrite {
OverwriteMode::NoClobber => {} OverwriteMode::NoClobber => {}
OverwriteMode::Interactive => { OverwriteMode::Interactive => {
print!("{}: overwrite '{}'? ", executable!(), dst.display()); print!("{}: overwrite {}? ", uucore::util_name(), dst.quote());
if !read_yes() { if !read_yes() {
return Ok(()); return Ok(());
} }
@ -458,9 +424,9 @@ fn link(src: &Path, dst: &Path, settings: &Settings) -> Result<()> {
} }
if settings.verbose { if settings.verbose {
print!("'{}' -> '{}'", dst.display(), &source.display()); print!("{} -> {}", dst.quote(), source.quote());
match backup_path { match backup_path {
Some(path) => println!(" (backup: '{}')", path.display()), Some(path) => println!(" (backup: {})", path.quote()),
None => println!(), None => println!(),
} }
} }

View file

@ -35,8 +35,8 @@ fn get_userlogin() -> Option<String> {
static SUMMARY: &str = "Print user's login name"; static SUMMARY: &str = "Print user's login name";
fn get_usage() -> String { fn usage() -> &'static str {
String::from(executable!()) uucore::execution_phrase()
} }
pub fn uumain(args: impl uucore::Args) -> i32 { pub fn uumain(args: impl uucore::Args) -> i32 {
@ -44,8 +44,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.collect_str(InvalidEncodingHandling::Ignore) .collect_str(InvalidEncodingHandling::Ignore)
.accept_any(); .accept_any();
let usage = get_usage(); let _ = uu_app().usage(usage()).get_matches_from(args);
let _ = uu_app().usage(&usage[..]).get_matches_from(args);
match get_userlogin() { match get_userlogin() {
Some(userlogin) => println!("{}", userlogin), Some(userlogin) => println!("{}", userlogin),
@ -56,7 +55,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
} }
pub fn uu_app() -> App<'static, 'static> { pub fn uu_app() -> App<'static, 'static> {
App::new(executable!()) App::new(uucore::util_name())
.version(crate_version!()) .version(crate_version!())
.about(SUMMARY) .about(SUMMARY)
} }

View file

@ -15,7 +15,6 @@ edition = "2018"
path = "src/ls.rs" path = "src/ls.rs"
[dependencies] [dependencies]
locale = "0.2.2"
chrono = "0.4.19" chrono = "0.4.19"
clap = { version = "2.33", features = ["wrap_help"] } clap = { version = "2.33", features = ["wrap_help"] }
unicode-width = "0.1.8" unicode-width = "0.1.8"
@ -35,3 +34,7 @@ lazy_static = "1.4.0"
[[bin]] [[bin]]
name = "ls" name = "ls"
path = "src/main.rs" path = "src/main.rs"
[package.metadata.cargo-udeps.ignore]
# Necessary for "make all"
normal = ["uucore_procs"]

View file

@ -21,6 +21,7 @@ use lscolors::LsColors;
use number_prefix::NumberPrefix; use number_prefix::NumberPrefix;
use once_cell::unsync::OnceCell; use once_cell::unsync::OnceCell;
use quoting_style::{escape_name, QuotingStyle}; use quoting_style::{escape_name, QuotingStyle};
use std::ffi::OsString;
#[cfg(windows)] #[cfg(windows)]
use std::os::windows::fs::MetadataExt; use std::os::windows::fs::MetadataExt;
use std::{ use std::{
@ -39,15 +40,18 @@ use std::{
time::Duration, time::Duration,
}; };
use term_grid::{Cell, Direction, Filling, Grid, GridOptions}; use term_grid::{Cell, Direction, Filling, Grid, GridOptions};
use uucore::error::{set_exit_code, FromIo, UError, UResult}; use uucore::{
display::Quotable,
error::{set_exit_code, UError, UResult},
};
use unicode_width::UnicodeWidthStr; use unicode_width::UnicodeWidthStr;
#[cfg(unix)] #[cfg(unix)]
use uucore::libc::{S_IXGRP, S_IXOTH, S_IXUSR}; use uucore::libc::{S_IXGRP, S_IXOTH, S_IXUSR};
use uucore::{fs::display_permissions, version_cmp::version_cmp}; use uucore::{fs::display_permissions, version_cmp::version_cmp};
fn get_usage() -> String { fn usage() -> String {
format!("{0} [OPTION]... [FILE]...", executable!()) format!("{0} [OPTION]... [FILE]...", uucore::execution_phrase())
} }
pub mod options { pub mod options {
@ -149,8 +153,8 @@ impl Error for LsError {}
impl Display for LsError { impl Display for LsError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self { match self {
LsError::InvalidLineWidth(s) => write!(f, "invalid line width: '{}'", s), LsError::InvalidLineWidth(s) => write!(f, "invalid line width: {}", s.quote()),
LsError::NoMetadata(p) => write!(f, "could not open file: '{}'", p.display()), LsError::NoMetadata(p) => write!(f, "could not open file: {}", p.quote()),
} }
} }
} }
@ -248,7 +252,7 @@ struct LongFormat {
impl Config { impl Config {
#[allow(clippy::cognitive_complexity)] #[allow(clippy::cognitive_complexity)]
fn from(options: clap::ArgMatches) -> UResult<Config> { fn from(options: &clap::ArgMatches) -> UResult<Config> {
let (mut format, opt) = if let Some(format_) = options.value_of(options::FORMAT) { let (mut format, opt) = if let Some(format_) = options.value_of(options::FORMAT) {
( (
match format_ { match format_ {
@ -409,18 +413,18 @@ impl Config {
}, },
None => match termsize::get() { None => match termsize::get() {
Some(size) => size.cols, Some(size) => size.cols,
None => match std::env::var("COLUMNS") { None => match std::env::var_os("COLUMNS") {
Ok(columns) => match columns.parse() { Some(columns) => match columns.to_str().and_then(|s| s.parse().ok()) {
Ok(columns) => columns, Some(columns) => columns,
Err(_) => { None => {
show_error!( show_error!(
"ignoring invalid width in environment variable COLUMNS: '{}'", "ignoring invalid width in environment variable COLUMNS: {}",
columns columns.quote()
); );
DEFAULT_TERM_WIDTH DEFAULT_TERM_WIDTH
} }
}, },
Err(_) => DEFAULT_TERM_WIDTH, None => DEFAULT_TERM_WIDTH,
}, },
}, },
}; };
@ -428,11 +432,10 @@ impl Config {
#[allow(clippy::needless_bool)] #[allow(clippy::needless_bool)]
let show_control = if options.is_present(options::HIDE_CONTROL_CHARS) { let show_control = if options.is_present(options::HIDE_CONTROL_CHARS) {
false false
} else if options.is_present(options::SHOW_CONTROL_CHARS) || atty::is(atty::Stream::Stdout) } else if options.is_present(options::SHOW_CONTROL_CHARS) {
{
true true
} else { } else {
false !atty::is(atty::Stream::Stdout)
}; };
let quoting_style = if let Some(style) = options.value_of(options::QUOTING_STYLE) { let quoting_style = if let Some(style) = options.value_of(options::QUOTING_STYLE) {
@ -538,7 +541,7 @@ impl Config {
Ok(p) => { Ok(p) => {
ignore_patterns.add(p); ignore_patterns.add(p);
} }
Err(_) => show_warning!("Invalid pattern for ignore: '{}'", pattern), Err(_) => show_warning!("Invalid pattern for ignore: {}", pattern.quote()),
} }
} }
@ -548,7 +551,7 @@ impl Config {
Ok(p) => { Ok(p) => {
ignore_patterns.add(p); ignore_patterns.add(p);
} }
Err(_) => show_warning!("Invalid pattern for hide: '{}'", pattern), Err(_) => show_warning!("Invalid pattern for hide: {}", pattern.quote()),
} }
} }
} }
@ -599,26 +602,23 @@ impl Config {
#[uucore_procs::gen_uumain] #[uucore_procs::gen_uumain]
pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let args = args let usage = usage();
.collect_str(InvalidEncodingHandling::Ignore)
.accept_any();
let usage = get_usage();
let app = uu_app().usage(&usage[..]); let app = uu_app().usage(&usage[..]);
let matches = app.get_matches_from(args); let matches = app.get_matches_from(args);
let config = Config::from(&matches)?;
let locs = matches let locs = matches
.values_of(options::PATHS) .values_of_os(options::PATHS)
.map(|v| v.map(ToString::to_string).collect()) .map(|v| v.map(Path::new).collect())
.unwrap_or_else(|| vec![String::from(".")]); .unwrap_or_else(|| vec![Path::new(".")]);
list(locs, Config::from(matches)?) list(locs, config)
} }
pub fn uu_app() -> App<'static, 'static> { pub fn uu_app() -> App<'static, 'static> {
App::new(executable!()) App::new(uucore::util_name())
.version(crate_version!()) .version(crate_version!())
.about( .about(
"By default, ls will list the files and contents of any directories on \ "By default, ls will list the files and contents of any directories on \
@ -1177,7 +1177,7 @@ struct PathData {
md: OnceCell<Option<Metadata>>, md: OnceCell<Option<Metadata>>,
ft: OnceCell<Option<FileType>>, ft: OnceCell<Option<FileType>>,
// Name of the file - will be empty for . or .. // Name of the file - will be empty for . or ..
display_name: String, display_name: OsString,
// PathBuf that all above data corresponds to // PathBuf that all above data corresponds to
p_buf: PathBuf, p_buf: PathBuf,
must_dereference: bool, must_dereference: bool,
@ -1187,7 +1187,7 @@ impl PathData {
fn new( fn new(
p_buf: PathBuf, p_buf: PathBuf,
file_type: Option<std::io::Result<FileType>>, file_type: Option<std::io::Result<FileType>>,
file_name: Option<String>, file_name: Option<OsString>,
config: &Config, config: &Config,
command_line: bool, command_line: bool,
) -> Self { ) -> Self {
@ -1195,16 +1195,13 @@ impl PathData {
// For '..', the filename is None // For '..', the filename is None
let display_name = if let Some(name) = file_name { let display_name = if let Some(name) = file_name {
name name
} else { } else if command_line {
let display_os_str = if command_line { p_buf.clone().into()
p_buf.as_os_str()
} else { } else {
p_buf p_buf
.file_name() .file_name()
.unwrap_or_else(|| p_buf.iter().next_back().unwrap()) .unwrap_or_else(|| p_buf.iter().next_back().unwrap())
}; .to_owned()
display_os_str.to_string_lossy().into_owned()
}; };
let must_dereference = match &config.dereference { let must_dereference = match &config.dereference {
Dereference::All => true, Dereference::All => true,
@ -1249,19 +1246,24 @@ impl PathData {
} }
} }
fn list(locs: Vec<String>, config: Config) -> UResult<()> { fn list(locs: Vec<&Path>, config: Config) -> UResult<()> {
let mut files = Vec::<PathData>::new(); let mut files = Vec::<PathData>::new();
let mut dirs = Vec::<PathData>::new(); let mut dirs = Vec::<PathData>::new();
let mut out = BufWriter::new(stdout()); let mut out = BufWriter::new(stdout());
for loc in &locs { for loc in &locs {
let p = PathBuf::from(&loc); let p = PathBuf::from(loc);
let path_data = PathData::new(p, None, None, &config, true); let path_data = PathData::new(p, None, None, &config, true);
if path_data.md().is_none() { if path_data.md().is_none() {
show!(std::io::ErrorKind::NotFound // FIXME: Would be nice to use the actual error instead of hardcoding it
.map_err_context(|| format!("cannot access '{}'", path_data.p_buf.display()))); // Presumably other errors can happen too?
show_error!(
"cannot access {}: No such file or directory",
path_data.p_buf.quote()
);
set_exit_code(1);
// We found an error, no need to continue the execution // We found an error, no need to continue the execution
continue; continue;
} }
@ -1286,6 +1288,7 @@ fn list(locs: Vec<String>, config: Config) -> UResult<()> {
sort_entries(&mut dirs, &config); sort_entries(&mut dirs, &config);
for dir in dirs { for dir in dirs {
if locs.len() > 1 || config.recursive { if locs.len() > 1 || config.recursive {
// FIXME: This should use the quoting style and propagate errors
let _ = writeln!(out, "\n{}:", dir.p_buf.display()); let _ = writeln!(out, "\n{}:", dir.p_buf.display());
} }
enter_directory(&dir, &config, &mut out); enter_directory(&dir, &config, &mut out);
@ -1362,8 +1365,8 @@ fn enter_directory(dir: &PathData, config: &Config, out: &mut BufWriter<Stdout>)
vec![] vec![]
}; };
let mut temp: Vec<_> = safe_unwrap!(fs::read_dir(&dir.p_buf)) let mut temp: Vec<_> = crash_if_err!(1, fs::read_dir(&dir.p_buf))
.map(|res| safe_unwrap!(res)) .map(|res| crash_if_err!(1, res))
.filter(|e| should_display(e, config)) .filter(|e| should_display(e, config))
.map(|e| PathData::new(DirEntry::path(&e), Some(e.file_type()), None, config, false)) .map(|e| PathData::new(DirEntry::path(&e), Some(e.file_type()), None, config, false))
.collect(); .collect();
@ -1394,14 +1397,17 @@ fn get_metadata(entry: &Path, dereference: bool) -> std::io::Result<Metadata> {
} }
} }
fn display_dir_entry_size(entry: &PathData, config: &Config) -> (usize, usize) { fn display_dir_entry_size(entry: &PathData, config: &Config) -> (usize, usize, usize, usize) {
// TODO: Cache/memoize the display_* results so we don't have to recalculate them.
if let Some(md) = entry.md() { if let Some(md) = entry.md() {
( (
display_symlink_count(md).len(), display_symlink_count(md).len(),
display_uname(md, config).len(),
display_group(md, config).len(),
display_size_or_rdev(md, config).len(), display_size_or_rdev(md, config).len(),
) )
} else { } else {
(0, 0) (0, 0, 0, 0)
} }
} }
@ -1409,15 +1415,28 @@ fn pad_left(string: String, count: usize) -> String {
format!("{:>width$}", string, width = count) format!("{:>width$}", string, width = count)
} }
fn pad_right(string: String, count: usize) -> String {
format!("{:<width$}", string, width = count)
}
fn display_items(items: &[PathData], config: &Config, out: &mut BufWriter<Stdout>) { fn display_items(items: &[PathData], config: &Config, out: &mut BufWriter<Stdout>) {
if config.format == Format::Long { if config.format == Format::Long {
let (mut max_links, mut max_width) = (1, 1); let (
mut longest_link_count_len,
mut longest_uname_len,
mut longest_group_len,
mut longest_size_len,
) = (1, 1, 1, 1);
let mut total_size = 0; let mut total_size = 0;
for item in items { for item in items {
let (links, width) = display_dir_entry_size(item, config); let (link_count_len, uname_len, group_len, size_len) =
max_links = links.max(max_links); display_dir_entry_size(item, config);
max_width = width.max(max_width); longest_link_count_len = link_count_len.max(longest_link_count_len);
longest_size_len = size_len.max(longest_size_len);
longest_uname_len = uname_len.max(longest_uname_len);
longest_group_len = group_len.max(longest_group_len);
longest_size_len = size_len.max(longest_size_len);
total_size += item.md().map_or(0, |md| get_block_size(md, config)); total_size += item.md().map_or(0, |md| get_block_size(md, config));
} }
@ -1426,7 +1445,15 @@ fn display_items(items: &[PathData], config: &Config, out: &mut BufWriter<Stdout
} }
for item in items { for item in items {
display_item_long(item, max_links, max_width, config, out); display_item_long(
item,
longest_link_count_len,
longest_uname_len,
longest_group_len,
longest_size_len,
config,
out,
);
} }
} else { } else {
let names = items.iter().filter_map(|i| display_file_name(i, config)); let names = items.iter().filter_map(|i| display_file_name(i, config));
@ -1531,10 +1558,38 @@ fn display_grid(
} }
} }
/// This writes to the BufWriter out a single string of the output of `ls -l`.
///
/// It writes the following keys, in order:
/// * `inode` ([`get_inode`], config-optional)
/// * `permissions` ([`display_permissions`])
/// * `symlink_count` ([`display_symlink_count`])
/// * `owner` ([`display_uname`], config-optional)
/// * `group` ([`display_group`], config-optional)
/// * `author` ([`display_uname`], config-optional)
/// * `size / rdev` ([`display_size_or_rdev`])
/// * `system_time` ([`get_system_time`])
/// * `file_name` ([`display_file_name`])
///
/// This function needs to display information in columns:
/// * permissions and system_time are already guaranteed to be pre-formatted in fixed length.
/// * file_name is the last column and is left-aligned.
/// * Everything else needs to be padded using [`pad_left`].
///
/// That's why we have the parameters:
/// ```txt
/// longest_link_count_len: usize,
/// longest_uname_len: usize,
/// longest_group_len: usize,
/// longest_size_len: usize,
/// ```
/// that decide the maximum possible character count of each field.
fn display_item_long( fn display_item_long(
item: &PathData, item: &PathData,
max_links: usize, longest_link_count_len: usize,
max_size: usize, longest_uname_len: usize,
longest_group_len: usize,
longest_size_len: usize,
config: &Config, config: &Config,
out: &mut BufWriter<Stdout>, out: &mut BufWriter<Stdout>,
) { ) {
@ -1557,27 +1612,39 @@ fn display_item_long(
out, out,
"{} {}", "{} {}",
display_permissions(md, true), display_permissions(md, true),
pad_left(display_symlink_count(md), max_links), pad_left(display_symlink_count(md), longest_link_count_len),
); );
if config.long.owner { if config.long.owner {
let _ = write!(out, " {}", display_uname(md, config)); let _ = write!(
out,
" {}",
pad_right(display_uname(md, config), longest_uname_len)
);
} }
if config.long.group { if config.long.group {
let _ = write!(out, " {}", display_group(md, config)); let _ = write!(
out,
" {}",
pad_right(display_group(md, config), longest_group_len)
);
} }
// Author is only different from owner on GNU/Hurd, so we reuse // Author is only different from owner on GNU/Hurd, so we reuse
// the owner, since GNU/Hurd is not currently supported by Rust. // the owner, since GNU/Hurd is not currently supported by Rust.
if config.long.author { if config.long.author {
let _ = write!(out, " {}", display_uname(md, config)); let _ = write!(
out,
" {}",
pad_right(display_uname(md, config), longest_uname_len)
);
} }
let _ = writeln!( let _ = writeln!(
out, out,
" {} {} {}", " {} {} {}",
pad_left(display_size_or_rdev(md, config), max_size), pad_left(display_size_or_rdev(md, config), longest_size_len),
display_date(md, config), display_date(md, config),
// unwrap is fine because it fails when metadata is not available // unwrap is fine because it fails when metadata is not available
// but we already know that it is because it's checked at the // but we already know that it is because it's checked at the
@ -1597,7 +1664,6 @@ fn get_inode(metadata: &Metadata) -> String {
use std::sync::Mutex; use std::sync::Mutex;
#[cfg(unix)] #[cfg(unix)]
use uucore::entries; use uucore::entries;
use uucore::InvalidEncodingHandling;
#[cfg(unix)] #[cfg(unix)]
fn cached_uid2usr(uid: u32) -> String { fn cached_uid2usr(uid: u32) -> String {
@ -1798,7 +1864,20 @@ fn classify_file(path: &PathData) -> Option<char> {
} }
} }
/// Takes a [`PathData`] struct and returns a cell with a name ready for displaying.
///
/// This function relies on the following parameters in the provided `&Config`:
/// * `config.quoting_style` to decide how we will escape `name` using [`escape_name`].
/// * `config.inode` decides whether to display inode numbers beside names using [`get_inode`].
/// * `config.color` decides whether it's going to color `name` using [`color_name`].
/// * `config.indicator_style` to append specific characters to `name` using [`classify_file`].
/// * `config.format` to display symlink targets if `Format::Long`. This function is also
/// responsible for coloring symlink target names if `config.color` is specified.
///
/// Note that non-unicode sequences in symlink targets are dealt with using
/// [`std::path::Path::to_string_lossy`].
fn display_file_name(path: &PathData, config: &Config) -> Option<Cell> { fn display_file_name(path: &PathData, config: &Config) -> Option<Cell> {
// This is our return value. We start by `&path.display_name` and modify it along the way.
let mut name = escape_name(&path.display_name, &config.quoting_style); let mut name = escape_name(&path.display_name, &config.quoting_style);
#[cfg(unix)] #[cfg(unix)]
@ -1851,9 +1930,43 @@ fn display_file_name(path: &PathData, config: &Config) -> Option<Cell> {
if config.format == Format::Long && path.file_type()?.is_symlink() { if config.format == Format::Long && path.file_type()?.is_symlink() {
if let Ok(target) = path.p_buf.read_link() { if let Ok(target) = path.p_buf.read_link() {
name.push_str(" -> "); name.push_str(" -> ");
// We might as well color the symlink output after the arrow.
// This makes extra system calls, but provides important information that
// people run `ls -l --color` are very interested in.
if let Some(ls_colors) = &config.color {
// We get the absolute path to be able to construct PathData with valid Metadata.
// This is because relative symlinks will fail to get_metadata.
let mut absolute_target = target.clone();
if target.is_relative() {
if let Some(parent) = path.p_buf.parent() {
absolute_target = parent.join(absolute_target);
}
}
let target_data = PathData::new(absolute_target, None, None, config, false);
// If we have a symlink to a valid file, we use the metadata of said file.
// Because we use an absolute path, we can assume this is guaranteed to exist.
// Otherwise, we use path.md(), which will guarantee we color to the same
// color of non-existent symlinks according to style_for_path_with_metadata.
let target_metadata = match target_data.md() {
Some(md) => md,
None => path.md()?,
};
name.push_str(&color_name(
ls_colors,
&target_data.p_buf,
target.to_string_lossy().into_owned(),
target_metadata,
));
} else {
// If no coloring is required, we just use target as is.
name.push_str(&target.to_string_lossy()); name.push_str(&target.to_string_lossy());
} }
} }
}
Some(Cell { Some(Cell {
contents: name, contents: name,

View file

@ -1,6 +1,10 @@
use std::char::from_digit; use std::char::from_digit;
use std::ffi::OsStr;
const SPECIAL_SHELL_CHARS: &str = "~`#$&*()|[]{};\\'\"<>?! "; // These are characters with special meaning in the shell (e.g. bash).
// The first const contains characters that only have a special meaning when they appear at the beginning of a name.
const SPECIAL_SHELL_CHARS_START: &[char] = &['~', '#'];
const SPECIAL_SHELL_CHARS: &str = "`$&*()|[]{};\\'\"<>?! ";
pub(super) enum QuotingStyle { pub(super) enum QuotingStyle {
Shell { Shell {
@ -198,6 +202,8 @@ fn shell_without_escape(name: &str, quotes: Quotes, show_control_chars: bool) ->
} }
} }
} }
must_quote = must_quote || name.starts_with(SPECIAL_SHELL_CHARS_START);
(escaped_str, must_quote) (escaped_str, must_quote)
} }
@ -246,22 +252,25 @@ fn shell_with_escape(name: &str, quotes: Quotes) -> (String, bool) {
} }
} }
} }
must_quote = must_quote || name.starts_with(SPECIAL_SHELL_CHARS_START);
(escaped_str, must_quote) (escaped_str, must_quote)
} }
pub(super) fn escape_name(name: &str, style: &QuotingStyle) -> String { pub(super) fn escape_name(name: &OsStr, style: &QuotingStyle) -> String {
match style { match style {
QuotingStyle::Literal { show_control } => { QuotingStyle::Literal { show_control } => {
if !show_control { if !show_control {
name.chars() name.to_string_lossy()
.chars()
.flat_map(|c| EscapedChar::new_literal(c).hide_control()) .flat_map(|c| EscapedChar::new_literal(c).hide_control())
.collect() .collect()
} else { } else {
name.into() name.to_string_lossy().into_owned()
} }
} }
QuotingStyle::C { quotes } => { QuotingStyle::C { quotes } => {
let escaped_str: String = name let escaped_str: String = name
.to_string_lossy()
.chars() .chars()
.flat_map(|c| EscapedChar::new_c(c, *quotes)) .flat_map(|c| EscapedChar::new_c(c, *quotes))
.collect(); .collect();
@ -277,7 +286,8 @@ pub(super) fn escape_name(name: &str, style: &QuotingStyle) -> String {
always_quote, always_quote,
show_control, show_control,
} => { } => {
let (quotes, must_quote) = if name.contains('"') { let name = name.to_string_lossy();
let (quotes, must_quote) = if name.contains(&['"', '`', '$', '\\'][..]) {
(Quotes::Single, true) (Quotes::Single, true)
} else if name.contains('\'') { } else if name.contains('\'') {
(Quotes::Double, true) (Quotes::Double, true)
@ -288,9 +298,9 @@ pub(super) fn escape_name(name: &str, style: &QuotingStyle) -> String {
}; };
let (escaped_str, contains_quote_chars) = if *escape { let (escaped_str, contains_quote_chars) = if *escape {
shell_with_escape(name, quotes) shell_with_escape(&name, quotes)
} else { } else {
shell_without_escape(name, quotes, *show_control) shell_without_escape(&name, quotes, *show_control)
}; };
match (must_quote | contains_quote_chars, quotes) { match (must_quote | contains_quote_chars, quotes) {
@ -356,7 +366,7 @@ mod tests {
fn check_names(name: &str, map: Vec<(&str, &str)>) { fn check_names(name: &str, map: Vec<(&str, &str)>) {
assert_eq!( assert_eq!(
map.iter() map.iter()
.map(|(_, style)| escape_name(name, &get_style(style))) .map(|(_, style)| escape_name(name.as_ref(), &get_style(style)))
.collect::<Vec<String>>(), .collect::<Vec<String>>(),
map.iter() map.iter()
.map(|(correct, _)| correct.to_string()) .map(|(correct, _)| correct.to_string())
@ -659,4 +669,62 @@ mod tests {
], ],
); );
} }
#[test]
fn test_tilde_and_hash() {
check_names("~", vec![("'~'", "shell"), ("'~'", "shell-escape")]);
check_names(
"~name",
vec![("'~name'", "shell"), ("'~name'", "shell-escape")],
);
check_names(
"some~name",
vec![("some~name", "shell"), ("some~name", "shell-escape")],
);
check_names("name~", vec![("name~", "shell"), ("name~", "shell-escape")]);
check_names("#", vec![("'#'", "shell"), ("'#'", "shell-escape")]);
check_names(
"#name",
vec![("'#name'", "shell"), ("'#name'", "shell-escape")],
);
check_names(
"some#name",
vec![("some#name", "shell"), ("some#name", "shell-escape")],
);
check_names("name#", vec![("name#", "shell"), ("name#", "shell-escape")]);
}
#[test]
fn test_special_chars_in_double_quotes() {
check_names(
"can'$t",
vec![
("'can'\\''$t'", "shell"),
("'can'\\''$t'", "shell-always"),
("'can'\\''$t'", "shell-escape"),
("'can'\\''$t'", "shell-escape-always"),
],
);
check_names(
"can'`t",
vec![
("'can'\\''`t'", "shell"),
("'can'\\''`t'", "shell-always"),
("'can'\\''`t'", "shell-escape"),
("'can'\\''`t'", "shell-escape-always"),
],
);
check_names(
"can'\\t",
vec![
("'can'\\''\\t'", "shell"),
("'can'\\''\\t'", "shell-always"),
("'can'\\''\\t'", "shell-escape"),
("'can'\\''\\t'", "shell-escape-always"),
],
);
}
} }

View file

@ -12,6 +12,7 @@ use clap::OsValues;
use clap::{crate_version, App, Arg}; use clap::{crate_version, App, Arg};
use std::fs; use std::fs;
use std::path::Path; use std::path::Path;
use uucore::display::Quotable;
use uucore::error::{FromIo, UResult, USimpleError}; use uucore::error::{FromIo, UResult, USimpleError};
static ABOUT: &str = "Create the given DIRECTORY(ies) if they do not exist"; static ABOUT: &str = "Create the given DIRECTORY(ies) if they do not exist";
@ -22,13 +23,13 @@ mod options {
pub const DIRS: &str = "dirs"; pub const DIRS: &str = "dirs";
} }
fn get_usage() -> String { fn usage() -> String {
format!("{0} [OPTION]... [USER]", executable!()) format!("{0} [OPTION]... [USER]", uucore::execution_phrase())
} }
#[uucore_procs::gen_uumain] #[uucore_procs::gen_uumain]
pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let usage = get_usage(); let usage = usage();
// Linux-specific options, not implemented // Linux-specific options, not implemented
// opts.optflag("Z", "context", "set SELinux security context" + // opts.optflag("Z", "context", "set SELinux security context" +
@ -43,7 +44,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
// Not tested on Windows // Not tested on Windows
let mode: u16 = match matches.value_of(options::MODE) { let mode: u16 = match matches.value_of(options::MODE) {
Some(m) => u16::from_str_radix(m, 8) Some(m) => u16::from_str_radix(m, 8)
.map_err(|_| USimpleError::new(1, format!("invalid mode '{}'", m)))?, .map_err(|_| USimpleError::new(1, format!("invalid mode {}", m.quote())))?,
None => 0o755_u16, None => 0o755_u16,
}; };
@ -51,7 +52,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
} }
pub fn uu_app() -> App<'static, 'static> { pub fn uu_app() -> App<'static, 'static> {
App::new(executable!()) App::new(uucore::util_name())
.version(crate_version!()) .version(crate_version!())
.about(ABOUT) .about(ABOUT)
.arg( .arg(
@ -100,10 +101,14 @@ fn mkdir(path: &Path, recursive: bool, mode: u16, verbose: bool) -> UResult<()>
fs::create_dir fs::create_dir
}; };
create_dir(path).map_err_context(|| format!("cannot create directory '{}'", path.display()))?; create_dir(path).map_err_context(|| format!("cannot create directory {}", path.quote()))?;
if verbose { if verbose {
println!("{}: created directory '{}'", executable!(), path.display()); println!(
"{}: created directory {}",
uucore::util_name(),
path.quote()
);
} }
chmod(path, mode) chmod(path, mode)
@ -117,7 +122,7 @@ fn chmod(path: &Path, mode: u16) -> UResult<()> {
let mode = Permissions::from_mode(u32::from(mode)); let mode = Permissions::from_mode(u32::from(mode));
set_permissions(path, mode) set_permissions(path, mode)
.map_err_context(|| format!("cannot set permissions '{}'", path.display())) .map_err_context(|| format!("cannot set permissions {}", path.quote()))
} }
#[cfg(windows)] #[cfg(windows)]

View file

@ -11,7 +11,7 @@ extern crate uucore;
use clap::{crate_version, App, Arg}; use clap::{crate_version, App, Arg};
use libc::mkfifo; use libc::mkfifo;
use std::ffi::CString; use std::ffi::CString;
use uucore::InvalidEncodingHandling; use uucore::{display::Quotable, InvalidEncodingHandling};
static NAME: &str = "mkfifo"; static NAME: &str = "mkfifo";
static USAGE: &str = "mkfifo [OPTION]... NAME..."; static USAGE: &str = "mkfifo [OPTION]... NAME...";
@ -61,7 +61,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
mkfifo(name.as_ptr(), mode as libc::mode_t) mkfifo(name.as_ptr(), mode as libc::mode_t)
}; };
if err == -1 { if err == -1 {
show_error!("cannot create fifo '{}': File exists", f); show_error!("cannot create fifo {}: File exists", f.quote());
exit_code = 1; exit_code = 1;
} }
} }
@ -70,7 +70,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
} }
pub fn uu_app() -> App<'static, 'static> { pub fn uu_app() -> App<'static, 'static> {
App::new(executable!()) App::new(uucore::util_name())
.name(NAME) .name(NAME)
.version(crate_version!()) .version(crate_version!())
.usage(USAGE) .usage(USAGE)

View file

@ -16,9 +16,9 @@ use clap::{crate_version, App, Arg, ArgMatches};
use libc::{dev_t, mode_t}; use libc::{dev_t, mode_t};
use libc::{S_IFBLK, S_IFCHR, S_IFIFO, S_IRGRP, S_IROTH, S_IRUSR, S_IWGRP, S_IWOTH, S_IWUSR}; use libc::{S_IFBLK, S_IFCHR, S_IFIFO, S_IRGRP, S_IROTH, S_IRUSR, S_IWGRP, S_IWOTH, S_IWUSR};
use uucore::display::Quotable;
use uucore::InvalidEncodingHandling; use uucore::InvalidEncodingHandling;
static NAME: &str = "mknod";
static ABOUT: &str = "Create the special file NAME of the given TYPE."; static ABOUT: &str = "Create the special file NAME of the given TYPE.";
static USAGE: &str = "mknod [OPTION]... NAME TYPE [MAJOR MINOR]"; static USAGE: &str = "mknod [OPTION]... NAME TYPE [MAJOR MINOR]";
static LONG_HELP: &str = "Mandatory arguments to long options are mandatory for short options too. static LONG_HELP: &str = "Mandatory arguments to long options are mandatory for short options too.
@ -72,7 +72,8 @@ fn _mknod(file_name: &str, mode: mode_t, dev: dev_t) -> i32 {
} }
if errno == -1 { if errno == -1 {
let c_str = CString::new(NAME).expect("Failed to convert to CString"); let c_str = CString::new(uucore::execution_phrase().as_bytes())
.expect("Failed to convert to CString");
// shows the error from the mknod syscall // shows the error from the mknod syscall
libc::perror(c_str.as_ptr()); libc::perror(c_str.as_ptr());
} }
@ -113,7 +114,10 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
if ch == 'p' { if ch == 'p' {
if matches.is_present("major") || matches.is_present("minor") { if matches.is_present("major") || matches.is_present("minor") {
eprintln!("Fifos do not have major and minor device numbers."); eprintln!("Fifos do not have major and minor device numbers.");
eprintln!("Try '{} --help' for more information.", NAME); eprintln!(
"Try '{} --help' for more information.",
uucore::execution_phrase()
);
1 1
} else { } else {
_mknod(file_name, S_IFIFO | mode, 0) _mknod(file_name, S_IFIFO | mode, 0)
@ -122,7 +126,10 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
match (matches.value_of("major"), matches.value_of("minor")) { match (matches.value_of("major"), matches.value_of("minor")) {
(None, None) | (_, None) | (None, _) => { (None, None) | (_, None) | (None, _) => {
eprintln!("Special files require major and minor device numbers."); eprintln!("Special files require major and minor device numbers.");
eprintln!("Try '{} --help' for more information.", NAME); eprintln!(
"Try '{} --help' for more information.",
uucore::execution_phrase()
);
1 1
} }
(Some(major), Some(minor)) => { (Some(major), Some(minor)) => {
@ -145,7 +152,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
} }
pub fn uu_app() -> App<'static, 'static> { pub fn uu_app() -> App<'static, 'static> {
App::new(executable!()) App::new(uucore::util_name())
.version(crate_version!()) .version(crate_version!())
.usage(USAGE) .usage(USAGE)
.after_help(LONG_HELP) .after_help(LONG_HELP)
@ -213,7 +220,7 @@ fn valid_type(tpe: String) -> Result<(), String> {
if vec!['b', 'c', 'u', 'p'].contains(&first_char) { if vec!['b', 'c', 'u', 'p'].contains(&first_char) {
Ok(()) Ok(())
} else { } else {
Err(format!("invalid device type '{}'", tpe)) Err(format!("invalid device type {}", tpe.quote()))
} }
}) })
} }

View file

@ -12,6 +12,7 @@
extern crate uucore; extern crate uucore;
use clap::{crate_version, App, Arg}; use clap::{crate_version, App, Arg};
use uucore::display::{println_verbatim, Quotable};
use uucore::error::{FromIo, UError, UResult}; use uucore::error::{FromIo, UError, UResult};
use std::env; use std::env;
@ -36,8 +37,8 @@ static OPT_T: &str = "t";
static ARG_TEMPLATE: &str = "template"; static ARG_TEMPLATE: &str = "template";
fn get_usage() -> String { fn usage() -> String {
format!("{0} [OPTION]... [TEMPLATE]", executable!()) format!("{0} [OPTION]... [TEMPLATE]", uucore::execution_phrase())
} }
#[derive(Debug)] #[derive(Debug)]
@ -57,16 +58,20 @@ impl Display for MkTempError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
use MkTempError::*; use MkTempError::*;
match self { match self {
PersistError(p) => write!(f, "could not persist file '{}'", p.display()), PersistError(p) => write!(f, "could not persist file {}", p.quote()),
MustEndInX(s) => write!(f, "with --suffix, template '{}' must end in X", s), MustEndInX(s) => write!(f, "with --suffix, template {} must end in X", s.quote()),
TooFewXs(s) => write!(f, "too few X's in template '{}'", s), TooFewXs(s) => write!(f, "too few X's in template {}", s.quote()),
ContainsDirSeparator(s) => { ContainsDirSeparator(s) => {
write!(f, "invalid suffix '{}', contains directory separator", s) write!(
f,
"invalid suffix {}, contains directory separator",
s.quote()
)
} }
InvalidTemplate(s) => write!( InvalidTemplate(s) => write!(
f, f,
"invalid template, '{}'; with --tmpdir, it may not be absolute", "invalid template, {}; with --tmpdir, it may not be absolute",
s s.quote()
), ),
} }
} }
@ -74,7 +79,7 @@ impl Display for MkTempError {
#[uucore_procs::gen_uumain] #[uucore_procs::gen_uumain]
pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let usage = get_usage(); let usage = usage();
let matches = uu_app().usage(&usage[..]).get_matches_from(args); let matches = uu_app().usage(&usage[..]).get_matches_from(args);
@ -134,7 +139,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
} }
pub fn uu_app() -> App<'static, 'static> { pub fn uu_app() -> App<'static, 'static> {
App::new(executable!()) App::new(uucore::util_name())
.version(crate_version!()) .version(crate_version!())
.about(ABOUT) .about(ABOUT)
.arg( .arg(
@ -224,15 +229,13 @@ fn parse_template<'a>(
pub fn dry_exec(mut tmpdir: PathBuf, prefix: &str, rand: usize, suffix: &str) -> UResult<()> { pub fn dry_exec(mut tmpdir: PathBuf, prefix: &str, rand: usize, suffix: &str) -> UResult<()> {
let len = prefix.len() + suffix.len() + rand; let len = prefix.len() + suffix.len() + rand;
let mut buf = String::with_capacity(len); let mut buf = Vec::with_capacity(len);
buf.push_str(prefix); buf.extend(prefix.as_bytes());
buf.extend(iter::repeat('X').take(rand)); buf.extend(iter::repeat(b'X').take(rand));
buf.push_str(suffix); buf.extend(suffix.as_bytes());
// Randomize. // Randomize.
unsafe { let bytes = &mut buf[prefix.len()..prefix.len() + rand];
// We guarantee utf8.
let bytes = &mut buf.as_mut_vec()[prefix.len()..prefix.len() + rand];
rand::thread_rng().fill(bytes); rand::thread_rng().fill(bytes);
for byte in bytes.iter_mut() { for byte in bytes.iter_mut() {
*byte = match *byte % 62 { *byte = match *byte % 62 {
@ -242,10 +245,10 @@ pub fn dry_exec(mut tmpdir: PathBuf, prefix: &str, rand: usize, suffix: &str) ->
_ => unreachable!(), _ => unreachable!(),
} }
} }
} // We guarantee utf8.
let buf = String::from_utf8(buf).unwrap();
tmpdir.push(buf); tmpdir.push(buf);
println!("{}", tmpdir.display()); println_verbatim(tmpdir).map_err_context(|| "failed to print directory name".to_owned())
Ok(())
} }
fn exec(dir: PathBuf, prefix: &str, rand: usize, suffix: &str, make_dir: bool) -> UResult<()> { fn exec(dir: PathBuf, prefix: &str, rand: usize, suffix: &str, make_dir: bool) -> UResult<()> {
@ -274,6 +277,5 @@ fn exec(dir: PathBuf, prefix: &str, rand: usize, suffix: &str, make_dir: bool) -
.map_err(|e| MkTempError::PersistError(e.file.path().to_path_buf()))? .map_err(|e| MkTempError::PersistError(e.file.path().to_path_buf()))?
.1 .1
}; };
println!("{}", path.display()); println_verbatim(path).map_err_context(|| "failed to print directory name".to_owned())
Ok(())
} }

View file

@ -28,8 +28,12 @@ redox_termios = "0.1"
redox_syscall = "0.2" redox_syscall = "0.2"
[target.'cfg(all(unix, not(target_os = "fuchsia")))'.dependencies] [target.'cfg(all(unix, not(target_os = "fuchsia")))'.dependencies]
nix = "<=0.13" nix = "0.19"
[[bin]] [[bin]]
name = "more" name = "more"
path = "src/main.rs" path = "src/main.rs"
[package.metadata.cargo-udeps.ignore]
# Necessary for "make all"
normal = ["uucore_procs"]

View file

@ -30,6 +30,7 @@ use crossterm::{
use unicode_segmentation::UnicodeSegmentation; use unicode_segmentation::UnicodeSegmentation;
use unicode_width::UnicodeWidthStr; use unicode_width::UnicodeWidthStr;
use uucore::display::Quotable;
const BELL: &str = "\x07"; const BELL: &str = "\x07";
@ -64,12 +65,12 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
let file = Path::new(file); let file = Path::new(file);
if file.is_dir() { if file.is_dir() {
terminal::disable_raw_mode().unwrap(); terminal::disable_raw_mode().unwrap();
show_usage_error!("'{}' is a directory.", file.display()); show_usage_error!("{} is a directory.", file.quote());
return 1; return 1;
} }
if !file.exists() { if !file.exists() {
terminal::disable_raw_mode().unwrap(); terminal::disable_raw_mode().unwrap();
show_error!("cannot open {}: No such file or directory", file.display()); show_error!("cannot open {}: No such file or directory", file.quote());
return 1; return 1;
} }
if length > 1 { if length > 1 {
@ -93,7 +94,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
} }
pub fn uu_app() -> App<'static, 'static> { pub fn uu_app() -> App<'static, 'static> {
App::new(executable!()) App::new(uucore::util_name())
.about("A file perusal filter for CRT viewing.") .about("A file perusal filter for CRT viewing.")
.version(crate_version!()) .version(crate_version!())
.arg( .arg(

View file

@ -23,3 +23,7 @@ uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_p
[[bin]] [[bin]]
name = "mv" name = "mv"
path = "src/main.rs" path = "src/main.rs"
[package.metadata.cargo-udeps.ignore]
# Necessary for "make all"
normal = ["uucore_procs"]

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