mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-29 12:07:46 +00:00
Merge branch 'master' of github.com:uutils/coreutils into hbina-rm-silently-accept-presume-input-tty
This commit is contained in:
commit
ac68bc9ac7
467 changed files with 21084 additions and 6739 deletions
21
.cirrus.yml
21
.cirrus.yml
|
@ -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
|
104
.github/workflows/CICD.yml
vendored
104
.github/workflows/CICD.yml
vendored
|
@ -13,8 +13,7 @@ env:
|
|||
PROJECT_NAME: coreutils
|
||||
PROJECT_DESC: "Core universal (cross-platform) utilities"
|
||||
PROJECT_AUTH: "uutils"
|
||||
RUST_MIN_SRV: "1.43.1" ## v1.43.0
|
||||
RUST_COV_SRV: "2020-08-01" ## (~v1.47.0) supported rust version for code coverage; (date required/used by 'coverage') ## !maint: refactor when code coverage support is included in the stable channel
|
||||
RUST_MIN_SRV: "1.47.0" ## MSRV v1.47.0
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
|
@ -102,11 +101,17 @@ jobs:
|
|||
fail-fast: false
|
||||
matrix:
|
||||
job:
|
||||
- { os: ubuntu-latest , features: feat_os_unix }
|
||||
- { os: ubuntu-latest }
|
||||
- { os: macos-latest , features: feat_os_macos }
|
||||
- { os: windows-latest , features: feat_os_windows }
|
||||
steps:
|
||||
- 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
|
||||
id: vars
|
||||
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; }
|
||||
# target-specific options
|
||||
# * CARGO_FEATURES_OPTION
|
||||
CARGO_FEATURES_OPTION='' ;
|
||||
if [ -n "${{ matrix.job.features }}" ]; then CARGO_FEATURES_OPTION='--features "${{ matrix.job.features }}"' ; fi
|
||||
CARGO_FEATURES_OPTION='--all-features' ;
|
||||
if [ -n "${{ matrix.job.features }}" ]; then CARGO_FEATURES_OPTION='--features ${{ matrix.job.features }}' ; fi
|
||||
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
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
|
@ -130,7 +140,7 @@ jobs:
|
|||
run: |
|
||||
## `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>
|
||||
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:
|
||||
name: Style/spelling
|
||||
|
@ -249,6 +259,8 @@ jobs:
|
|||
# { os, target, cargo-options, features, use-cross, toolchain }
|
||||
- { os: ubuntu-latest , target: arm-unknown-linux-gnueabihf , features: feat_os_unix_gnueabihf , use-cross: use-cross }
|
||||
- { os: ubuntu-latest , target: aarch64-unknown-linux-gnu , features: feat_os_unix_gnueabihf , use-cross: use-cross }
|
||||
- { os: ubuntu-latest , target: x86_64-unknown-linux-gnu , features: feat_os_unix , use-cross: use-cross }
|
||||
# - { os: ubuntu-latest , target: x86_64-unknown-linux-gnu , features: feat_selinux , use-cross: use-cross }
|
||||
# - { os: ubuntu-18.04 , target: i586-unknown-linux-gnu , features: feat_os_unix , use-cross: use-cross } ## note: older windows platform; not required, dev-FYI only
|
||||
# - { os: ubuntu-18.04 , target: i586-unknown-linux-gnu , features: feat_os_unix , use-cross: use-cross } ## note: older windows platform; not required, dev-FYI only
|
||||
- { os: ubuntu-18.04 , target: i686-unknown-linux-gnu , features: feat_os_unix , use-cross: use-cross }
|
||||
|
@ -519,6 +531,52 @@ jobs:
|
|||
n_fails=$(echo "$output" | grep "^FAIL:\s" | wc --lines)
|
||||
if [ $n_fails -gt 0 ] ; then echo "::warning ::${n_fails}+ test failures" ; fi
|
||||
|
||||
test_freebsd:
|
||||
runs-on: macos-10.15
|
||||
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:
|
||||
name: Code Coverage
|
||||
runs-on: ${{ matrix.job.os }}
|
||||
|
@ -548,7 +606,7 @@ jobs:
|
|||
## VARs setup
|
||||
outputs() { step_id="vars"; for var in "$@" ; do echo steps.${step_id}.outputs.${var}="${!var}"; echo ::set-output name=${var}::${!var}; done; }
|
||||
# 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
|
||||
case ${{ matrix.job.os }} in windows-*) TOOLCHAIN="$TOOLCHAIN-x86_64-pc-windows-gnu" ;; esac;
|
||||
# * use requested TOOLCHAIN if specified
|
||||
|
@ -650,3 +708,35 @@ jobs:
|
|||
flags: ${{ steps.vars.outputs.CODECOV_FLAGS }}
|
||||
name: codecov-umbrella
|
||||
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
|
||||
|
|
39
.github/workflows/GnuTests.yml
vendored
39
.github/workflows/GnuTests.yml
vendored
|
@ -90,3 +90,42 @@ jobs:
|
|||
with:
|
||||
name: gnu-result
|
||||
path: gnu-result.json
|
||||
- name: Download the result
|
||||
uses: dawidd6/action-download-artifact@v2
|
||||
with:
|
||||
workflow: GnuTests.yml
|
||||
name: gnu-result
|
||||
repo: uutils/coreutils
|
||||
branch: master
|
||||
path: dl
|
||||
- name: Download the log
|
||||
uses: dawidd6/action-download-artifact@v2
|
||||
with:
|
||||
workflow: GnuTests.yml
|
||||
name: test-report
|
||||
repo: uutils/coreutils
|
||||
branch: master
|
||||
path: dl
|
||||
- name: Compare failing tests against master
|
||||
shell: bash
|
||||
run: |
|
||||
OLD_FAILING=$(sed -n "s/^FAIL: \([[:print:]]\+\).*/\1/p" dl/test-suite.log | sort)
|
||||
NEW_FAILING=$(sed -n "s/^FAIL: \([[:print:]]\+\).*/\1/p" gnu/tests/test-suite.log | sort)
|
||||
for LINE in $OLD_FAILING
|
||||
do
|
||||
if ! grep -Fxq $LINE<<<"$NEW_FAILING"; then
|
||||
echo "::warning ::Congrats! The gnu test $LINE is now passing!"
|
||||
fi
|
||||
done
|
||||
for LINE in $NEW_FAILING
|
||||
do
|
||||
if ! grep -Fxq $LINE<<<"$OLD_FAILING"
|
||||
then
|
||||
echo "::error ::GNU test failed: $LINE. $LINE is passing on 'master'. Maybe you have to rebase?"
|
||||
fi
|
||||
done
|
||||
- name: Compare against master results
|
||||
shell: bash
|
||||
run: |
|
||||
mv dl/gnu-result.json master-gnu-result.json
|
||||
python uutils/util/compare_gnu_result.py
|
||||
|
|
|
@ -11,7 +11,7 @@ repos:
|
|||
- id: rust-clippy
|
||||
name: Rust clippy
|
||||
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
|
||||
types: [file, rust]
|
||||
language: system
|
||||
|
|
2
.vscode/cSpell.json
vendored
2
.vscode/cSpell.json
vendored
|
@ -11,7 +11,7 @@
|
|||
{ "name": "workspace", "path": "./cspell.dictionaries/workspace.wordlist.txt" }
|
||||
],
|
||||
// ignorePaths - a list of globs to specify which files are to be ignored
|
||||
"ignorePaths": ["Cargo.lock", "target/**", "tests/**/fixtures/**"],
|
||||
"ignorePaths": ["Cargo.lock", "target/**", "tests/**/fixtures/**", "src/uu/dd/test-resources/**"],
|
||||
// ignoreWords - a list of words to be ignored (even if they are in the flagWords)
|
||||
"ignoreWords": [],
|
||||
// words - list of words to be always considered correct
|
||||
|
|
38
.vscode/cspell.dictionaries/jargon.wordlist.txt
vendored
38
.vscode/cspell.dictionaries/jargon.wordlist.txt
vendored
|
@ -1,3 +1,4 @@
|
|||
AFAICT
|
||||
arity
|
||||
autogenerate
|
||||
autogenerated
|
||||
|
@ -8,10 +9,13 @@ bytewise
|
|||
canonicalization
|
||||
canonicalize
|
||||
canonicalizing
|
||||
codepoint
|
||||
codepoints
|
||||
colorizable
|
||||
colorize
|
||||
coprime
|
||||
consts
|
||||
conv
|
||||
cyclomatic
|
||||
dedup
|
||||
deduplication
|
||||
|
@ -23,6 +27,7 @@ dev
|
|||
devs
|
||||
discoverability
|
||||
duplicative
|
||||
dsync
|
||||
enqueue
|
||||
errored
|
||||
executable
|
||||
|
@ -30,17 +35,30 @@ executables
|
|||
exponentiate
|
||||
eval
|
||||
falsey
|
||||
fileio
|
||||
flamegraph
|
||||
fullblock
|
||||
getfacl
|
||||
gibi
|
||||
gibibytes
|
||||
glob
|
||||
globbing
|
||||
hardcode
|
||||
hardcoded
|
||||
hardcoding
|
||||
hardfloat
|
||||
hardlink
|
||||
hardlinks
|
||||
hasher
|
||||
hashsums
|
||||
infile
|
||||
iflag
|
||||
iflags
|
||||
kibi
|
||||
kibibytes
|
||||
libacl
|
||||
lcase
|
||||
lossily
|
||||
mebi
|
||||
mebibytes
|
||||
mergeable
|
||||
|
@ -49,8 +67,21 @@ microbenchmarks
|
|||
microbenchmarking
|
||||
multibyte
|
||||
multicall
|
||||
noatime
|
||||
nocache
|
||||
nocreat
|
||||
noctty
|
||||
noerror
|
||||
nofollow
|
||||
nolinks
|
||||
nonblock
|
||||
nonportable
|
||||
nonprinting
|
||||
notrunc
|
||||
noxfer
|
||||
ofile
|
||||
oflag
|
||||
oflags
|
||||
peekable
|
||||
performant
|
||||
precompiled
|
||||
|
@ -68,11 +99,15 @@ seedable
|
|||
semver
|
||||
semiprime
|
||||
semiprimes
|
||||
setfacl
|
||||
shortcode
|
||||
shortcodes
|
||||
siginfo
|
||||
sigusr
|
||||
subcommand
|
||||
subexpression
|
||||
submodule
|
||||
sync
|
||||
symlink
|
||||
symlinks
|
||||
syscall
|
||||
|
@ -80,12 +115,15 @@ syscalls
|
|||
tokenize
|
||||
toolchain
|
||||
truthy
|
||||
ucase
|
||||
unbuffered
|
||||
udeps
|
||||
unescape
|
||||
unintuitive
|
||||
unprefixed
|
||||
unportable
|
||||
unsync
|
||||
urand
|
||||
whitespace
|
||||
wordlist
|
||||
wordlists
|
||||
|
|
|
@ -151,6 +151,9 @@ Sylvestre Ledru
|
|||
T Jameson Little
|
||||
Jameson
|
||||
Little
|
||||
Thomas Queiroz
|
||||
Thomas
|
||||
Queiroz
|
||||
Tobias Bohumir Schottdorf
|
||||
Tobias
|
||||
Bohumir
|
||||
|
|
|
@ -8,6 +8,7 @@ csh
|
|||
globstar
|
||||
inotify
|
||||
localtime
|
||||
mksh
|
||||
mountinfo
|
||||
mountpoint
|
||||
mtab
|
||||
|
@ -63,6 +64,7 @@ abspath
|
|||
addprefix
|
||||
addsuffix
|
||||
endef
|
||||
findstring
|
||||
firstword
|
||||
ifeq
|
||||
ifneq
|
||||
|
@ -89,5 +91,10 @@ markdownlint
|
|||
rerast
|
||||
rollup
|
||||
sed
|
||||
selinuxenabled
|
||||
sestatus
|
||||
wslpath
|
||||
xargs
|
||||
|
||||
# * directories
|
||||
sbin
|
||||
|
|
|
@ -9,12 +9,14 @@ aho-corasick
|
|||
backtrace
|
||||
blake2b_simd
|
||||
bstr
|
||||
bytecount
|
||||
byteorder
|
||||
chacha
|
||||
chrono
|
||||
conv
|
||||
corasick
|
||||
crossterm
|
||||
exacl
|
||||
filetime
|
||||
formatteriteminfo
|
||||
fsext
|
||||
|
@ -66,8 +68,10 @@ structs
|
|||
substr
|
||||
splitn
|
||||
trunc
|
||||
uninit
|
||||
|
||||
# * uutils
|
||||
basenc
|
||||
chcon
|
||||
chgrp
|
||||
chmod
|
||||
|
@ -105,11 +109,18 @@ whoami
|
|||
|
||||
# * vars/errno
|
||||
errno
|
||||
EACCES
|
||||
EBADF
|
||||
EBUSY
|
||||
EEXIST
|
||||
EINVAL
|
||||
ENODATA
|
||||
ENOENT
|
||||
ENOSYS
|
||||
EPERM
|
||||
ENOTEMPTY
|
||||
EOPNOTSUPP
|
||||
EPERM
|
||||
EROFS
|
||||
|
||||
# * vars/fcntl
|
||||
F_GETFL
|
||||
|
@ -118,7 +129,9 @@ fcntl
|
|||
vmsplice
|
||||
|
||||
# * vars/libc
|
||||
COMFOLLOW
|
||||
FILENO
|
||||
FTSENT
|
||||
HOSTSIZE
|
||||
IDSIZE
|
||||
IFBLK
|
||||
|
@ -151,12 +164,14 @@ SIGTERM
|
|||
SYS_fdatasync
|
||||
SYS_syncfs
|
||||
USERSIZE
|
||||
accpath
|
||||
addrinfo
|
||||
addrlen
|
||||
blocksize
|
||||
canonname
|
||||
chroot
|
||||
dlsym
|
||||
execvp
|
||||
fdatasync
|
||||
freeaddrinfo
|
||||
getaddrinfo
|
||||
|
@ -174,15 +189,18 @@ inode
|
|||
inodes
|
||||
isatty
|
||||
lchown
|
||||
pathlen
|
||||
setgid
|
||||
setgroups
|
||||
settime
|
||||
setuid
|
||||
socktype
|
||||
statfs
|
||||
statp
|
||||
statvfs
|
||||
strcmp
|
||||
strerror
|
||||
strlen
|
||||
syncfs
|
||||
umask
|
||||
waitpid
|
||||
|
@ -260,6 +278,7 @@ ULONG
|
|||
ULONGLONG
|
||||
UNLEN
|
||||
WCHAR
|
||||
WSADATA
|
||||
errhandlingapi
|
||||
fileapi
|
||||
handleapi
|
||||
|
@ -274,6 +293,13 @@ winerror
|
|||
winnt
|
||||
winsock
|
||||
|
||||
# * vars/selinux
|
||||
freecon
|
||||
getfilecon
|
||||
lgetfilecon
|
||||
lsetfilecon
|
||||
setfilecon
|
||||
|
||||
# * vars/uucore
|
||||
optflag
|
||||
optflagmulti
|
||||
|
@ -298,3 +324,6 @@ uucore_procs
|
|||
uumain
|
||||
uutil
|
||||
uutils
|
||||
|
||||
# * function names
|
||||
getcwd
|
||||
|
|
2
.vscode/settings.json
vendored
2
.vscode/settings.json
vendored
|
@ -1,2 +0,0 @@
|
|||
{
|
||||
}
|
890
Cargo.lock
generated
890
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
237
Cargo.toml
237
Cargo.toml
|
@ -1,9 +1,11 @@
|
|||
# coreutils (uutils)
|
||||
# * see the repository LICENSE, README, and CONTRIBUTING files for more information
|
||||
|
||||
# spell-checker:ignore (libs) libselinux
|
||||
|
||||
[package]
|
||||
name = "coreutils"
|
||||
version = "0.0.7"
|
||||
version = "0.0.8"
|
||||
authors = ["uutils developers"]
|
||||
license = "MIT"
|
||||
description = "coreutils ~ GNU coreutils (updated); implemented as universal (cross-platform) utils, written in Rust"
|
||||
|
@ -33,6 +35,7 @@ feat_common_core = [
|
|||
"base32",
|
||||
"base64",
|
||||
"basename",
|
||||
"basenc",
|
||||
"cat",
|
||||
"cksum",
|
||||
"comm",
|
||||
|
@ -43,6 +46,7 @@ feat_common_core = [
|
|||
"df",
|
||||
"dircolors",
|
||||
"dirname",
|
||||
"dd",
|
||||
"du",
|
||||
"echo",
|
||||
"env",
|
||||
|
@ -91,8 +95,10 @@ feat_common_core = [
|
|||
"true",
|
||||
"truncate",
|
||||
"tsort",
|
||||
"touch",
|
||||
"unexpand",
|
||||
"uniq",
|
||||
"unlink",
|
||||
"wc",
|
||||
"yes",
|
||||
]
|
||||
|
@ -138,6 +144,16 @@ feat_os_unix_musl = [
|
|||
#
|
||||
"feat_require_unix",
|
||||
]
|
||||
# "feat_selinux" == set of utilities providing support for SELinux Security Context if enabled with `--features feat_selinux`.
|
||||
# NOTE:
|
||||
# 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.
|
||||
feat_selinux = ["cp/selinux", "id/selinux", "ls/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)
|
||||
#
|
||||
# ** NOTE: these `feat_require_...` sets should be minimized as much as possible to encourage cross-platform availability of utilities
|
||||
|
@ -167,7 +183,6 @@ feat_require_unix = [
|
|||
"timeout",
|
||||
"tty",
|
||||
"uname",
|
||||
"unlink",
|
||||
]
|
||||
# "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?>
|
||||
|
@ -177,6 +192,11 @@ feat_require_unix_utmpx = [
|
|||
"users",
|
||||
"who",
|
||||
]
|
||||
# "feat_require_selinux" == set of utilities depending on SELinux.
|
||||
feat_require_selinux = [
|
||||
"chcon",
|
||||
"runcon",
|
||||
]
|
||||
## (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 = [
|
||||
|
@ -204,9 +224,8 @@ feat_os_unix_fuchsia = [
|
|||
feat_os_unix_redox = [
|
||||
"feat_common_core",
|
||||
#
|
||||
"uname",
|
||||
"chmod",
|
||||
"install",
|
||||
"uname",
|
||||
]
|
||||
# "feat_os_windows_legacy" == slightly restricted set of utilities which can be built/run on early windows platforms (eg, "WinXP")
|
||||
feat_os_windows_legacy = [
|
||||
|
@ -227,106 +246,111 @@ test = [ "uu_test" ]
|
|||
[dependencies]
|
||||
clap = { version = "2.33", features = ["wrap_help"] }
|
||||
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
|
||||
uucore = { version=">=0.0.9", package="uucore", path="src/uucore" }
|
||||
textwrap = { version="0.14", features=["terminal_size"] }
|
||||
uucore = { version=">=0.0.10", package="uucore", path="src/uucore" }
|
||||
selinux = { version="0.2.3", optional = true }
|
||||
# * uutils
|
||||
uu_test = { optional=true, version="0.0.7", package="uu_test", path="src/uu/test" }
|
||||
uu_test = { optional=true, version="0.0.8", package="uu_test", path="src/uu/test" }
|
||||
#
|
||||
arch = { optional=true, version="0.0.7", package="uu_arch", path="src/uu/arch" }
|
||||
base32 = { optional=true, version="0.0.7", package="uu_base32", path="src/uu/base32" }
|
||||
base64 = { optional=true, version="0.0.7", package="uu_base64", path="src/uu/base64" }
|
||||
basename = { optional=true, version="0.0.7", package="uu_basename", path="src/uu/basename" }
|
||||
cat = { optional=true, version="0.0.7", package="uu_cat", path="src/uu/cat" }
|
||||
chgrp = { optional=true, version="0.0.7", package="uu_chgrp", path="src/uu/chgrp" }
|
||||
chmod = { optional=true, version="0.0.7", package="uu_chmod", path="src/uu/chmod" }
|
||||
chown = { optional=true, version="0.0.7", package="uu_chown", path="src/uu/chown" }
|
||||
chroot = { optional=true, version="0.0.7", package="uu_chroot", path="src/uu/chroot" }
|
||||
cksum = { optional=true, version="0.0.7", package="uu_cksum", path="src/uu/cksum" }
|
||||
comm = { optional=true, version="0.0.7", package="uu_comm", path="src/uu/comm" }
|
||||
cp = { optional=true, version="0.0.7", package="uu_cp", path="src/uu/cp" }
|
||||
csplit = { optional=true, version="0.0.7", package="uu_csplit", path="src/uu/csplit" }
|
||||
cut = { optional=true, version="0.0.7", package="uu_cut", path="src/uu/cut" }
|
||||
date = { optional=true, version="0.0.7", package="uu_date", path="src/uu/date" }
|
||||
df = { optional=true, version="0.0.7", package="uu_df", path="src/uu/df" }
|
||||
dircolors= { optional=true, version="0.0.7", package="uu_dircolors", path="src/uu/dircolors" }
|
||||
dirname = { optional=true, version="0.0.7", package="uu_dirname", path="src/uu/dirname" }
|
||||
du = { optional=true, version="0.0.7", package="uu_du", path="src/uu/du" }
|
||||
echo = { optional=true, version="0.0.7", package="uu_echo", path="src/uu/echo" }
|
||||
env = { optional=true, version="0.0.7", package="uu_env", path="src/uu/env" }
|
||||
expand = { optional=true, version="0.0.7", package="uu_expand", path="src/uu/expand" }
|
||||
expr = { optional=true, version="0.0.7", package="uu_expr", path="src/uu/expr" }
|
||||
factor = { optional=true, version="0.0.7", package="uu_factor", path="src/uu/factor" }
|
||||
false = { optional=true, version="0.0.7", package="uu_false", path="src/uu/false" }
|
||||
fmt = { optional=true, version="0.0.7", package="uu_fmt", path="src/uu/fmt" }
|
||||
fold = { optional=true, version="0.0.7", package="uu_fold", path="src/uu/fold" }
|
||||
groups = { optional=true, version="0.0.7", package="uu_groups", path="src/uu/groups" }
|
||||
hashsum = { optional=true, version="0.0.7", package="uu_hashsum", path="src/uu/hashsum" }
|
||||
head = { optional=true, version="0.0.7", package="uu_head", path="src/uu/head" }
|
||||
hostid = { optional=true, version="0.0.7", package="uu_hostid", path="src/uu/hostid" }
|
||||
hostname = { optional=true, version="0.0.7", package="uu_hostname", path="src/uu/hostname" }
|
||||
id = { optional=true, version="0.0.7", package="uu_id", path="src/uu/id" }
|
||||
install = { optional=true, version="0.0.7", package="uu_install", path="src/uu/install" }
|
||||
join = { optional=true, version="0.0.7", package="uu_join", path="src/uu/join" }
|
||||
kill = { optional=true, version="0.0.7", package="uu_kill", path="src/uu/kill" }
|
||||
link = { optional=true, version="0.0.7", package="uu_link", path="src/uu/link" }
|
||||
ln = { optional=true, version="0.0.7", package="uu_ln", path="src/uu/ln" }
|
||||
ls = { optional=true, version="0.0.7", package="uu_ls", path="src/uu/ls" }
|
||||
logname = { optional=true, version="0.0.7", package="uu_logname", path="src/uu/logname" }
|
||||
mkdir = { optional=true, version="0.0.7", package="uu_mkdir", path="src/uu/mkdir" }
|
||||
mkfifo = { optional=true, version="0.0.7", package="uu_mkfifo", path="src/uu/mkfifo" }
|
||||
mknod = { optional=true, version="0.0.7", package="uu_mknod", path="src/uu/mknod" }
|
||||
mktemp = { optional=true, version="0.0.7", package="uu_mktemp", path="src/uu/mktemp" }
|
||||
more = { optional=true, version="0.0.7", package="uu_more", path="src/uu/more" }
|
||||
mv = { optional=true, version="0.0.7", package="uu_mv", path="src/uu/mv" }
|
||||
nice = { optional=true, version="0.0.7", package="uu_nice", path="src/uu/nice" }
|
||||
nl = { optional=true, version="0.0.7", package="uu_nl", path="src/uu/nl" }
|
||||
nohup = { optional=true, version="0.0.7", package="uu_nohup", path="src/uu/nohup" }
|
||||
nproc = { optional=true, version="0.0.7", package="uu_nproc", path="src/uu/nproc" }
|
||||
numfmt = { optional=true, version="0.0.7", package="uu_numfmt", path="src/uu/numfmt" }
|
||||
od = { optional=true, version="0.0.7", package="uu_od", path="src/uu/od" }
|
||||
paste = { optional=true, version="0.0.7", package="uu_paste", path="src/uu/paste" }
|
||||
pathchk = { optional=true, version="0.0.7", package="uu_pathchk", path="src/uu/pathchk" }
|
||||
pinky = { optional=true, version="0.0.7", package="uu_pinky", path="src/uu/pinky" }
|
||||
pr = { optional=true, version="0.0.7", package="uu_pr", path="src/uu/pr" }
|
||||
printenv = { optional=true, version="0.0.7", package="uu_printenv", path="src/uu/printenv" }
|
||||
printf = { optional=true, version="0.0.7", package="uu_printf", path="src/uu/printf" }
|
||||
ptx = { optional=true, version="0.0.7", package="uu_ptx", path="src/uu/ptx" }
|
||||
pwd = { optional=true, version="0.0.7", package="uu_pwd", path="src/uu/pwd" }
|
||||
readlink = { optional=true, version="0.0.7", package="uu_readlink", path="src/uu/readlink" }
|
||||
realpath = { optional=true, version="0.0.7", package="uu_realpath", path="src/uu/realpath" }
|
||||
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" }
|
||||
rmdir = { optional=true, version="0.0.7", package="uu_rmdir", path="src/uu/rmdir" }
|
||||
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" }
|
||||
shuf = { optional=true, version="0.0.7", package="uu_shuf", path="src/uu/shuf" }
|
||||
sleep = { optional=true, version="0.0.7", package="uu_sleep", path="src/uu/sleep" }
|
||||
sort = { optional=true, version="0.0.7", package="uu_sort", path="src/uu/sort" }
|
||||
split = { optional=true, version="0.0.7", package="uu_split", path="src/uu/split" }
|
||||
stat = { optional=true, version="0.0.7", package="uu_stat", path="src/uu/stat" }
|
||||
stdbuf = { optional=true, version="0.0.7", package="uu_stdbuf", path="src/uu/stdbuf" }
|
||||
sum = { optional=true, version="0.0.7", package="uu_sum", path="src/uu/sum" }
|
||||
sync = { optional=true, version="0.0.7", package="uu_sync", path="src/uu/sync" }
|
||||
tac = { optional=true, version="0.0.7", package="uu_tac", path="src/uu/tac" }
|
||||
tail = { optional=true, version="0.0.7", package="uu_tail", path="src/uu/tail" }
|
||||
tee = { optional=true, version="0.0.7", package="uu_tee", path="src/uu/tee" }
|
||||
timeout = { optional=true, version="0.0.7", package="uu_timeout", path="src/uu/timeout" }
|
||||
touch = { optional=true, version="0.0.7", package="uu_touch", path="src/uu/touch" }
|
||||
tr = { optional=true, version="0.0.7", package="uu_tr", path="src/uu/tr" }
|
||||
true = { optional=true, version="0.0.7", package="uu_true", path="src/uu/true" }
|
||||
truncate = { optional=true, version="0.0.7", package="uu_truncate", path="src/uu/truncate" }
|
||||
tsort = { optional=true, version="0.0.7", package="uu_tsort", path="src/uu/tsort" }
|
||||
tty = { optional=true, version="0.0.7", package="uu_tty", path="src/uu/tty" }
|
||||
uname = { optional=true, version="0.0.7", package="uu_uname", path="src/uu/uname" }
|
||||
unexpand = { optional=true, version="0.0.7", package="uu_unexpand", path="src/uu/unexpand" }
|
||||
uniq = { optional=true, version="0.0.7", package="uu_uniq", path="src/uu/uniq" }
|
||||
unlink = { optional=true, version="0.0.7", package="uu_unlink", path="src/uu/unlink" }
|
||||
uptime = { optional=true, version="0.0.7", package="uu_uptime", path="src/uu/uptime" }
|
||||
users = { optional=true, version="0.0.7", package="uu_users", path="src/uu/users" }
|
||||
wc = { optional=true, version="0.0.7", package="uu_wc", path="src/uu/wc" }
|
||||
who = { optional=true, version="0.0.7", package="uu_who", path="src/uu/who" }
|
||||
whoami = { optional=true, version="0.0.7", package="uu_whoami", path="src/uu/whoami" }
|
||||
yes = { optional=true, version="0.0.7", package="uu_yes", path="src/uu/yes" }
|
||||
arch = { optional=true, version="0.0.8", package="uu_arch", path="src/uu/arch" }
|
||||
base32 = { optional=true, version="0.0.8", package="uu_base32", path="src/uu/base32" }
|
||||
base64 = { optional=true, version="0.0.8", package="uu_base64", path="src/uu/base64" }
|
||||
basename = { optional=true, version="0.0.8", package="uu_basename", path="src/uu/basename" }
|
||||
basenc = { optional=true, version="0.0.8", package="uu_basenc", path="src/uu/basenc" }
|
||||
cat = { optional=true, version="0.0.8", package="uu_cat", path="src/uu/cat" }
|
||||
chcon = { optional=true, version="0.0.8", package="uu_chcon", path="src/uu/chcon" }
|
||||
chgrp = { optional=true, version="0.0.8", package="uu_chgrp", path="src/uu/chgrp" }
|
||||
chmod = { optional=true, version="0.0.8", package="uu_chmod", path="src/uu/chmod" }
|
||||
chown = { optional=true, version="0.0.8", package="uu_chown", path="src/uu/chown" }
|
||||
chroot = { optional=true, version="0.0.8", package="uu_chroot", path="src/uu/chroot" }
|
||||
cksum = { optional=true, version="0.0.8", package="uu_cksum", path="src/uu/cksum" }
|
||||
comm = { optional=true, version="0.0.8", package="uu_comm", path="src/uu/comm" }
|
||||
cp = { optional=true, version="0.0.8", package="uu_cp", path="src/uu/cp" }
|
||||
csplit = { optional=true, version="0.0.8", package="uu_csplit", path="src/uu/csplit" }
|
||||
cut = { optional=true, version="0.0.8", package="uu_cut", path="src/uu/cut" }
|
||||
date = { optional=true, version="0.0.8", package="uu_date", path="src/uu/date" }
|
||||
dd = { optional=true, version="0.0.8", package="uu_dd", path="src/uu/dd" }
|
||||
df = { optional=true, version="0.0.8", package="uu_df", path="src/uu/df" }
|
||||
dircolors= { optional=true, version="0.0.8", package="uu_dircolors", path="src/uu/dircolors" }
|
||||
dirname = { optional=true, version="0.0.8", package="uu_dirname", path="src/uu/dirname" }
|
||||
du = { optional=true, version="0.0.8", package="uu_du", path="src/uu/du" }
|
||||
echo = { optional=true, version="0.0.8", package="uu_echo", path="src/uu/echo" }
|
||||
env = { optional=true, version="0.0.8", package="uu_env", path="src/uu/env" }
|
||||
expand = { optional=true, version="0.0.8", package="uu_expand", path="src/uu/expand" }
|
||||
expr = { optional=true, version="0.0.8", package="uu_expr", path="src/uu/expr" }
|
||||
factor = { optional=true, version="0.0.8", package="uu_factor", path="src/uu/factor" }
|
||||
false = { optional=true, version="0.0.8", package="uu_false", path="src/uu/false" }
|
||||
fmt = { optional=true, version="0.0.8", package="uu_fmt", path="src/uu/fmt" }
|
||||
fold = { optional=true, version="0.0.8", package="uu_fold", path="src/uu/fold" }
|
||||
groups = { optional=true, version="0.0.8", package="uu_groups", path="src/uu/groups" }
|
||||
hashsum = { optional=true, version="0.0.8", package="uu_hashsum", path="src/uu/hashsum" }
|
||||
head = { optional=true, version="0.0.8", package="uu_head", path="src/uu/head" }
|
||||
hostid = { optional=true, version="0.0.8", package="uu_hostid", path="src/uu/hostid" }
|
||||
hostname = { optional=true, version="0.0.8", package="uu_hostname", path="src/uu/hostname" }
|
||||
id = { optional=true, version="0.0.8", package="uu_id", path="src/uu/id" }
|
||||
install = { optional=true, version="0.0.8", package="uu_install", path="src/uu/install" }
|
||||
join = { optional=true, version="0.0.8", package="uu_join", path="src/uu/join" }
|
||||
kill = { optional=true, version="0.0.8", package="uu_kill", path="src/uu/kill" }
|
||||
link = { optional=true, version="0.0.8", package="uu_link", path="src/uu/link" }
|
||||
ln = { optional=true, version="0.0.8", package="uu_ln", path="src/uu/ln" }
|
||||
ls = { optional=true, version="0.0.8", package="uu_ls", path="src/uu/ls" }
|
||||
logname = { optional=true, version="0.0.8", package="uu_logname", path="src/uu/logname" }
|
||||
mkdir = { optional=true, version="0.0.8", package="uu_mkdir", path="src/uu/mkdir" }
|
||||
mkfifo = { optional=true, version="0.0.8", package="uu_mkfifo", path="src/uu/mkfifo" }
|
||||
mknod = { optional=true, version="0.0.8", package="uu_mknod", path="src/uu/mknod" }
|
||||
mktemp = { optional=true, version="0.0.8", package="uu_mktemp", path="src/uu/mktemp" }
|
||||
more = { optional=true, version="0.0.8", package="uu_more", path="src/uu/more" }
|
||||
mv = { optional=true, version="0.0.8", package="uu_mv", path="src/uu/mv" }
|
||||
nice = { optional=true, version="0.0.8", package="uu_nice", path="src/uu/nice" }
|
||||
nl = { optional=true, version="0.0.8", package="uu_nl", path="src/uu/nl" }
|
||||
nohup = { optional=true, version="0.0.8", package="uu_nohup", path="src/uu/nohup" }
|
||||
nproc = { optional=true, version="0.0.8", package="uu_nproc", path="src/uu/nproc" }
|
||||
numfmt = { optional=true, version="0.0.8", package="uu_numfmt", path="src/uu/numfmt" }
|
||||
od = { optional=true, version="0.0.8", package="uu_od", path="src/uu/od" }
|
||||
paste = { optional=true, version="0.0.8", package="uu_paste", path="src/uu/paste" }
|
||||
pathchk = { optional=true, version="0.0.8", package="uu_pathchk", path="src/uu/pathchk" }
|
||||
pinky = { optional=true, version="0.0.8", package="uu_pinky", path="src/uu/pinky" }
|
||||
pr = { optional=true, version="0.0.8", package="uu_pr", path="src/uu/pr" }
|
||||
printenv = { optional=true, version="0.0.8", package="uu_printenv", path="src/uu/printenv" }
|
||||
printf = { optional=true, version="0.0.8", package="uu_printf", path="src/uu/printf" }
|
||||
ptx = { optional=true, version="0.0.8", package="uu_ptx", path="src/uu/ptx" }
|
||||
pwd = { optional=true, version="0.0.8", package="uu_pwd", path="src/uu/pwd" }
|
||||
readlink = { optional=true, version="0.0.8", package="uu_readlink", path="src/uu/readlink" }
|
||||
realpath = { optional=true, version="0.0.8", package="uu_realpath", path="src/uu/realpath" }
|
||||
relpath = { optional=true, version="0.0.8", package="uu_relpath", path="src/uu/relpath" }
|
||||
rm = { optional=true, version="0.0.8", package="uu_rm", path="src/uu/rm" }
|
||||
rmdir = { optional=true, version="0.0.8", package="uu_rmdir", path="src/uu/rmdir" }
|
||||
runcon = { optional=true, version="0.0.8", package="uu_runcon", path="src/uu/runcon" }
|
||||
seq = { optional=true, version="0.0.8", package="uu_seq", path="src/uu/seq" }
|
||||
shred = { optional=true, version="0.0.8", package="uu_shred", path="src/uu/shred" }
|
||||
shuf = { optional=true, version="0.0.8", package="uu_shuf", path="src/uu/shuf" }
|
||||
sleep = { optional=true, version="0.0.8", package="uu_sleep", path="src/uu/sleep" }
|
||||
sort = { optional=true, version="0.0.8", package="uu_sort", path="src/uu/sort" }
|
||||
split = { optional=true, version="0.0.8", package="uu_split", path="src/uu/split" }
|
||||
stat = { optional=true, version="0.0.8", package="uu_stat", path="src/uu/stat" }
|
||||
stdbuf = { optional=true, version="0.0.8", package="uu_stdbuf", path="src/uu/stdbuf" }
|
||||
sum = { optional=true, version="0.0.8", package="uu_sum", path="src/uu/sum" }
|
||||
sync = { optional=true, version="0.0.8", package="uu_sync", path="src/uu/sync" }
|
||||
tac = { optional=true, version="0.0.8", package="uu_tac", path="src/uu/tac" }
|
||||
tail = { optional=true, version="0.0.8", package="uu_tail", path="src/uu/tail" }
|
||||
tee = { optional=true, version="0.0.8", package="uu_tee", path="src/uu/tee" }
|
||||
timeout = { optional=true, version="0.0.8", package="uu_timeout", path="src/uu/timeout" }
|
||||
touch = { optional=true, version="0.0.8", package="uu_touch", path="src/uu/touch" }
|
||||
tr = { optional=true, version="0.0.8", package="uu_tr", path="src/uu/tr" }
|
||||
true = { optional=true, version="0.0.8", package="uu_true", path="src/uu/true" }
|
||||
truncate = { optional=true, version="0.0.8", package="uu_truncate", path="src/uu/truncate" }
|
||||
tsort = { optional=true, version="0.0.8", package="uu_tsort", path="src/uu/tsort" }
|
||||
tty = { optional=true, version="0.0.8", package="uu_tty", path="src/uu/tty" }
|
||||
uname = { optional=true, version="0.0.8", package="uu_uname", path="src/uu/uname" }
|
||||
unexpand = { optional=true, version="0.0.8", package="uu_unexpand", path="src/uu/unexpand" }
|
||||
uniq = { optional=true, version="0.0.8", package="uu_uniq", path="src/uu/uniq" }
|
||||
unlink = { optional=true, version="0.0.8", package="uu_unlink", path="src/uu/unlink" }
|
||||
uptime = { optional=true, version="0.0.8", package="uu_uptime", path="src/uu/uptime" }
|
||||
users = { optional=true, version="0.0.8", package="uu_users", path="src/uu/users" }
|
||||
wc = { optional=true, version="0.0.8", package="uu_wc", path="src/uu/wc" }
|
||||
who = { optional=true, version="0.0.8", package="uu_who", path="src/uu/who" }
|
||||
whoami = { optional=true, version="0.0.8", package="uu_whoami", path="src/uu/whoami" }
|
||||
yes = { optional=true, version="0.0.8", package="uu_yes", path="src/uu/yes" }
|
||||
|
||||
# this breaks clippy linting with: "tests/by-util/test_factor_benches.rs: No such file or directory (os error 2)"
|
||||
# factor_benches = { optional = true, version = "0.0.0", package = "uu_factor_benches", path = "tests/benches/factor" }
|
||||
|
@ -342,7 +366,6 @@ conv = "0.3"
|
|||
filetime = "0.2"
|
||||
glob = "0.3.0"
|
||||
libc = "0.2"
|
||||
nix = "0.20.0"
|
||||
pretty_assertions = "0.7.2"
|
||||
rand = "0.7"
|
||||
regex = "1.0"
|
||||
|
@ -350,12 +373,16 @@ sha1 = { version="0.6", features=["std"] }
|
|||
tempfile = "3.2.0"
|
||||
time = "0.1"
|
||||
unindent = "0.1"
|
||||
uucore = { version=">=0.0.9", package="uucore", path="src/uucore", features=["entries", "process"] }
|
||||
uucore = { version=">=0.0.10", package="uucore", path="src/uucore", features=["entries", "process"] }
|
||||
walkdir = "2.2"
|
||||
atty = "0.2"
|
||||
|
||||
[target.'cfg(unix)'.dev-dependencies]
|
||||
[target.'cfg(target_os = "linux")'.dev-dependencies]
|
||||
rlimit = "0.4.0"
|
||||
|
||||
[target.'cfg(unix)'.dev-dependencies]
|
||||
nix = "0.20.0"
|
||||
|
||||
rust-users = { version="0.10", package="users" }
|
||||
unix_socket = "0.5.0"
|
||||
|
||||
|
|
|
@ -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
|
||||
---------------------------------
|
||||
|
||||
|
|
22
GNUmakefile
22
GNUmakefile
|
@ -46,6 +46,15 @@ BUSYBOX_ROOT := $(BASEDIR)/tmp
|
|||
BUSYBOX_VER := 1.32.1
|
||||
BUSYBOX_SRC := $(BUSYBOX_ROOT)/busybox-$(BUSYBOX_VER)
|
||||
|
||||
ifeq ($(SELINUX_ENABLED),)
|
||||
SELINUX_ENABLED := 0
|
||||
ifneq ($(OS),Windows_NT)
|
||||
ifeq ($(shell /sbin/selinuxenabled 2>/dev/null ; echo $$?),0)
|
||||
SELINUX_ENABLED := 1
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
|
||||
# Possible programs
|
||||
PROGS := \
|
||||
base32 \
|
||||
|
@ -147,10 +156,18 @@ UNIX_PROGS := \
|
|||
users \
|
||||
who
|
||||
|
||||
SELINUX_PROGS := \
|
||||
chcon \
|
||||
runcon
|
||||
|
||||
ifneq ($(OS),Windows_NT)
|
||||
PROGS := $(PROGS) $(UNIX_PROGS)
|
||||
endif
|
||||
|
||||
ifeq ($(SELINUX_ENABLED),1)
|
||||
PROGS := $(PROGS) $(SELINUX_PROGS)
|
||||
endif
|
||||
|
||||
UTILS ?= $(PROGS)
|
||||
|
||||
# Programs with usable tests
|
||||
|
@ -159,6 +176,7 @@ TEST_PROGS := \
|
|||
base64 \
|
||||
basename \
|
||||
cat \
|
||||
chcon \
|
||||
chgrp \
|
||||
chmod \
|
||||
chown \
|
||||
|
@ -199,6 +217,7 @@ TEST_PROGS := \
|
|||
realpath \
|
||||
rm \
|
||||
rmdir \
|
||||
runcon \
|
||||
seq \
|
||||
sort \
|
||||
split \
|
||||
|
@ -228,6 +247,9 @@ TEST_SPEC_FEATURE :=
|
|||
ifneq ($(SPEC),)
|
||||
TEST_NO_FAIL_FAST :=--no-fail-fast
|
||||
TEST_SPEC_FEATURE := test_unimplemented
|
||||
else ifeq ($(SELINUX_ENABLED),1)
|
||||
TEST_NO_FAIL_FAST :=
|
||||
TEST_SPEC_FEATURE := feat_selinux
|
||||
endif
|
||||
|
||||
define TEST_BUSYBOX
|
||||
|
|
37
README.md
37
README.md
|
@ -39,7 +39,7 @@ to compile anywhere, and this is as good a way as any to try and learn it.
|
|||
### Rust Version
|
||||
|
||||
uutils follows Rust's release channels and is tested against stable, beta and nightly.
|
||||
The current oldest supported version of the Rust compiler is `1.43.1`.
|
||||
The current oldest supported version of the Rust compiler is `1.47`.
|
||||
|
||||
On both Windows and Redox, only the nightly version is tested currently.
|
||||
|
||||
|
@ -365,23 +365,25 @@ To contribute to uutils, please see [CONTRIBUTING](CONTRIBUTING.md).
|
|||
|
||||
| Done | Semi-Done | To Do |
|
||||
|-----------|-----------|--------|
|
||||
| arch | cp | chcon |
|
||||
| base32 | date | dd |
|
||||
| base64 | df | runcon |
|
||||
| basename | expr | stty |
|
||||
| arch | cp | stty |
|
||||
| base32 | date | |
|
||||
| base64 | dd | |
|
||||
| basename | df | |
|
||||
| basenc | expr | |
|
||||
| cat | install | |
|
||||
| chgrp | join | |
|
||||
| chmod | ls | |
|
||||
| chown | more | |
|
||||
| chroot | numfmt | |
|
||||
| cksum | od (`--strings` and 128-bit data types missing) | |
|
||||
| comm | pr | |
|
||||
| csplit | printf | |
|
||||
| cut | sort | |
|
||||
| dircolors | split | |
|
||||
| dirname | tac | |
|
||||
| du | tail | |
|
||||
| echo | test | |
|
||||
| chcon | join | |
|
||||
| chgrp | ls | |
|
||||
| chmod | more | |
|
||||
| chown | numfmt | |
|
||||
| chroot | od (`--strings` and 128-bit data types missing) | |
|
||||
| cksum | pr | |
|
||||
| comm | printf | |
|
||||
| csplit | sort | |
|
||||
| cut | split | |
|
||||
| dircolors | tac | |
|
||||
| dirname | tail | |
|
||||
| du | test | |
|
||||
| echo | | |
|
||||
| env | | |
|
||||
| expand | | |
|
||||
| factor | | |
|
||||
|
@ -424,6 +426,7 @@ To contribute to uutils, please see [CONTRIBUTING](CONTRIBUTING.md).
|
|||
| relpath | | |
|
||||
| rm | | |
|
||||
| rmdir | | |
|
||||
| runcon | | |
|
||||
| seq | | |
|
||||
| shred | | |
|
||||
| shuf | | |
|
||||
|
|
4
build.rs
4
build.rs
|
@ -28,7 +28,7 @@ pub fn main() {
|
|||
if val == "1" && key.starts_with(env_feature_prefix) {
|
||||
let krate = key[env_feature_prefix.len()..].to_lowercase();
|
||||
match krate.as_ref() {
|
||||
"default" | "macos" | "unix" | "windows" => continue, // common/standard feature names
|
||||
"default" | "macos" | "unix" | "windows" | "selinux" => continue, // common/standard feature names
|
||||
"nightly" | "test_unimplemented" => continue, // crate-local custom features
|
||||
"test" => continue, // over-ridden with 'uu_test' to avoid collision with rust core crate 'test'
|
||||
s if s.starts_with(feature_prefix) => continue, // crate feature sets
|
||||
|
@ -46,6 +46,8 @@ pub fn main() {
|
|||
"type UtilityMap<T> = HashMap<&'static str, (fn(T) -> i32, fn() -> App<'static, 'static>)>;\n\
|
||||
\n\
|
||||
fn util_map<T: uucore::Args>() -> UtilityMap<T> {\n\
|
||||
\t#[allow(unused_mut)]\n\
|
||||
\t#[allow(clippy::let_and_return)]\n\
|
||||
\tlet mut map = UtilityMap::new();\n\
|
||||
"
|
||||
.as_bytes(),
|
||||
|
|
|
@ -1 +1 @@
|
|||
msrv = "1.43.1"
|
||||
msrv = "1.47.0"
|
||||
|
|
|
@ -10,10 +10,12 @@ use clap::Arg;
|
|||
use clap::Shell;
|
||||
use std::cmp;
|
||||
use std::collections::hash_map::HashMap;
|
||||
use std::ffi::OsStr;
|
||||
use std::ffi::OsString;
|
||||
use std::io::{self, Write};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process;
|
||||
use uucore::display::Quotable;
|
||||
|
||||
const VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
|
||||
|
@ -70,18 +72,27 @@ fn main() {
|
|||
Some(OsString::from(*util))
|
||||
} else {
|
||||
// unmatched binary name => regard as multi-binary container and advance argument list
|
||||
uucore::set_utility_is_second_arg();
|
||||
args.next()
|
||||
};
|
||||
|
||||
// 0th argument equals 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" {
|
||||
gen_completions(args, utils);
|
||||
}
|
||||
|
||||
match utils.get(&util[..]) {
|
||||
match utils.get(util) {
|
||||
Some(&(uumain, _)) => {
|
||||
process::exit(uumain((vec![util_os].into_iter()).chain(args)));
|
||||
}
|
||||
|
@ -89,9 +100,12 @@ fn main() {
|
|||
if util == "--help" || util == "-h" {
|
||||
// see if they want help on a specific util
|
||||
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, _)) => {
|
||||
let code = uumain(
|
||||
(vec![util_os, OsString::from("--help")].into_iter())
|
||||
|
@ -100,17 +114,13 @@ fn main() {
|
|||
io::stdout().flush().expect("could not flush stdout");
|
||||
process::exit(code);
|
||||
}
|
||||
None => {
|
||||
println!("{}: function/utility not found", util);
|
||||
process::exit(1);
|
||||
}
|
||||
None => not_found(&util_os),
|
||||
}
|
||||
}
|
||||
usage(&utils, binary_as_util);
|
||||
process::exit(0);
|
||||
} else {
|
||||
println!("{}: function/utility not found", util);
|
||||
process::exit(1);
|
||||
not_found(&util_os);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "uu_arch"
|
||||
version = "0.0.7"
|
||||
version = "0.0.8"
|
||||
authors = ["uutils developers"]
|
||||
license = "MIT"
|
||||
description = "arch ~ (uutils) display machine architecture"
|
||||
|
@ -17,8 +17,8 @@ path = "src/arch.rs"
|
|||
[dependencies]
|
||||
platform-info = "0.1"
|
||||
clap = { version = "2.33", features = ["wrap_help"] }
|
||||
uucore = { version=">=0.0.9", package="uucore", path="../../uucore" }
|
||||
uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_procs" }
|
||||
uucore = { version=">=0.0.10", package="uucore", path="../../uucore" }
|
||||
uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../uucore_procs" }
|
||||
|
||||
[[bin]]
|
||||
name = "arch"
|
||||
|
|
|
@ -6,9 +6,6 @@
|
|||
// For the full copyright and license information, please view the LICENSE
|
||||
// file that was distributed with this source code.
|
||||
|
||||
#[macro_use]
|
||||
extern crate uucore;
|
||||
|
||||
use platform_info::*;
|
||||
|
||||
use clap::{crate_version, App};
|
||||
|
@ -27,7 +24,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
|||
}
|
||||
|
||||
pub fn uu_app() -> App<'static, 'static> {
|
||||
App::new(executable!())
|
||||
App::new(uucore::util_name())
|
||||
.version(crate_version!())
|
||||
.about(ABOUT)
|
||||
.after_help(SUMMARY)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "uu_base32"
|
||||
version = "0.0.7"
|
||||
version = "0.0.8"
|
||||
authors = ["uutils developers"]
|
||||
license = "MIT"
|
||||
description = "base32 ~ (uutils) decode/encode input (base32-encoding)"
|
||||
|
@ -16,9 +16,13 @@ path = "src/base32.rs"
|
|||
|
||||
[dependencies]
|
||||
clap = { version = "2.33", features = ["wrap_help"] }
|
||||
uucore = { version=">=0.0.9", package="uucore", path="../../uucore", features = ["encoding"] }
|
||||
uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_procs" }
|
||||
uucore = { version=">=0.0.10", package="uucore", path="../../uucore", features = ["encoding"] }
|
||||
uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../uucore_procs" }
|
||||
|
||||
[[bin]]
|
||||
name = "base32"
|
||||
path = "src/main.rs"
|
||||
|
||||
[package.metadata.cargo-udeps.ignore]
|
||||
# Necessary for "make all"
|
||||
normal = ["uucore_procs"]
|
||||
|
|
|
@ -5,13 +5,10 @@
|
|||
// For the full copyright and license information, please view the LICENSE file
|
||||
// that was distributed with this source code.
|
||||
|
||||
#[macro_use]
|
||||
extern crate uucore;
|
||||
|
||||
use std::io::{stdin, Read};
|
||||
|
||||
use clap::App;
|
||||
use uucore::encoding::Format;
|
||||
use uucore::{encoding::Format, error::UResult};
|
||||
|
||||
pub mod base_common;
|
||||
|
||||
|
@ -24,27 +21,22 @@ static ABOUT: &str = "
|
|||
to attempt to recover from any other non-alphabet bytes in the
|
||||
encoded stream.
|
||||
";
|
||||
static VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
|
||||
static BASE_CMD_PARSE_ERROR: i32 = 1;
|
||||
|
||||
fn get_usage() -> String {
|
||||
format!("{0} [OPTION]... [FILE]", executable!())
|
||||
fn usage() -> String {
|
||||
format!("{0} [OPTION]... [FILE]", uucore::execution_phrase())
|
||||
}
|
||||
|
||||
pub fn uumain(args: impl uucore::Args) -> i32 {
|
||||
#[uucore_procs::gen_uumain]
|
||||
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||
let format = Format::Base32;
|
||||
let usage = get_usage();
|
||||
let name = executable!();
|
||||
let usage = usage();
|
||||
|
||||
let config_result: Result<base_common::Config, String> =
|
||||
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: base_common::Config = base_common::parse_base_cmd_args(args, ABOUT, &usage)?;
|
||||
|
||||
// Create a reference to stdin so we can return a locked stdin from
|
||||
// parse_base_cmd_args
|
||||
let stdin_raw = stdin();
|
||||
let mut input: Box<dyn Read> = base_common::get_input(&config, &stdin_raw);
|
||||
let mut input: Box<dyn Read> = base_common::get_input(&config, &stdin_raw)?;
|
||||
|
||||
base_common::handle_input(
|
||||
&mut input,
|
||||
|
@ -52,12 +44,9 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
config.wrap_cols,
|
||||
config.ignore_garbage,
|
||||
config.decode,
|
||||
name,
|
||||
);
|
||||
|
||||
0
|
||||
)
|
||||
}
|
||||
|
||||
pub fn uu_app() -> App<'static, 'static> {
|
||||
base_common::base_app(executable!(), VERSION, ABOUT)
|
||||
base_common::base_app(ABOUT)
|
||||
}
|
||||
|
|
|
@ -9,14 +9,18 @@
|
|||
|
||||
use std::io::{stdout, Read, Write};
|
||||
|
||||
use uucore::display::Quotable;
|
||||
use uucore::encoding::{wrap_print, Data, Format};
|
||||
use uucore::error::{FromIo, UResult, USimpleError, UUsageError};
|
||||
use uucore::InvalidEncodingHandling;
|
||||
|
||||
use std::fs::File;
|
||||
use std::io::{BufReader, Stdin};
|
||||
use std::path::Path;
|
||||
|
||||
use clap::{App, Arg};
|
||||
use clap::{crate_version, App, Arg};
|
||||
|
||||
pub static BASE_CMD_PARSE_ERROR: i32 = 1;
|
||||
|
||||
// Config.
|
||||
pub struct Config {
|
||||
|
@ -34,19 +38,25 @@ pub mod options {
|
|||
}
|
||||
|
||||
impl Config {
|
||||
fn from(options: clap::ArgMatches) -> Result<Config, String> {
|
||||
pub fn from(options: &clap::ArgMatches) -> UResult<Config> {
|
||||
let file: Option<String> = match options.values_of(options::FILE) {
|
||||
Some(mut values) => {
|
||||
let name = values.next().unwrap();
|
||||
if values.len() != 0 {
|
||||
return Err(format!("extra operand '{}'", name));
|
||||
if let Some(extra_op) = values.next() {
|
||||
return Err(UUsageError::new(
|
||||
BASE_CMD_PARSE_ERROR,
|
||||
format!("extra operand {}", extra_op.quote(),),
|
||||
));
|
||||
}
|
||||
|
||||
if name == "-" {
|
||||
None
|
||||
} else {
|
||||
if !Path::exists(Path::new(name)) {
|
||||
return Err(format!("{}: No such file or directory", name));
|
||||
return Err(USimpleError::new(
|
||||
BASE_CMD_PARSE_ERROR,
|
||||
format!("{}: No such file or directory", name.maybe_quote()),
|
||||
));
|
||||
}
|
||||
Some(name.to_owned())
|
||||
}
|
||||
|
@ -57,8 +67,12 @@ impl Config {
|
|||
let cols = options
|
||||
.value_of(options::WRAP)
|
||||
.map(|num| {
|
||||
num.parse::<usize>()
|
||||
.map_err(|e| format!("Invalid wrap size: '{}': {}", num, e))
|
||||
num.parse::<usize>().map_err(|_| {
|
||||
USimpleError::new(
|
||||
BASE_CMD_PARSE_ERROR,
|
||||
format!("invalid wrap size: {}", num.quote()),
|
||||
)
|
||||
})
|
||||
})
|
||||
.transpose()?;
|
||||
|
||||
|
@ -71,23 +85,17 @@ impl Config {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn parse_base_cmd_args(
|
||||
args: impl uucore::Args,
|
||||
name: &str,
|
||||
version: &str,
|
||||
about: &str,
|
||||
usage: &str,
|
||||
) -> Result<Config, String> {
|
||||
let app = base_app(name, version, about).usage(usage);
|
||||
pub fn parse_base_cmd_args(args: impl uucore::Args, about: &str, usage: &str) -> UResult<Config> {
|
||||
let app = base_app(about).usage(usage);
|
||||
let arg_list = args
|
||||
.collect_str(InvalidEncodingHandling::ConvertLossy)
|
||||
.accept_any();
|
||||
Config::from(app.get_matches_from(arg_list))
|
||||
Config::from(&app.get_matches_from(arg_list))
|
||||
}
|
||||
|
||||
pub fn base_app<'a>(name: &str, version: &'a str, about: &'a str) -> App<'static, 'a> {
|
||||
App::new(name)
|
||||
.version(version)
|
||||
pub fn base_app<'a>(about: &'a str) -> App<'static, 'a> {
|
||||
App::new(uucore::util_name())
|
||||
.version(crate_version!())
|
||||
.about(about)
|
||||
// Format arguments.
|
||||
.arg(
|
||||
|
@ -116,14 +124,15 @@ pub fn base_app<'a>(name: &str, version: &'a str, about: &'a str) -> App<'static
|
|||
.arg(Arg::with_name(options::FILE).index(1).multiple(true))
|
||||
}
|
||||
|
||||
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) -> UResult<Box<dyn Read + 'a>> {
|
||||
match &config.to_read {
|
||||
Some(name) => {
|
||||
let file_buf = safe_unwrap!(File::open(Path::new(name)));
|
||||
Box::new(BufReader::new(file_buf)) // as Box<dyn Read>
|
||||
let file_buf =
|
||||
File::open(Path::new(name)).map_err_context(|| name.maybe_quote().to_string())?;
|
||||
Ok(Box::new(BufReader::new(file_buf))) // as Box<dyn Read>
|
||||
}
|
||||
None => {
|
||||
Box::new(stdin_ref.lock()) // as Box<dyn Read>
|
||||
Ok(Box::new(stdin_ref.lock())) // as Box<dyn Read>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -134,29 +143,35 @@ pub fn handle_input<R: Read>(
|
|||
line_wrap: Option<usize>,
|
||||
ignore_garbage: bool,
|
||||
decode: bool,
|
||||
name: &str,
|
||||
) {
|
||||
) -> UResult<()> {
|
||||
let mut data = Data::new(input, format).ignore_garbage(ignore_garbage);
|
||||
if let Some(wrap) = line_wrap {
|
||||
data = data.line_wrap(wrap);
|
||||
}
|
||||
|
||||
if !decode {
|
||||
let encoded = data.encode();
|
||||
wrap_print(&data, encoded);
|
||||
match data.encode() {
|
||||
Ok(s) => {
|
||||
wrap_print(&data, s);
|
||||
Ok(())
|
||||
}
|
||||
Err(_) => Err(USimpleError::new(
|
||||
1,
|
||||
"error: invalid input (length must be multiple of 4 characters)",
|
||||
)),
|
||||
}
|
||||
} else {
|
||||
match data.decode() {
|
||||
Ok(s) => {
|
||||
// Silent the warning as we want to the error message
|
||||
#[allow(clippy::question_mark)]
|
||||
if stdout().write_all(&s).is_err() {
|
||||
// on windows console, writing invalid utf8 returns an error
|
||||
eprintln!("{}: error: Cannot write non-utf8 data", name);
|
||||
exit!(1)
|
||||
return Err(USimpleError::new(1, "error: cannot write non-utf8 data"));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Err(_) => {
|
||||
eprintln!("{}: error: invalid input", name);
|
||||
exit!(1)
|
||||
}
|
||||
Err(_) => Err(USimpleError::new(1, "error: invalid input")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "uu_base64"
|
||||
version = "0.0.7"
|
||||
version = "0.0.8"
|
||||
authors = ["uutils developers"]
|
||||
license = "MIT"
|
||||
description = "base64 ~ (uutils) decode/encode input (base64-encoding)"
|
||||
|
@ -16,10 +16,14 @@ path = "src/base64.rs"
|
|||
|
||||
[dependencies]
|
||||
clap = { version = "2.33", features = ["wrap_help"] }
|
||||
uucore = { version=">=0.0.9", package="uucore", path="../../uucore", features = ["encoding"] }
|
||||
uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_procs" }
|
||||
uu_base32 = { version=">=0.0.6", package="uu_base32", path="../base32"}
|
||||
uucore = { version=">=0.0.10", package="uucore", path="../../uucore", features = ["encoding"] }
|
||||
uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../uucore_procs" }
|
||||
uu_base32 = { version=">=0.0.8", package="uu_base32", path="../base32"}
|
||||
|
||||
[[bin]]
|
||||
name = "base64"
|
||||
path = "src/main.rs"
|
||||
|
||||
[package.metadata.cargo-udeps.ignore]
|
||||
# Necessary for "make all"
|
||||
normal = ["uucore_procs"]
|
||||
|
|
|
@ -6,13 +6,10 @@
|
|||
// For the full copyright and license information, please view the LICENSE file
|
||||
// that was distributed with this source code.
|
||||
|
||||
#[macro_use]
|
||||
extern crate uucore;
|
||||
|
||||
use uu_base32::base_common;
|
||||
pub use uu_base32::uu_app;
|
||||
|
||||
use uucore::encoding::Format;
|
||||
use uucore::{encoding::Format, error::UResult};
|
||||
|
||||
use std::io::{stdin, Read};
|
||||
|
||||
|
@ -25,26 +22,22 @@ static ABOUT: &str = "
|
|||
to attempt to recover from any other non-alphabet bytes in the
|
||||
encoded stream.
|
||||
";
|
||||
static VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
|
||||
static BASE_CMD_PARSE_ERROR: i32 = 1;
|
||||
|
||||
fn get_usage() -> String {
|
||||
format!("{0} [OPTION]... [FILE]", executable!())
|
||||
fn usage() -> String {
|
||||
format!("{0} [OPTION]... [FILE]", uucore::execution_phrase())
|
||||
}
|
||||
|
||||
pub fn uumain(args: impl uucore::Args) -> i32 {
|
||||
#[uucore_procs::gen_uumain]
|
||||
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||
let format = Format::Base64;
|
||||
let usage = get_usage();
|
||||
let name = executable!();
|
||||
let config_result: Result<base_common::Config, String> =
|
||||
base_common::parse_base_cmd_args(args, name, VERSION, ABOUT, &usage);
|
||||
let config = config_result.unwrap_or_else(|s| crash!(BASE_CMD_PARSE_ERROR, "{}", s));
|
||||
let usage = usage();
|
||||
|
||||
let config: base_common::Config = base_common::parse_base_cmd_args(args, ABOUT, &usage)?;
|
||||
|
||||
// Create a reference to stdin so we can return a locked stdin from
|
||||
// parse_base_cmd_args
|
||||
let stdin_raw = stdin();
|
||||
let mut input: Box<dyn Read> = base_common::get_input(&config, &stdin_raw);
|
||||
let mut input: Box<dyn Read> = base_common::get_input(&config, &stdin_raw)?;
|
||||
|
||||
base_common::handle_input(
|
||||
&mut input,
|
||||
|
@ -52,8 +45,5 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
config.wrap_cols,
|
||||
config.ignore_garbage,
|
||||
config.decode,
|
||||
name,
|
||||
);
|
||||
|
||||
0
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "uu_basename"
|
||||
version = "0.0.7"
|
||||
version = "0.0.8"
|
||||
authors = ["uutils developers"]
|
||||
license = "MIT"
|
||||
description = "basename ~ (uutils) display PATHNAME with leading directory components removed"
|
||||
|
@ -16,9 +16,13 @@ path = "src/basename.rs"
|
|||
|
||||
[dependencies]
|
||||
clap = { version = "2.33", features = ["wrap_help"] }
|
||||
uucore = { version=">=0.0.9", package="uucore", path="../../uucore" }
|
||||
uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_procs" }
|
||||
uucore = { version=">=0.0.10", package="uucore", path="../../uucore" }
|
||||
uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../uucore_procs" }
|
||||
|
||||
[[bin]]
|
||||
name = "basename"
|
||||
path = "src/main.rs"
|
||||
|
||||
[package.metadata.cargo-udeps.ignore]
|
||||
# Necessary for "make all"
|
||||
normal = ["uucore_procs"]
|
||||
|
|
|
@ -17,11 +17,11 @@ use uucore::InvalidEncodingHandling;
|
|||
static SUMMARY: &str = "Print NAME with any leading directory components removed
|
||||
If specified, also remove a trailing SUFFIX";
|
||||
|
||||
fn get_usage() -> String {
|
||||
fn usage() -> String {
|
||||
format!(
|
||||
"{0} NAME [SUFFIX]
|
||||
{0} OPTION... NAME...",
|
||||
executable!()
|
||||
uucore::execution_phrase()
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -36,7 +36,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
let args = args
|
||||
.collect_str(InvalidEncodingHandling::ConvertLossy)
|
||||
.accept_any();
|
||||
let usage = get_usage();
|
||||
let usage = usage();
|
||||
//
|
||||
// Argument parsing
|
||||
//
|
||||
|
@ -47,7 +47,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
crash!(
|
||||
1,
|
||||
"{1}\nTry '{0} --help' for more information.",
|
||||
executable!(),
|
||||
uucore::execution_phrase(),
|
||||
"missing operand"
|
||||
);
|
||||
}
|
||||
|
@ -61,7 +61,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
crash!(
|
||||
1,
|
||||
"extra operand '{1}'\nTry '{0} --help' for more information.",
|
||||
executable!(),
|
||||
uucore::execution_phrase(),
|
||||
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> {
|
||||
App::new(executable!())
|
||||
App::new(uucore::util_name())
|
||||
.version(crate_version!())
|
||||
.about(SUMMARY)
|
||||
.arg(
|
||||
|
@ -119,27 +119,27 @@ pub fn uu_app() -> App<'static, 'static> {
|
|||
}
|
||||
|
||||
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);
|
||||
|
||||
// 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
|
||||
let pb = PathBuf::from(path);
|
||||
match pb.components().last() {
|
||||
Some(c) => strip_suffix(c.as_os_str().to_str().unwrap(), suffix),
|
||||
Some(c) => {
|
||||
let name = c.as_os_str().to_str().unwrap();
|
||||
if name == suffix {
|
||||
name.to_string()
|
||||
} else {
|
||||
name.strip_suffix(suffix).unwrap_or(name).to_string()
|
||||
}
|
||||
}
|
||||
|
||||
None => "".to_owned(),
|
||||
}
|
||||
}
|
||||
|
||||
// can be replaced with strip_suffix once MSRV is 1.45
|
||||
#[allow(clippy::manual_strip)]
|
||||
fn strip_suffix(name: &str, suffix: &str) -> String {
|
||||
if name == suffix {
|
||||
return name.to_owned();
|
||||
}
|
||||
|
||||
if name.ends_with(suffix) {
|
||||
return name[..name.len() - suffix.len()].to_owned();
|
||||
}
|
||||
|
||||
name.to_owned()
|
||||
}
|
||||
|
|
29
src/uu/basenc/Cargo.toml
Normal file
29
src/uu/basenc/Cargo.toml
Normal file
|
@ -0,0 +1,29 @@
|
|||
[package]
|
||||
name = "uu_basenc"
|
||||
version = "0.0.8"
|
||||
authors = ["uutils developers"]
|
||||
license = "MIT"
|
||||
description = "basenc ~ (uutils) decode/encode input"
|
||||
|
||||
homepage = "https://github.com/uutils/coreutils"
|
||||
repository = "https://github.com/uutils/coreutils/tree/master/src/uu/basenc"
|
||||
keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"]
|
||||
categories = ["command-line-utilities"]
|
||||
edition = "2018"
|
||||
|
||||
[lib]
|
||||
path = "src/basenc.rs"
|
||||
|
||||
[dependencies]
|
||||
clap = { version = "2.33", features = ["wrap_help"] }
|
||||
uucore = { version=">=0.0.10", package="uucore", path="../../uucore", features = ["encoding"] }
|
||||
uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../uucore_procs" }
|
||||
uu_base32 = { version=">=0.0.8", package="uu_base32", path="../base32"}
|
||||
|
||||
[[bin]]
|
||||
name = "basenc"
|
||||
path = "src/main.rs"
|
||||
|
||||
[package.metadata.cargo-udeps.ignore]
|
||||
# Necessary for "make all"
|
||||
normal = ["uucore_procs"]
|
86
src/uu/basenc/src/basenc.rs
Normal file
86
src/uu/basenc/src/basenc.rs
Normal file
|
@ -0,0 +1,86 @@
|
|||
// This file is part of the uutils coreutils package.
|
||||
//
|
||||
// (c) Jordy Dickinson <jordy.dickinson@gmail.com>
|
||||
// (c) Jian Zeng <anonymousknight96@gmail.com>
|
||||
//
|
||||
// For the full copyright and license information, please view the LICENSE file
|
||||
// that was distributed with this source code.
|
||||
|
||||
//spell-checker:ignore (args) lsbf msbf
|
||||
|
||||
use clap::{App, Arg};
|
||||
use uu_base32::base_common::{self, Config, BASE_CMD_PARSE_ERROR};
|
||||
|
||||
use uucore::{
|
||||
encoding::Format,
|
||||
error::{UResult, UUsageError},
|
||||
InvalidEncodingHandling,
|
||||
};
|
||||
|
||||
use std::io::{stdin, Read};
|
||||
|
||||
static ABOUT: &str = "
|
||||
With no FILE, or when FILE is -, read standard input.
|
||||
|
||||
When decoding, the input may contain newlines in addition to the bytes of
|
||||
the formal alphabet. Use --ignore-garbage to attempt to recover
|
||||
from any other non-alphabet bytes in the encoded stream.
|
||||
";
|
||||
|
||||
const ENCODINGS: &[(&str, Format)] = &[
|
||||
("base64", Format::Base64),
|
||||
("base64url", Format::Base64Url),
|
||||
("base32", Format::Base32),
|
||||
("base32hex", Format::Base32Hex),
|
||||
("base16", Format::Base16),
|
||||
("base2lsbf", Format::Base2Lsbf),
|
||||
("base2msbf", Format::Base2Msbf),
|
||||
("z85", Format::Z85),
|
||||
// common abbreviations. TODO: once we have clap 3.0 we can use `AppSettings::InferLongArgs` to get all abbreviations automatically
|
||||
("base2l", Format::Base2Lsbf),
|
||||
("base2m", Format::Base2Msbf),
|
||||
];
|
||||
|
||||
fn usage() -> String {
|
||||
format!("{0} [OPTION]... [FILE]", uucore::execution_phrase())
|
||||
}
|
||||
|
||||
pub fn uu_app() -> App<'static, 'static> {
|
||||
let mut app = base_common::base_app(ABOUT);
|
||||
for encoding in ENCODINGS {
|
||||
app = app.arg(Arg::with_name(encoding.0).long(encoding.0));
|
||||
}
|
||||
app
|
||||
}
|
||||
|
||||
fn parse_cmd_args(args: impl uucore::Args) -> UResult<(Config, Format)> {
|
||||
let usage = usage();
|
||||
let matches = uu_app().usage(&usage[..]).get_matches_from(
|
||||
args.collect_str(InvalidEncodingHandling::ConvertLossy)
|
||||
.accept_any(),
|
||||
);
|
||||
let format = ENCODINGS
|
||||
.iter()
|
||||
.find(|encoding| matches.is_present(encoding.0))
|
||||
.ok_or_else(|| UUsageError::new(BASE_CMD_PARSE_ERROR, "missing encoding type"))?
|
||||
.1;
|
||||
let config = Config::from(&matches)?;
|
||||
Ok((config, format))
|
||||
}
|
||||
|
||||
#[uucore_procs::gen_uumain]
|
||||
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||
let (config, format) = parse_cmd_args(args)?;
|
||||
// Create a reference to stdin so we can return a locked stdin from
|
||||
// parse_base_cmd_args
|
||||
let stdin_raw = stdin();
|
||||
let mut input: Box<dyn Read> = base_common::get_input(&config, &stdin_raw)?;
|
||||
|
||||
base_common::handle_input(
|
||||
&mut input,
|
||||
format,
|
||||
config.wrap_cols,
|
||||
config.ignore_garbage,
|
||||
config.decode,
|
||||
)
|
||||
}
|
1
src/uu/basenc/src/main.rs
Normal file
1
src/uu/basenc/src/main.rs
Normal file
|
@ -0,0 +1 @@
|
|||
uucore_procs::main!(uu_basenc);
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "uu_cat"
|
||||
version = "0.0.7"
|
||||
version = "0.0.8"
|
||||
authors = ["uutils developers"]
|
||||
license = "MIT"
|
||||
description = "cat ~ (uutils) concatenate and display input"
|
||||
|
@ -18,14 +18,15 @@ path = "src/cat.rs"
|
|||
clap = { version = "2.33", features = ["wrap_help"] }
|
||||
thiserror = "1.0"
|
||||
atty = "0.2"
|
||||
uucore = { version=">=0.0.9", package="uucore", path="../../uucore", features=["fs"] }
|
||||
uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_procs" }
|
||||
uucore = { version=">=0.0.10", package="uucore", path="../../uucore", features=["fs", "pipes"] }
|
||||
uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../uucore_procs" }
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
unix_socket = "0.5.0"
|
||||
nix = "0.20.0"
|
||||
|
||||
[target.'cfg(any(target_os = "linux", target_os = "android"))'.dependencies]
|
||||
nix = "0.20"
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
winapi-util = "0.1.5"
|
||||
|
||||
[[bin]]
|
||||
name = "cat"
|
||||
|
|
|
@ -12,20 +12,21 @@
|
|||
|
||||
#[cfg(unix)]
|
||||
extern crate unix_socket;
|
||||
#[macro_use]
|
||||
extern crate uucore;
|
||||
|
||||
// last synced with: cat (GNU coreutils) 8.13
|
||||
use clap::{crate_version, App, Arg};
|
||||
use std::fs::{metadata, File};
|
||||
use std::io::{self, Read, Write};
|
||||
use thiserror::Error;
|
||||
use uucore::display::Quotable;
|
||||
use uucore::error::UResult;
|
||||
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::io::AsRawFd;
|
||||
|
||||
/// Linux splice support
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
mod splice;
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
use std::os::unix::io::{AsRawFd, RawFd};
|
||||
|
||||
/// Unix domain socket support
|
||||
#[cfg(unix)]
|
||||
|
@ -58,6 +59,8 @@ enum CatError {
|
|||
},
|
||||
#[error("Is a directory")]
|
||||
IsDirectory,
|
||||
#[error("input file is output file")]
|
||||
OutputIsInput,
|
||||
}
|
||||
|
||||
type CatResult<T> = Result<T, CatError>;
|
||||
|
@ -122,12 +125,26 @@ struct OutputState {
|
|||
|
||||
/// Whether the output cursor is at the beginning of a new line
|
||||
at_line_start: bool,
|
||||
|
||||
/// Whether we skipped a \r, which still needs to be printed
|
||||
skipped_carriage_return: bool,
|
||||
|
||||
/// Whether we have already printed a blank line
|
||||
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
|
||||
struct InputHandle<R: Read> {
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
file_descriptor: RawFd,
|
||||
struct InputHandle<R: FdReadable> {
|
||||
reader: R,
|
||||
is_interactive: bool,
|
||||
}
|
||||
|
@ -164,7 +181,8 @@ mod options {
|
|||
pub static SHOW_NONPRINTING: &str = "show-nonprinting";
|
||||
}
|
||||
|
||||
pub fn uumain(args: impl uucore::Args) -> i32 {
|
||||
#[uucore_procs::gen_uumain]
|
||||
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||
let args = args
|
||||
.collect_str(InvalidEncodingHandling::Ignore)
|
||||
.accept_any();
|
||||
|
@ -217,17 +235,11 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
show_tabs,
|
||||
squeeze_blank,
|
||||
};
|
||||
let success = cat_files(files, &options).is_ok();
|
||||
|
||||
if success {
|
||||
0
|
||||
} else {
|
||||
1
|
||||
}
|
||||
cat_files(files, &options)
|
||||
}
|
||||
|
||||
pub fn uu_app() -> App<'static, 'static> {
|
||||
App::new(executable!())
|
||||
App::new(uucore::util_name())
|
||||
.name(NAME)
|
||||
.version(crate_version!())
|
||||
.usage(SYNTAX)
|
||||
|
@ -289,7 +301,7 @@ pub fn uu_app() -> App<'static, 'static> {
|
|||
)
|
||||
}
|
||||
|
||||
fn cat_handle<R: Read>(
|
||||
fn cat_handle<R: FdReadable>(
|
||||
handle: &mut InputHandle<R>,
|
||||
options: &OutputOptions,
|
||||
state: &mut OutputState,
|
||||
|
@ -301,12 +313,16 @@ fn cat_handle<R: Read>(
|
|||
}
|
||||
}
|
||||
|
||||
fn cat_path(path: &str, options: &OutputOptions, state: &mut OutputState) -> CatResult<()> {
|
||||
fn cat_path(
|
||||
path: &str,
|
||||
options: &OutputOptions,
|
||||
state: &mut OutputState,
|
||||
#[cfg(unix)] out_info: &nix::sys::stat::FileStat,
|
||||
#[cfg(windows)] out_info: &winapi_util::file::Information,
|
||||
) -> CatResult<()> {
|
||||
if path == "-" {
|
||||
let stdin = io::stdin();
|
||||
let mut handle = InputHandle {
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
file_descriptor: stdin.as_raw_fd(),
|
||||
reader: stdin,
|
||||
is_interactive: atty::is(atty::Stream::Stdin),
|
||||
};
|
||||
|
@ -319,8 +335,6 @@ fn cat_path(path: &str, options: &OutputOptions, state: &mut OutputState) -> Cat
|
|||
let socket = UnixStream::connect(path)?;
|
||||
socket.shutdown(Shutdown::Write)?;
|
||||
let mut handle = InputHandle {
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
file_descriptor: socket.as_raw_fd(),
|
||||
reader: socket,
|
||||
is_interactive: false,
|
||||
};
|
||||
|
@ -328,9 +342,11 @@ fn cat_path(path: &str, options: &OutputOptions, state: &mut OutputState) -> Cat
|
|||
}
|
||||
_ => {
|
||||
let file = File::open(path)?;
|
||||
#[cfg(any(windows, unix))]
|
||||
if same_file(out_info, &file) {
|
||||
return Err(CatError::OutputIsInput);
|
||||
}
|
||||
let mut handle = InputHandle {
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
file_descriptor: file.as_raw_fd(),
|
||||
reader: file,
|
||||
is_interactive: false,
|
||||
};
|
||||
|
@ -339,23 +355,52 @@ fn cat_path(path: &str, options: &OutputOptions, state: &mut OutputState) -> Cat
|
|||
}
|
||||
}
|
||||
|
||||
fn cat_files(files: Vec<String>, options: &OutputOptions) -> Result<(), u32> {
|
||||
let mut error_count = 0;
|
||||
#[cfg(unix)]
|
||||
fn same_file(a_info: &nix::sys::stat::FileStat, b: &File) -> bool {
|
||||
let b_info = nix::sys::stat::fstat(b.as_raw_fd()).unwrap();
|
||||
b_info.st_size != 0 && b_info.st_dev == a_info.st_dev && b_info.st_ino == a_info.st_ino
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn same_file(a_info: &winapi_util::file::Information, b: &File) -> bool {
|
||||
let b_info = winapi_util::file::information(b).unwrap();
|
||||
b_info.file_size() != 0
|
||||
&& b_info.volume_serial_number() == a_info.volume_serial_number()
|
||||
&& b_info.file_index() == a_info.file_index()
|
||||
}
|
||||
|
||||
fn cat_files(files: Vec<String>, options: &OutputOptions) -> UResult<()> {
|
||||
#[cfg(windows)]
|
||||
let out_info = winapi_util::file::information(&std::io::stdout()).unwrap();
|
||||
#[cfg(unix)]
|
||||
let out_info = nix::sys::stat::fstat(std::io::stdout().as_raw_fd()).unwrap();
|
||||
|
||||
let mut state = OutputState {
|
||||
line_number: 1,
|
||||
at_line_start: true,
|
||||
skipped_carriage_return: false,
|
||||
one_blank_kept: false,
|
||||
};
|
||||
let mut error_messages: Vec<String> = Vec::new();
|
||||
|
||||
for path in &files {
|
||||
if let Err(err) = cat_path(path, options, &mut state) {
|
||||
show_error!("{}: {}", path, err);
|
||||
error_count += 1;
|
||||
if let Err(err) = cat_path(path, options, &mut state, &out_info) {
|
||||
error_messages.push(format!("{}: {}", path.maybe_quote(), err));
|
||||
}
|
||||
}
|
||||
if error_count == 0 {
|
||||
if state.skipped_carriage_return {
|
||||
print!("\r");
|
||||
}
|
||||
if error_messages.is_empty() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(error_count)
|
||||
// each next line is expected to display "cat: …"
|
||||
let line_joiner = format!("\n{}: ", uucore::util_name());
|
||||
|
||||
Err(uucore::error::USimpleError::new(
|
||||
error_messages.len() as i32,
|
||||
error_messages.join(&line_joiner),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -390,14 +435,14 @@ fn get_input_type(path: &str) -> CatResult<InputType> {
|
|||
|
||||
/// Writes handle to stdout with no configuration. This allows a
|
||||
/// 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 mut stdout_lock = stdout.lock();
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
{
|
||||
// If we're on Linux or Android, try to use the splice() system call
|
||||
// for faster writing. If it works, we're done.
|
||||
if !splice::write_fast_using_splice(handle, stdout_lock.as_raw_fd())? {
|
||||
if !splice::write_fast_using_splice(handle, &stdout_lock)? {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
@ -415,7 +460,7 @@ fn write_fast<R: Read>(handle: &mut InputHandle<R>) -> CatResult<()> {
|
|||
|
||||
/// Outputs file contents to stdout in a line-by-line fashion,
|
||||
/// propagating any errors that might occur.
|
||||
fn write_lines<R: Read>(
|
||||
fn write_lines<R: FdReadable>(
|
||||
handle: &mut InputHandle<R>,
|
||||
options: &OutputOptions,
|
||||
state: &mut OutputState,
|
||||
|
@ -423,7 +468,6 @@ fn write_lines<R: Read>(
|
|||
let mut in_buf = [0; 1024 * 31];
|
||||
let stdout = io::stdout();
|
||||
let mut writer = stdout.lock();
|
||||
let mut one_blank_kept = false;
|
||||
|
||||
while let Ok(n) = handle.reader.read(&mut in_buf) {
|
||||
if n == 0 {
|
||||
|
@ -434,8 +478,13 @@ fn write_lines<R: Read>(
|
|||
while pos < n {
|
||||
// skip empty line_number enumerating them if needed
|
||||
if in_buf[pos] == b'\n' {
|
||||
if !state.at_line_start || !options.squeeze_blank || !one_blank_kept {
|
||||
one_blank_kept = true;
|
||||
// \r followed by \n is printed as ^M when show_ends is enabled, so that \r\n prints as ^M$
|
||||
if state.skipped_carriage_return && options.show_ends {
|
||||
writer.write_all(b"^M")?;
|
||||
state.skipped_carriage_return = false;
|
||||
}
|
||||
if !state.at_line_start || !options.squeeze_blank || !state.one_blank_kept {
|
||||
state.one_blank_kept = true;
|
||||
if state.at_line_start && options.number == NumberingMode::All {
|
||||
write!(&mut writer, "{0:6}\t", state.line_number)?;
|
||||
state.line_number += 1;
|
||||
|
@ -449,7 +498,12 @@ fn write_lines<R: Read>(
|
|||
pos += 1;
|
||||
continue;
|
||||
}
|
||||
one_blank_kept = false;
|
||||
if state.skipped_carriage_return {
|
||||
writer.write_all(b"\r")?;
|
||||
state.skipped_carriage_return = false;
|
||||
state.at_line_start = false;
|
||||
}
|
||||
state.one_blank_kept = false;
|
||||
if state.at_line_start && options.number != NumberingMode::None {
|
||||
write!(&mut writer, "{0:6}\t", state.line_number)?;
|
||||
state.line_number += 1;
|
||||
|
@ -464,17 +518,22 @@ fn write_lines<R: Read>(
|
|||
write_to_end(&in_buf[pos..], &mut writer)
|
||||
};
|
||||
// end of buffer?
|
||||
if offset == 0 {
|
||||
if offset + pos == in_buf.len() {
|
||||
state.at_line_start = false;
|
||||
break;
|
||||
}
|
||||
if in_buf[pos + offset] == b'\r' {
|
||||
state.skipped_carriage_return = true;
|
||||
} else {
|
||||
assert_eq!(in_buf[pos + offset], b'\n');
|
||||
// print suitable end of line
|
||||
writer.write_all(options.end_of_line().as_bytes())?;
|
||||
if handle.is_interactive {
|
||||
writer.flush()?;
|
||||
}
|
||||
state.at_line_start = true;
|
||||
pos += offset;
|
||||
}
|
||||
pos += offset + 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -482,17 +541,19 @@ fn write_lines<R: Read>(
|
|||
}
|
||||
|
||||
// write***_to_end methods
|
||||
// Write all symbols till end of line or end of buffer is reached
|
||||
// Return the (number of written symbols + 1) or 0 if the end of buffer is reached
|
||||
// Write all symbols till \n or \r or end of buffer is reached
|
||||
// We need to stop at \r because it may be written as ^M depending on the byte after and settings;
|
||||
// however, write_nonprint_to_end doesn't need to stop at \r because it will always write \r as ^M.
|
||||
// Return the number of written symbols
|
||||
fn write_to_end<W: Write>(in_buf: &[u8], writer: &mut W) -> usize {
|
||||
match in_buf.iter().position(|c| *c == b'\n') {
|
||||
match in_buf.iter().position(|c| *c == b'\n' || *c == b'\r') {
|
||||
Some(p) => {
|
||||
writer.write_all(&in_buf[..p]).unwrap();
|
||||
p + 1
|
||||
p
|
||||
}
|
||||
None => {
|
||||
writer.write_all(in_buf).unwrap();
|
||||
0
|
||||
in_buf.len()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -500,20 +561,25 @@ fn write_to_end<W: Write>(in_buf: &[u8], writer: &mut W) -> usize {
|
|||
fn write_tab_to_end<W: Write>(mut in_buf: &[u8], writer: &mut W) -> usize {
|
||||
let mut count = 0;
|
||||
loop {
|
||||
match in_buf.iter().position(|c| *c == b'\n' || *c == b'\t') {
|
||||
match in_buf
|
||||
.iter()
|
||||
.position(|c| *c == b'\n' || *c == b'\t' || *c == b'\r')
|
||||
{
|
||||
Some(p) => {
|
||||
writer.write_all(&in_buf[..p]).unwrap();
|
||||
if in_buf[p] == b'\n' {
|
||||
return count + p + 1;
|
||||
} else {
|
||||
return count + p;
|
||||
} else if in_buf[p] == b'\t' {
|
||||
writer.write_all(b"^I").unwrap();
|
||||
in_buf = &in_buf[p + 1..];
|
||||
count += p + 1;
|
||||
} else {
|
||||
return count + p;
|
||||
}
|
||||
}
|
||||
None => {
|
||||
writer.write_all(in_buf).unwrap();
|
||||
return 0;
|
||||
return in_buf.len();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -522,7 +588,7 @@ fn write_tab_to_end<W: Write>(mut in_buf: &[u8], writer: &mut W) -> usize {
|
|||
fn write_nonprint_to_end<W: Write>(in_buf: &[u8], writer: &mut W, tab: &[u8]) -> usize {
|
||||
let mut count = 0;
|
||||
|
||||
for byte in in_buf.iter().map(|c| *c) {
|
||||
for byte in in_buf.iter().copied() {
|
||||
if byte == b'\n' {
|
||||
break;
|
||||
}
|
||||
|
@ -538,11 +604,7 @@ fn write_nonprint_to_end<W: Write>(in_buf: &[u8], writer: &mut W, tab: &[u8]) ->
|
|||
.unwrap();
|
||||
count += 1;
|
||||
}
|
||||
if count != in_buf.len() {
|
||||
count + 1
|
||||
} else {
|
||||
0
|
||||
}
|
||||
count
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
use super::{CatResult, InputHandle};
|
||||
use super::{CatResult, FdReadable, InputHandle};
|
||||
|
||||
use nix::fcntl::{splice, SpliceFFlags};
|
||||
use nix::unistd::{self, pipe};
|
||||
use std::io::Read;
|
||||
use std::os::unix::io::RawFd;
|
||||
use nix::unistd;
|
||||
use std::os::unix::io::{AsRawFd, RawFd};
|
||||
|
||||
use uucore::pipes::{pipe, splice, splice_exact};
|
||||
|
||||
const SPLICE_SIZE: usize = 1024 * 128;
|
||||
const BUF_SIZE: usize = 1024 * 16;
|
||||
|
||||
/// 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
|
||||
/// copying or not. False means we don't have to.
|
||||
#[inline]
|
||||
pub(super) fn write_fast_using_splice<R: Read>(
|
||||
pub(super) fn write_fast_using_splice<R: FdReadable>(
|
||||
handle: &mut InputHandle<R>,
|
||||
write_fd: RawFd,
|
||||
write_fd: &impl AsRawFd,
|
||||
) -> CatResult<bool> {
|
||||
let (pipe_rd, pipe_wr) = match pipe() {
|
||||
Ok(r) => r,
|
||||
Err(_) => {
|
||||
// It is very rare that creating a pipe fails, but it can happen.
|
||||
return Ok(true);
|
||||
}
|
||||
};
|
||||
let (pipe_rd, pipe_wr) = pipe()?;
|
||||
|
||||
loop {
|
||||
match splice(
|
||||
handle.file_descriptor,
|
||||
None,
|
||||
pipe_wr,
|
||||
None,
|
||||
BUF_SIZE,
|
||||
SpliceFFlags::empty(),
|
||||
) {
|
||||
match splice(&handle.reader, &pipe_wr, SPLICE_SIZE) {
|
||||
Ok(n) => {
|
||||
if n == 0 {
|
||||
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
|
||||
// pipe, but the second splice to stdout fails for some reason
|
||||
// we can recover by copying the data that we have from the
|
||||
// intermediate pipe to stdout using normal read/write. Then
|
||||
// we tell the caller to fall back.
|
||||
copy_exact(pipe_rd, write_fd, n)?;
|
||||
copy_exact(pipe_rd.as_raw_fd(), write_fd.as_raw_fd(), n)?;
|
||||
return Ok(true);
|
||||
}
|
||||
}
|
||||
|
@ -57,35 +45,23 @@ pub(super) fn write_fast_using_splice<R: Read>(
|
|||
}
|
||||
}
|
||||
|
||||
/// Splice wrapper which handles short writes.
|
||||
#[inline]
|
||||
fn splice_exact(read_fd: RawFd, write_fd: RawFd, num_bytes: usize) -> nix::Result<()> {
|
||||
let mut left = num_bytes;
|
||||
loop {
|
||||
let written = splice(read_fd, None, write_fd, None, left, SpliceFFlags::empty())?;
|
||||
left -= written;
|
||||
if left == 0 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Caller must ensure that `num_bytes <= BUF_SIZE`, otherwise this function
|
||||
/// will panic. The way we use this function in `write_fast_using_splice`
|
||||
/// above is safe because `splice` is set to write at most `BUF_SIZE` to the
|
||||
/// pipe.
|
||||
#[inline]
|
||||
/// Move exactly `num_bytes` bytes from `read_fd` to `write_fd`.
|
||||
///
|
||||
/// Panics if not enough bytes can be read.
|
||||
fn copy_exact(read_fd: RawFd, write_fd: RawFd, num_bytes: usize) -> nix::Result<()> {
|
||||
let mut left = num_bytes;
|
||||
let mut buf = [0; BUF_SIZE];
|
||||
loop {
|
||||
let read = unistd::read(read_fd, &mut buf[..left])?;
|
||||
let written = unistd::write(write_fd, &buf[..read])?;
|
||||
left -= written;
|
||||
if left == 0 {
|
||||
break;
|
||||
while left > 0 {
|
||||
let read = unistd::read(read_fd, &mut buf)?;
|
||||
assert_ne!(read, 0, "unexpected end of pipe");
|
||||
let mut written = 0;
|
||||
while written < read {
|
||||
match unistd::write(write_fd, &buf[written..read])? {
|
||||
0 => panic!(),
|
||||
n => written += n,
|
||||
}
|
||||
}
|
||||
left -= read;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
|
27
src/uu/chcon/Cargo.toml
Normal file
27
src/uu/chcon/Cargo.toml
Normal file
|
@ -0,0 +1,27 @@
|
|||
[package]
|
||||
name = "uu_chcon"
|
||||
version = "0.0.8"
|
||||
authors = ["uutils developers"]
|
||||
license = "MIT"
|
||||
description = "chcon ~ (uutils) change file security context"
|
||||
homepage = "https://github.com/uutils/coreutils"
|
||||
repository = "https://github.com/uutils/coreutils/tree/master/src/uu/chcon"
|
||||
keywords = ["coreutils", "uutils", "cli", "utility"]
|
||||
categories = ["command-line-utilities"]
|
||||
edition = "2018"
|
||||
|
||||
[lib]
|
||||
path = "src/chcon.rs"
|
||||
|
||||
[dependencies]
|
||||
clap = { version = "2.33", features = ["wrap_help"] }
|
||||
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" }
|
||||
selinux = { version = "0.2" }
|
||||
fts-sys = { version = "0.2" }
|
||||
thiserror = { version = "1.0" }
|
||||
libc = { version = "0.2" }
|
||||
|
||||
[[bin]]
|
||||
name = "chcon"
|
||||
path = "src/main.rs"
|
745
src/uu/chcon/src/chcon.rs
Normal file
745
src/uu/chcon/src/chcon.rs
Normal file
|
@ -0,0 +1,745 @@
|
|||
// spell-checker:ignore (vars) RFILE
|
||||
|
||||
#![allow(clippy::upper_case_acronyms)]
|
||||
|
||||
use uucore::{display::Quotable, show_error, show_usage_error, show_warning};
|
||||
|
||||
use clap::{App, Arg};
|
||||
use selinux::{OpaqueSecurityContext, SecurityContext};
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::ffi::{CStr, CString, OsStr, OsString};
|
||||
use std::os::raw::c_int;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::{fs, io};
|
||||
|
||||
mod errors;
|
||||
mod fts;
|
||||
|
||||
use errors::*;
|
||||
|
||||
static VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
static ABOUT: &str = "Change the SELinux security context of each FILE to CONTEXT. \n\
|
||||
With --reference, change the security context of each FILE to that of RFILE.";
|
||||
|
||||
pub mod options {
|
||||
pub static VERBOSE: &str = "verbose";
|
||||
|
||||
pub static REFERENCE: &str = "reference";
|
||||
|
||||
pub static USER: &str = "user";
|
||||
pub static ROLE: &str = "role";
|
||||
pub static TYPE: &str = "type";
|
||||
pub static RANGE: &str = "range";
|
||||
|
||||
pub static RECURSIVE: &str = "recursive";
|
||||
|
||||
pub mod sym_links {
|
||||
pub static FOLLOW_ARG_DIR_SYM_LINK: &str = "follow-arg-dir-sym-link";
|
||||
pub static FOLLOW_DIR_SYM_LINKS: &str = "follow-dir-sym-links";
|
||||
pub static NO_FOLLOW_SYM_LINKS: &str = "no-follow-sym-links";
|
||||
}
|
||||
|
||||
pub mod dereference {
|
||||
pub static DEREFERENCE: &str = "dereference";
|
||||
pub static NO_DEREFERENCE: &str = "no-dereference";
|
||||
}
|
||||
|
||||
pub mod preserve_root {
|
||||
pub static PRESERVE_ROOT: &str = "preserve-root";
|
||||
pub static NO_PRESERVE_ROOT: &str = "no-preserve-root";
|
||||
}
|
||||
}
|
||||
|
||||
fn get_usage() -> String {
|
||||
format!(
|
||||
"{0} [OPTION]... CONTEXT FILE... \n \
|
||||
{0} [OPTION]... [-u USER] [-r ROLE] [-l RANGE] [-t TYPE] FILE... \n \
|
||||
{0} [OPTION]... --reference=RFILE FILE...",
|
||||
uucore::execution_phrase()
|
||||
)
|
||||
}
|
||||
|
||||
pub fn uumain(args: impl uucore::Args) -> i32 {
|
||||
let usage = get_usage();
|
||||
|
||||
let config = uu_app().usage(usage.as_ref());
|
||||
|
||||
let options = match parse_command_line(config, args) {
|
||||
Ok(r) => r,
|
||||
Err(r) => {
|
||||
if let Error::CommandLine(r) = &r {
|
||||
match r.kind {
|
||||
clap::ErrorKind::HelpDisplayed | clap::ErrorKind::VersionDisplayed => {
|
||||
println!("{}", r);
|
||||
return libc::EXIT_SUCCESS;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
show_usage_error!("{}.\n", r);
|
||||
return libc::EXIT_FAILURE;
|
||||
}
|
||||
};
|
||||
|
||||
let context = match &options.mode {
|
||||
CommandLineMode::ReferenceBased { reference } => {
|
||||
let result = match SecurityContext::of_path(reference, true, false) {
|
||||
Ok(Some(context)) => Ok(context),
|
||||
|
||||
Ok(None) => {
|
||||
let err = io::Error::from_raw_os_error(libc::ENODATA);
|
||||
Err(Error::from_io1("Getting security context", reference, err))
|
||||
}
|
||||
|
||||
Err(r) => Err(Error::from_selinux("Getting security context", r)),
|
||||
};
|
||||
|
||||
match result {
|
||||
Err(r) => {
|
||||
show_error!("{}.", report_full_error(&r));
|
||||
return libc::EXIT_FAILURE;
|
||||
}
|
||||
|
||||
Ok(file_context) => SELinuxSecurityContext::File(file_context),
|
||||
}
|
||||
}
|
||||
|
||||
CommandLineMode::ContextBased { context } => {
|
||||
let c_context = match os_str_to_c_string(context) {
|
||||
Ok(context) => context,
|
||||
|
||||
Err(_r) => {
|
||||
show_error!("Invalid security context {}.", context.quote());
|
||||
return libc::EXIT_FAILURE;
|
||||
}
|
||||
};
|
||||
|
||||
if SecurityContext::from_c_str(&c_context, false).check() == Some(false) {
|
||||
show_error!("Invalid security context {}.", context.quote());
|
||||
return libc::EXIT_FAILURE;
|
||||
}
|
||||
|
||||
SELinuxSecurityContext::String(Some(c_context))
|
||||
}
|
||||
|
||||
CommandLineMode::Custom { .. } => SELinuxSecurityContext::String(None),
|
||||
};
|
||||
|
||||
let root_dev_ino = if options.preserve_root && options.recursive_mode.is_recursive() {
|
||||
match get_root_dev_ino() {
|
||||
Ok(r) => Some(r),
|
||||
|
||||
Err(r) => {
|
||||
show_error!("{}.", report_full_error(&r));
|
||||
return libc::EXIT_FAILURE;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let results = process_files(&options, &context, root_dev_ino);
|
||||
if results.is_empty() {
|
||||
return libc::EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
for result in &results {
|
||||
show_error!("{}.", report_full_error(result));
|
||||
}
|
||||
libc::EXIT_FAILURE
|
||||
}
|
||||
|
||||
pub fn uu_app() -> App<'static, 'static> {
|
||||
App::new(uucore::util_name())
|
||||
.version(VERSION)
|
||||
.about(ABOUT)
|
||||
.arg(
|
||||
Arg::with_name(options::dereference::DEREFERENCE)
|
||||
.long(options::dereference::DEREFERENCE)
|
||||
.conflicts_with(options::dereference::NO_DEREFERENCE)
|
||||
.help(
|
||||
"Affect the referent of each symbolic link (this is the default), \
|
||||
rather than the symbolic link itself.",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(options::dereference::NO_DEREFERENCE)
|
||||
.short("h")
|
||||
.long(options::dereference::NO_DEREFERENCE)
|
||||
.help("Affect symbolic links instead of any referenced file."),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(options::preserve_root::PRESERVE_ROOT)
|
||||
.long(options::preserve_root::PRESERVE_ROOT)
|
||||
.conflicts_with(options::preserve_root::NO_PRESERVE_ROOT)
|
||||
.help("Fail to operate recursively on '/'."),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(options::preserve_root::NO_PRESERVE_ROOT)
|
||||
.long(options::preserve_root::NO_PRESERVE_ROOT)
|
||||
.help("Do not treat '/' specially (the default)."),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(options::REFERENCE)
|
||||
.long(options::REFERENCE)
|
||||
.takes_value(true)
|
||||
.value_name("RFILE")
|
||||
.conflicts_with_all(&[options::USER, options::ROLE, options::TYPE, options::RANGE])
|
||||
.help(
|
||||
"Use security context of RFILE, rather than specifying \
|
||||
a CONTEXT value.",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(options::USER)
|
||||
.short("u")
|
||||
.long(options::USER)
|
||||
.takes_value(true)
|
||||
.value_name("USER")
|
||||
.help("Set user USER in the target security context."),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(options::ROLE)
|
||||
.short("r")
|
||||
.long(options::ROLE)
|
||||
.takes_value(true)
|
||||
.value_name("ROLE")
|
||||
.help("Set role ROLE in the target security context."),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(options::TYPE)
|
||||
.short("t")
|
||||
.long(options::TYPE)
|
||||
.takes_value(true)
|
||||
.value_name("TYPE")
|
||||
.help("Set type TYPE in the target security context."),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(options::RANGE)
|
||||
.short("l")
|
||||
.long(options::RANGE)
|
||||
.takes_value(true)
|
||||
.value_name("RANGE")
|
||||
.help("Set range RANGE in the target security context."),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(options::RECURSIVE)
|
||||
.short("R")
|
||||
.long(options::RECURSIVE)
|
||||
.help("Operate on files and directories recursively."),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(options::sym_links::FOLLOW_ARG_DIR_SYM_LINK)
|
||||
.short("H")
|
||||
.requires(options::RECURSIVE)
|
||||
.overrides_with_all(&[
|
||||
options::sym_links::FOLLOW_DIR_SYM_LINKS,
|
||||
options::sym_links::NO_FOLLOW_SYM_LINKS,
|
||||
])
|
||||
.help(
|
||||
"If a command line argument is a symbolic link to a directory, \
|
||||
traverse it. Only valid when -R is specified.",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(options::sym_links::FOLLOW_DIR_SYM_LINKS)
|
||||
.short("L")
|
||||
.requires(options::RECURSIVE)
|
||||
.overrides_with_all(&[
|
||||
options::sym_links::FOLLOW_ARG_DIR_SYM_LINK,
|
||||
options::sym_links::NO_FOLLOW_SYM_LINKS,
|
||||
])
|
||||
.help(
|
||||
"Traverse every symbolic link to a directory encountered. \
|
||||
Only valid when -R is specified.",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(options::sym_links::NO_FOLLOW_SYM_LINKS)
|
||||
.short("P")
|
||||
.requires(options::RECURSIVE)
|
||||
.overrides_with_all(&[
|
||||
options::sym_links::FOLLOW_ARG_DIR_SYM_LINK,
|
||||
options::sym_links::FOLLOW_DIR_SYM_LINKS,
|
||||
])
|
||||
.help(
|
||||
"Do not traverse any symbolic links (default). \
|
||||
Only valid when -R is specified.",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(options::VERBOSE)
|
||||
.short("v")
|
||||
.long(options::VERBOSE)
|
||||
.help("Output a diagnostic for every file processed."),
|
||||
)
|
||||
.arg(Arg::with_name("FILE").multiple(true).min_values(1))
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Options {
|
||||
verbose: bool,
|
||||
preserve_root: bool,
|
||||
recursive_mode: RecursiveMode,
|
||||
affect_symlink_referent: bool,
|
||||
mode: CommandLineMode,
|
||||
files: Vec<PathBuf>,
|
||||
}
|
||||
|
||||
fn parse_command_line(config: clap::App, args: impl uucore::Args) -> Result<Options> {
|
||||
let matches = config.get_matches_from_safe(args)?;
|
||||
|
||||
let verbose = matches.is_present(options::VERBOSE);
|
||||
|
||||
let (recursive_mode, affect_symlink_referent) = if matches.is_present(options::RECURSIVE) {
|
||||
if matches.is_present(options::sym_links::FOLLOW_DIR_SYM_LINKS) {
|
||||
if matches.is_present(options::dereference::NO_DEREFERENCE) {
|
||||
return Err(Error::ArgumentsMismatch(format!(
|
||||
"'--{}' with '--{}' require '-P'",
|
||||
options::RECURSIVE,
|
||||
options::dereference::NO_DEREFERENCE
|
||||
)));
|
||||
}
|
||||
|
||||
(RecursiveMode::RecursiveAndFollowAllDirSymLinks, true)
|
||||
} else if matches.is_present(options::sym_links::FOLLOW_ARG_DIR_SYM_LINK) {
|
||||
if matches.is_present(options::dereference::NO_DEREFERENCE) {
|
||||
return Err(Error::ArgumentsMismatch(format!(
|
||||
"'--{}' with '--{}' require '-P'",
|
||||
options::RECURSIVE,
|
||||
options::dereference::NO_DEREFERENCE
|
||||
)));
|
||||
}
|
||||
|
||||
(RecursiveMode::RecursiveAndFollowArgDirSymLinks, true)
|
||||
} else {
|
||||
if matches.is_present(options::dereference::DEREFERENCE) {
|
||||
return Err(Error::ArgumentsMismatch(format!(
|
||||
"'--{}' with '--{}' require either '-H' or '-L'",
|
||||
options::RECURSIVE,
|
||||
options::dereference::DEREFERENCE
|
||||
)));
|
||||
}
|
||||
|
||||
(RecursiveMode::RecursiveButDoNotFollowSymLinks, false)
|
||||
}
|
||||
} else {
|
||||
let no_dereference = matches.is_present(options::dereference::NO_DEREFERENCE);
|
||||
(RecursiveMode::NotRecursive, !no_dereference)
|
||||
};
|
||||
|
||||
// By default, do not preserve root.
|
||||
let preserve_root = matches.is_present(options::preserve_root::PRESERVE_ROOT);
|
||||
|
||||
let mut files = matches.values_of_os("FILE").unwrap_or_default();
|
||||
|
||||
let mode = if let Some(path) = matches.value_of_os(options::REFERENCE) {
|
||||
CommandLineMode::ReferenceBased {
|
||||
reference: PathBuf::from(path),
|
||||
}
|
||||
} else if matches.is_present(options::USER)
|
||||
|| matches.is_present(options::ROLE)
|
||||
|| matches.is_present(options::TYPE)
|
||||
|| matches.is_present(options::RANGE)
|
||||
{
|
||||
CommandLineMode::Custom {
|
||||
user: matches.value_of_os(options::USER).map(Into::into),
|
||||
role: matches.value_of_os(options::ROLE).map(Into::into),
|
||||
the_type: matches.value_of_os(options::TYPE).map(Into::into),
|
||||
range: matches.value_of_os(options::RANGE).map(Into::into),
|
||||
}
|
||||
} else if let Some(context) = files.next() {
|
||||
CommandLineMode::ContextBased {
|
||||
context: context.into(),
|
||||
}
|
||||
} else {
|
||||
return Err(Error::MissingContext);
|
||||
};
|
||||
|
||||
let files: Vec<_> = files.map(PathBuf::from).collect();
|
||||
if files.is_empty() {
|
||||
return Err(Error::MissingFiles);
|
||||
}
|
||||
|
||||
Ok(Options {
|
||||
verbose,
|
||||
preserve_root,
|
||||
recursive_mode,
|
||||
affect_symlink_referent,
|
||||
mode,
|
||||
files,
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
enum RecursiveMode {
|
||||
NotRecursive,
|
||||
/// Do not traverse any symbolic links.
|
||||
RecursiveButDoNotFollowSymLinks,
|
||||
/// Traverse every symbolic link to a directory encountered.
|
||||
RecursiveAndFollowAllDirSymLinks,
|
||||
/// If a command line argument is a symbolic link to a directory, traverse it.
|
||||
RecursiveAndFollowArgDirSymLinks,
|
||||
}
|
||||
|
||||
impl RecursiveMode {
|
||||
fn is_recursive(self) -> bool {
|
||||
match self {
|
||||
RecursiveMode::NotRecursive => false,
|
||||
|
||||
RecursiveMode::RecursiveButDoNotFollowSymLinks
|
||||
| RecursiveMode::RecursiveAndFollowAllDirSymLinks
|
||||
| RecursiveMode::RecursiveAndFollowArgDirSymLinks => true,
|
||||
}
|
||||
}
|
||||
|
||||
fn fts_open_options(self) -> c_int {
|
||||
match self {
|
||||
RecursiveMode::NotRecursive | RecursiveMode::RecursiveButDoNotFollowSymLinks => {
|
||||
fts_sys::FTS_PHYSICAL
|
||||
}
|
||||
|
||||
RecursiveMode::RecursiveAndFollowAllDirSymLinks => fts_sys::FTS_LOGICAL,
|
||||
|
||||
RecursiveMode::RecursiveAndFollowArgDirSymLinks => {
|
||||
fts_sys::FTS_PHYSICAL | fts_sys::FTS_COMFOLLOW
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum CommandLineMode {
|
||||
ReferenceBased {
|
||||
reference: PathBuf,
|
||||
},
|
||||
ContextBased {
|
||||
context: OsString,
|
||||
},
|
||||
Custom {
|
||||
user: Option<OsString>,
|
||||
role: Option<OsString>,
|
||||
the_type: Option<OsString>,
|
||||
range: Option<OsString>,
|
||||
},
|
||||
}
|
||||
|
||||
fn process_files(
|
||||
options: &Options,
|
||||
context: &SELinuxSecurityContext,
|
||||
root_dev_ino: Option<(libc::ino_t, libc::dev_t)>,
|
||||
) -> Vec<Error> {
|
||||
let fts_options = options.recursive_mode.fts_open_options();
|
||||
let mut fts = match fts::FTS::new(options.files.iter(), fts_options) {
|
||||
Ok(fts) => fts,
|
||||
Err(err) => return vec![err],
|
||||
};
|
||||
|
||||
let mut errors = Vec::default();
|
||||
loop {
|
||||
match fts.read_next_entry() {
|
||||
Ok(true) => {
|
||||
if let Err(err) = process_file(options, context, &mut fts, root_dev_ino) {
|
||||
errors.push(err);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(false) => break,
|
||||
|
||||
Err(err) => {
|
||||
errors.push(err);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
errors
|
||||
}
|
||||
|
||||
fn process_file(
|
||||
options: &Options,
|
||||
context: &SELinuxSecurityContext,
|
||||
fts: &mut fts::FTS,
|
||||
root_dev_ino: Option<(libc::ino_t, libc::dev_t)>,
|
||||
) -> Result<()> {
|
||||
let mut entry = fts.last_entry_ref().unwrap();
|
||||
|
||||
let file_full_name = entry.path().map(PathBuf::from).ok_or_else(|| {
|
||||
Error::from_io("File name validation", io::ErrorKind::InvalidInput.into())
|
||||
})?;
|
||||
|
||||
let fts_access_path = entry.access_path().ok_or_else(|| {
|
||||
let err = io::ErrorKind::InvalidInput.into();
|
||||
Error::from_io1("File name validation", &file_full_name, err)
|
||||
})?;
|
||||
|
||||
let err = |s, k: io::ErrorKind| Error::from_io1(s, &file_full_name, k.into());
|
||||
|
||||
let fts_err = |s| {
|
||||
let r = io::Error::from_raw_os_error(entry.errno());
|
||||
Err(Error::from_io1(s, &file_full_name, r))
|
||||
};
|
||||
|
||||
// SAFETY: If `entry.fts_statp` is not null, then is is assumed to be valid.
|
||||
let file_dev_ino = if let Some(stat) = entry.stat() {
|
||||
(stat.st_ino, stat.st_dev)
|
||||
} else {
|
||||
return Err(err("Getting meta data", io::ErrorKind::InvalidInput));
|
||||
};
|
||||
|
||||
let mut result = Ok(());
|
||||
|
||||
match entry.flags() {
|
||||
fts_sys::FTS_D => {
|
||||
if options.recursive_mode.is_recursive() {
|
||||
if root_dev_ino_check(root_dev_ino, file_dev_ino) {
|
||||
// This happens e.g., with "chcon -R --preserve-root ... /"
|
||||
// and with "chcon -RH --preserve-root ... symlink-to-root".
|
||||
root_dev_ino_warn(&file_full_name);
|
||||
|
||||
// Tell fts not to traverse into this hierarchy.
|
||||
let _ignored = fts.set(fts_sys::FTS_SKIP);
|
||||
|
||||
// Ensure that we do not process "/" on the second visit.
|
||||
let _ignored = fts.read_next_entry();
|
||||
|
||||
return Err(err("Modifying root path", io::ErrorKind::PermissionDenied));
|
||||
}
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
fts_sys::FTS_DP => {
|
||||
if !options.recursive_mode.is_recursive() {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
fts_sys::FTS_NS => {
|
||||
// For a top-level file or directory, this FTS_NS (stat failed) indicator is determined
|
||||
// at the time of the initial fts_open call. With programs like chmod, chown, and chgrp,
|
||||
// that modify permissions, it is possible that the file in question is accessible when
|
||||
// control reaches this point. So, if this is the first time we've seen the FTS_NS for
|
||||
// this file, tell fts_read to stat it "again".
|
||||
if entry.level() == 0 && entry.number() == 0 {
|
||||
entry.set_number(1);
|
||||
let _ignored = fts.set(fts_sys::FTS_AGAIN);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
result = fts_err("Accessing");
|
||||
}
|
||||
|
||||
fts_sys::FTS_ERR => result = fts_err("Accessing"),
|
||||
|
||||
fts_sys::FTS_DNR => result = fts_err("Reading directory"),
|
||||
|
||||
fts_sys::FTS_DC => {
|
||||
if cycle_warning_required(options.recursive_mode.fts_open_options(), &entry) {
|
||||
emit_cycle_warning(&file_full_name);
|
||||
return Err(err("Reading cyclic directory", io::ErrorKind::InvalidData));
|
||||
}
|
||||
}
|
||||
|
||||
_ => {}
|
||||
}
|
||||
|
||||
if entry.flags() == fts_sys::FTS_DP
|
||||
&& result.is_ok()
|
||||
&& root_dev_ino_check(root_dev_ino, file_dev_ino)
|
||||
{
|
||||
root_dev_ino_warn(&file_full_name);
|
||||
result = Err(err("Modifying root path", io::ErrorKind::PermissionDenied));
|
||||
}
|
||||
|
||||
if result.is_ok() {
|
||||
if options.verbose {
|
||||
println!(
|
||||
"{}: Changing security context of: {}",
|
||||
uucore::util_name(),
|
||||
file_full_name.quote()
|
||||
);
|
||||
}
|
||||
|
||||
result = change_file_context(options, context, fts_access_path);
|
||||
}
|
||||
|
||||
if !options.recursive_mode.is_recursive() {
|
||||
let _ignored = fts.set(fts_sys::FTS_SKIP);
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
fn change_file_context(
|
||||
options: &Options,
|
||||
context: &SELinuxSecurityContext,
|
||||
path: &Path,
|
||||
) -> Result<()> {
|
||||
match &options.mode {
|
||||
CommandLineMode::Custom {
|
||||
user,
|
||||
role,
|
||||
the_type,
|
||||
range,
|
||||
} => {
|
||||
let err0 = || -> Result<()> {
|
||||
// If the file doesn't have a context, and we're not setting all of the context
|
||||
// components, there isn't really an obvious default. Thus, we just give up.
|
||||
let op = "Applying partial security context to unlabeled file";
|
||||
let err = io::ErrorKind::InvalidInput.into();
|
||||
Err(Error::from_io1(op, path, err))
|
||||
};
|
||||
|
||||
let file_context =
|
||||
match SecurityContext::of_path(path, options.affect_symlink_referent, false) {
|
||||
Ok(Some(context)) => context,
|
||||
|
||||
Ok(None) => return err0(),
|
||||
Err(r) => return Err(Error::from_selinux("Getting security context", r)),
|
||||
};
|
||||
|
||||
let c_file_context = match file_context.to_c_string() {
|
||||
Ok(Some(context)) => context,
|
||||
|
||||
Ok(None) => return err0(),
|
||||
Err(r) => return Err(Error::from_selinux("Getting security context", r)),
|
||||
};
|
||||
|
||||
let se_context =
|
||||
OpaqueSecurityContext::from_c_str(c_file_context.as_ref()).map_err(|_r| {
|
||||
let err = io::ErrorKind::InvalidInput.into();
|
||||
Error::from_io1("Creating security context", path, err)
|
||||
})?;
|
||||
|
||||
type SetValueProc = fn(&OpaqueSecurityContext, &CStr) -> selinux::errors::Result<()>;
|
||||
|
||||
let list: &[(&Option<OsString>, SetValueProc)] = &[
|
||||
(user, OpaqueSecurityContext::set_user),
|
||||
(role, OpaqueSecurityContext::set_role),
|
||||
(the_type, OpaqueSecurityContext::set_type),
|
||||
(range, OpaqueSecurityContext::set_range),
|
||||
];
|
||||
|
||||
for (new_value, set_value_proc) in list {
|
||||
if let Some(new_value) = new_value {
|
||||
let c_new_value = os_str_to_c_string(new_value).map_err(|_r| {
|
||||
let err = io::ErrorKind::InvalidInput.into();
|
||||
Error::from_io1("Creating security context", path, err)
|
||||
})?;
|
||||
|
||||
set_value_proc(&se_context, &c_new_value)
|
||||
.map_err(|r| Error::from_selinux("Setting security context user", r))?;
|
||||
}
|
||||
}
|
||||
|
||||
let context_string = se_context
|
||||
.to_c_string()
|
||||
.map_err(|r| Error::from_selinux("Getting security context", r))?;
|
||||
|
||||
if c_file_context.as_ref().to_bytes() == context_string.as_ref().to_bytes() {
|
||||
Ok(()) // Nothing to change.
|
||||
} else {
|
||||
SecurityContext::from_c_str(&context_string, false)
|
||||
.set_for_path(path, options.affect_symlink_referent, false)
|
||||
.map_err(|r| Error::from_selinux("Setting security context", r))
|
||||
}
|
||||
}
|
||||
|
||||
CommandLineMode::ReferenceBased { .. } | CommandLineMode::ContextBased { .. } => {
|
||||
if let Some(c_context) = context.to_c_string()? {
|
||||
SecurityContext::from_c_str(c_context.as_ref(), false)
|
||||
.set_for_path(path, options.affect_symlink_referent, false)
|
||||
.map_err(|r| Error::from_selinux("Setting security context", r))
|
||||
} else {
|
||||
let err = io::ErrorKind::InvalidInput.into();
|
||||
Err(Error::from_io1("Setting security context", path, err))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
pub(crate) fn os_str_to_c_string(s: &OsStr) -> Result<CString> {
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
|
||||
CString::new(s.as_bytes())
|
||||
.map_err(|_r| Error::from_io("CString::new()", io::ErrorKind::InvalidInput.into()))
|
||||
}
|
||||
|
||||
/// Call `lstat()` to get the device and inode numbers for `/`.
|
||||
#[cfg(unix)]
|
||||
fn get_root_dev_ino() -> Result<(libc::ino_t, libc::dev_t)> {
|
||||
use std::os::unix::fs::MetadataExt;
|
||||
|
||||
fs::symlink_metadata("/")
|
||||
.map(|md| (md.ino(), md.dev()))
|
||||
.map_err(|r| Error::from_io1("std::fs::symlink_metadata", "/", r))
|
||||
}
|
||||
|
||||
fn root_dev_ino_check(
|
||||
root_dev_ino: Option<(libc::ino_t, libc::dev_t)>,
|
||||
dir_dev_ino: (libc::ino_t, libc::dev_t),
|
||||
) -> bool {
|
||||
root_dev_ino.map_or(false, |root_dev_ino| root_dev_ino == dir_dev_ino)
|
||||
}
|
||||
|
||||
fn root_dev_ino_warn(dir_name: &Path) {
|
||||
if dir_name.as_os_str() == "/" {
|
||||
show_warning!(
|
||||
"It is dangerous to operate recursively on '/'. \
|
||||
Use --{} to override this failsafe.",
|
||||
options::preserve_root::NO_PRESERVE_ROOT,
|
||||
);
|
||||
} else {
|
||||
show_warning!(
|
||||
"It is dangerous to operate recursively on {} (same as '/'). \
|
||||
Use --{} to override this failsafe.",
|
||||
dir_name.quote(),
|
||||
options::preserve_root::NO_PRESERVE_ROOT,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// When fts_read returns FTS_DC to indicate a directory cycle, it may or may not indicate
|
||||
// a real problem.
|
||||
// When a program like chgrp performs a recursive traversal that requires traversing symbolic links,
|
||||
// it is *not* a problem.
|
||||
// However, when invoked with "-P -R", it deserves a warning.
|
||||
// The fts_options parameter records the options that control this aspect of fts's behavior,
|
||||
// so test that.
|
||||
fn cycle_warning_required(fts_options: c_int, entry: &fts::EntryRef) -> bool {
|
||||
// When dereferencing no symlinks, or when dereferencing only those listed on the command line
|
||||
// and we're not processing a command-line argument, then a cycle is a serious problem.
|
||||
((fts_options & fts_sys::FTS_PHYSICAL) != 0)
|
||||
&& (((fts_options & fts_sys::FTS_COMFOLLOW) == 0) || entry.level() != 0)
|
||||
}
|
||||
|
||||
fn emit_cycle_warning(file_name: &Path) {
|
||||
show_warning!(
|
||||
"Circular directory structure.\n\
|
||||
This almost certainly means that you have a corrupted file system.\n\
|
||||
NOTIFY YOUR SYSTEM MANAGER.\n\
|
||||
The following directory is part of the cycle {}.",
|
||||
file_name.quote()
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum SELinuxSecurityContext<'t> {
|
||||
File(SecurityContext<'t>),
|
||||
String(Option<CString>),
|
||||
}
|
||||
|
||||
impl<'t> SELinuxSecurityContext<'t> {
|
||||
fn to_c_string(&self) -> Result<Option<Cow<CStr>>> {
|
||||
match self {
|
||||
Self::File(context) => context
|
||||
.to_c_string()
|
||||
.map_err(|r| Error::from_selinux("SELinuxSecurityContext::to_c_string()", r)),
|
||||
|
||||
Self::String(context) => Ok(context.as_deref().map(Cow::Borrowed)),
|
||||
}
|
||||
}
|
||||
}
|
73
src/uu/chcon/src/errors.rs
Normal file
73
src/uu/chcon/src/errors.rs
Normal file
|
@ -0,0 +1,73 @@
|
|||
use std::ffi::OsString;
|
||||
use std::fmt::Write;
|
||||
use std::io;
|
||||
|
||||
use uucore::display::Quotable;
|
||||
|
||||
pub(crate) type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub(crate) enum Error {
|
||||
#[error("No context is specified")]
|
||||
MissingContext,
|
||||
|
||||
#[error("No files are specified")]
|
||||
MissingFiles,
|
||||
|
||||
#[error("{0}")]
|
||||
ArgumentsMismatch(String),
|
||||
|
||||
#[error(transparent)]
|
||||
CommandLine(#[from] clap::Error),
|
||||
|
||||
#[error("{operation} failed")]
|
||||
SELinux {
|
||||
operation: &'static str,
|
||||
source: selinux::errors::Error,
|
||||
},
|
||||
|
||||
#[error("{operation} failed")]
|
||||
Io {
|
||||
operation: &'static str,
|
||||
source: io::Error,
|
||||
},
|
||||
|
||||
#[error("{operation} failed on {}", .operand1.quote())]
|
||||
Io1 {
|
||||
operation: &'static str,
|
||||
operand1: OsString,
|
||||
source: io::Error,
|
||||
},
|
||||
}
|
||||
|
||||
impl Error {
|
||||
pub(crate) fn from_io(operation: &'static str, source: io::Error) -> Self {
|
||||
Self::Io { operation, source }
|
||||
}
|
||||
|
||||
pub(crate) fn from_io1(
|
||||
operation: &'static str,
|
||||
operand1: impl Into<OsString>,
|
||||
source: io::Error,
|
||||
) -> Self {
|
||||
Self::Io1 {
|
||||
operation,
|
||||
operand1: operand1.into(),
|
||||
source,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn from_selinux(operation: &'static str, source: selinux::errors::Error) -> Self {
|
||||
Self::SELinux { operation, source }
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn report_full_error(mut err: &dyn std::error::Error) -> String {
|
||||
let mut desc = String::with_capacity(256);
|
||||
write!(&mut desc, "{}", err).unwrap();
|
||||
while let Some(source) = err.source() {
|
||||
err = source;
|
||||
write!(&mut desc, ". {}", err).unwrap();
|
||||
}
|
||||
desc
|
||||
}
|
193
src/uu/chcon/src/fts.rs
Normal file
193
src/uu/chcon/src/fts.rs
Normal file
|
@ -0,0 +1,193 @@
|
|||
use std::ffi::{CStr, CString, OsStr};
|
||||
use std::marker::PhantomData;
|
||||
use std::os::raw::{c_int, c_long, c_short};
|
||||
use std::path::Path;
|
||||
use std::ptr::NonNull;
|
||||
use std::{io, iter, ptr, slice};
|
||||
|
||||
use crate::errors::{Error, Result};
|
||||
use crate::os_str_to_c_string;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct FTS {
|
||||
fts: ptr::NonNull<fts_sys::FTS>,
|
||||
|
||||
entry: Option<ptr::NonNull<fts_sys::FTSENT>>,
|
||||
_phantom_data: PhantomData<fts_sys::FTSENT>,
|
||||
}
|
||||
|
||||
impl FTS {
|
||||
pub(crate) fn new<I>(paths: I, options: c_int) -> Result<Self>
|
||||
where
|
||||
I: IntoIterator,
|
||||
I::Item: AsRef<OsStr>,
|
||||
{
|
||||
let files_paths: Vec<CString> = paths
|
||||
.into_iter()
|
||||
.map(|s| os_str_to_c_string(s.as_ref()))
|
||||
.collect::<Result<_>>()?;
|
||||
|
||||
if files_paths.is_empty() {
|
||||
return Err(Error::from_io(
|
||||
"FTS::new()",
|
||||
io::ErrorKind::InvalidInput.into(),
|
||||
));
|
||||
}
|
||||
|
||||
let path_argv: Vec<_> = files_paths
|
||||
.iter()
|
||||
.map(CString::as_ref)
|
||||
.map(CStr::as_ptr)
|
||||
.chain(iter::once(ptr::null()))
|
||||
.collect();
|
||||
|
||||
// SAFETY: We assume calling fts_open() is safe:
|
||||
// - `path_argv` is an array holding at least one path, and null-terminated.
|
||||
// - `compar` is None.
|
||||
let fts = unsafe { fts_sys::fts_open(path_argv.as_ptr().cast(), options, None) };
|
||||
|
||||
let fts = ptr::NonNull::new(fts)
|
||||
.ok_or_else(|| Error::from_io("fts_open()", io::Error::last_os_error()))?;
|
||||
|
||||
Ok(Self {
|
||||
fts,
|
||||
entry: None,
|
||||
_phantom_data: PhantomData,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn last_entry_ref(&mut self) -> Option<EntryRef> {
|
||||
self.entry.map(move |entry| EntryRef::new(self, entry))
|
||||
}
|
||||
|
||||
pub(crate) fn read_next_entry(&mut self) -> Result<bool> {
|
||||
// SAFETY: We assume calling fts_read() is safe with a non-null `fts`
|
||||
// pointer assumed to be valid.
|
||||
let new_entry = unsafe { fts_sys::fts_read(self.fts.as_ptr()) };
|
||||
|
||||
self.entry = NonNull::new(new_entry);
|
||||
if self.entry.is_none() {
|
||||
let r = io::Error::last_os_error();
|
||||
if let Some(0) = r.raw_os_error() {
|
||||
Ok(false)
|
||||
} else {
|
||||
Err(Error::from_io("fts_read()", r))
|
||||
}
|
||||
} else {
|
||||
Ok(true)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn set(&mut self, instr: c_int) -> Result<()> {
|
||||
let fts = self.fts.as_ptr();
|
||||
let entry = self
|
||||
.entry
|
||||
.ok_or_else(|| Error::from_io("FTS::set()", io::ErrorKind::UnexpectedEof.into()))?;
|
||||
|
||||
// SAFETY: We assume calling fts_set() is safe with non-null `fts`
|
||||
// and `entry` pointers assumed to be valid.
|
||||
if unsafe { fts_sys::fts_set(fts, entry.as_ptr(), instr) } == -1 {
|
||||
Err(Error::from_io("fts_set()", io::Error::last_os_error()))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for FTS {
|
||||
fn drop(&mut self) {
|
||||
// SAFETY: We assume calling fts_close() is safe with a non-null `fts`
|
||||
// pointer assumed to be valid.
|
||||
unsafe { fts_sys::fts_close(self.fts.as_ptr()) };
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct EntryRef<'fts> {
|
||||
pub(crate) pointer: ptr::NonNull<fts_sys::FTSENT>,
|
||||
|
||||
_fts: PhantomData<&'fts FTS>,
|
||||
_phantom_data: PhantomData<fts_sys::FTSENT>,
|
||||
}
|
||||
|
||||
impl<'fts> EntryRef<'fts> {
|
||||
fn new(_fts: &'fts FTS, entry: ptr::NonNull<fts_sys::FTSENT>) -> Self {
|
||||
Self {
|
||||
pointer: entry,
|
||||
_fts: PhantomData,
|
||||
_phantom_data: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
fn as_ref(&self) -> &fts_sys::FTSENT {
|
||||
// SAFETY: `self.pointer` is a non-null pointer that is assumed to be valid.
|
||||
unsafe { self.pointer.as_ref() }
|
||||
}
|
||||
|
||||
fn as_mut(&mut self) -> &mut fts_sys::FTSENT {
|
||||
// SAFETY: `self.pointer` is a non-null pointer that is assumed to be valid.
|
||||
unsafe { self.pointer.as_mut() }
|
||||
}
|
||||
|
||||
pub(crate) fn flags(&self) -> c_int {
|
||||
c_int::from(self.as_ref().fts_info)
|
||||
}
|
||||
|
||||
pub(crate) fn errno(&self) -> c_int {
|
||||
self.as_ref().fts_errno
|
||||
}
|
||||
|
||||
pub(crate) fn level(&self) -> c_short {
|
||||
self.as_ref().fts_level
|
||||
}
|
||||
|
||||
pub(crate) fn number(&self) -> c_long {
|
||||
self.as_ref().fts_number
|
||||
}
|
||||
|
||||
pub(crate) fn set_number(&mut self, new_number: c_long) {
|
||||
self.as_mut().fts_number = new_number;
|
||||
}
|
||||
|
||||
pub(crate) fn path(&self) -> Option<&Path> {
|
||||
let entry = self.as_ref();
|
||||
if entry.fts_pathlen == 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
NonNull::new(entry.fts_path)
|
||||
.map(|path_ptr| {
|
||||
let path_size = usize::from(entry.fts_pathlen).saturating_add(1);
|
||||
|
||||
// SAFETY: `entry.fts_path` is a non-null pointer that is assumed to be valid.
|
||||
unsafe { slice::from_raw_parts(path_ptr.as_ptr().cast(), path_size) }
|
||||
})
|
||||
.and_then(|bytes| CStr::from_bytes_with_nul(bytes).ok())
|
||||
.map(c_str_to_os_str)
|
||||
.map(Path::new)
|
||||
}
|
||||
|
||||
pub(crate) fn access_path(&self) -> Option<&Path> {
|
||||
ptr::NonNull::new(self.as_ref().fts_accpath)
|
||||
.map(|path_ptr| {
|
||||
// SAFETY: `entry.fts_accpath` is a non-null pointer that is assumed to be valid.
|
||||
unsafe { CStr::from_ptr(path_ptr.as_ptr()) }
|
||||
})
|
||||
.map(c_str_to_os_str)
|
||||
.map(Path::new)
|
||||
}
|
||||
|
||||
pub(crate) fn stat(&self) -> Option<&libc::stat> {
|
||||
ptr::NonNull::new(self.as_ref().fts_statp).map(|stat_ptr| {
|
||||
// SAFETY: `entry.fts_statp` is a non-null pointer that is assumed to be valid.
|
||||
unsafe { stat_ptr.as_ref() }
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn c_str_to_os_str(s: &CStr) -> &OsStr {
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
|
||||
OsStr::from_bytes(s.to_bytes())
|
||||
}
|
1
src/uu/chcon/src/main.rs
Normal file
1
src/uu/chcon/src/main.rs
Normal file
|
@ -0,0 +1 @@
|
|||
uucore_procs::main!(uu_chcon);
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "uu_chgrp"
|
||||
version = "0.0.7"
|
||||
version = "0.0.8"
|
||||
authors = ["uutils developers"]
|
||||
license = "MIT"
|
||||
description = "chgrp ~ (uutils) change the group ownership of FILE"
|
||||
|
@ -16,9 +16,8 @@ path = "src/chgrp.rs"
|
|||
|
||||
[dependencies]
|
||||
clap = { version = "2.33", features = ["wrap_help"] }
|
||||
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" }
|
||||
walkdir = "2.2"
|
||||
uucore = { version=">=0.0.10", package="uucore", path="../../uucore", features=["entries", "fs", "perms"] }
|
||||
uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../uucore_procs" }
|
||||
|
||||
[[bin]]
|
||||
name = "chgrp"
|
||||
|
|
|
@ -5,200 +5,67 @@
|
|||
// For the full copyright and license information, please view the LICENSE
|
||||
// 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]
|
||||
extern crate uucore;
|
||||
use uucore::display::Quotable;
|
||||
pub use uucore::entries;
|
||||
use uucore::fs::resolve_relative_path;
|
||||
use uucore::libc::gid_t;
|
||||
use uucore::perms::{wrap_chgrp, Verbosity};
|
||||
use uucore::error::{FromIo, UResult, USimpleError};
|
||||
use uucore::perms::{chown_base, options, IfFrom};
|
||||
|
||||
use clap::{App, Arg};
|
||||
|
||||
extern crate walkdir;
|
||||
use walkdir::WalkDir;
|
||||
use clap::{App, Arg, ArgMatches};
|
||||
|
||||
use std::fs;
|
||||
use std::fs::Metadata;
|
||||
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 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 {
|
||||
format!(
|
||||
"{0} [OPTION]... GROUP FILE...\n {0} [OPTION]... --reference=RFILE FILE...",
|
||||
executable!()
|
||||
uucore::execution_phrase()
|
||||
)
|
||||
}
|
||||
|
||||
pub fn uumain(args: impl uucore::Args) -> i32 {
|
||||
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: u32;
|
||||
if let Some(file) = matches.value_of(options::REFERENCE) {
|
||||
match fs::metadata(&file) {
|
||||
Ok(meta) => {
|
||||
dest_gid = meta.gid();
|
||||
}
|
||||
Err(e) => {
|
||||
show_error!("failed to get attributes of '{}': {}", file, e);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
fn parse_gid_and_uid(matches: &ArgMatches) -> UResult<(Option<u32>, Option<u32>, IfFrom)> {
|
||||
let dest_gid = if let Some(file) = matches.value_of(options::REFERENCE) {
|
||||
fs::metadata(&file)
|
||||
.map(|meta| Some(meta.gid()))
|
||||
.map_err_context(|| format!("failed to get attributes of {}", file.quote()))?
|
||||
} else {
|
||||
let group = matches.value_of(options::ARG_GROUP).unwrap_or_default();
|
||||
if group.is_empty() {
|
||||
None
|
||||
} else {
|
||||
match entries::grp2gid(group) {
|
||||
Ok(g) => {
|
||||
dest_gid = g;
|
||||
}
|
||||
Ok(g) => Some(g),
|
||||
_ => {
|
||||
show_error!("invalid group: {}", group);
|
||||
return 1;
|
||||
return Err(USimpleError::new(
|
||||
1,
|
||||
format!("invalid group: {}", group.quote()),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
Ok((dest_gid, None, IfFrom::All))
|
||||
}
|
||||
|
||||
let executor = Chgrper {
|
||||
bit_flag,
|
||||
dest_gid,
|
||||
verbosity,
|
||||
recursive,
|
||||
dereference: derefer != 0,
|
||||
preserve_root,
|
||||
files,
|
||||
};
|
||||
executor.exec()
|
||||
#[uucore_procs::gen_uumain]
|
||||
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||
let usage = get_usage();
|
||||
|
||||
chown_base(
|
||||
uu_app().usage(&usage[..]),
|
||||
args,
|
||||
options::ARG_GROUP,
|
||||
parse_gid_and_uid,
|
||||
true,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn uu_app() -> App<'static, 'static> {
|
||||
App::new(executable!())
|
||||
App::new(uucore::util_name())
|
||||
.version(VERSION)
|
||||
.about(ABOUT)
|
||||
.arg(
|
||||
|
@ -276,170 +143,3 @@ pub fn uu_app() -> App<'static, 'static> {
|
|||
.help("traverse every symbolic link to a directory encountered"),
|
||||
)
|
||||
}
|
||||
|
||||
struct Chgrper {
|
||||
dest_gid: 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) => {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "uu_chmod"
|
||||
version = "0.0.7"
|
||||
version = "0.0.8"
|
||||
authors = ["uutils developers"]
|
||||
license = "MIT"
|
||||
description = "chmod ~ (uutils) change mode of FILE"
|
||||
|
@ -17,8 +17,8 @@ path = "src/chmod.rs"
|
|||
[dependencies]
|
||||
clap = { version = "2.33", features = ["wrap_help"] }
|
||||
libc = "0.2.42"
|
||||
uucore = { version=">=0.0.9", package="uucore", path="../../uucore", features=["fs", "mode"] }
|
||||
uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_procs" }
|
||||
uucore = { version=">=0.0.10", package="uucore", path="../../uucore", features=["fs", "mode"] }
|
||||
uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../uucore_procs" }
|
||||
walkdir = "2.2"
|
||||
|
||||
[[bin]]
|
||||
|
|
|
@ -14,6 +14,7 @@ use clap::{crate_version, App, Arg};
|
|||
use std::fs;
|
||||
use std::os::unix::fs::{MetadataExt, PermissionsExt};
|
||||
use std::path::Path;
|
||||
use uucore::display::Quotable;
|
||||
use uucore::fs::display_permissions_unix;
|
||||
use uucore::libc::mode_t;
|
||||
#[cfg(not(windows))]
|
||||
|
@ -36,12 +37,12 @@ mod options {
|
|||
pub const FILE: &str = "FILE";
|
||||
}
|
||||
|
||||
fn get_usage() -> String {
|
||||
fn usage() -> String {
|
||||
format!(
|
||||
"{0} [OPTION]... MODE[,MODE]... FILE...
|
||||
or: {0} [OPTION]... OCTAL-MODE FILE...
|
||||
or: {0} [OPTION]... --reference=RFILE FILE...",
|
||||
executable!()
|
||||
uucore::execution_phrase()
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -56,9 +57,9 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
|
||||
// Before we can parse 'args' with clap (and previously getopts),
|
||||
// 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 = mode::strip_minus_from_mode(&mut args);
|
||||
|
||||
let usage = get_usage();
|
||||
let usage = usage();
|
||||
let after_help = get_long_usage();
|
||||
|
||||
let matches = uu_app()
|
||||
|
@ -75,7 +76,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
.value_of(options::REFERENCE)
|
||||
.and_then(|fref| match fs::metadata(fref) {
|
||||
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 cmode = if mode_had_minus_prefix {
|
||||
|
@ -98,6 +99,10 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
Some(cmode)
|
||||
};
|
||||
|
||||
if files.is_empty() {
|
||||
crash!(1, "missing operand");
|
||||
}
|
||||
|
||||
let chmoder = Chmoder {
|
||||
changes,
|
||||
quiet,
|
||||
|
@ -116,7 +121,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
}
|
||||
|
||||
pub fn uu_app() -> App<'static, 'static> {
|
||||
App::new(executable!())
|
||||
App::new(uucore::util_name())
|
||||
.version(crate_version!())
|
||||
.about(ABOUT)
|
||||
.arg(
|
||||
|
@ -175,27 +180,6 @@ pub fn uu_app() -> App<'static, 'static> {
|
|||
)
|
||||
}
|
||||
|
||||
// Iterate 'args' and delete the first occurrence
|
||||
// of a prefix '-' if it's associated with MODE
|
||||
// e.g. "chmod -v -xw -R FILE" -> "chmod -v xw -R FILE"
|
||||
pub fn strip_minus_from_mode(args: &mut Vec<String>) -> bool {
|
||||
for arg in args {
|
||||
if arg.starts_with('-') {
|
||||
if let Some(second) = arg.chars().nth(1) {
|
||||
match second {
|
||||
'r' | 'w' | 'x' | 'X' | 's' | 't' | 'u' | 'g' | 'o' | '0'..='7' => {
|
||||
// TODO: use strip_prefix() once minimum rust version reaches 1.45.0
|
||||
*arg = arg[1..arg.len()].to_string();
|
||||
return true;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
struct Chmoder {
|
||||
changes: bool,
|
||||
quiet: bool,
|
||||
|
@ -216,21 +200,24 @@ impl Chmoder {
|
|||
if !file.exists() {
|
||||
if is_symlink(file) {
|
||||
println!(
|
||||
"failed to change mode of '{}' from 0000 (---------) to 0000 (---------)",
|
||||
filename
|
||||
"failed to change mode of {} from 0000 (---------) to 0000 (---------)",
|
||||
filename.quote()
|
||||
);
|
||||
if !self.quiet {
|
||||
show_error!("cannot operate on dangling symlink '{}'", filename);
|
||||
show_error!("cannot operate on dangling symlink {}", filename.quote());
|
||||
}
|
||||
} else {
|
||||
show_error!("cannot access '{}': No such file or directory", filename);
|
||||
} else if !self.quiet {
|
||||
show_error!(
|
||||
"cannot access {}: No such file or directory",
|
||||
filename.quote()
|
||||
);
|
||||
}
|
||||
return Err(1);
|
||||
}
|
||||
if self.recursive && self.preserve_root && filename == "/" {
|
||||
show_error!(
|
||||
"it is dangerous to operate recursively on '{}'\nuse --no-preserve-root to override this failsafe",
|
||||
filename
|
||||
"it is dangerous to operate recursively on {}\nuse --no-preserve-root to override this failsafe",
|
||||
filename.quote()
|
||||
);
|
||||
return Err(1);
|
||||
}
|
||||
|
@ -253,23 +240,27 @@ impl Chmoder {
|
|||
// instead it just sets the readonly attribute on the file
|
||||
Err(0)
|
||||
}
|
||||
#[cfg(any(unix, target_os = "redox"))]
|
||||
#[cfg(unix)]
|
||||
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,
|
||||
Err(err) => {
|
||||
if is_symlink(file) {
|
||||
if self.verbose {
|
||||
println!(
|
||||
"neither symbolic link '{}' nor referent has been changed",
|
||||
file.display()
|
||||
"neither symbolic link {} nor referent has been changed",
|
||||
file.quote()
|
||||
);
|
||||
}
|
||||
return Ok(());
|
||||
} 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 {
|
||||
show_error!("'{}': {}", file.display(), err);
|
||||
show_error!("{}: {}", file.quote(), err);
|
||||
}
|
||||
return Err(1);
|
||||
}
|
||||
|
@ -278,18 +269,30 @@ impl Chmoder {
|
|||
Some(mode) => self.change_file(fperm, mode, file)?,
|
||||
None => {
|
||||
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(',') {
|
||||
// cmode is guaranteed to be Some in this case
|
||||
let arr: &[char] = &['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
|
||||
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 {
|
||||
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 {
|
||||
Ok(mode) => {
|
||||
self.change_file(fperm, mode, file)?;
|
||||
fperm = mode;
|
||||
Ok((mode, naive_mode)) => {
|
||||
new_mode = mode;
|
||||
naively_expected_new_mode = naive_mode;
|
||||
}
|
||||
Err(f) => {
|
||||
if !self.quiet {
|
||||
|
@ -299,6 +302,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 +324,8 @@ impl Chmoder {
|
|||
if fperm == mode {
|
||||
if self.verbose && !self.changes {
|
||||
println!(
|
||||
"mode of '{}' retained as {:04o} ({})",
|
||||
file.display(),
|
||||
"mode of {} retained as {:04o} ({})",
|
||||
file.quote(),
|
||||
fperm,
|
||||
display_permissions_unix(fperm as mode_t, false),
|
||||
);
|
||||
|
@ -322,9 +336,9 @@ impl Chmoder {
|
|||
show_error!("{}", err);
|
||||
}
|
||||
if self.verbose {
|
||||
show_error!(
|
||||
"failed to change mode of file '{}' from {:o} ({}) to {:o} ({})",
|
||||
file.display(),
|
||||
println!(
|
||||
"failed to change mode of file {} from {:04o} ({}) to {:04o} ({})",
|
||||
file.quote(),
|
||||
fperm,
|
||||
display_permissions_unix(fperm as mode_t, false),
|
||||
mode,
|
||||
|
@ -334,9 +348,9 @@ impl Chmoder {
|
|||
Err(1)
|
||||
} else {
|
||||
if self.verbose || self.changes {
|
||||
show_error!(
|
||||
"mode of '{}' changed from {:o} ({}) to {:o} ({})",
|
||||
file.display(),
|
||||
println!(
|
||||
"mode of {} changed from {:04o} ({}) to {:04o} ({})",
|
||||
file.quote(),
|
||||
fperm,
|
||||
display_permissions_unix(fperm as mode_t, false),
|
||||
mode,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "uu_chown"
|
||||
version = "0.0.7"
|
||||
version = "0.0.8"
|
||||
authors = ["uutils developers"]
|
||||
license = "MIT"
|
||||
description = "chown ~ (uutils) change the ownership of FILE"
|
||||
|
@ -16,10 +16,8 @@ path = "src/chown.rs"
|
|||
|
||||
[dependencies]
|
||||
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_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_procs" }
|
||||
walkdir = "2.2"
|
||||
uucore = { version=">=0.0.10", package="uucore", path="../../uucore", features=["entries", "fs", "perms"] }
|
||||
uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../uucore_procs" }
|
||||
|
||||
[[bin]]
|
||||
name = "chown"
|
||||
|
|
|
@ -5,133 +5,31 @@
|
|||
// For the full copyright and license information, please view the LICENSE
|
||||
// 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]
|
||||
extern crate uucore;
|
||||
use uucore::display::Quotable;
|
||||
pub use uucore::entries::{self, Group, Locate, Passwd};
|
||||
use uucore::fs::resolve_relative_path;
|
||||
use uucore::libc::{gid_t, uid_t};
|
||||
use uucore::perms::{wrap_chown, Verbosity};
|
||||
use uucore::perms::{chown_base, options, IfFrom};
|
||||
|
||||
use uucore::error::{FromIo, UError, 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::{self, Metadata};
|
||||
use std::fs;
|
||||
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";
|
||||
|
||||
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 {
|
||||
format!(
|
||||
"{0} [OPTION]... [OWNER][:[GROUP]] FILE...\n{0} [OPTION]... --reference=RFILE FILE...",
|
||||
executable!()
|
||||
uucore::execution_phrase()
|
||||
)
|
||||
}
|
||||
|
||||
#[uucore_procs::gen_uumain]
|
||||
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".to_string(),
|
||||
));
|
||||
}
|
||||
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
|
||||
};
|
||||
|
||||
fn parse_gid_uid_and_filter(matches: &ArgMatches) -> UResult<(Option<u32>, Option<u32>, IfFrom)> {
|
||||
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),
|
||||
(None, Some(gid)) => IfFrom::Group(gid),
|
||||
(Some(uid), Some(gid)) => IfFrom::UserGroup(uid, gid),
|
||||
|
@ -145,30 +43,32 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
|||
let dest_gid: Option<u32>;
|
||||
if let Some(file) = matches.value_of(options::REFERENCE) {
|
||||
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_uid = Some(meta.uid());
|
||||
} else {
|
||||
let (u, g) = parse_spec(owner)?;
|
||||
let (u, g) = parse_spec(matches.value_of(options::ARG_OWNER).unwrap(), ':')?;
|
||||
dest_uid = u;
|
||||
dest_gid = g;
|
||||
}
|
||||
let executor = Chowner {
|
||||
bit_flag,
|
||||
dest_uid,
|
||||
dest_gid,
|
||||
verbosity,
|
||||
recursive,
|
||||
dereference: derefer != 0,
|
||||
filter,
|
||||
preserve_root,
|
||||
files,
|
||||
};
|
||||
executor.exec()
|
||||
Ok((dest_gid, dest_uid, filter))
|
||||
}
|
||||
|
||||
#[uucore_procs::gen_uumain]
|
||||
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||
let usage = get_usage();
|
||||
|
||||
chown_base(
|
||||
uu_app().usage(&usage[..]),
|
||||
args,
|
||||
options::ARG_OWNER,
|
||||
parse_gid_uid_and_filter,
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn uu_app() -> App<'static, 'static> {
|
||||
App::new(executable!())
|
||||
App::new(uucore::util_name())
|
||||
.version(crate_version!())
|
||||
.about(ABOUT)
|
||||
.arg(
|
||||
|
@ -177,22 +77,31 @@ pub fn uu_app() -> App<'static, 'static> {
|
|||
.long(options::verbosity::CHANGES)
|
||||
.help("like verbose but report only when a change is made"),
|
||||
)
|
||||
.arg(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::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::with_name(options::dereference::NO_DEREFERENCE)
|
||||
.short("h")
|
||||
.long(options::dereference::NO_DEREFERENCE)
|
||||
.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::with_name(options::FROM)
|
||||
.long(options::FROM)
|
||||
.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"),
|
||||
)
|
||||
|
@ -224,7 +133,11 @@ pub fn uu_app() -> App<'static, 'static> {
|
|||
.value_name("RFILE")
|
||||
.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::with_name(options::traverse::TRAVERSE)
|
||||
.short(options::traverse::TRAVERSE)
|
||||
|
@ -246,41 +159,55 @@ pub fn uu_app() -> App<'static, 'static> {
|
|||
.arg(
|
||||
Arg::with_name(options::verbosity::VERBOSE)
|
||||
.long(options::verbosity::VERBOSE)
|
||||
.short("v")
|
||||
.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>)> {
|
||||
let args = spec.split_terminator(':').collect::<Vec<_>>();
|
||||
let usr_only = args.len() == 1 && !args[0].is_empty();
|
||||
let grp_only = args.len() == 2 && args[0].is_empty();
|
||||
let usr_grp = args.len() == 2 && !args[0].is_empty() && !args[1].is_empty();
|
||||
let uid = if usr_only || usr_grp {
|
||||
Some(
|
||||
Passwd::locate(args[0])
|
||||
.map_err(|_| USimpleError::new(1, format!("invalid user: '{}'", spec)))?
|
||||
.uid(),
|
||||
)
|
||||
/// Parse the username and groupname
|
||||
///
|
||||
/// In theory, it should be username:groupname
|
||||
/// but ...
|
||||
/// it can user.name:groupname
|
||||
/// or username.groupname
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `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 {
|
||||
None
|
||||
};
|
||||
let gid = if grp_only || usr_grp {
|
||||
let gid = if !group.is_empty() {
|
||||
Some(
|
||||
Group::locate(args[1])
|
||||
.map_err(|_| USimpleError::new(1, format!("invalid group: '{}'", spec)))?
|
||||
Group::locate(group)
|
||||
.map_err(|_| USimpleError::new(1, format!("invalid group: {}", spec.quote())))?
|
||||
.gid(),
|
||||
)
|
||||
} else {
|
||||
|
@ -289,206 +216,16 @@ fn parse_spec(spec: &str) -> UResult<(Option<u32>, Option<u32>)> {
|
|||
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(UError::from(ret));
|
||||
}
|
||||
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)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_parse_spec() {
|
||||
assert_eq!(parse_spec(":"), Ok((None, None)));
|
||||
assert!(parse_spec("::")
|
||||
.err()
|
||||
.unwrap()
|
||||
.starts_with("invalid group: "));
|
||||
assert!(matches!(parse_spec(":", ':'), Ok((None, None))));
|
||||
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: "));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "uu_chroot"
|
||||
version = "0.0.7"
|
||||
version = "0.0.8"
|
||||
authors = ["uutils developers"]
|
||||
license = "MIT"
|
||||
description = "chroot ~ (uutils) run COMMAND under a new root directory"
|
||||
|
@ -16,8 +16,8 @@ path = "src/chroot.rs"
|
|||
|
||||
[dependencies]
|
||||
clap= "2.33"
|
||||
uucore = { version=">=0.0.9", package="uucore", path="../../uucore", features=["entries"] }
|
||||
uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_procs" }
|
||||
uucore = { version=">=0.0.10", package="uucore", path="../../uucore", features=["entries"] }
|
||||
uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../uucore_procs" }
|
||||
|
||||
[[bin]]
|
||||
name = "chroot"
|
||||
|
|
|
@ -15,10 +15,10 @@ use std::ffi::CString;
|
|||
use std::io::Error;
|
||||
use std::path::Path;
|
||||
use std::process::Command;
|
||||
use uucore::display::Quotable;
|
||||
use uucore::libc::{self, chroot, setgid, setgroups, setuid};
|
||||
use uucore::{entries, InvalidEncodingHandling};
|
||||
|
||||
static NAME: &str = "chroot";
|
||||
static ABOUT: &str = "Run COMMAND with root directory set to NEWROOT.";
|
||||
static SYNTAX: &str = "[OPTION]... NEWROOT [COMMAND [ARG]...]";
|
||||
|
||||
|
@ -47,15 +47,15 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
None => crash!(
|
||||
1,
|
||||
"Missing operand: NEWROOT\nTry '{} --help' for more information.",
|
||||
NAME
|
||||
uucore::execution_phrase()
|
||||
),
|
||||
};
|
||||
|
||||
if !newroot.is_dir() {
|
||||
crash!(
|
||||
1,
|
||||
"cannot change root directory to `{}`: no such directory",
|
||||
newroot.display()
|
||||
"cannot change root directory to {}: no such directory",
|
||||
newroot.quote()
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -67,7 +67,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
// TODO: refactor the args and command matching
|
||||
// See: https://github.com/uutils/coreutils/pull/2365#discussion_r647849967
|
||||
let command: Vec<&str> = match commands.len() {
|
||||
1 => {
|
||||
0 => {
|
||||
let shell: &str = match user_shell {
|
||||
Err(_) => default_shell,
|
||||
Ok(ref s) => s.as_ref(),
|
||||
|
@ -77,12 +77,28 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
_ => commands,
|
||||
};
|
||||
|
||||
assert!(!command.is_empty());
|
||||
let chroot_command = command[0];
|
||||
let chroot_args = &command[1..];
|
||||
|
||||
// NOTE: Tests can only trigger code beyond this point if they're invoked with root permissions
|
||||
set_context(newroot, &matches);
|
||||
|
||||
let pstatus = Command::new(command[0])
|
||||
.args(&command[1..])
|
||||
let pstatus = Command::new(chroot_command)
|
||||
.args(chroot_args)
|
||||
.status()
|
||||
.unwrap_or_else(|e| crash!(1, "Cannot exec: {}", e));
|
||||
.unwrap_or_else(|e| {
|
||||
// TODO: Exit status:
|
||||
// 125 if chroot itself fails
|
||||
// 126 if command is found but cannot be invoked
|
||||
// 127 if command cannot be found
|
||||
crash!(
|
||||
1,
|
||||
"failed to run command {}: {}",
|
||||
command[0].to_string().quote(),
|
||||
e
|
||||
)
|
||||
});
|
||||
|
||||
if pstatus.success() {
|
||||
0
|
||||
|
@ -92,7 +108,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
}
|
||||
|
||||
pub fn uu_app() -> App<'static, 'static> {
|
||||
App::new(executable!())
|
||||
App::new(uucore::util_name())
|
||||
.version(crate_version!())
|
||||
.about(ABOUT)
|
||||
.usage(SYNTAX)
|
||||
|
@ -150,7 +166,7 @@ fn set_context(root: &Path, options: &clap::ArgMatches) {
|
|||
Some(u) => {
|
||||
let s: Vec<&str> = u.split(':').collect();
|
||||
if s.len() != 2 || s.iter().any(|&spec| spec.is_empty()) {
|
||||
crash!(1, "invalid userspec: `{}`", u)
|
||||
crash!(1, "invalid userspec: {}", u.quote())
|
||||
};
|
||||
s
|
||||
}
|
||||
|
@ -171,7 +187,6 @@ fn set_context(root: &Path, options: &clap::ArgMatches) {
|
|||
}
|
||||
|
||||
fn enter_chroot(root: &Path) {
|
||||
let root_str = root.display();
|
||||
std::env::set_current_dir(root).unwrap();
|
||||
let err = unsafe {
|
||||
chroot(CString::new(".").unwrap().as_bytes_with_nul().as_ptr() as *const libc::c_char)
|
||||
|
@ -180,7 +195,7 @@ fn enter_chroot(root: &Path) {
|
|||
crash!(
|
||||
1,
|
||||
"cannot chroot to {}: {}",
|
||||
root_str,
|
||||
root.quote(),
|
||||
Error::last_os_error()
|
||||
)
|
||||
};
|
||||
|
@ -190,7 +205,7 @@ fn set_main_group(group: &str) {
|
|||
if !group.is_empty() {
|
||||
let group_id = match entries::grp2gid(group) {
|
||||
Ok(g) => g,
|
||||
_ => crash!(1, "no such group: {}", group),
|
||||
_ => crash!(1, "no such group: {}", group.maybe_quote()),
|
||||
};
|
||||
let err = unsafe { setgid(group_id) };
|
||||
if err != 0 {
|
||||
|
@ -235,7 +250,12 @@ fn set_user(user: &str) {
|
|||
let user_id = entries::usr2uid(user).unwrap();
|
||||
let err = unsafe { setuid(user_id as libc::uid_t) };
|
||||
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()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "uu_cksum"
|
||||
version = "0.0.7"
|
||||
version = "0.0.8"
|
||||
authors = ["uutils developers"]
|
||||
license = "MIT"
|
||||
description = "cksum ~ (uutils) display CRC and size of input"
|
||||
|
@ -17,9 +17,13 @@ path = "src/cksum.rs"
|
|||
[dependencies]
|
||||
clap = { version = "2.33", features = ["wrap_help"] }
|
||||
libc = "0.2.42"
|
||||
uucore = { version=">=0.0.9", package="uucore", path="../../uucore" }
|
||||
uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_procs" }
|
||||
uucore = { version=">=0.0.10", package="uucore", path="../../uucore" }
|
||||
uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../uucore_procs" }
|
||||
|
||||
[[bin]]
|
||||
name = "cksum"
|
||||
path = "src/main.rs"
|
||||
|
||||
[package.metadata.cargo-udeps.ignore]
|
||||
# Necessary for "make all"
|
||||
normal = ["uucore_procs"]
|
||||
|
|
|
@ -14,6 +14,7 @@ use clap::{crate_version, App, Arg};
|
|||
use std::fs::File;
|
||||
use std::io::{self, stdin, BufReader, Read};
|
||||
use std::path::Path;
|
||||
use uucore::display::Quotable;
|
||||
use uucore::InvalidEncodingHandling;
|
||||
|
||||
// NOTE: CRC_TABLE_LEN *must* be <= 256 as we cast 0..CRC_TABLE_LEN to u8
|
||||
|
@ -24,60 +25,15 @@ const NAME: &str = "cksum";
|
|||
const SYNTAX: &str = "[OPTIONS] [FILE]...";
|
||||
const SUMMARY: &str = "Print CRC and size for each file";
|
||||
|
||||
// this is basically a hack to get "loops" to work on Rust 1.33. Once we update to Rust 1.46 or
|
||||
// greater, we can just use while loops
|
||||
macro_rules! unroll {
|
||||
(256, |$i:ident| $s:expr) => {{
|
||||
unroll!(@ 32, 0 * 32, $i, $s);
|
||||
unroll!(@ 32, 1 * 32, $i, $s);
|
||||
unroll!(@ 32, 2 * 32, $i, $s);
|
||||
unroll!(@ 32, 3 * 32, $i, $s);
|
||||
unroll!(@ 32, 4 * 32, $i, $s);
|
||||
unroll!(@ 32, 5 * 32, $i, $s);
|
||||
unroll!(@ 32, 6 * 32, $i, $s);
|
||||
unroll!(@ 32, 7 * 32, $i, $s);
|
||||
}};
|
||||
(8, |$i:ident| $s:expr) => {{
|
||||
unroll!(@ 8, 0, $i, $s);
|
||||
}};
|
||||
|
||||
(@ 32, $start:expr, $i:ident, $s:expr) => {{
|
||||
unroll!(@ 8, $start + 0 * 8, $i, $s);
|
||||
unroll!(@ 8, $start + 1 * 8, $i, $s);
|
||||
unroll!(@ 8, $start + 2 * 8, $i, $s);
|
||||
unroll!(@ 8, $start + 3 * 8, $i, $s);
|
||||
}};
|
||||
(@ 8, $start:expr, $i:ident, $s:expr) => {{
|
||||
unroll!(@ 4, $start, $i, $s);
|
||||
unroll!(@ 4, $start + 4, $i, $s);
|
||||
}};
|
||||
(@ 4, $start:expr, $i:ident, $s:expr) => {{
|
||||
unroll!(@ 2, $start, $i, $s);
|
||||
unroll!(@ 2, $start + 2, $i, $s);
|
||||
}};
|
||||
(@ 2, $start:expr, $i:ident, $s:expr) => {{
|
||||
unroll!(@ 1, $start, $i, $s);
|
||||
unroll!(@ 1, $start + 1, $i, $s);
|
||||
}};
|
||||
(@ 1, $start:expr, $i:ident, $s:expr) => {{
|
||||
let $i = $start;
|
||||
let _ = $s;
|
||||
}};
|
||||
}
|
||||
|
||||
const fn generate_crc_table() -> [u32; CRC_TABLE_LEN] {
|
||||
let mut table = [0; CRC_TABLE_LEN];
|
||||
|
||||
// NOTE: works on Rust 1.46
|
||||
//let mut i = 0;
|
||||
//while i < CRC_TABLE_LEN {
|
||||
// table[i] = crc_entry(i as u8) as u32;
|
||||
//
|
||||
// i += 1;
|
||||
//}
|
||||
unroll!(256, |i| {
|
||||
let mut i = 0;
|
||||
while i < CRC_TABLE_LEN {
|
||||
table[i] = crc_entry(i as u8) as u32;
|
||||
});
|
||||
|
||||
i += 1;
|
||||
}
|
||||
|
||||
table
|
||||
}
|
||||
|
@ -85,19 +41,8 @@ const fn generate_crc_table() -> [u32; CRC_TABLE_LEN] {
|
|||
const fn crc_entry(input: u8) -> u32 {
|
||||
let mut crc = (input as u32) << 24;
|
||||
|
||||
// NOTE: this does not work on Rust 1.33, but *does* on 1.46
|
||||
//let mut i = 0;
|
||||
//while i < 8 {
|
||||
// if crc & 0x8000_0000 != 0 {
|
||||
// crc <<= 1;
|
||||
// crc ^= 0x04c1_1db7;
|
||||
// } else {
|
||||
// crc <<= 1;
|
||||
// }
|
||||
//
|
||||
// i += 1;
|
||||
//}
|
||||
unroll!(8, |_i| {
|
||||
let mut i = 0;
|
||||
while i < 8 {
|
||||
let if_condition = crc & 0x8000_0000;
|
||||
let if_body = (crc << 1) ^ 0x04c1_1db7;
|
||||
let else_body = crc << 1;
|
||||
|
@ -107,7 +52,8 @@ const fn crc_entry(input: u8) -> u32 {
|
|||
let condition_table = [else_body, if_body];
|
||||
|
||||
crc = condition_table[(if_condition != 0) as usize];
|
||||
});
|
||||
i += 1;
|
||||
}
|
||||
|
||||
crc
|
||||
}
|
||||
|
@ -147,6 +93,8 @@ fn cksum(fname: &str) -> io::Result<(u32, usize)> {
|
|||
"Is a directory",
|
||||
));
|
||||
};
|
||||
// Silent the warning as we want to the error message
|
||||
#[allow(clippy::question_mark)]
|
||||
if path.metadata().is_err() {
|
||||
return Err(std::io::Error::new(
|
||||
io::ErrorKind::NotFound,
|
||||
|
@ -191,7 +139,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
match cksum("-") {
|
||||
Ok((crc, size)) => println!("{} {}", crc, size),
|
||||
Err(err) => {
|
||||
show_error!("{}", err);
|
||||
show_error!("-: {}", err);
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
|
@ -203,7 +151,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
match cksum(fname.as_ref()) {
|
||||
Ok((crc, size)) => println!("{} {} {}", crc, size, fname),
|
||||
Err(err) => {
|
||||
show_error!("'{}' {}", fname, err);
|
||||
show_error!("{}: {}", fname.maybe_quote(), err);
|
||||
exit_code = 2;
|
||||
}
|
||||
}
|
||||
|
@ -213,7 +161,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
}
|
||||
|
||||
pub fn uu_app() -> App<'static, 'static> {
|
||||
App::new(executable!())
|
||||
App::new(uucore::util_name())
|
||||
.name(NAME)
|
||||
.version(crate_version!())
|
||||
.about(SUMMARY)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "uu_comm"
|
||||
version = "0.0.7"
|
||||
version = "0.0.8"
|
||||
authors = ["uutils developers"]
|
||||
license = "MIT"
|
||||
description = "comm ~ (uutils) compare sorted inputs"
|
||||
|
@ -17,9 +17,13 @@ path = "src/comm.rs"
|
|||
[dependencies]
|
||||
clap = { version = "2.33", features = ["wrap_help"] }
|
||||
libc = "0.2.42"
|
||||
uucore = { version=">=0.0.9", package="uucore", path="../../uucore" }
|
||||
uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_procs" }
|
||||
uucore = { version=">=0.0.10", package="uucore", path="../../uucore" }
|
||||
uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../uucore_procs" }
|
||||
|
||||
[[bin]]
|
||||
name = "comm"
|
||||
path = "src/main.rs"
|
||||
|
||||
[package.metadata.cargo-udeps.ignore]
|
||||
# Necessary for "make all"
|
||||
normal = ["uucore_procs"]
|
||||
|
|
|
@ -7,9 +7,6 @@
|
|||
|
||||
// spell-checker:ignore (ToDO) delim mkdelim
|
||||
|
||||
#[macro_use]
|
||||
extern crate uucore;
|
||||
|
||||
use std::cmp::Ordering;
|
||||
use std::fs::File;
|
||||
use std::io::{self, stdin, BufRead, BufReader, Stdin};
|
||||
|
@ -31,8 +28,8 @@ mod options {
|
|||
pub const FILE_2: &str = "FILE2";
|
||||
}
|
||||
|
||||
fn get_usage() -> String {
|
||||
format!("{} [OPTION]... FILE1 FILE2", executable!())
|
||||
fn usage() -> String {
|
||||
format!("{} [OPTION]... FILE1 FILE2", uucore::execution_phrase())
|
||||
}
|
||||
|
||||
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 {
|
||||
let usage = get_usage();
|
||||
let usage = usage();
|
||||
let args = args
|
||||
.collect_str(InvalidEncodingHandling::ConvertLossy)
|
||||
.accept_any();
|
||||
|
@ -148,7 +145,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
}
|
||||
|
||||
pub fn uu_app() -> App<'static, 'static> {
|
||||
App::new(executable!())
|
||||
App::new(uucore::util_name())
|
||||
.version(crate_version!())
|
||||
.about(ABOUT)
|
||||
.after_help(LONG_HELP)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "uu_cp"
|
||||
version = "0.0.7"
|
||||
version = "0.0.8"
|
||||
authors = [
|
||||
"Jordy Dickinson <jordy.dickinson@gmail.com>",
|
||||
"Joshua S. Miller <jsmiller@uchicago.edu>",
|
||||
|
@ -23,19 +23,29 @@ clap = { version = "2.33", features = ["wrap_help"] }
|
|||
filetime = "0.2"
|
||||
libc = "0.2.85"
|
||||
quick-error = "1.2.3"
|
||||
uucore = { version=">=0.0.9", package="uucore", path="../../uucore", features=["fs"] }
|
||||
uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_procs" }
|
||||
selinux = { version="0.2.3", optional=true }
|
||||
uucore = { version=">=0.0.10", package="uucore", path="../../uucore", features=["entries", "fs", "perms", "mode"] }
|
||||
uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../uucore_procs" }
|
||||
walkdir = "2.2"
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
ioctl-sys = "0.5.2"
|
||||
ioctl-sys = "0.6"
|
||||
|
||||
[target.'cfg(target_os = "windows")'.dependencies]
|
||||
winapi = { version="0.3", features=["fileapi"] }
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
xattr="0.2.1"
|
||||
exacl= { version = "0.6.0", optional=true }
|
||||
|
||||
[[bin]]
|
||||
name = "cp"
|
||||
path = "src/main.rs"
|
||||
|
||||
[features]
|
||||
feat_selinux = ["selinux"]
|
||||
feat_acl = ["exacl"]
|
||||
|
||||
[package.metadata.cargo-udeps.ignore]
|
||||
# Necessary for "make all"
|
||||
normal = ["uucore_procs"]
|
||||
|
|
|
@ -18,6 +18,7 @@ extern crate quick_error;
|
|||
#[macro_use]
|
||||
extern crate uucore;
|
||||
|
||||
use uucore::display::Quotable;
|
||||
#[cfg(windows)]
|
||||
use winapi::um::fileapi::CreateFileW;
|
||||
#[cfg(windows)]
|
||||
|
@ -48,7 +49,8 @@ use std::path::{Path, PathBuf, StripPrefixError};
|
|||
use std::str::FromStr;
|
||||
use std::string::ToString;
|
||||
use uucore::backup_control::{self, BackupMode};
|
||||
use uucore::fs::{canonicalize, CanonicalizeMode};
|
||||
use uucore::error::{set_exit_code, ExitCode, UError, UResult};
|
||||
use uucore::fs::{canonicalize, MissingHandling, ResolveMode};
|
||||
use walkdir::WalkDir;
|
||||
|
||||
#[cfg(unix)]
|
||||
|
@ -67,6 +69,7 @@ quick_error! {
|
|||
IoErrContext(err: io::Error, path: String) {
|
||||
display("{}: {}", path, err)
|
||||
context(path: &'a str, err: io::Error) -> (err, path.to_owned())
|
||||
context(context: String, err: io::Error) -> (err, context)
|
||||
cause(err)
|
||||
}
|
||||
|
||||
|
@ -99,7 +102,13 @@ quick_error! {
|
|||
NotImplemented(opt: String) { display("Option '{}' not yet implemented.", opt) }
|
||||
|
||||
/// 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()) }
|
||||
}
|
||||
}
|
||||
|
||||
impl UError for Error {
|
||||
fn code(&self) -> i32 {
|
||||
EXIT_ERR
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -180,12 +189,15 @@ pub enum CopyMode {
|
|||
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 {
|
||||
#[cfg(unix)]
|
||||
Mode,
|
||||
Ownership,
|
||||
Mode,
|
||||
Timestamps,
|
||||
#[cfg(feature = "feat_selinux")]
|
||||
Context,
|
||||
Links,
|
||||
Xattr,
|
||||
|
@ -215,15 +227,14 @@ pub struct Options {
|
|||
|
||||
static ABOUT: &str = "Copy SOURCE to DEST, or multiple SOURCE(s) to DIRECTORY.";
|
||||
static LONG_HELP: &str = "";
|
||||
static EXIT_OK: i32 = 0;
|
||||
static EXIT_ERR: i32 = 1;
|
||||
|
||||
fn get_usage() -> String {
|
||||
fn usage() -> String {
|
||||
format!(
|
||||
"{0} [OPTION]... [-T] SOURCE DEST
|
||||
{0} [OPTION]... SOURCE... DIRECTORY
|
||||
{0} [OPTION]... -t DIRECTORY SOURCE...",
|
||||
executable!()
|
||||
uucore::execution_phrase()
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -231,8 +242,6 @@ fn get_usage() -> String {
|
|||
mod options {
|
||||
pub const ARCHIVE: &str = "archive";
|
||||
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 CONTEXT: &str = "context";
|
||||
pub const COPY_CONTENTS: &str = "copy-contents";
|
||||
|
@ -242,7 +251,7 @@ mod options {
|
|||
pub const LINK: &str = "link";
|
||||
pub const NO_CLOBBER: &str = "no-clobber";
|
||||
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_TARGET_DIRECTORY: &str = "no-target-directory";
|
||||
pub const ONE_FILE_SYSTEM: &str = "one-file-system";
|
||||
|
@ -257,7 +266,6 @@ mod options {
|
|||
pub const REMOVE_DESTINATION: &str = "remove-destination";
|
||||
pub const SPARSE: &str = "sparse";
|
||||
pub const STRIP_TRAILING_SLASHES: &str = "strip-trailing-slashes";
|
||||
pub const SUFFIX: &str = "suffix";
|
||||
pub const SYMBOLIC_LINK: &str = "symbolic-link";
|
||||
pub const TARGET_DIRECTORY: &str = "target-directory";
|
||||
pub const UPDATE: &str = "update";
|
||||
|
@ -269,6 +277,7 @@ static PRESERVABLE_ATTRIBUTES: &[&str] = &[
|
|||
"mode",
|
||||
"ownership",
|
||||
"timestamps",
|
||||
#[cfg(feature = "feat_selinux")]
|
||||
"context",
|
||||
"links",
|
||||
"xattr",
|
||||
|
@ -276,24 +285,18 @@ static PRESERVABLE_ATTRIBUTES: &[&str] = &[
|
|||
];
|
||||
|
||||
#[cfg(not(unix))]
|
||||
static PRESERVABLE_ATTRIBUTES: &[&str] = &[
|
||||
"ownership",
|
||||
"timestamps",
|
||||
"context",
|
||||
"links",
|
||||
"xattr",
|
||||
"all",
|
||||
];
|
||||
static PRESERVABLE_ATTRIBUTES: &[&str] =
|
||||
&["mode", "timestamps", "context", "links", "xattr", "all"];
|
||||
|
||||
static DEFAULT_ATTRIBUTES: &[Attribute] = &[
|
||||
#[cfg(unix)]
|
||||
Attribute::Mode,
|
||||
#[cfg(unix)]
|
||||
Attribute::Ownership,
|
||||
Attribute::Timestamps,
|
||||
];
|
||||
|
||||
pub fn uu_app() -> App<'static, 'static> {
|
||||
App::new(executable!())
|
||||
App::new(uucore::util_name())
|
||||
.version(crate_version!())
|
||||
.about(ABOUT)
|
||||
.arg(Arg::with_name(options::TARGET_DIRECTORY)
|
||||
|
@ -355,24 +358,9 @@ pub fn uu_app() -> App<'static, 'static> {
|
|||
.conflicts_with(options::FORCE)
|
||||
.help("remove each existing destination file before attempting to open it \
|
||||
(contrast with --force). On Windows, current only works for writeable files."))
|
||||
.arg(Arg::with_name(options::BACKUP)
|
||||
.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")
|
||||
)
|
||||
.arg(Arg::with_name(options::SUFFIX)
|
||||
.short("S")
|
||||
.long(options::SUFFIX)
|
||||
.takes_value(true)
|
||||
.value_name("SUFFIX")
|
||||
.help("override the usual backup suffix"))
|
||||
.arg(backup_control::arguments::backup())
|
||||
.arg(backup_control::arguments::backup_no_args())
|
||||
.arg(backup_control::arguments::suffix())
|
||||
.arg(Arg::with_name(options::UPDATE)
|
||||
.short("u")
|
||||
.long(options::UPDATE)
|
||||
|
@ -399,13 +387,13 @@ pub fn uu_app() -> App<'static, 'static> {
|
|||
.conflicts_with_all(&[options::PRESERVE_DEFAULT_ATTRIBUTES, options::NO_PRESERVE])
|
||||
// -d 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"))
|
||||
.arg(Arg::with_name(options::PRESERVE_DEFAULT_ATTRIBUTES)
|
||||
.short("-p")
|
||||
.long(options::PRESERVE_DEFAULT_ATTRIBUTES)
|
||||
.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)
|
||||
.long(options::NO_PRESERVE)
|
||||
.takes_value(true)
|
||||
|
@ -464,8 +452,9 @@ pub fn uu_app() -> App<'static, 'static> {
|
|||
.multiple(true))
|
||||
}
|
||||
|
||||
pub fn uumain(args: impl uucore::Args) -> i32 {
|
||||
let usage = get_usage();
|
||||
#[uucore_procs::gen_uumain]
|
||||
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||
let usage = usage();
|
||||
let matches = uu_app()
|
||||
.after_help(&*format!(
|
||||
"{}\n{}",
|
||||
|
@ -475,11 +464,11 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
.usage(&usage[..])
|
||||
.get_matches_from(args);
|
||||
|
||||
let options = crash_if_err!(EXIT_ERR, Options::from_matches(&matches));
|
||||
let options = Options::from_matches(&matches)?;
|
||||
|
||||
if options.overwrite == OverwriteMode::NoClobber && options.backup != BackupMode::NoBackup {
|
||||
show_usage_error!("options --backup and --no-clobber are mutually exclusive");
|
||||
return 1;
|
||||
return Err(ExitCode(EXIT_ERR).into());
|
||||
}
|
||||
|
||||
let paths: Vec<String> = matches
|
||||
|
@ -487,7 +476,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
.map(|v| v.map(ToString::to_string).collect())
|
||||
.unwrap_or_default();
|
||||
|
||||
let (sources, target) = crash_if_err!(EXIT_ERR, parse_path_args(&paths, &options));
|
||||
let (sources, target) = parse_path_args(&paths, &options)?;
|
||||
|
||||
if let Err(error) = copy(&sources, &target, &options) {
|
||||
match error {
|
||||
|
@ -497,10 +486,10 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
// Else we caught a fatal bubbled-up error, log it to stderr
|
||||
_ => show_error!("{}", error),
|
||||
};
|
||||
return EXIT_ERR;
|
||||
set_exit_code(EXIT_ERR);
|
||||
}
|
||||
|
||||
EXIT_OK
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl ClobberMode {
|
||||
|
@ -550,17 +539,18 @@ impl FromStr for Attribute {
|
|||
|
||||
fn from_str(value: &str) -> CopyResult<Attribute> {
|
||||
Ok(match &*value.to_lowercase() {
|
||||
#[cfg(unix)]
|
||||
"mode" => Attribute::Mode,
|
||||
#[cfg(unix)]
|
||||
"ownership" => Attribute::Ownership,
|
||||
"timestamps" => Attribute::Timestamps,
|
||||
#[cfg(feature = "feat_selinux")]
|
||||
"context" => Attribute::Context,
|
||||
"links" => Attribute::Links,
|
||||
"xattr" => Attribute::Xattr,
|
||||
_ => {
|
||||
return Err(Error::InvalidArgument(format!(
|
||||
"invalid attribute '{}'",
|
||||
value
|
||||
"invalid attribute {}",
|
||||
value.quote()
|
||||
)));
|
||||
}
|
||||
})
|
||||
|
@ -570,14 +560,16 @@ impl FromStr for Attribute {
|
|||
fn add_all_attributes() -> Vec<Attribute> {
|
||||
use Attribute::*;
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
let attr = vec![Ownership, Timestamps, Context, Xattr, Links];
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
let mut attr = vec![Ownership, Timestamps, Context, Xattr, Links];
|
||||
|
||||
let attr = vec![
|
||||
#[cfg(unix)]
|
||||
attr.insert(0, Mode);
|
||||
Ownership,
|
||||
Mode,
|
||||
Timestamps,
|
||||
#[cfg(feature = "feat_selinux")]
|
||||
Context,
|
||||
Links,
|
||||
Xattr,
|
||||
];
|
||||
|
||||
attr
|
||||
}
|
||||
|
@ -604,20 +596,12 @@ impl Options {
|
|||
|| matches.is_present(options::RECURSIVE_ALIAS)
|
||||
|| matches.is_present(options::ARCHIVE);
|
||||
|
||||
let backup_mode = backup_control::determine_backup_mode(
|
||||
matches.is_present(options::BACKUP_NO_ARG),
|
||||
matches.is_present(options::BACKUP),
|
||||
matches.value_of(options::BACKUP),
|
||||
);
|
||||
let backup_mode = match backup_mode {
|
||||
Err(err) => {
|
||||
return Err(Error::Backup(err));
|
||||
}
|
||||
let backup_mode = match backup_control::determine_backup_mode(matches) {
|
||||
Err(e) => return Err(Error::Backup(format!("{}", e))),
|
||||
Ok(mode) => mode,
|
||||
};
|
||||
|
||||
let backup_suffix =
|
||||
backup_control::determine_backup_suffix(matches.value_of(options::SUFFIX));
|
||||
let backup_suffix = backup_control::determine_backup_suffix(matches);
|
||||
|
||||
let overwrite = OverwriteMode::from_matches(matches);
|
||||
|
||||
|
@ -628,7 +612,7 @@ impl Options {
|
|||
.map(ToString::to_string);
|
||||
|
||||
// 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) {
|
||||
None => DEFAULT_ATTRIBUTES.to_vec(),
|
||||
Some(attribute_strs) => {
|
||||
|
@ -655,6 +639,11 @@ impl Options {
|
|||
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 {
|
||||
attributes_only: matches.is_present(options::ATTRIBUTES_ONLY),
|
||||
copy_contents: matches.is_present(options::COPY_CONTENTS),
|
||||
|
@ -678,8 +667,8 @@ impl Options {
|
|||
"never" => ReflinkMode::Never,
|
||||
value => {
|
||||
return Err(Error::InvalidArgument(format!(
|
||||
"invalid argument '{}' for \'reflink\'",
|
||||
value
|
||||
"invalid argument {} for \'reflink\'",
|
||||
value.quote()
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
@ -851,7 +840,7 @@ fn copy(sources: &[Source], target: &TargetSlice, options: &Options) -> CopyResu
|
|||
let mut seen_sources = HashSet::with_capacity(sources.len());
|
||||
for source in sources {
|
||||
if seen_sources.contains(source) {
|
||||
show_warning!("source '{}' specified more than once", source.display());
|
||||
show_warning!("source {} specified more than once", source.quote());
|
||||
} else {
|
||||
let mut found_hard_link = false;
|
||||
if preserve_hard_links {
|
||||
|
@ -892,8 +881,8 @@ fn construct_dest_path(
|
|||
) -> CopyResult<PathBuf> {
|
||||
if options.no_target_dir && target.is_dir() {
|
||||
return Err(format!(
|
||||
"cannot overwrite directory '{}' with non-directory",
|
||||
target.display()
|
||||
"cannot overwrite directory {} with non-directory",
|
||||
target.quote()
|
||||
)
|
||||
.into());
|
||||
}
|
||||
|
@ -960,7 +949,7 @@ fn adjust_canonicalization(p: &Path) -> Cow<Path> {
|
|||
/// will not cause a short-circuit.
|
||||
fn copy_directory(root: &Path, target: &TargetSlice, options: &Options) -> CopyResult<()> {
|
||||
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
|
||||
|
@ -1060,12 +1049,12 @@ impl OverwriteMode {
|
|||
match *self {
|
||||
OverwriteMode::NoClobber => Err(Error::NotAllFilesCopied),
|
||||
OverwriteMode::Interactive(_) => {
|
||||
if prompt_yes!("{}: overwrite {}? ", executable!(), path.display()) {
|
||||
if prompt_yes!("{}: overwrite {}? ", uucore::util_name(), path.quote()) {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::Skipped(format!(
|
||||
"Not overwriting {} at user request",
|
||||
path.display()
|
||||
path.quote()
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
@ -1075,27 +1064,66 @@ impl OverwriteMode {
|
|||
}
|
||||
|
||||
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 {
|
||||
#[cfg(unix)]
|
||||
Attribute::Mode => {
|
||||
let mode = fs::metadata(source).context(context)?.permissions().mode();
|
||||
let mut dest_metadata = fs::metadata(source).context(context)?.permissions();
|
||||
dest_metadata.set_mode(mode);
|
||||
fs::set_permissions(dest, source_metadata.permissions()).context(context)?;
|
||||
// FIXME: Implement this for windows as well
|
||||
#[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 => {
|
||||
let metadata = fs::metadata(source).context(context)?;
|
||||
fs::set_permissions(dest, metadata.permissions()).context(context)?;
|
||||
use std::os::unix::prelude::MetadataExt;
|
||||
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 => {
|
||||
let metadata = fs::metadata(source)?;
|
||||
filetime::set_file_times(
|
||||
Path::new(dest),
|
||||
FileTime::from_last_access_time(&metadata),
|
||||
FileTime::from_last_modification_time(&metadata),
|
||||
FileTime::from_last_access_time(&source_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::Xattr => {
|
||||
#[cfg(unix)]
|
||||
|
@ -1103,7 +1131,7 @@ fn copy_attribute(source: &Path, dest: &Path, attribute: &Attribute) -> CopyResu
|
|||
let xattrs = xattr::list(source)?;
|
||||
for attr in xattrs {
|
||||
if let Some(attr_value) = xattr::get(source, attr.clone())? {
|
||||
crash_if_err!(EXIT_ERR, xattr::set(dest, attr, &attr_value[..]));
|
||||
xattr::set(dest, attr, &attr_value[..])?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1132,7 +1160,7 @@ fn symlink_file(source: &Path, dest: &Path, context: &str) -> CopyResult<()> {
|
|||
}
|
||||
|
||||
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.
|
||||
|
@ -1169,8 +1197,8 @@ fn handle_existing_dest(source: &Path, dest: &Path, options: &Options) -> CopyRe
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Copy the a file from `source` to `dest`. No path manipulation is
|
||||
/// done on either `source` or `dest`, the are used as provided.
|
||||
/// Copy the a file from `source` to `dest`. `source` will be dereferenced if
|
||||
/// `options.dereference` is set to true. `dest` will always be dereferenced.
|
||||
///
|
||||
/// Behavior when copying to existing files is contingent on the
|
||||
/// `options.overwrite` mode. If a file is skipped, the return type
|
||||
|
@ -1187,41 +1215,66 @@ fn copy_file(source: &Path, dest: &Path, options: &Options) -> CopyResult<()> {
|
|||
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
|
||||
let mut preserve_context = false;
|
||||
for attribute in &options.preserve_attributes {
|
||||
if *attribute == Attribute::Context {
|
||||
preserve_context = true;
|
||||
}
|
||||
}
|
||||
use uucore::mode::get_umask;
|
||||
|
||||
let mut mode = permissions.mode();
|
||||
|
||||
// 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 {
|
||||
CopyMode::Link => {
|
||||
fs::hard_link(source, dest).context(&*context_for(source, dest))?;
|
||||
fs::hard_link(&source, &dest).context(context)?;
|
||||
}
|
||||
CopyMode::Copy => {
|
||||
copy_helper(source, dest, options)?;
|
||||
copy_helper(&source, &dest, options, context)?;
|
||||
}
|
||||
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::Update => {
|
||||
if dest.exists() {
|
||||
let src_metadata = fs::metadata(source)?;
|
||||
let dest_metadata = fs::metadata(dest)?;
|
||||
let src_metadata = fs::symlink_metadata(&source)?;
|
||||
let dest_metadata = fs::symlink_metadata(&dest)?;
|
||||
|
||||
let src_time = src_metadata.modified()?;
|
||||
let dest_time = dest_metadata.modified()?;
|
||||
if src_time <= dest_time {
|
||||
return Ok(());
|
||||
} else {
|
||||
copy_helper(source, dest, options)?;
|
||||
copy_helper(&source, &dest, options, context)?;
|
||||
}
|
||||
} else {
|
||||
copy_helper(source, dest, options)?;
|
||||
copy_helper(&source, &dest, options, context)?;
|
||||
}
|
||||
}
|
||||
CopyMode::AttrOnly => {
|
||||
|
@ -1229,53 +1282,51 @@ fn copy_file(source: &Path, dest: &Path, options: &Options) -> CopyResult<()> {
|
|||
.write(true)
|
||||
.truncate(false)
|
||||
.create(true)
|
||||
.open(dest)
|
||||
.open(&dest)
|
||||
.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 {
|
||||
copy_attribute(source, dest, attribute)?;
|
||||
copy_attribute(&source, &dest, attribute)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Copy the file from `source` to `dest` either using the normal `fs::copy` or a
|
||||
/// copy-on-write scheme if --reflink is specified and the filesystem supports it.
|
||||
fn copy_helper(source: &Path, dest: &Path, options: &Options) -> CopyResult<()> {
|
||||
fn copy_helper(source: &Path, dest: &Path, options: &Options, context: &str) -> CopyResult<()> {
|
||||
if options.parents {
|
||||
let parent = dest.parent().unwrap_or(dest);
|
||||
fs::create_dir_all(parent)?;
|
||||
}
|
||||
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
|
||||
* https://github.com/rust-lang/rust/issues/79390
|
||||
*/
|
||||
File::create(dest)?;
|
||||
} else if !options.dereference && is_symlink {
|
||||
} else if is_symlink {
|
||||
copy_link(source, dest)?;
|
||||
} else if options.reflink_mode != ReflinkMode::Never {
|
||||
#[cfg(not(any(target_os = "linux", target_os = "macos")))]
|
||||
return Err("--reflink is only supported on linux and macOS"
|
||||
.to_string()
|
||||
.into());
|
||||
#[cfg(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")]
|
||||
copy_on_write_macos(&real_path, dest, options.reflink_mode)?;
|
||||
copy_on_write_macos(source, dest, options.reflink_mode, context)?;
|
||||
#[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 {
|
||||
#[cfg(target_os = "macos")]
|
||||
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))?;
|
||||
fs::copy(source, dest).context(context)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
@ -1289,8 +1340,8 @@ fn copy_link(source: &Path, dest: &Path) -> CopyResult<()> {
|
|||
Some(name) => dest.join(name).into(),
|
||||
None => crash!(
|
||||
EXIT_ERR,
|
||||
"cannot stat '{}': No such file or directory",
|
||||
source.display()
|
||||
"cannot stat {}: No such file or directory",
|
||||
source.quote()
|
||||
),
|
||||
}
|
||||
} else {
|
||||
|
@ -1306,16 +1357,21 @@ fn copy_link(source: &Path, dest: &Path) -> CopyResult<()> {
|
|||
|
||||
/// Copies `source` to `dest` using copy-on-write if possible.
|
||||
#[cfg(target_os = "linux")]
|
||||
fn copy_on_write_linux(source: &Path, dest: &Path, mode: ReflinkMode) -> CopyResult<()> {
|
||||
fn copy_on_write_linux(
|
||||
source: &Path,
|
||||
dest: &Path,
|
||||
mode: ReflinkMode,
|
||||
context: &str,
|
||||
) -> CopyResult<()> {
|
||||
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()
|
||||
.write(true)
|
||||
.truncate(false)
|
||||
.create(true)
|
||||
.open(dest)
|
||||
.context(&*context_for(source, dest))?;
|
||||
.context(context)?;
|
||||
match mode {
|
||||
ReflinkMode::Always => unsafe {
|
||||
let result = ficlone(dst_file.as_raw_fd(), src_file.as_raw_fd() as *const i32);
|
||||
|
@ -1334,7 +1390,7 @@ fn copy_on_write_linux(source: &Path, dest: &Path, mode: ReflinkMode) -> CopyRes
|
|||
ReflinkMode::Auto => unsafe {
|
||||
let result = ficlone(dst_file.as_raw_fd(), src_file.as_raw_fd() as *const i32);
|
||||
if result != 0 {
|
||||
fs::copy(source, dest).context(&*context_for(source, dest))?;
|
||||
fs::copy(source, dest).context(context)?;
|
||||
}
|
||||
Ok(())
|
||||
},
|
||||
|
@ -1344,7 +1400,12 @@ fn copy_on_write_linux(source: &Path, dest: &Path, mode: ReflinkMode) -> CopyRes
|
|||
|
||||
/// Copies `source` to `dest` using copy-on-write if possible.
|
||||
#[cfg(target_os = "macos")]
|
||||
fn copy_on_write_macos(source: &Path, dest: &Path, mode: ReflinkMode) -> CopyResult<()> {
|
||||
fn copy_on_write_macos(
|
||||
source: &Path,
|
||||
dest: &Path,
|
||||
mode: ReflinkMode,
|
||||
context: &str,
|
||||
) -> CopyResult<()> {
|
||||
debug_assert!(mode != ReflinkMode::Never);
|
||||
|
||||
// Extract paths in a form suitable to be passed to a syscall.
|
||||
|
@ -1389,7 +1450,7 @@ fn copy_on_write_macos(source: &Path, dest: &Path, mode: ReflinkMode) -> CopyRes
|
|||
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!(),
|
||||
};
|
||||
}
|
||||
|
@ -1401,11 +1462,11 @@ fn copy_on_write_macos(source: &Path, dest: &Path, mode: ReflinkMode) -> CopyRes
|
|||
pub fn verify_target_type(target: &Path, target_type: &TargetType) -> CopyResult<()> {
|
||||
match (target_type, target.is_dir()) {
|
||||
(&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!(
|
||||
"cannot overwrite directory '{}' with non-directory",
|
||||
target.display()
|
||||
"cannot overwrite directory {} with non-directory",
|
||||
target.quote()
|
||||
)
|
||||
.into()),
|
||||
_ => Ok(()),
|
||||
|
@ -1430,8 +1491,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> {
|
||||
// We have to take symlinks and relative paths into account.
|
||||
let pathbuf1 = canonicalize(p1, CanonicalizeMode::Normal)?;
|
||||
let pathbuf2 = canonicalize(p2, CanonicalizeMode::Normal)?;
|
||||
let pathbuf1 = canonicalize(p1, MissingHandling::Normal, ResolveMode::Logical)?;
|
||||
let pathbuf2 = canonicalize(p2, MissingHandling::Normal, ResolveMode::Logical)?;
|
||||
|
||||
Ok(pathbuf1 == pathbuf2)
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "uu_csplit"
|
||||
version = "0.0.7"
|
||||
version = "0.0.8"
|
||||
authors = ["uutils developers"]
|
||||
license = "MIT"
|
||||
description = "csplit ~ (uutils) Output pieces of FILE separated by PATTERN(s) to files 'xx00', 'xx01', ..., and output byte counts of each piece to standard output"
|
||||
|
@ -18,10 +18,13 @@ path = "src/csplit.rs"
|
|||
clap = { version = "2.33", features = ["wrap_help"] }
|
||||
thiserror = "1.0"
|
||||
regex = "1.0.0"
|
||||
glob = "0.2.11"
|
||||
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 = { version=">=0.0.10", package="uucore", path="../../uucore", features=["entries", "fs"] }
|
||||
uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../uucore_procs" }
|
||||
|
||||
[[bin]]
|
||||
name = "csplit"
|
||||
path = "src/main.rs"
|
||||
|
||||
[package.metadata.cargo-udeps.ignore]
|
||||
# Necessary for "make all"
|
||||
normal = ["uucore_procs"]
|
||||
|
|
|
@ -10,6 +10,7 @@ use std::{
|
|||
fs::{remove_file, File},
|
||||
io::{BufRead, BufWriter, Write},
|
||||
};
|
||||
use uucore::display::Quotable;
|
||||
|
||||
mod csplit_error;
|
||||
mod patterns;
|
||||
|
@ -34,8 +35,11 @@ mod options {
|
|||
pub const PATTERN: &str = "pattern";
|
||||
}
|
||||
|
||||
fn get_usage() -> String {
|
||||
format!("{0} [OPTION]... FILE PATTERN...", executable!())
|
||||
fn usage() -> String {
|
||||
format!(
|
||||
"{0} [OPTION]... FILE PATTERN...",
|
||||
uucore::execution_phrase()
|
||||
)
|
||||
}
|
||||
|
||||
/// Command line options for csplit.
|
||||
|
@ -316,18 +320,19 @@ impl<'a> SplitWriter<'a> {
|
|||
let l = line?;
|
||||
match n.cmp(&(&ln + 1)) {
|
||||
Ordering::Less => {
|
||||
if input_iter.add_line_to_buffer(ln, l).is_some() {
|
||||
panic!("the buffer is big enough to contain 1 line");
|
||||
}
|
||||
assert!(
|
||||
input_iter.add_line_to_buffer(ln, l).is_none(),
|
||||
"the buffer is big enough to contain 1 line"
|
||||
);
|
||||
ret = Ok(());
|
||||
break;
|
||||
}
|
||||
Ordering::Equal => {
|
||||
if !self.options.suppress_matched
|
||||
&& input_iter.add_line_to_buffer(ln, l).is_some()
|
||||
{
|
||||
panic!("the buffer is big enough to contain 1 line");
|
||||
}
|
||||
assert!(
|
||||
self.options.suppress_matched
|
||||
|| input_iter.add_line_to_buffer(ln, l).is_none(),
|
||||
"the buffer is big enough to contain 1 line"
|
||||
);
|
||||
ret = Ok(());
|
||||
break;
|
||||
}
|
||||
|
@ -374,9 +379,10 @@ impl<'a> SplitWriter<'a> {
|
|||
match (self.options.suppress_matched, offset) {
|
||||
// no offset, add the line to the next split
|
||||
(false, 0) => {
|
||||
if input_iter.add_line_to_buffer(ln, l).is_some() {
|
||||
panic!("the buffer is big enough to contain 1 line");
|
||||
}
|
||||
assert!(
|
||||
input_iter.add_line_to_buffer(ln, l).is_none(),
|
||||
"the buffer is big enough to contain 1 line"
|
||||
);
|
||||
}
|
||||
// a positive offset, some more lines need to be added to the current split
|
||||
(false, _) => self.writeln(l)?,
|
||||
|
@ -421,9 +427,10 @@ impl<'a> SplitWriter<'a> {
|
|||
if !self.options.suppress_matched {
|
||||
// add 1 to the buffer size to make place for the matched line
|
||||
input_iter.set_size_of_buffer(offset_usize + 1);
|
||||
if input_iter.add_line_to_buffer(ln, l).is_some() {
|
||||
panic!("should be big enough to hold every lines");
|
||||
}
|
||||
assert!(
|
||||
input_iter.add_line_to_buffer(ln, l).is_none(),
|
||||
"should be big enough to hold every lines"
|
||||
);
|
||||
}
|
||||
self.finish_split();
|
||||
if input_iter.buffer_len() < offset_usize {
|
||||
|
@ -565,7 +572,7 @@ mod tests {
|
|||
assert_eq!(input_splitter.add_line_to_buffer(0, line), None);
|
||||
assert_eq!(input_splitter.buffer_len(), 1);
|
||||
}
|
||||
item @ _ => panic!("wrong item: {:?}", item),
|
||||
item => panic!("wrong item: {:?}", item),
|
||||
};
|
||||
|
||||
match input_splitter.next() {
|
||||
|
@ -574,7 +581,7 @@ mod tests {
|
|||
assert_eq!(input_splitter.add_line_to_buffer(1, line), None);
|
||||
assert_eq!(input_splitter.buffer_len(), 2);
|
||||
}
|
||||
item @ _ => panic!("wrong item: {:?}", item),
|
||||
item => panic!("wrong item: {:?}", item),
|
||||
};
|
||||
|
||||
match input_splitter.next() {
|
||||
|
@ -586,7 +593,7 @@ mod tests {
|
|||
);
|
||||
assert_eq!(input_splitter.buffer_len(), 2);
|
||||
}
|
||||
item @ _ => panic!("wrong item: {:?}", item),
|
||||
item => panic!("wrong item: {:?}", item),
|
||||
};
|
||||
|
||||
input_splitter.rewind_buffer();
|
||||
|
@ -596,7 +603,7 @@ mod tests {
|
|||
assert_eq!(line, String::from("bbb"));
|
||||
assert_eq!(input_splitter.buffer_len(), 1);
|
||||
}
|
||||
item @ _ => panic!("wrong item: {:?}", item),
|
||||
item => panic!("wrong item: {:?}", item),
|
||||
};
|
||||
|
||||
match input_splitter.next() {
|
||||
|
@ -604,7 +611,7 @@ mod tests {
|
|||
assert_eq!(line, String::from("ccc"));
|
||||
assert_eq!(input_splitter.buffer_len(), 0);
|
||||
}
|
||||
item @ _ => panic!("wrong item: {:?}", item),
|
||||
item => panic!("wrong item: {:?}", item),
|
||||
};
|
||||
|
||||
match input_splitter.next() {
|
||||
|
@ -612,7 +619,7 @@ mod tests {
|
|||
assert_eq!(line, String::from("ddd"));
|
||||
assert_eq!(input_splitter.buffer_len(), 0);
|
||||
}
|
||||
item @ _ => panic!("wrong item: {:?}", item),
|
||||
item => panic!("wrong item: {:?}", item),
|
||||
};
|
||||
|
||||
assert!(input_splitter.next().is_none());
|
||||
|
@ -637,7 +644,7 @@ mod tests {
|
|||
assert_eq!(input_splitter.add_line_to_buffer(0, line), None);
|
||||
assert_eq!(input_splitter.buffer_len(), 1);
|
||||
}
|
||||
item @ _ => panic!("wrong item: {:?}", item),
|
||||
item => panic!("wrong item: {:?}", item),
|
||||
};
|
||||
|
||||
match input_splitter.next() {
|
||||
|
@ -646,7 +653,7 @@ mod tests {
|
|||
assert_eq!(input_splitter.add_line_to_buffer(1, line), None);
|
||||
assert_eq!(input_splitter.buffer_len(), 2);
|
||||
}
|
||||
item @ _ => panic!("wrong item: {:?}", item),
|
||||
item => panic!("wrong item: {:?}", item),
|
||||
};
|
||||
|
||||
match input_splitter.next() {
|
||||
|
@ -655,7 +662,7 @@ mod tests {
|
|||
assert_eq!(input_splitter.add_line_to_buffer(2, line), None);
|
||||
assert_eq!(input_splitter.buffer_len(), 3);
|
||||
}
|
||||
item @ _ => panic!("wrong item: {:?}", item),
|
||||
item => panic!("wrong item: {:?}", item),
|
||||
};
|
||||
|
||||
input_splitter.rewind_buffer();
|
||||
|
@ -666,7 +673,7 @@ mod tests {
|
|||
assert_eq!(input_splitter.add_line_to_buffer(0, line), None);
|
||||
assert_eq!(input_splitter.buffer_len(), 3);
|
||||
}
|
||||
item @ _ => panic!("wrong item: {:?}", item),
|
||||
item => panic!("wrong item: {:?}", item),
|
||||
};
|
||||
|
||||
match input_splitter.next() {
|
||||
|
@ -674,7 +681,7 @@ mod tests {
|
|||
assert_eq!(line, String::from("aaa"));
|
||||
assert_eq!(input_splitter.buffer_len(), 2);
|
||||
}
|
||||
item @ _ => panic!("wrong item: {:?}", item),
|
||||
item => panic!("wrong item: {:?}", item),
|
||||
};
|
||||
|
||||
match input_splitter.next() {
|
||||
|
@ -682,7 +689,7 @@ mod tests {
|
|||
assert_eq!(line, String::from("bbb"));
|
||||
assert_eq!(input_splitter.buffer_len(), 1);
|
||||
}
|
||||
item @ _ => panic!("wrong item: {:?}", item),
|
||||
item => panic!("wrong item: {:?}", item),
|
||||
};
|
||||
|
||||
match input_splitter.next() {
|
||||
|
@ -690,7 +697,7 @@ mod tests {
|
|||
assert_eq!(line, String::from("ccc"));
|
||||
assert_eq!(input_splitter.buffer_len(), 0);
|
||||
}
|
||||
item @ _ => panic!("wrong item: {:?}", item),
|
||||
item => panic!("wrong item: {:?}", item),
|
||||
};
|
||||
|
||||
match input_splitter.next() {
|
||||
|
@ -698,7 +705,7 @@ mod tests {
|
|||
assert_eq!(line, String::from("ddd"));
|
||||
assert_eq!(input_splitter.buffer_len(), 0);
|
||||
}
|
||||
item @ _ => panic!("wrong item: {:?}", item),
|
||||
item => panic!("wrong item: {:?}", item),
|
||||
};
|
||||
|
||||
assert!(input_splitter.next().is_none());
|
||||
|
@ -706,7 +713,7 @@ mod tests {
|
|||
}
|
||||
|
||||
pub fn uumain(args: impl uucore::Args) -> i32 {
|
||||
let usage = get_usage();
|
||||
let usage = usage();
|
||||
let args = args
|
||||
.collect_str(InvalidEncodingHandling::Ignore)
|
||||
.accept_any();
|
||||
|
@ -722,16 +729,16 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
.unwrap()
|
||||
.map(str::to_string)
|
||||
.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);
|
||||
if file_name == "-" {
|
||||
let stdin = io::stdin();
|
||||
crash_if_err!(1, csplit(&options, patterns, stdin.lock()));
|
||||
} else {
|
||||
let file = return_if_err!(1, File::open(file_name));
|
||||
let file_metadata = return_if_err!(1, file.metadata());
|
||||
let file = crash_if_err!(1, File::open(file_name));
|
||||
let file_metadata = crash_if_err!(1, file.metadata());
|
||||
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)));
|
||||
};
|
||||
|
@ -739,7 +746,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
}
|
||||
|
||||
pub fn uu_app() -> App<'static, 'static> {
|
||||
App::new(executable!())
|
||||
App::new(uucore::util_name())
|
||||
.version(crate_version!())
|
||||
.about(SUMMARY)
|
||||
.arg(
|
||||
|
|
|
@ -1,26 +1,28 @@
|
|||
use std::io;
|
||||
use thiserror::Error;
|
||||
|
||||
use uucore::display::Quotable;
|
||||
|
||||
/// Errors thrown by the csplit command
|
||||
#[derive(Debug, Error)]
|
||||
pub enum CsplitError {
|
||||
#[error("IO error: {}", _0)]
|
||||
IoError(io::Error),
|
||||
#[error("'{}': line number out of range", _0)]
|
||||
#[error("{}: line number out of range", ._0.quote())]
|
||||
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),
|
||||
#[error("'{}': match not found", _0)]
|
||||
#[error("{}: match not found", ._0.quote())]
|
||||
MatchNotFound(String),
|
||||
#[error("'{}': match not found on repetition {}", _0, _1)]
|
||||
#[error("{}: match not found on repetition {}", ._0.quote(), _1)]
|
||||
MatchNotFoundOnRepetition(String, usize),
|
||||
#[error("line number must be greater than zero")]
|
||||
LineNumberIsZero,
|
||||
#[error("line number '{}' is smaller than preceding line number, {}", _0, _1)]
|
||||
LineNumberSmallerThanPrevious(usize, usize),
|
||||
#[error("invalid pattern: {}", _0)]
|
||||
#[error("{}: invalid pattern", ._0.quote())]
|
||||
InvalidPattern(String),
|
||||
#[error("invalid number: '{}'", _0)]
|
||||
#[error("invalid number: {}", ._0.quote())]
|
||||
InvalidNumber(String),
|
||||
#[error("incorrect conversion specification in suffix")]
|
||||
SuffixFormatIncorrect,
|
||||
|
|
|
@ -47,7 +47,7 @@ impl SplitName {
|
|||
}),
|
||||
Some(custom) => {
|
||||
let spec =
|
||||
Regex::new(r"(?P<ALL>%(?P<FLAG>[0#-])(?P<WIDTH>\d+)?(?P<TYPE>[diuoxX]))")
|
||||
Regex::new(r"(?P<ALL>%((?P<FLAG>[0#-])(?P<WIDTH>\d+)?)?(?P<TYPE>[diuoxX]))")
|
||||
.unwrap();
|
||||
let mut captures_iter = spec.captures_iter(&custom);
|
||||
let custom_fn: Box<dyn Fn(usize) -> String> = match captures_iter.next() {
|
||||
|
@ -60,6 +60,21 @@ impl SplitName {
|
|||
Some(m) => m.as_str().parse::<usize>().unwrap(),
|
||||
};
|
||||
match (captures.name("FLAG"), captures.name("TYPE")) {
|
||||
(None, Some(ref t)) => match t.as_str() {
|
||||
"d" | "i" | "u" => Box::new(move |n: usize| -> String {
|
||||
format!("{}{}{}{}", prefix, before, n, after)
|
||||
}),
|
||||
"o" => Box::new(move |n: usize| -> String {
|
||||
format!("{}{}{:o}{}", prefix, before, n, after)
|
||||
}),
|
||||
"x" => Box::new(move |n: usize| -> String {
|
||||
format!("{}{}{:x}{}", prefix, before, n, after)
|
||||
}),
|
||||
"X" => Box::new(move |n: usize| -> String {
|
||||
format!("{}{}{:X}{}", prefix, before, n, after)
|
||||
}),
|
||||
_ => return Err(CsplitError::SuffixFormatIncorrect),
|
||||
},
|
||||
(Some(ref f), Some(ref t)) => {
|
||||
match (f.as_str(), t.as_str()) {
|
||||
/*
|
||||
|
@ -276,6 +291,12 @@ mod tests {
|
|||
assert_eq!(split_name.get(2), "xx00002");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_padding_decimal() {
|
||||
let split_name = SplitName::new(None, Some(String::from("cst-%d-")), None).unwrap();
|
||||
assert_eq!(split_name.get(2), "xxcst-2-");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn zero_padding_decimal1() {
|
||||
let split_name = SplitName::new(None, Some(String::from("cst-%03d-")), None).unwrap();
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "uu_cut"
|
||||
version = "0.0.7"
|
||||
version = "0.0.8"
|
||||
authors = ["uutils developers"]
|
||||
license = "MIT"
|
||||
description = "cut ~ (uutils) display byte/field columns of input lines"
|
||||
|
@ -16,8 +16,8 @@ path = "src/cut.rs"
|
|||
|
||||
[dependencies]
|
||||
clap = { version = "2.33", features = ["wrap_help"] }
|
||||
uucore = { version=">=0.0.9", package="uucore", path="../../uucore" }
|
||||
uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_procs" }
|
||||
uucore = { version=">=0.0.10", package="uucore", path="../../uucore" }
|
||||
uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../uucore_procs" }
|
||||
memchr = "2"
|
||||
bstr = "0.2"
|
||||
atty = "0.2"
|
||||
|
@ -25,3 +25,7 @@ atty = "0.2"
|
|||
[[bin]]
|
||||
name = "cut"
|
||||
path = "src/main.rs"
|
||||
|
||||
[package.metadata.cargo-udeps.ignore]
|
||||
# Necessary for "make all"
|
||||
normal = ["uucore_procs"]
|
||||
|
|
|
@ -15,6 +15,7 @@ use clap::{crate_version, App, Arg};
|
|||
use std::fs::File;
|
||||
use std::io::{stdin, stdout, BufReader, BufWriter, Read, Write};
|
||||
use std::path::Path;
|
||||
use uucore::display::Quotable;
|
||||
|
||||
use self::searcher::Searcher;
|
||||
use uucore::ranges::Range;
|
||||
|
@ -351,19 +352,19 @@ fn cut_files(mut filenames: Vec<String>, mode: Mode) -> i32 {
|
|||
let path = Path::new(&filename[..]);
|
||||
|
||||
if path.is_dir() {
|
||||
show_error!("{}: Is a directory", filename);
|
||||
show_error!("{}: Is a directory", filename.maybe_quote());
|
||||
continue;
|
||||
}
|
||||
|
||||
if path.metadata().is_err() {
|
||||
show_error!("{}: No such file or directory", filename);
|
||||
show_error!("{}: No such file or directory", filename.maybe_quote());
|
||||
continue;
|
||||
}
|
||||
|
||||
let file = match File::open(&path) {
|
||||
Ok(f) => f,
|
||||
Err(e) => {
|
||||
show_error!("opening '{}': {}", &filename[..], e);
|
||||
show_error!("opening {}: {}", filename.quote(), e);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
@ -548,7 +549,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
}
|
||||
|
||||
pub fn uu_app() -> App<'static, 'static> {
|
||||
App::new(executable!())
|
||||
App::new(uucore::util_name())
|
||||
.name(NAME)
|
||||
.version(crate_version!())
|
||||
.usage(SYNTAX)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "uu_date"
|
||||
version = "0.0.7"
|
||||
version = "0.0.8"
|
||||
authors = ["uutils developers"]
|
||||
license = "MIT"
|
||||
description = "date ~ (uutils) display or set the current time"
|
||||
|
@ -17,8 +17,8 @@ path = "src/date.rs"
|
|||
[dependencies]
|
||||
chrono = "0.4.4"
|
||||
clap = { version = "2.33", features = ["wrap_help"] }
|
||||
uucore = { version=">=0.0.9", package="uucore", path="../../uucore" }
|
||||
uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_procs" }
|
||||
uucore = { version=">=0.0.10", package="uucore", path="../../uucore" }
|
||||
uucore_procs = { version=">=0.0.7", package="uucore_procs", path="../../uucore_procs" }
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
libc = "0.2"
|
||||
|
@ -29,3 +29,7 @@ winapi = { version = "0.3", features = ["minwinbase", "sysinfoapi", "minwindef"]
|
|||
[[bin]]
|
||||
name = "date"
|
||||
path = "src/main.rs"
|
||||
|
||||
[package.metadata.cargo-udeps.ignore]
|
||||
# Necessary for "make all"
|
||||
normal = ["uucore_procs"]
|
||||
|
|
|
@ -8,18 +8,17 @@
|
|||
|
||||
// spell-checker:ignore (chrono) Datelike Timelike ; (format) DATEFILE MMDDhhmm ; (vars) datetime datetimes
|
||||
|
||||
#[macro_use]
|
||||
extern crate uucore;
|
||||
|
||||
use chrono::{DateTime, FixedOffset, Local, Offset, Utc};
|
||||
#[cfg(windows)]
|
||||
use chrono::{Datelike, Timelike};
|
||||
use clap::{crate_version, App, Arg};
|
||||
#[cfg(all(unix, not(target_os = "macos")))]
|
||||
#[cfg(all(unix, not(target_os = "macos"), not(target_os = "redox")))]
|
||||
use libc::{clock_settime, timespec, CLOCK_REALTIME};
|
||||
use std::fs::File;
|
||||
use std::io::{BufRead, BufReader};
|
||||
use std::path::PathBuf;
|
||||
use uucore::display::Quotable;
|
||||
use uucore::show_error;
|
||||
#[cfg(windows)]
|
||||
use winapi::{
|
||||
shared::minwindef::WORD,
|
||||
|
@ -67,10 +66,12 @@ static RFC_3339_HELP_STRING: &str = "output date/time in RFC 3339 format.
|
|||
for date and time to the indicated precision.
|
||||
Example: 2006-08-14 02:34:56-06:00";
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
#[cfg(not(any(target_os = "macos", target_os = "redox")))]
|
||||
static OPT_SET_HELP_STRING: &str = "set time described by STRING";
|
||||
#[cfg(target_os = "macos")]
|
||||
static OPT_SET_HELP_STRING: &str = "set time described by STRING (not available on mac yet)";
|
||||
#[cfg(target_os = "redox")]
|
||||
static OPT_SET_HELP_STRING: &str = "set time described by STRING (not available on redox yet)";
|
||||
|
||||
/// Settings for this program, parsed from the command line
|
||||
struct Settings {
|
||||
|
@ -146,7 +147,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
|
||||
let format = if let Some(form) = matches.value_of(OPT_FORMAT) {
|
||||
if !form.starts_with('+') {
|
||||
eprintln!("date: invalid date '{}'", form);
|
||||
show_error!("invalid date {}", form.quote());
|
||||
return 1;
|
||||
}
|
||||
let form = form[1..].to_string();
|
||||
|
@ -175,7 +176,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
let set_to = match matches.value_of(OPT_SET).map(parse_date) {
|
||||
None => None,
|
||||
Some(Err((input, _err))) => {
|
||||
eprintln!("date: invalid date '{}'", input);
|
||||
show_error!("invalid date {}", input.quote());
|
||||
return 1;
|
||||
}
|
||||
Some(Ok(date)) => Some(date),
|
||||
|
@ -241,7 +242,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
println!("{}", formatted);
|
||||
}
|
||||
Err((input, _err)) => {
|
||||
println!("date: invalid date '{}'", input);
|
||||
show_error!("invalid date {}", input.quote());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -251,7 +252,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
}
|
||||
|
||||
pub fn uu_app() -> App<'static, 'static> {
|
||||
App::new(executable!())
|
||||
App::new(uucore::util_name())
|
||||
.version(crate_version!())
|
||||
.about(ABOUT)
|
||||
.arg(
|
||||
|
@ -353,11 +354,17 @@ fn set_system_datetime(_date: DateTime<Utc>) -> i32 {
|
|||
|
||||
#[cfg(target_os = "macos")]
|
||||
fn set_system_datetime(_date: DateTime<Utc>) -> i32 {
|
||||
eprintln!("date: setting the date is not supported by macOS");
|
||||
show_error!("setting the date is not supported by macOS");
|
||||
1
|
||||
}
|
||||
|
||||
#[cfg(all(unix, not(target_os = "macos")))]
|
||||
#[cfg(target_os = "redox")]
|
||||
fn set_system_datetime(_date: DateTime<Utc>) -> i32 {
|
||||
show_error!("setting the date is not supported by Redox");
|
||||
1
|
||||
}
|
||||
|
||||
#[cfg(all(unix, not(target_os = "macos"), not(target_os = "redox")))]
|
||||
/// System call to set date (unix).
|
||||
/// See here for more:
|
||||
/// https://doc.rust-lang.org/libc/i686-unknown-linux-gnu/libc/fn.clock_settime.html
|
||||
|
@ -373,7 +380,7 @@ fn set_system_datetime(date: DateTime<Utc>) -> i32 {
|
|||
|
||||
if result != 0 {
|
||||
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()
|
||||
} else {
|
||||
0
|
||||
|
@ -403,7 +410,7 @@ fn set_system_datetime(date: DateTime<Utc>) -> i32 {
|
|||
|
||||
if result == 0 {
|
||||
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()
|
||||
} else {
|
||||
0
|
||||
|
|
37
src/uu/dd/Cargo.toml
Normal file
37
src/uu/dd/Cargo.toml
Normal file
|
@ -0,0 +1,37 @@
|
|||
[package]
|
||||
name = "uu_dd"
|
||||
version = "0.0.8"
|
||||
authors = ["uutils developers"]
|
||||
license = "MIT"
|
||||
description = "dd ~ (uutils) copy and convert files"
|
||||
|
||||
homepage = "https://github.com/uutils/coreutils"
|
||||
repository = "https://github.com/uutils/coreutils/tree/master/src/uu/dd"
|
||||
keywords = ["coreutils", "uutils", "cross-platform", "cli", "utility"]
|
||||
categories = ["command-line-utilities"]
|
||||
edition = "2018"
|
||||
|
||||
[lib]
|
||||
path = "src/dd.rs"
|
||||
|
||||
[dependencies]
|
||||
byte-unit = "4.0"
|
||||
clap = { version = "2.33", features = [ "wrap_help" ] }
|
||||
gcd = "2.0"
|
||||
libc = "0.2"
|
||||
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" }
|
||||
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "^3"
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
signal-hook = "0.3.9"
|
||||
|
||||
[[bin]]
|
||||
name = "dd"
|
||||
path = "src/main.rs"
|
||||
|
||||
[package.metadata.cargo-udeps.ignore]
|
||||
# Necessary for "make all"
|
||||
normal = ["uucore_procs"]
|
221
src/uu/dd/src/conversion_tables.rs
Normal file
221
src/uu/dd/src/conversion_tables.rs
Normal file
|
@ -0,0 +1,221 @@
|
|||
// This file is part of the uutils coreutils package.
|
||||
//
|
||||
// (c) Tyler Steele <tyler.steele@protonmail.com>
|
||||
//
|
||||
// For the full copyright and license information, please view the LICENSE
|
||||
// file that was distributed with this source code.
|
||||
|
||||
// Note: Conversion tables are just lookup tables.
|
||||
// eg. The ASCII->EBCDIC table stores the EBCDIC code at the index
|
||||
// obtained by treating the ASCII representation as a number.
|
||||
|
||||
pub type ConversionTable = [u8; 256];
|
||||
|
||||
pub const ASCII_UCASE_TO_LCASE: ConversionTable = [
|
||||
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
|
||||
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
|
||||
0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
|
||||
0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f,
|
||||
0x40, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f,
|
||||
0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f,
|
||||
0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f,
|
||||
0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f,
|
||||
0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f,
|
||||
0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f,
|
||||
0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf,
|
||||
0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf,
|
||||
0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf,
|
||||
0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf,
|
||||
0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef,
|
||||
0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff,
|
||||
];
|
||||
|
||||
pub const ASCII_LCASE_TO_UCASE: ConversionTable = [
|
||||
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
|
||||
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
|
||||
0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
|
||||
0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f,
|
||||
0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f,
|
||||
0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f,
|
||||
0x60, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f,
|
||||
0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f,
|
||||
0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f,
|
||||
0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f,
|
||||
0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf,
|
||||
0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf,
|
||||
0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf,
|
||||
0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf,
|
||||
0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef,
|
||||
0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff,
|
||||
];
|
||||
|
||||
pub const ASCII_TO_EBCDIC: ConversionTable = [
|
||||
0x00, 0x01, 0x02, 0x03, 0x37, 0x2d, 0x2e, 0x2f, 0x16, 0x05, 0x25, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
|
||||
0x10, 0x11, 0x12, 0x13, 0x3c, 0x3d, 0x32, 0x26, 0x18, 0x19, 0x3f, 0x27, 0x1c, 0x1d, 0x1e, 0x1f,
|
||||
0x40, 0x5a, 0x7f, 0x7b, 0x5b, 0x6c, 0x50, 0x7d, 0x4d, 0x5d, 0x5c, 0x4e, 0x6b, 0x60, 0x4b, 0x61,
|
||||
0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0x7a, 0x5e, 0x4c, 0x7e, 0x6e, 0x6f,
|
||||
0x7c, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6,
|
||||
0xd7, 0xd8, 0xd9, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xad, 0xe0, 0xbd, 0x9a, 0x6d,
|
||||
0x79, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96,
|
||||
0x97, 0x98, 0x99, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xc0, 0x4f, 0xd0, 0x5f, 0x07,
|
||||
0x20, 0x21, 0x22, 0x23, 0x24, 0x15, 0x06, 0x17, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x09, 0x0a, 0x1b,
|
||||
0x30, 0x31, 0x1a, 0x33, 0x34, 0x35, 0x36, 0x08, 0x38, 0x39, 0x3a, 0x3b, 0x04, 0x14, 0x3e, 0xe1,
|
||||
0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57,
|
||||
0x58, 0x59, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75,
|
||||
0x76, 0x77, 0x78, 0x80, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0x90, 0x6a, 0x9b, 0x9c, 0x9d, 0x9e,
|
||||
0x9f, 0xa0, 0xaa, 0xab, 0xac, 0x4a, 0xae, 0xaf, 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7,
|
||||
0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xa1, 0xbe, 0xbf, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, 0xda, 0xdb,
|
||||
0xdc, 0xdd, 0xde, 0xdf, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff,
|
||||
];
|
||||
|
||||
pub const ASCII_TO_EBCDIC_UCASE_TO_LCASE: ConversionTable = [
|
||||
0x00, 0x01, 0x02, 0x03, 0x37, 0x2d, 0x2e, 0x2f, 0x16, 0x05, 0x25, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
|
||||
0x10, 0x11, 0x12, 0x13, 0x3c, 0x3d, 0x32, 0x26, 0x18, 0x19, 0x3f, 0x27, 0x1c, 0x1d, 0x1e, 0x1f,
|
||||
0x40, 0x5a, 0x7f, 0x7b, 0x5b, 0x6c, 0x50, 0x7d, 0x4d, 0x5d, 0x5c, 0x4e, 0x6b, 0x60, 0x4b, 0x61,
|
||||
0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0x7a, 0x5e, 0x4c, 0x7e, 0x6e, 0x6f,
|
||||
0x7c, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96,
|
||||
0x97, 0x98, 0x99, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xad, 0xe0, 0xbd, 0x9a, 0x6d,
|
||||
0x79, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96,
|
||||
0x97, 0x98, 0x99, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xc0, 0x4f, 0xd0, 0x5f, 0x07,
|
||||
0x20, 0x21, 0x22, 0x23, 0x24, 0x15, 0x06, 0x17, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x09, 0x0a, 0x1b,
|
||||
0x30, 0x31, 0x1a, 0x33, 0x34, 0x35, 0x36, 0x08, 0x38, 0x39, 0x3a, 0x3b, 0x04, 0x14, 0x3e, 0xe1,
|
||||
0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57,
|
||||
0x58, 0x59, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75,
|
||||
0x76, 0x77, 0x78, 0x80, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0x90, 0x6a, 0x9b, 0x9c, 0x9d, 0x9e,
|
||||
0x9f, 0xa0, 0xaa, 0xab, 0xac, 0x4a, 0xae, 0xaf, 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7,
|
||||
0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xa1, 0xbe, 0xbf, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, 0xda, 0xdb,
|
||||
0xdc, 0xdd, 0xde, 0xdf, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff,
|
||||
];
|
||||
|
||||
pub const ASCII_TO_EBCDIC_LCASE_TO_UCASE: ConversionTable = [
|
||||
0x00, 0x01, 0x02, 0x03, 0x37, 0x2d, 0x2e, 0x2f, 0x16, 0x05, 0x25, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
|
||||
0x10, 0x11, 0x12, 0x13, 0x3c, 0x3d, 0x32, 0x26, 0x18, 0x19, 0x3f, 0x27, 0x1c, 0x1d, 0x1e, 0x1f,
|
||||
0x40, 0x5a, 0x7f, 0x7b, 0x5b, 0x6c, 0x50, 0x7d, 0x4d, 0x5d, 0x5c, 0x4e, 0x6b, 0x60, 0x4b, 0x61,
|
||||
0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0x7a, 0x5e, 0x4c, 0x7e, 0x6e, 0x6f,
|
||||
0x7c, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6,
|
||||
0xd7, 0xd8, 0xd9, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xad, 0xe0, 0xbd, 0x9a, 0x6d,
|
||||
0x79, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6,
|
||||
0xd7, 0xd8, 0xd9, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xc0, 0x4f, 0xd0, 0x5f, 0x07,
|
||||
0x20, 0x21, 0x22, 0x23, 0x24, 0x15, 0x06, 0x17, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x09, 0x0a, 0x1b,
|
||||
0x30, 0x31, 0x1a, 0x33, 0x34, 0x35, 0x36, 0x08, 0x38, 0x39, 0x3a, 0x3b, 0x04, 0x14, 0x3e, 0xe1,
|
||||
0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57,
|
||||
0x58, 0x59, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75,
|
||||
0x76, 0x77, 0x78, 0x80, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0x90, 0x6a, 0x9b, 0x9c, 0x9d, 0x9e,
|
||||
0x9f, 0xa0, 0xaa, 0xab, 0xac, 0x4a, 0xae, 0xaf, 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7,
|
||||
0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xa1, 0xbe, 0xbf, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, 0xda, 0xdb,
|
||||
0xdc, 0xdd, 0xde, 0xdf, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff,
|
||||
];
|
||||
|
||||
pub const ASCII_TO_IBM: ConversionTable = [
|
||||
0x00, 0x01, 0x02, 0x03, 0x37, 0x2d, 0x2e, 0x2f, 0x16, 0x05, 0x25, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
|
||||
0x10, 0x11, 0x12, 0x13, 0x3c, 0x3d, 0x32, 0x26, 0x18, 0x19, 0x3f, 0x27, 0x1c, 0x1d, 0x1e, 0x1f,
|
||||
0x40, 0x5a, 0x7f, 0x7b, 0x5b, 0x6c, 0x50, 0x7d, 0x4d, 0x5d, 0x5c, 0x4e, 0x6b, 0x60, 0x4b, 0x61,
|
||||
0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0x7a, 0x5e, 0x4c, 0x7e, 0x6e, 0x6f,
|
||||
0x7c, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6,
|
||||
0xd7, 0xd8, 0xd9, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xad, 0xe0, 0xbd, 0x5f, 0x6d,
|
||||
0x79, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96,
|
||||
0x97, 0x98, 0x99, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xc0, 0x4f, 0xd0, 0xa1, 0x07,
|
||||
0x20, 0x21, 0x22, 0x23, 0x24, 0x15, 0x06, 0x17, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x09, 0x0a, 0x1b,
|
||||
0x30, 0x31, 0x1a, 0x33, 0x34, 0x35, 0x36, 0x08, 0x38, 0x39, 0x3a, 0x3b, 0x04, 0x14, 0x3e, 0xe1,
|
||||
0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57,
|
||||
0x58, 0x59, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75,
|
||||
0x76, 0x77, 0x78, 0x80, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0x90, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e,
|
||||
0x9f, 0xa0, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7,
|
||||
0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, 0xda, 0xdb,
|
||||
0xdc, 0xdd, 0xde, 0xdf, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff,
|
||||
];
|
||||
|
||||
pub const ASCII_TO_IBM_UCASE_TO_LCASE: ConversionTable = [
|
||||
0x00, 0x01, 0x02, 0x03, 0x37, 0x2d, 0x2e, 0x2f, 0x16, 0x05, 0x25, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
|
||||
0x10, 0x11, 0x12, 0x13, 0x3c, 0x3d, 0x32, 0x26, 0x18, 0x19, 0x3f, 0x27, 0x1c, 0x1d, 0x1e, 0x1f,
|
||||
0x40, 0x5a, 0x7f, 0x7b, 0x5b, 0x6c, 0x50, 0x7d, 0x4d, 0x5d, 0x5c, 0x4e, 0x6b, 0x60, 0x4b, 0x61,
|
||||
0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0x7a, 0x5e, 0x4c, 0x7e, 0x6e, 0x6f,
|
||||
0x7c, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96,
|
||||
0x97, 0x98, 0x99, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xad, 0xe0, 0xbd, 0x5f, 0x6d,
|
||||
0x79, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96,
|
||||
0x97, 0x98, 0x99, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xc0, 0x4f, 0xd0, 0xa1, 0x07,
|
||||
0x20, 0x21, 0x22, 0x23, 0x24, 0x15, 0x06, 0x17, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x09, 0x0a, 0x1b,
|
||||
0x30, 0x31, 0x1a, 0x33, 0x34, 0x35, 0x36, 0x08, 0x38, 0x39, 0x3a, 0x3b, 0x04, 0x14, 0x3e, 0xe1,
|
||||
0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57,
|
||||
0x58, 0x59, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75,
|
||||
0x76, 0x77, 0x78, 0x80, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0x90, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e,
|
||||
0x9f, 0xa0, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7,
|
||||
0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, 0xda, 0xdb,
|
||||
0xdc, 0xdd, 0xde, 0xdf, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff,
|
||||
];
|
||||
|
||||
pub const ASCII_TO_IBM_LCASE_TO_UCASE: ConversionTable = [
|
||||
0x00, 0x01, 0x02, 0x03, 0x37, 0x2d, 0x2e, 0x2f, 0x16, 0x05, 0x25, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
|
||||
0x10, 0x11, 0x12, 0x13, 0x3c, 0x3d, 0x32, 0x26, 0x18, 0x19, 0x3f, 0x27, 0x1c, 0x1d, 0x1e, 0x1f,
|
||||
0x40, 0x5a, 0x7f, 0x7b, 0x5b, 0x6c, 0x50, 0x7d, 0x4d, 0x5d, 0x5c, 0x4e, 0x6b, 0x60, 0x4b, 0x61,
|
||||
0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0x7a, 0x5e, 0x4c, 0x7e, 0x6e, 0x6f,
|
||||
0x7c, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6,
|
||||
0xd7, 0xd8, 0xd9, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xad, 0xe0, 0xbd, 0x5f, 0x6d,
|
||||
0x79, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6,
|
||||
0xd7, 0xd8, 0xd9, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xc0, 0x4f, 0xd0, 0xa1, 0x07,
|
||||
0x20, 0x21, 0x22, 0x23, 0x24, 0x15, 0x06, 0x17, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x09, 0x0a, 0x1b,
|
||||
0x30, 0x31, 0x1a, 0x33, 0x34, 0x35, 0x36, 0x08, 0x38, 0x39, 0x3a, 0x3b, 0x04, 0x14, 0x3e, 0xe1,
|
||||
0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57,
|
||||
0x58, 0x59, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75,
|
||||
0x76, 0x77, 0x78, 0x80, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0x90, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e,
|
||||
0x9f, 0xa0, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7,
|
||||
0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, 0xda, 0xdb,
|
||||
0xdc, 0xdd, 0xde, 0xdf, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff,
|
||||
];
|
||||
|
||||
pub const EBCDIC_TO_ASCII: ConversionTable = [
|
||||
0x00, 0x01, 0x02, 0x03, 0x9c, 0x09, 0x86, 0x7f, 0x97, 0x8d, 0x8e, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
|
||||
0x10, 0x11, 0x12, 0x13, 0x9d, 0x85, 0x08, 0x87, 0x18, 0x19, 0x92, 0x8f, 0x1c, 0x1d, 0x1e, 0x1f,
|
||||
0x80, 0x81, 0x82, 0x83, 0x84, 0x0a, 0x17, 0x1b, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x05, 0x06, 0x07,
|
||||
0x90, 0x91, 0x16, 0x93, 0x94, 0x95, 0x96, 0x04, 0x98, 0x99, 0x9a, 0x9b, 0x14, 0x15, 0x9e, 0x1a,
|
||||
0x20, 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xd5, 0x2e, 0x3c, 0x28, 0x2b, 0x7c,
|
||||
0x26, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, 0xb0, 0xb1, 0x21, 0x24, 0x2a, 0x29, 0x3b, 0x7e,
|
||||
0x2d, 0x2f, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xcb, 0x2c, 0x25, 0x5f, 0x3e, 0x3f,
|
||||
0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, 0xc0, 0xc1, 0xc2, 0x60, 0x3a, 0x23, 0x40, 0x27, 0x3d, 0x22,
|
||||
0xc3, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9,
|
||||
0xca, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x5e, 0xcc, 0xcd, 0xce, 0xcf, 0xd0,
|
||||
0xd1, 0xe5, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0xd2, 0xd3, 0xd4, 0x5b, 0xd6, 0xd7,
|
||||
0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf, 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0x5d, 0xe6, 0xe7,
|
||||
0x7b, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed,
|
||||
0x7d, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, 0x51, 0x52, 0xee, 0xef, 0xf0, 0xf1, 0xf2, 0xf3,
|
||||
0x5c, 0x9f, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9,
|
||||
0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff,
|
||||
];
|
||||
|
||||
pub const EBCDIC_TO_ASCII_UCASE_TO_LCASE: ConversionTable = [
|
||||
0x00, 0x01, 0x02, 0x03, 0x37, 0x2d, 0x2e, 0x2f, 0x16, 0x05, 0x25, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
|
||||
0x10, 0x11, 0x12, 0x13, 0x3c, 0x3d, 0x32, 0x26, 0x18, 0x19, 0x3f, 0x27, 0x1c, 0x1d, 0x1e, 0x1f,
|
||||
0x40, 0x5a, 0x7f, 0x7b, 0x5b, 0x6c, 0x50, 0x7d, 0x4d, 0x5d, 0x5c, 0x4e, 0x6b, 0x60, 0x4b, 0x61,
|
||||
0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0x7a, 0x5e, 0x4c, 0x7e, 0x6e, 0x6f,
|
||||
0x7c, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96,
|
||||
0x97, 0x98, 0x99, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xad, 0xe0, 0xbd, 0x5f, 0x6d,
|
||||
0x79, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96,
|
||||
0x97, 0x98, 0x99, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xc0, 0x4f, 0xd0, 0xa1, 0x07,
|
||||
0x20, 0x21, 0x22, 0x23, 0x24, 0x15, 0x06, 0x17, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x09, 0x0a, 0x1b,
|
||||
0x30, 0x31, 0x1a, 0x33, 0x34, 0x35, 0x36, 0x08, 0x38, 0x39, 0x3a, 0x3b, 0x04, 0x14, 0x3e, 0xe1,
|
||||
0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57,
|
||||
0x58, 0x59, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75,
|
||||
0x76, 0x77, 0x78, 0x80, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0x90, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e,
|
||||
0x9f, 0xa0, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7,
|
||||
0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, 0xda, 0xdb,
|
||||
0xdc, 0xdd, 0xde, 0xdf, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff,
|
||||
];
|
||||
|
||||
pub const EBCDIC_TO_ASCII_LCASE_TO_UCASE: ConversionTable = [
|
||||
0x00, 0x01, 0x02, 0x03, 0x37, 0x2d, 0x2e, 0x2f, 0x16, 0x05, 0x25, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
|
||||
0x10, 0x11, 0x12, 0x13, 0x3c, 0x3d, 0x32, 0x26, 0x18, 0x19, 0x3f, 0x27, 0x1c, 0x1d, 0x1e, 0x1f,
|
||||
0x40, 0x5a, 0x7f, 0x7b, 0x5b, 0x6c, 0x50, 0x7d, 0x4d, 0x5d, 0x5c, 0x4e, 0x6b, 0x60, 0x4b, 0x61,
|
||||
0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0x7a, 0x5e, 0x4c, 0x7e, 0x6e, 0x6f,
|
||||
0x7c, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6,
|
||||
0xd7, 0xd8, 0xd9, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xad, 0xe0, 0xbd, 0x5f, 0x6d,
|
||||
0x79, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6,
|
||||
0xd7, 0xd8, 0xd9, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xc0, 0x4f, 0xd0, 0xa1, 0x07,
|
||||
0x20, 0x21, 0x22, 0x23, 0x24, 0x15, 0x06, 0x17, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x09, 0x0a, 0x1b,
|
||||
0x30, 0x31, 0x1a, 0x33, 0x34, 0x35, 0x36, 0x08, 0x38, 0x39, 0x3a, 0x3b, 0x04, 0x14, 0x3e, 0xe1,
|
||||
0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57,
|
||||
0x58, 0x59, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75,
|
||||
0x76, 0x77, 0x78, 0x80, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0x90, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e,
|
||||
0x9f, 0xa0, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7,
|
||||
0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, 0xda, 0xdb,
|
||||
0xdc, 0xdd, 0xde, 0xdf, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff,
|
||||
];
|
172
src/uu/dd/src/datastructures.rs
Normal file
172
src/uu/dd/src/datastructures.rs
Normal file
|
@ -0,0 +1,172 @@
|
|||
// This file is part of the uutils coreutils package.
|
||||
//
|
||||
// (c) Tyler Steele <tyler.steele@protonmail.com>
|
||||
//
|
||||
// For the full copyright and license information, please view the LICENSE
|
||||
// file that was distributed with this source code.
|
||||
// spell-checker:ignore ctable, outfile
|
||||
|
||||
use crate::conversion_tables::*;
|
||||
|
||||
use std::error::Error;
|
||||
use std::time;
|
||||
|
||||
pub struct ProgUpdate {
|
||||
pub read_stat: ReadStat,
|
||||
pub write_stat: WriteStat,
|
||||
pub duration: time::Duration,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Default)]
|
||||
pub struct ReadStat {
|
||||
pub reads_complete: u64,
|
||||
pub reads_partial: u64,
|
||||
pub records_truncated: u32,
|
||||
}
|
||||
impl std::ops::AddAssign for ReadStat {
|
||||
fn add_assign(&mut self, other: Self) {
|
||||
*self = Self {
|
||||
reads_complete: self.reads_complete + other.reads_complete,
|
||||
reads_partial: self.reads_partial + other.reads_partial,
|
||||
records_truncated: self.records_truncated + other.records_truncated,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct WriteStat {
|
||||
pub writes_complete: u64,
|
||||
pub writes_partial: u64,
|
||||
pub bytes_total: u128,
|
||||
}
|
||||
impl std::ops::AddAssign for WriteStat {
|
||||
fn add_assign(&mut self, other: Self) {
|
||||
*self = Self {
|
||||
writes_complete: self.writes_complete + other.writes_complete,
|
||||
writes_partial: self.writes_partial + other.writes_partial,
|
||||
bytes_total: self.bytes_total + other.bytes_total,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type Cbs = usize;
|
||||
|
||||
/// Stores all Conv Flags that apply to the input
|
||||
#[derive(Debug, Default, PartialEq)]
|
||||
pub struct IConvFlags {
|
||||
pub ctable: Option<&'static ConversionTable>,
|
||||
pub block: Option<Cbs>,
|
||||
pub unblock: Option<Cbs>,
|
||||
pub swab: bool,
|
||||
pub sync: Option<u8>,
|
||||
pub noerror: bool,
|
||||
}
|
||||
|
||||
/// Stores all Conv Flags that apply to the output
|
||||
#[derive(Debug, Default, PartialEq)]
|
||||
pub struct OConvFlags {
|
||||
pub sparse: bool,
|
||||
pub excl: bool,
|
||||
pub nocreat: bool,
|
||||
pub notrunc: bool,
|
||||
pub fdatasync: bool,
|
||||
pub fsync: bool,
|
||||
}
|
||||
|
||||
/// Stores all Flags that apply to the input
|
||||
#[derive(Debug, Default, PartialEq)]
|
||||
pub struct IFlags {
|
||||
pub cio: bool,
|
||||
pub direct: bool,
|
||||
pub directory: bool,
|
||||
pub dsync: bool,
|
||||
pub sync: bool,
|
||||
pub nocache: bool,
|
||||
pub nonblock: bool,
|
||||
pub noatime: bool,
|
||||
pub noctty: bool,
|
||||
pub nofollow: bool,
|
||||
pub nolinks: bool,
|
||||
pub binary: bool,
|
||||
pub text: bool,
|
||||
pub fullblock: bool,
|
||||
pub count_bytes: bool,
|
||||
pub skip_bytes: bool,
|
||||
}
|
||||
|
||||
/// Stores all Flags that apply to the output
|
||||
#[derive(Debug, Default, PartialEq)]
|
||||
pub struct OFlags {
|
||||
pub append: bool,
|
||||
pub cio: bool,
|
||||
pub direct: bool,
|
||||
pub directory: bool,
|
||||
pub dsync: bool,
|
||||
pub sync: bool,
|
||||
pub nocache: bool,
|
||||
pub nonblock: bool,
|
||||
pub noatime: bool,
|
||||
pub noctty: bool,
|
||||
pub nofollow: bool,
|
||||
pub nolinks: bool,
|
||||
pub binary: bool,
|
||||
pub text: bool,
|
||||
pub seek_bytes: bool,
|
||||
}
|
||||
|
||||
/// The value of the status cl-option.
|
||||
/// Controls printing of transfer stats
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
pub enum StatusLevel {
|
||||
Progress,
|
||||
Noxfer,
|
||||
None,
|
||||
}
|
||||
|
||||
/// The value of count=N
|
||||
/// Defaults to Reads(N)
|
||||
/// if iflag=count_bytes
|
||||
/// then becomes Bytes(N)
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum CountType {
|
||||
Reads(usize),
|
||||
Bytes(usize),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum InternalError {
|
||||
WrongInputType,
|
||||
WrongOutputType,
|
||||
InvalidConvBlockUnblockCase,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for InternalError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::WrongInputType | Self::WrongOutputType => {
|
||||
write!(f, "Internal dd error: Wrong Input/Output data type")
|
||||
}
|
||||
Self::InvalidConvBlockUnblockCase => {
|
||||
write!(f, "Invalid Conversion, Block, or Unblock data")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for InternalError {}
|
||||
|
||||
pub mod options {
|
||||
pub const INFILE: &str = "if";
|
||||
pub const OUTFILE: &str = "of";
|
||||
pub const IBS: &str = "ibs";
|
||||
pub const OBS: &str = "obs";
|
||||
pub const BS: &str = "bs";
|
||||
pub const CBS: &str = "cbs";
|
||||
pub const COUNT: &str = "count";
|
||||
pub const SKIP: &str = "skip";
|
||||
pub const SEEK: &str = "seek";
|
||||
pub const STATUS: &str = "status";
|
||||
pub const CONV: &str = "conv";
|
||||
pub const IFLAG: &str = "iflag";
|
||||
pub const OFLAG: &str = "oflag";
|
||||
}
|
1144
src/uu/dd/src/dd.rs
Normal file
1144
src/uu/dd/src/dd.rs
Normal file
File diff suppressed because it is too large
Load diff
351
src/uu/dd/src/dd_unit_tests/block_unblock_tests.rs
Normal file
351
src/uu/dd/src/dd_unit_tests/block_unblock_tests.rs
Normal file
|
@ -0,0 +1,351 @@
|
|||
// 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
|
||||
|
||||
use super::*;
|
||||
|
||||
#[cfg(unix)]
|
||||
macro_rules! make_block_test (
|
||||
( $test_id:ident, $test_name:expr, $src:expr, $block:expr, $spec:expr ) =>
|
||||
{
|
||||
make_spec_test!($test_id,
|
||||
$test_name,
|
||||
Input {
|
||||
src: $src,
|
||||
non_ascii: false,
|
||||
ibs: 512,
|
||||
print_level: None,
|
||||
count: None,
|
||||
cflags: IConvFlags {
|
||||
block: $block,
|
||||
..IConvFlags::default()
|
||||
},
|
||||
iflags: IFlags::default(),
|
||||
},
|
||||
Output {
|
||||
dst: File::create(format!("./test-resources/FAILED-{}.test", $test_name)).unwrap(),
|
||||
obs: 512,
|
||||
cflags: OConvFlags::default(),
|
||||
},
|
||||
$spec,
|
||||
format!("./test-resources/FAILED-{}.test", $test_name)
|
||||
);
|
||||
};
|
||||
);
|
||||
|
||||
#[cfg(unix)]
|
||||
macro_rules! make_unblock_test (
|
||||
( $test_id:ident, $test_name:expr, $src:expr, $unblock:expr, $spec:expr ) =>
|
||||
{
|
||||
make_spec_test!($test_id,
|
||||
$test_name,
|
||||
Input {
|
||||
src: $src,
|
||||
non_ascii: false,
|
||||
ibs: 512,
|
||||
print_level: None,
|
||||
count: None,
|
||||
cflags: IConvFlags {
|
||||
unblock: $unblock,
|
||||
..IConvFlags::default()
|
||||
},
|
||||
iflags: IFlags::default(),
|
||||
},
|
||||
Output {
|
||||
dst: File::create(format!("./test-resources/FAILED-{}.test", $test_name)).unwrap(),
|
||||
obs: 512,
|
||||
cflags: OConvFlags::default(),
|
||||
},
|
||||
$spec,
|
||||
format!("./test-resources/FAILED-{}.test", $test_name)
|
||||
);
|
||||
};
|
||||
);
|
||||
|
||||
#[test]
|
||||
fn block_test_no_nl() {
|
||||
let mut rs = ReadStat::default();
|
||||
let buf = vec![0u8, 1u8, 2u8, 3u8];
|
||||
let res = block(buf, 4, &mut rs);
|
||||
|
||||
assert_eq!(res, vec![vec![0u8, 1u8, 2u8, 3u8],]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn block_test_no_nl_short_record() {
|
||||
let mut rs = ReadStat::default();
|
||||
let buf = vec![0u8, 1u8, 2u8, 3u8];
|
||||
let res = block(buf, 8, &mut rs);
|
||||
|
||||
assert_eq!(
|
||||
res,
|
||||
vec![vec![0u8, 1u8, 2u8, 3u8, SPACE, SPACE, SPACE, SPACE],]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn block_test_no_nl_trunc() {
|
||||
let mut rs = ReadStat::default();
|
||||
let buf = vec![0u8, 1u8, 2u8, 3u8, 4u8];
|
||||
let res = block(buf, 4, &mut rs);
|
||||
|
||||
// Commented section(s) should be truncated and appear for reference only.
|
||||
assert_eq!(res, vec![vec![0u8, 1u8, 2u8, 3u8 /*, 4u8*/],]);
|
||||
assert_eq!(rs.records_truncated, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn block_test_nl_gt_cbs_trunc() {
|
||||
let mut rs = ReadStat::default();
|
||||
let buf = vec![
|
||||
0u8, 1u8, 2u8, 3u8, 4u8, NEWLINE, 0u8, 1u8, 2u8, 3u8, 4u8, NEWLINE, 5u8, 6u8, 7u8, 8u8,
|
||||
];
|
||||
let res = block(buf, 4, &mut rs);
|
||||
|
||||
assert_eq!(
|
||||
res,
|
||||
vec![
|
||||
// Commented section(s) should be truncated and appear for reference only.
|
||||
vec![0u8, 1u8, 2u8, 3u8],
|
||||
// vec![4u8, SPACE, SPACE, SPACE],
|
||||
vec![0u8, 1u8, 2u8, 3u8],
|
||||
// vec![4u8, SPACE, SPACE, SPACE],
|
||||
vec![5u8, 6u8, 7u8, 8u8],
|
||||
]
|
||||
);
|
||||
assert_eq!(rs.records_truncated, 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn block_test_surrounded_nl() {
|
||||
let mut rs = ReadStat::default();
|
||||
let buf = vec![0u8, 1u8, 2u8, 3u8, NEWLINE, 4u8, 5u8, 6u8, 7u8, 8u8];
|
||||
let res = block(buf, 8, &mut rs);
|
||||
|
||||
assert_eq!(
|
||||
res,
|
||||
vec![
|
||||
vec![0u8, 1u8, 2u8, 3u8, SPACE, SPACE, SPACE, SPACE],
|
||||
vec![4u8, 5u8, 6u8, 7u8, 8u8, SPACE, SPACE, SPACE],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn block_test_multiple_nl_same_cbs_block() {
|
||||
let mut rs = ReadStat::default();
|
||||
let buf = vec![
|
||||
0u8, 1u8, 2u8, 3u8, NEWLINE, 4u8, NEWLINE, 5u8, 6u8, 7u8, 8u8, 9u8,
|
||||
];
|
||||
let res = block(buf, 8, &mut rs);
|
||||
|
||||
assert_eq!(
|
||||
res,
|
||||
vec![
|
||||
vec![0u8, 1u8, 2u8, 3u8, SPACE, SPACE, SPACE, SPACE],
|
||||
vec![4u8, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE],
|
||||
vec![5u8, 6u8, 7u8, 8u8, 9u8, SPACE, SPACE, SPACE],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn block_test_multiple_nl_diff_cbs_block() {
|
||||
let mut rs = ReadStat::default();
|
||||
let buf = vec![
|
||||
0u8, 1u8, 2u8, 3u8, NEWLINE, 4u8, 5u8, 6u8, 7u8, NEWLINE, 8u8, 9u8,
|
||||
];
|
||||
let res = block(buf, 8, &mut rs);
|
||||
|
||||
assert_eq!(
|
||||
res,
|
||||
vec![
|
||||
vec![0u8, 1u8, 2u8, 3u8, SPACE, SPACE, SPACE, SPACE],
|
||||
vec![4u8, 5u8, 6u8, 7u8, SPACE, SPACE, SPACE, SPACE],
|
||||
vec![8u8, 9u8, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn block_test_end_nl_diff_cbs_block() {
|
||||
let mut rs = ReadStat::default();
|
||||
let buf = vec![0u8, 1u8, 2u8, 3u8, NEWLINE];
|
||||
let res = block(buf, 4, &mut rs);
|
||||
|
||||
assert_eq!(res, vec![vec![0u8, 1u8, 2u8, 3u8],]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn block_test_end_nl_same_cbs_block() {
|
||||
let mut rs = ReadStat::default();
|
||||
let buf = vec![0u8, 1u8, 2u8, NEWLINE];
|
||||
let res = block(buf, 4, &mut rs);
|
||||
|
||||
assert_eq!(res, vec![vec![0u8, 1u8, 2u8, SPACE]]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn block_test_double_end_nl() {
|
||||
let mut rs = ReadStat::default();
|
||||
let buf = vec![0u8, 1u8, 2u8, NEWLINE, NEWLINE];
|
||||
let res = block(buf, 4, &mut rs);
|
||||
|
||||
assert_eq!(
|
||||
res,
|
||||
vec![vec![0u8, 1u8, 2u8, SPACE], vec![SPACE, SPACE, SPACE, SPACE],]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn block_test_start_nl() {
|
||||
let mut rs = ReadStat::default();
|
||||
let buf = vec![NEWLINE, 0u8, 1u8, 2u8, 3u8];
|
||||
let res = block(buf, 4, &mut rs);
|
||||
|
||||
assert_eq!(
|
||||
res,
|
||||
vec![vec![SPACE, SPACE, SPACE, SPACE], vec![0u8, 1u8, 2u8, 3u8],]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn block_test_double_surrounded_nl_no_trunc() {
|
||||
let mut rs = ReadStat::default();
|
||||
let buf = vec![0u8, 1u8, 2u8, 3u8, NEWLINE, NEWLINE, 4u8, 5u8, 6u8, 7u8];
|
||||
let res = block(buf, 8, &mut rs);
|
||||
|
||||
assert_eq!(
|
||||
res,
|
||||
vec![
|
||||
vec![0u8, 1u8, 2u8, 3u8, SPACE, SPACE, SPACE, SPACE],
|
||||
vec![SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE],
|
||||
vec![4u8, 5u8, 6u8, 7u8, SPACE, SPACE, SPACE, SPACE],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn block_test_double_surrounded_nl_double_trunc() {
|
||||
let mut rs = ReadStat::default();
|
||||
let buf = vec![
|
||||
0u8, 1u8, 2u8, 3u8, NEWLINE, NEWLINE, 4u8, 5u8, 6u8, 7u8, 8u8,
|
||||
];
|
||||
let res = block(buf, 4, &mut rs);
|
||||
|
||||
assert_eq!(
|
||||
res,
|
||||
vec![
|
||||
// Commented section(s) should be truncated and appear for reference only.
|
||||
vec![0u8, 1u8, 2u8, 3u8],
|
||||
vec![SPACE, SPACE, SPACE, SPACE],
|
||||
vec![4u8, 5u8, 6u8, 7u8 /*, 8u8*/],
|
||||
]
|
||||
);
|
||||
assert_eq!(rs.records_truncated, 1);
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
make_block_test!(
|
||||
block_cbs16,
|
||||
"block-cbs-16",
|
||||
File::open("./test-resources/dd-block-cbs16.test").unwrap(),
|
||||
Some(16),
|
||||
File::open("./test-resources/dd-block-cbs16.spec").unwrap()
|
||||
);
|
||||
|
||||
#[cfg(unix)]
|
||||
make_block_test!(
|
||||
block_cbs16_as_cbs8,
|
||||
"block-cbs-16-as-cbs8",
|
||||
File::open("./test-resources/dd-block-cbs16.test").unwrap(),
|
||||
Some(8),
|
||||
File::open("./test-resources/dd-block-cbs8.spec").unwrap()
|
||||
);
|
||||
|
||||
#[cfg(unix)]
|
||||
make_block_test!(
|
||||
block_consecutive_nl,
|
||||
"block-consecutive-nl",
|
||||
File::open("./test-resources/dd-block-consecutive-nl.test").unwrap(),
|
||||
Some(16),
|
||||
File::open("./test-resources/dd-block-consecutive-nl-cbs16.spec").unwrap()
|
||||
);
|
||||
|
||||
#[test]
|
||||
fn unblock_test_full_cbs() {
|
||||
let buf = vec![0u8, 1u8, 2u8, 3u8, 4u8, 5u8, 6u8, 7u8];
|
||||
let res = unblock(buf, 8);
|
||||
|
||||
assert_eq!(res, vec![0u8, 1u8, 2u8, 3u8, 4u8, 5u8, 6u8, 7u8, NEWLINE],);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unblock_test_all_space() {
|
||||
let buf = vec![SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE];
|
||||
let res = unblock(buf, 8);
|
||||
|
||||
assert_eq!(res, vec![NEWLINE],);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unblock_test_decoy_spaces() {
|
||||
let buf = vec![0u8, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, 7u8];
|
||||
let res = unblock(buf, 8);
|
||||
|
||||
assert_eq!(
|
||||
res,
|
||||
vec![0u8, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, 7u8, NEWLINE],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unblock_test_strip_single_cbs() {
|
||||
let buf = vec![0u8, 1u8, 2u8, 3u8, SPACE, SPACE, SPACE, SPACE];
|
||||
let res = unblock(buf, 8);
|
||||
|
||||
assert_eq!(res, vec![0u8, 1u8, 2u8, 3u8, NEWLINE],);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unblock_test_strip_multi_cbs() {
|
||||
let buf = vec![
|
||||
vec![0u8, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE],
|
||||
vec![0u8, 1u8, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE],
|
||||
vec![0u8, 1u8, 2u8, SPACE, SPACE, SPACE, SPACE, SPACE],
|
||||
vec![0u8, 1u8, 2u8, 3u8, SPACE, SPACE, SPACE, SPACE],
|
||||
]
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let res = unblock(buf, 8);
|
||||
|
||||
let exp = vec![
|
||||
vec![0u8, NEWLINE],
|
||||
vec![0u8, 1u8, NEWLINE],
|
||||
vec![0u8, 1u8, 2u8, NEWLINE],
|
||||
vec![0u8, 1u8, 2u8, 3u8, NEWLINE],
|
||||
]
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
assert_eq!(res, exp);
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
make_unblock_test!(
|
||||
unblock_multi_16,
|
||||
"unblock-multi-16",
|
||||
File::open("./test-resources/dd-unblock-cbs16.test").unwrap(),
|
||||
Some(16),
|
||||
File::open("./test-resources/dd-unblock-cbs16.spec").unwrap()
|
||||
);
|
||||
|
||||
#[cfg(unix)]
|
||||
make_unblock_test!(
|
||||
unblock_multi_16_as_8,
|
||||
"unblock-multi-16-as-8",
|
||||
File::open("./test-resources/dd-unblock-cbs16.test").unwrap(),
|
||||
Some(8),
|
||||
File::open("./test-resources/dd-unblock-cbs8.spec").unwrap()
|
||||
);
|
106
src/uu/dd/src/dd_unit_tests/conv_sync_tests.rs
Normal file
106
src/uu/dd/src/dd_unit_tests/conv_sync_tests.rs
Normal file
|
@ -0,0 +1,106 @@
|
|||
// 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
|
||||
|
||||
use super::*;
|
||||
|
||||
macro_rules! make_sync_test (
|
||||
( $test_id:ident, $test_name:expr, $src:expr, $sync:expr, $ibs:expr, $obs:expr, $spec:expr ) =>
|
||||
{
|
||||
make_spec_test!($test_id,
|
||||
$test_name,
|
||||
Input {
|
||||
src: $src,
|
||||
non_ascii: false,
|
||||
ibs: $ibs,
|
||||
print_level: None,
|
||||
count: None,
|
||||
cflags: IConvFlags {
|
||||
sync: $sync,
|
||||
..IConvFlags::default()
|
||||
},
|
||||
iflags: IFlags::default(),
|
||||
},
|
||||
Output {
|
||||
dst: File::create(format!("./test-resources/FAILED-{}.test", $test_name)).unwrap(),
|
||||
obs: $obs,
|
||||
cflags: OConvFlags::default(),
|
||||
},
|
||||
$spec,
|
||||
format!("./test-resources/FAILED-{}.test", $test_name)
|
||||
);
|
||||
};
|
||||
);
|
||||
|
||||
// Zeros
|
||||
make_sync_test!(
|
||||
zeros_4k_conv_sync_obs_gt_ibs,
|
||||
"zeros_4k_conv_sync_obs_gt_ibs",
|
||||
File::open("./test-resources/zeros-620f0b67a91f7f74151bc5be745b7110.test").unwrap(),
|
||||
Some(0u8),
|
||||
521,
|
||||
1031,
|
||||
File::open("./test-resources/gnudd-conv-sync-ibs-521-obs-1031-zeros.spec").unwrap()
|
||||
);
|
||||
|
||||
make_sync_test!(
|
||||
zeros_4k_conv_sync_ibs_gt_obs,
|
||||
"zeros_4k_conv_sync_ibs_gt_obs",
|
||||
File::open("./test-resources/zeros-620f0b67a91f7f74151bc5be745b7110.test").unwrap(),
|
||||
Some(0u8),
|
||||
1031,
|
||||
521,
|
||||
File::open("./test-resources/gnudd-conv-sync-ibs-1031-obs-521-zeros.spec").unwrap()
|
||||
);
|
||||
|
||||
// Deadbeef
|
||||
make_sync_test!(
|
||||
deadbeef_32k_conv_sync_obs_gt_ibs,
|
||||
"deadbeef_32k_conv_sync_obs_gt_ibs",
|
||||
File::open("./test-resources/deadbeef-18d99661a1de1fc9af21b0ec2cd67ba3.test").unwrap(),
|
||||
Some(0u8),
|
||||
521,
|
||||
1031,
|
||||
File::open("./test-resources/gnudd-conv-sync-ibs-521-obs-1031-deadbeef.spec").unwrap()
|
||||
);
|
||||
|
||||
make_sync_test!(
|
||||
deadbeef_32k_conv_sync_ibs_gt_obs,
|
||||
"deadbeef_32k_conv_sync_ibs_gt_obs",
|
||||
File::open("./test-resources/deadbeef-18d99661a1de1fc9af21b0ec2cd67ba3.test").unwrap(),
|
||||
Some(0u8),
|
||||
1031,
|
||||
521,
|
||||
File::open("./test-resources/gnudd-conv-sync-ibs-1031-obs-521-deadbeef.spec").unwrap()
|
||||
);
|
||||
|
||||
// Random
|
||||
make_sync_test!(
|
||||
random_73k_test_bs_prime_obs_gt_ibs_sync,
|
||||
"random-73k-test-bs-prime-obs-gt-ibs-sync",
|
||||
File::open("./test-resources/random-5828891cb1230748e146f34223bbd3b5.test").unwrap(),
|
||||
Some(0u8),
|
||||
521,
|
||||
1031,
|
||||
File::open("./test-resources/gnudd-conv-sync-ibs-521-obs-1031-random.spec").unwrap()
|
||||
);
|
||||
|
||||
make_sync_test!(
|
||||
random_73k_test_bs_prime_ibs_gt_obs_sync,
|
||||
"random-73k-test-bs-prime-ibs-gt-obs-sync",
|
||||
File::open("./test-resources/random-5828891cb1230748e146f34223bbd3b5.test").unwrap(),
|
||||
Some(0u8),
|
||||
1031,
|
||||
521,
|
||||
File::open("./test-resources/gnudd-conv-sync-ibs-1031-obs-521-random.spec").unwrap()
|
||||
);
|
||||
|
||||
make_sync_test!(
|
||||
deadbeef_16_delayed,
|
||||
"deadbeef-16-delayed",
|
||||
LazyReader {
|
||||
src: File::open("./test-resources/deadbeef-16.test").unwrap()
|
||||
},
|
||||
Some(0u8),
|
||||
16,
|
||||
32,
|
||||
File::open("./test-resources/deadbeef-16.spec").unwrap()
|
||||
);
|
233
src/uu/dd/src/dd_unit_tests/conversion_tests.rs
Normal file
233
src/uu/dd/src/dd_unit_tests/conversion_tests.rs
Normal file
|
@ -0,0 +1,233 @@
|
|||
// 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
|
||||
|
||||
use super::*;
|
||||
|
||||
macro_rules! make_conv_test (
|
||||
( $test_id:ident, $test_name:expr, $src:expr, $ctable:expr, $spec:expr ) =>
|
||||
{
|
||||
make_spec_test!($test_id,
|
||||
$test_name,
|
||||
Input {
|
||||
src: $src,
|
||||
non_ascii: false,
|
||||
ibs: 512,
|
||||
print_level: None,
|
||||
count: None,
|
||||
cflags: icf!($ctable),
|
||||
iflags: IFlags::default(),
|
||||
},
|
||||
Output {
|
||||
dst: File::create(format!("./test-resources/FAILED-{}.test", $test_name)).unwrap(),
|
||||
obs: 512,
|
||||
cflags: OConvFlags::default(),
|
||||
},
|
||||
$spec,
|
||||
format!("./test-resources/FAILED-{}.test", $test_name)
|
||||
);
|
||||
};
|
||||
);
|
||||
|
||||
macro_rules! make_icf_test (
|
||||
( $test_id:ident, $test_name:expr, $src:expr, $icf:expr, $spec:expr ) =>
|
||||
{
|
||||
make_spec_test!($test_id,
|
||||
$test_name,
|
||||
Input {
|
||||
src: $src,
|
||||
non_ascii: false,
|
||||
ibs: 512,
|
||||
print_level: None,
|
||||
count: None,
|
||||
cflags: $icf,
|
||||
iflags: IFlags::default(),
|
||||
},
|
||||
Output {
|
||||
dst: File::create(format!("./test-resources/FAILED-{}.test", $test_name)).unwrap(),
|
||||
obs: 512,
|
||||
cflags: OConvFlags::default(),
|
||||
},
|
||||
$spec,
|
||||
format!("./test-resources/FAILED-{}.test", $test_name)
|
||||
);
|
||||
};
|
||||
);
|
||||
|
||||
make_conv_test!(
|
||||
atoe_conv_spec_test,
|
||||
"atoe-conv-spec-test",
|
||||
File::open("./test-resources/seq-byte-values-b632a992d3aed5d8d1a59cc5a5a455ba.test").unwrap(),
|
||||
Some(&ASCII_TO_EBCDIC),
|
||||
File::open("./test-resources/gnudd-conv-atoe-seq-byte-values.spec").unwrap()
|
||||
);
|
||||
|
||||
make_conv_test!(
|
||||
etoa_conv_spec_test,
|
||||
"etoa-conv-spec-test",
|
||||
File::open("./test-resources/seq-byte-values-b632a992d3aed5d8d1a59cc5a5a455ba.test").unwrap(),
|
||||
Some(&EBCDIC_TO_ASCII),
|
||||
File::open("./test-resources/gnudd-conv-etoa-seq-byte-values.spec").unwrap()
|
||||
);
|
||||
|
||||
make_conv_test!(
|
||||
atoibm_conv_spec_test,
|
||||
"atoibm-conv-spec-test",
|
||||
File::open("./test-resources/seq-byte-values-b632a992d3aed5d8d1a59cc5a5a455ba.test").unwrap(),
|
||||
Some(&ASCII_TO_IBM),
|
||||
File::open("./test-resources/gnudd-conv-atoibm-seq-byte-values.spec").unwrap()
|
||||
);
|
||||
|
||||
make_conv_test!(
|
||||
lcase_ascii_to_ucase_ascii,
|
||||
"lcase_ascii_to_ucase_ascii",
|
||||
File::open("./test-resources/lcase-ascii.test").unwrap(),
|
||||
Some(&ASCII_LCASE_TO_UCASE),
|
||||
File::open("./test-resources/ucase-ascii.test").unwrap()
|
||||
);
|
||||
|
||||
make_conv_test!(
|
||||
ucase_ascii_to_lcase_ascii,
|
||||
"ucase_ascii_to_lcase_ascii",
|
||||
File::open("./test-resources/ucase-ascii.test").unwrap(),
|
||||
Some(&ASCII_UCASE_TO_LCASE),
|
||||
File::open("./test-resources/lcase-ascii.test").unwrap()
|
||||
);
|
||||
|
||||
make_conv_test!(
|
||||
// conv=ebcdic,ucase
|
||||
atoe_and_ucase_conv_spec_test,
|
||||
"atoe-and-ucase-conv-spec-test",
|
||||
File::open("./test-resources/seq-byte-values-b632a992d3aed5d8d1a59cc5a5a455ba.test").unwrap(),
|
||||
Some(&ASCII_TO_EBCDIC_LCASE_TO_UCASE),
|
||||
File::open("./test-resources/ucase-ebcdic.test").unwrap()
|
||||
);
|
||||
|
||||
make_conv_test!(
|
||||
// conv=ebcdic,lcase
|
||||
atoe_and_lcase_conv_spec_test,
|
||||
"atoe-and-lcase-conv-spec-test",
|
||||
File::open("./test-resources/seq-byte-values-b632a992d3aed5d8d1a59cc5a5a455ba.test").unwrap(),
|
||||
Some(&ASCII_TO_EBCDIC_UCASE_TO_LCASE),
|
||||
File::open("./test-resources/lcase-ebcdic.test").unwrap()
|
||||
);
|
||||
|
||||
make_conv_test!(
|
||||
// conv=ibm,ucase
|
||||
atoibm_and_ucase_conv_spec_test,
|
||||
"atoibm-and-ucase-conv-spec-test",
|
||||
File::open("./test-resources/seq-byte-values-b632a992d3aed5d8d1a59cc5a5a455ba.test").unwrap(),
|
||||
Some(&ASCII_TO_IBM_UCASE_TO_LCASE),
|
||||
File::open("./test-resources/lcase-ibm.test").unwrap()
|
||||
);
|
||||
|
||||
make_conv_test!(
|
||||
// conv=ibm,lcase
|
||||
atoibm_and_lcase_conv_spec_test,
|
||||
"atoibm-and-lcase-conv-spec-test",
|
||||
File::open("./test-resources/seq-byte-values-b632a992d3aed5d8d1a59cc5a5a455ba.test").unwrap(),
|
||||
Some(&ASCII_TO_IBM_LCASE_TO_UCASE),
|
||||
File::open("./test-resources/ucase-ibm.test").unwrap()
|
||||
);
|
||||
|
||||
#[test]
|
||||
fn all_valid_ascii_ebcdic_ascii_roundtrip_conv_test() {
|
||||
// ASCII->EBCDIC
|
||||
let test_name = "all-valid-ascii-to-ebcdic";
|
||||
let tmp_fname_ae = format!("./test-resources/FAILED-{}.test", test_name);
|
||||
|
||||
let i = Input {
|
||||
src: File::open(
|
||||
"./test-resources/all-valid-ascii-chars-37eff01866ba3f538421b30b7cbefcac.test",
|
||||
)
|
||||
.unwrap(),
|
||||
non_ascii: false,
|
||||
ibs: 128,
|
||||
print_level: None,
|
||||
count: None,
|
||||
cflags: icf!(Some(&ASCII_TO_EBCDIC)),
|
||||
iflags: IFlags::default(),
|
||||
};
|
||||
|
||||
let o = Output {
|
||||
dst: File::create(&tmp_fname_ae).unwrap(),
|
||||
obs: 1024,
|
||||
cflags: OConvFlags::default(),
|
||||
};
|
||||
|
||||
o.dd_out(i).unwrap();
|
||||
|
||||
// EBCDIC->ASCII
|
||||
let test_name = "all-valid-ebcdic-to-ascii";
|
||||
let tmp_fname_ea = format!("./test-resources/FAILED-{}.test", test_name);
|
||||
|
||||
let i = Input {
|
||||
src: File::open(&tmp_fname_ae).unwrap(),
|
||||
non_ascii: false,
|
||||
ibs: 256,
|
||||
print_level: None,
|
||||
count: None,
|
||||
cflags: icf!(Some(&EBCDIC_TO_ASCII)),
|
||||
iflags: IFlags::default(),
|
||||
};
|
||||
|
||||
let o = Output {
|
||||
dst: File::create(&tmp_fname_ea).unwrap(),
|
||||
obs: 1024,
|
||||
cflags: OConvFlags::default(),
|
||||
};
|
||||
|
||||
o.dd_out(i).unwrap();
|
||||
|
||||
// Final Comparison
|
||||
let res = File::open(&tmp_fname_ea).unwrap();
|
||||
let spec =
|
||||
File::open("./test-resources/all-valid-ascii-chars-37eff01866ba3f538421b30b7cbefcac.test")
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
res.metadata().unwrap().len(),
|
||||
spec.metadata().unwrap().len()
|
||||
);
|
||||
|
||||
let res = BufReader::new(res);
|
||||
let spec = BufReader::new(spec);
|
||||
|
||||
let res = BufReader::new(res);
|
||||
|
||||
// Check all bytes match
|
||||
for (b_res, b_spec) in res.bytes().zip(spec.bytes()) {
|
||||
assert_eq!(b_res.unwrap(), b_spec.unwrap());
|
||||
}
|
||||
|
||||
fs::remove_file(&tmp_fname_ae).unwrap();
|
||||
fs::remove_file(&tmp_fname_ea).unwrap();
|
||||
}
|
||||
|
||||
make_icf_test!(
|
||||
swab_256_test,
|
||||
"swab-256",
|
||||
File::open("./test-resources/seq-byte-values.test").unwrap(),
|
||||
IConvFlags {
|
||||
ctable: None,
|
||||
block: None,
|
||||
unblock: None,
|
||||
swab: true,
|
||||
sync: None,
|
||||
noerror: false,
|
||||
},
|
||||
File::open("./test-resources/seq-byte-values-swapped.test").unwrap()
|
||||
);
|
||||
|
||||
make_icf_test!(
|
||||
swab_257_test,
|
||||
"swab-257",
|
||||
File::open("./test-resources/seq-byte-values-odd.test").unwrap(),
|
||||
IConvFlags {
|
||||
ctable: None,
|
||||
block: None,
|
||||
unblock: None,
|
||||
swab: true,
|
||||
sync: None,
|
||||
noerror: false,
|
||||
},
|
||||
File::open("./test-resources/seq-byte-values-odd.spec").unwrap()
|
||||
);
|
89
src/uu/dd/src/dd_unit_tests/mod.rs
Normal file
89
src/uu/dd/src/dd_unit_tests/mod.rs
Normal file
|
@ -0,0 +1,89 @@
|
|||
// 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
|
||||
|
||||
use super::*;
|
||||
|
||||
mod block_unblock_tests;
|
||||
mod conv_sync_tests;
|
||||
mod conversion_tests;
|
||||
mod sanity_tests;
|
||||
|
||||
use std::fs;
|
||||
use std::io::prelude::*;
|
||||
use std::io::BufReader;
|
||||
|
||||
struct LazyReader<R: Read> {
|
||||
src: R,
|
||||
}
|
||||
|
||||
impl<R: Read> Read for LazyReader<R> {
|
||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||
let reduced = cmp::max(buf.len() / 2, 1);
|
||||
self.src.read(&mut buf[..reduced])
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! icf (
|
||||
( $ctable:expr ) =>
|
||||
{
|
||||
IConvFlags {
|
||||
ctable: $ctable,
|
||||
..IConvFlags::default()
|
||||
}
|
||||
};
|
||||
);
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! make_spec_test (
|
||||
( $test_id:ident, $test_name:expr, $src:expr ) =>
|
||||
{
|
||||
// When spec not given, output should match input
|
||||
make_spec_test!($test_id, $test_name, $src, $src);
|
||||
};
|
||||
( $test_id:ident, $test_name:expr, $src:expr, $spec:expr ) =>
|
||||
{
|
||||
make_spec_test!($test_id,
|
||||
$test_name,
|
||||
Input {
|
||||
src: $src,
|
||||
non_ascii: false,
|
||||
ibs: 512,
|
||||
print_level: None,
|
||||
count: None,
|
||||
cflags: IConvFlags::default(),
|
||||
iflags: IFlags::default(),
|
||||
},
|
||||
Output {
|
||||
dst: File::create(format!("./test-resources/FAILED-{}.test", $test_name)).unwrap(),
|
||||
obs: 512,
|
||||
cflags: OConvFlags::default(),
|
||||
},
|
||||
$spec,
|
||||
format!("./test-resources/FAILED-{}.test", $test_name)
|
||||
);
|
||||
};
|
||||
( $test_id:ident, $test_name:expr, $i:expr, $o:expr, $spec:expr, $tmp_fname:expr ) =>
|
||||
{
|
||||
#[test]
|
||||
fn $test_id()
|
||||
{
|
||||
$o.dd_out($i).unwrap();
|
||||
|
||||
let res = File::open($tmp_fname).unwrap();
|
||||
// Check test file isn't empty (unless spec file is too)
|
||||
assert_eq!(res.metadata().unwrap().len(), $spec.metadata().unwrap().len());
|
||||
|
||||
let spec = BufReader::new($spec);
|
||||
let res = BufReader::new(res);
|
||||
|
||||
// Check all bytes match
|
||||
for (b_res, b_spec) in res.bytes().zip(spec.bytes())
|
||||
{
|
||||
assert_eq!(b_res.unwrap(),
|
||||
b_spec.unwrap());
|
||||
}
|
||||
|
||||
fs::remove_file($tmp_fname).unwrap();
|
||||
}
|
||||
};
|
||||
);
|
316
src/uu/dd/src/dd_unit_tests/sanity_tests.rs
Normal file
316
src/uu/dd/src/dd_unit_tests/sanity_tests.rs
Normal file
|
@ -0,0 +1,316 @@
|
|||
// 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
|
||||
|
||||
use super::*;
|
||||
|
||||
const DST_PLACEHOLDER: Vec<u8> = Vec::new();
|
||||
|
||||
macro_rules! make_io_test (
|
||||
( $test_id:ident, $test_name:expr, $i:expr, $o:expr, $spec:expr ) =>
|
||||
{
|
||||
make_spec_test!($test_id,
|
||||
$test_name,
|
||||
$i,
|
||||
Output {
|
||||
dst: File::create(format!("./test-resources/FAILED-{}.test", $test_name)).unwrap(),
|
||||
obs: $o.obs,
|
||||
cflags: $o.cflags,
|
||||
},
|
||||
$spec,
|
||||
format!("./test-resources/FAILED-{}.test", $test_name)
|
||||
);
|
||||
};
|
||||
);
|
||||
|
||||
make_spec_test!(
|
||||
zeros_4k_test,
|
||||
"zeros-4k",
|
||||
File::open("./test-resources/zeros-620f0b67a91f7f74151bc5be745b7110.test").unwrap()
|
||||
);
|
||||
|
||||
make_spec_test!(
|
||||
ones_4k_test,
|
||||
"ones-4k",
|
||||
File::open("./test-resources/ones-6ae59e64850377ee5470c854761551ea.test").unwrap()
|
||||
);
|
||||
|
||||
make_spec_test!(
|
||||
deadbeef_32k_test,
|
||||
"deadbeef-32k",
|
||||
File::open("./test-resources/deadbeef-18d99661a1de1fc9af21b0ec2cd67ba3.test").unwrap()
|
||||
);
|
||||
|
||||
make_spec_test!(
|
||||
random_73k_test,
|
||||
"random-73k",
|
||||
File::open("./test-resources/random-5828891cb1230748e146f34223bbd3b5.test").unwrap()
|
||||
);
|
||||
|
||||
make_io_test!(
|
||||
random_73k_test_not_a_multiple_obs_gt_ibs,
|
||||
"random-73k-not-a-multiple-obs-gt-ibs",
|
||||
Input {
|
||||
src: File::open("./test-resources/random-5828891cb1230748e146f34223bbd3b5.test").unwrap(),
|
||||
non_ascii: false,
|
||||
ibs: 521,
|
||||
print_level: None,
|
||||
count: None,
|
||||
cflags: IConvFlags::default(),
|
||||
iflags: IFlags::default(),
|
||||
},
|
||||
Output {
|
||||
dst: DST_PLACEHOLDER,
|
||||
obs: 1031,
|
||||
cflags: OConvFlags::default(),
|
||||
},
|
||||
File::open("./test-resources/random-5828891cb1230748e146f34223bbd3b5.test").unwrap()
|
||||
);
|
||||
|
||||
make_io_test!(
|
||||
random_73k_test_obs_lt_not_a_multiple_ibs,
|
||||
"random-73k-obs-lt-not-a-multiple-ibs",
|
||||
Input {
|
||||
src: File::open("./test-resources/random-5828891cb1230748e146f34223bbd3b5.test").unwrap(),
|
||||
non_ascii: false,
|
||||
ibs: 1031,
|
||||
print_level: None,
|
||||
count: None,
|
||||
cflags: IConvFlags::default(),
|
||||
iflags: IFlags::default(),
|
||||
},
|
||||
Output {
|
||||
dst: DST_PLACEHOLDER,
|
||||
obs: 521,
|
||||
cflags: OConvFlags::default(),
|
||||
},
|
||||
File::open("./test-resources/random-5828891cb1230748e146f34223bbd3b5.test").unwrap()
|
||||
);
|
||||
|
||||
make_io_test!(
|
||||
deadbeef_all_32k_test_count_reads,
|
||||
"deadbeef_all_32k_test_count_reads",
|
||||
Input {
|
||||
src: File::open("./test-resources/deadbeef-18d99661a1de1fc9af21b0ec2cd67ba3.test").unwrap(),
|
||||
non_ascii: false,
|
||||
ibs: 1024,
|
||||
print_level: None,
|
||||
count: Some(CountType::Reads(32)),
|
||||
cflags: IConvFlags::default(),
|
||||
iflags: IFlags::default(),
|
||||
},
|
||||
Output {
|
||||
dst: DST_PLACEHOLDER,
|
||||
obs: 1024,
|
||||
cflags: OConvFlags::default(),
|
||||
},
|
||||
File::open("./test-resources/deadbeef-18d99661a1de1fc9af21b0ec2cd67ba3.test").unwrap()
|
||||
);
|
||||
|
||||
make_io_test!(
|
||||
deadbeef_all_32k_test_count_bytes,
|
||||
"deadbeef_all_32k_test_count_bytes",
|
||||
Input {
|
||||
src: File::open("./test-resources/deadbeef-18d99661a1de1fc9af21b0ec2cd67ba3.test").unwrap(),
|
||||
non_ascii: false,
|
||||
ibs: 531,
|
||||
print_level: None,
|
||||
count: Some(CountType::Bytes(32 * 1024)),
|
||||
cflags: IConvFlags::default(),
|
||||
iflags: IFlags::default(),
|
||||
},
|
||||
Output {
|
||||
dst: DST_PLACEHOLDER,
|
||||
obs: 1031,
|
||||
cflags: OConvFlags::default(),
|
||||
},
|
||||
File::open("./test-resources/deadbeef-18d99661a1de1fc9af21b0ec2cd67ba3.test").unwrap()
|
||||
);
|
||||
|
||||
make_io_test!(
|
||||
deadbeef_32k_to_16k_test_count_reads,
|
||||
"deadbeef_32k_test_count_reads",
|
||||
Input {
|
||||
src: File::open("./test-resources/deadbeef-18d99661a1de1fc9af21b0ec2cd67ba3.test").unwrap(),
|
||||
non_ascii: false,
|
||||
ibs: 1024,
|
||||
print_level: None,
|
||||
count: Some(CountType::Reads(16)),
|
||||
cflags: IConvFlags::default(),
|
||||
iflags: IFlags::default(),
|
||||
},
|
||||
Output {
|
||||
dst: DST_PLACEHOLDER,
|
||||
obs: 1031,
|
||||
cflags: OConvFlags::default(),
|
||||
},
|
||||
File::open("./test-resources/gnudd-deadbeef-first-16k.spec").unwrap()
|
||||
);
|
||||
|
||||
make_io_test!(
|
||||
deadbeef_32k_to_12345_test_count_bytes,
|
||||
"deadbeef_32k_to_12345_test_count_bytes",
|
||||
Input {
|
||||
src: File::open("./test-resources/deadbeef-18d99661a1de1fc9af21b0ec2cd67ba3.test").unwrap(),
|
||||
non_ascii: false,
|
||||
ibs: 531,
|
||||
print_level: None,
|
||||
count: Some(CountType::Bytes(12345)),
|
||||
cflags: IConvFlags::default(),
|
||||
iflags: IFlags::default(),
|
||||
},
|
||||
Output {
|
||||
dst: DST_PLACEHOLDER,
|
||||
obs: 1031,
|
||||
cflags: OConvFlags::default(),
|
||||
},
|
||||
File::open("./test-resources/gnudd-deadbeef-first-12345.spec").unwrap()
|
||||
);
|
||||
|
||||
make_io_test!(
|
||||
random_73k_test_count_reads,
|
||||
"random-73k-test-count-reads",
|
||||
Input {
|
||||
src: File::open("./test-resources/random-5828891cb1230748e146f34223bbd3b5.test").unwrap(),
|
||||
non_ascii: false,
|
||||
ibs: 1024,
|
||||
print_level: None,
|
||||
count: Some(CountType::Reads(32)),
|
||||
cflags: IConvFlags::default(),
|
||||
iflags: IFlags::default(),
|
||||
},
|
||||
Output {
|
||||
dst: DST_PLACEHOLDER,
|
||||
obs: 1024,
|
||||
cflags: OConvFlags::default(),
|
||||
},
|
||||
File::open("./test-resources/gnudd-random-first-32k.spec").unwrap()
|
||||
);
|
||||
|
||||
make_io_test!(
|
||||
random_73k_test_count_bytes,
|
||||
"random-73k-test-count-bytes",
|
||||
Input {
|
||||
src: File::open("./test-resources/random-5828891cb1230748e146f34223bbd3b5.test").unwrap(),
|
||||
non_ascii: false,
|
||||
ibs: 521,
|
||||
print_level: None,
|
||||
count: Some(CountType::Bytes(32 * 1024)),
|
||||
cflags: IConvFlags::default(),
|
||||
iflags: IFlags::default(),
|
||||
},
|
||||
Output {
|
||||
dst: DST_PLACEHOLDER,
|
||||
obs: 1031,
|
||||
cflags: OConvFlags::default(),
|
||||
},
|
||||
File::open("./test-resources/gnudd-random-first-32k.spec").unwrap()
|
||||
);
|
||||
|
||||
make_io_test!(
|
||||
random_73k_test_lazy_fullblock,
|
||||
"random-73k-test-lazy-fullblock",
|
||||
Input {
|
||||
src: LazyReader {
|
||||
src: File::open("./test-resources/random-5828891cb1230748e146f34223bbd3b5.test")
|
||||
.unwrap()
|
||||
},
|
||||
non_ascii: false,
|
||||
ibs: 521,
|
||||
print_level: None,
|
||||
count: None,
|
||||
cflags: IConvFlags::default(),
|
||||
iflags: IFlags {
|
||||
fullblock: true,
|
||||
..IFlags::default()
|
||||
},
|
||||
},
|
||||
Output {
|
||||
dst: DST_PLACEHOLDER,
|
||||
obs: 1031,
|
||||
cflags: OConvFlags::default(),
|
||||
},
|
||||
File::open("./test-resources/random-5828891cb1230748e146f34223bbd3b5.test").unwrap()
|
||||
);
|
||||
|
||||
// Test internal buffer size fn
|
||||
#[test]
|
||||
fn bsize_test_primes() {
|
||||
let (n, m) = (7901, 7919);
|
||||
let res = calc_bsize(n, m);
|
||||
assert!(res % n == 0);
|
||||
assert!(res % m == 0);
|
||||
|
||||
assert_eq!(res, n * m);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bsize_test_rel_prime_obs_greater() {
|
||||
let (n, m) = (7 * 5119, 13 * 5119);
|
||||
let res = calc_bsize(n, m);
|
||||
assert!(res % n == 0);
|
||||
assert!(res % m == 0);
|
||||
|
||||
assert_eq!(res, 7 * 13 * 5119);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bsize_test_rel_prime_ibs_greater() {
|
||||
let (n, m) = (13 * 5119, 7 * 5119);
|
||||
let res = calc_bsize(n, m);
|
||||
assert!(res % n == 0);
|
||||
assert!(res % m == 0);
|
||||
|
||||
assert_eq!(res, 7 * 13 * 5119);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bsize_test_3fac_rel_prime() {
|
||||
let (n, m) = (11 * 13 * 5119, 7 * 11 * 5119);
|
||||
let res = calc_bsize(n, m);
|
||||
assert!(res % n == 0);
|
||||
assert!(res % m == 0);
|
||||
|
||||
assert_eq!(res, 7 * 11 * 13 * 5119);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bsize_test_ibs_greater() {
|
||||
let (n, m) = (512 * 1024, 256 * 1024);
|
||||
let res = calc_bsize(n, m);
|
||||
assert!(res % n == 0);
|
||||
assert!(res % m == 0);
|
||||
|
||||
assert_eq!(res, n);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bsize_test_obs_greater() {
|
||||
let (n, m) = (256 * 1024, 512 * 1024);
|
||||
let res = calc_bsize(n, m);
|
||||
assert!(res % n == 0);
|
||||
assert!(res % m == 0);
|
||||
|
||||
assert_eq!(res, m);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bsize_test_bs_eq() {
|
||||
let (n, m) = (1024, 1024);
|
||||
let res = calc_bsize(n, m);
|
||||
assert!(res % n == 0);
|
||||
assert!(res % m == 0);
|
||||
|
||||
assert_eq!(res, m);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn test_nocreat_causes_failure_when_ofile_doesnt_exist() {
|
||||
let args = vec![
|
||||
String::from("dd"),
|
||||
String::from("--conv=nocreat"),
|
||||
String::from("--of=not-a-real.file"),
|
||||
];
|
||||
|
||||
let matches = uu_app().get_matches_from_safe(args).unwrap();
|
||||
let _ = Output::<File>::new(&matches).unwrap();
|
||||
}
|
1
src/uu/dd/src/main.rs
Normal file
1
src/uu/dd/src/main.rs
Normal file
|
@ -0,0 +1 @@
|
|||
uucore_procs::main!(uu_dd); // spell-checker:ignore procs uucore
|
664
src/uu/dd/src/parseargs.rs
Normal file
664
src/uu/dd/src/parseargs.rs
Normal file
|
@ -0,0 +1,664 @@
|
|||
// This file is part of the uutils coreutils package.
|
||||
//
|
||||
// (c) Tyler Steele <tyler.steele@protonmail.com>
|
||||
//
|
||||
// For the full copyright and license information, please view the LICENSE
|
||||
// file that was distributed with this source code.
|
||||
// spell-checker:ignore ctty, ctable, iconvflags, oconvflags
|
||||
|
||||
#[cfg(test)]
|
||||
mod unit_tests;
|
||||
|
||||
use super::*;
|
||||
use std::error::Error;
|
||||
|
||||
pub type Matches = clap::ArgMatches<'static>;
|
||||
|
||||
/// Parser Errors describe errors with parser input
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum ParseError {
|
||||
MultipleFmtTable,
|
||||
MultipleUCaseLCase,
|
||||
MultipleBlockUnblock,
|
||||
MultipleExclNoCreat,
|
||||
FlagNoMatch(String),
|
||||
ConvFlagNoMatch(String),
|
||||
MultiplierStringParseFailure(String),
|
||||
MultiplierStringOverflow(String),
|
||||
BlockUnblockWithoutCBS,
|
||||
StatusLevelNotRecognized(String),
|
||||
Unimplemented(String),
|
||||
}
|
||||
|
||||
impl std::fmt::Display for ParseError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::MultipleFmtTable => {
|
||||
write!(
|
||||
f,
|
||||
"Only one of conv=ascii conv=ebcdic or conv=ibm may be specified"
|
||||
)
|
||||
}
|
||||
Self::MultipleUCaseLCase => {
|
||||
write!(f, "Only one of conv=lcase or conv=ucase may be specified")
|
||||
}
|
||||
Self::MultipleBlockUnblock => {
|
||||
write!(f, "Only one of conv=block or conv=unblock may be specified")
|
||||
}
|
||||
Self::MultipleExclNoCreat => {
|
||||
write!(f, "Only one ov conv=excl or conv=nocreat may be specified")
|
||||
}
|
||||
Self::FlagNoMatch(arg) => {
|
||||
write!(f, "Unrecognized iflag=FLAG or oflag=FLAG -> {}", arg)
|
||||
}
|
||||
Self::ConvFlagNoMatch(arg) => {
|
||||
write!(f, "Unrecognized conv=CONV -> {}", arg)
|
||||
}
|
||||
Self::MultiplierStringParseFailure(arg) => {
|
||||
write!(f, "Unrecognized byte multiplier -> {}", arg)
|
||||
}
|
||||
Self::MultiplierStringOverflow(arg) => {
|
||||
write!(
|
||||
f,
|
||||
"Multiplier string would overflow on current system -> {}",
|
||||
arg
|
||||
)
|
||||
}
|
||||
Self::BlockUnblockWithoutCBS => {
|
||||
write!(f, "conv=block or conv=unblock specified without cbs=N")
|
||||
}
|
||||
Self::StatusLevelNotRecognized(arg) => {
|
||||
write!(f, "status=LEVEL not recognized -> {}", arg)
|
||||
}
|
||||
Self::Unimplemented(arg) => {
|
||||
write!(f, "feature not implemented on this system -> {}", arg)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for ParseError {}
|
||||
|
||||
/// Some flags specified as part of a conv=CONV[,CONV]... block
|
||||
/// relate to the input file, others to the output file.
|
||||
#[derive(Debug, PartialEq)]
|
||||
enum ConvFlag {
|
||||
// Input
|
||||
FmtAtoE,
|
||||
FmtEtoA,
|
||||
FmtAtoI,
|
||||
Block,
|
||||
Unblock,
|
||||
UCase,
|
||||
LCase,
|
||||
Swab,
|
||||
Sync,
|
||||
NoError,
|
||||
// Output
|
||||
Sparse,
|
||||
Excl,
|
||||
NoCreat,
|
||||
NoTrunc,
|
||||
FDataSync,
|
||||
FSync,
|
||||
}
|
||||
|
||||
impl std::str::FromStr for ConvFlag {
|
||||
type Err = ParseError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
// Input
|
||||
"ascii" => Ok(Self::FmtEtoA),
|
||||
"ebcdic" => Ok(Self::FmtAtoE),
|
||||
"ibm" => Ok(Self::FmtAtoI),
|
||||
"lcase" => Ok(Self::LCase),
|
||||
"ucase" => Ok(Self::UCase),
|
||||
"block" => Ok(Self::Block),
|
||||
"unblock" => Ok(Self::Unblock),
|
||||
"swab" => Ok(Self::Swab),
|
||||
"sync" => Ok(Self::Sync),
|
||||
"noerror" => Ok(Self::NoError),
|
||||
// Output
|
||||
"sparse" => Ok(Self::Sparse),
|
||||
"excl" => Ok(Self::Excl),
|
||||
"nocreat" => Ok(Self::NoCreat),
|
||||
"notrunc" => Ok(Self::NoTrunc),
|
||||
"fdatasync" => Ok(Self::FDataSync),
|
||||
"fsync" => Ok(Self::FSync),
|
||||
_ => Err(ParseError::ConvFlagNoMatch(String::from(s))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
enum Flag {
|
||||
// Input only
|
||||
FullBlock,
|
||||
CountBytes,
|
||||
SkipBytes,
|
||||
// Either
|
||||
#[allow(unused)]
|
||||
Cio,
|
||||
#[allow(unused)]
|
||||
Direct,
|
||||
#[allow(unused)]
|
||||
Directory,
|
||||
#[allow(unused)]
|
||||
Dsync,
|
||||
#[allow(unused)]
|
||||
Sync,
|
||||
#[allow(unused)]
|
||||
NoCache,
|
||||
#[allow(unused)]
|
||||
NonBlock,
|
||||
#[allow(unused)]
|
||||
NoATime,
|
||||
#[allow(unused)]
|
||||
NoCtty,
|
||||
#[allow(unused)]
|
||||
NoFollow,
|
||||
#[allow(unused)]
|
||||
NoLinks,
|
||||
#[allow(unused)]
|
||||
Binary,
|
||||
#[allow(unused)]
|
||||
Text,
|
||||
// Output only
|
||||
Append,
|
||||
SeekBytes,
|
||||
}
|
||||
|
||||
impl std::str::FromStr for Flag {
|
||||
type Err = ParseError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
// Input only
|
||||
"fullblock" => Ok(Self::FullBlock),
|
||||
"count_bytes" => Ok(Self::CountBytes),
|
||||
"skip_bytes" => Ok(Self::SkipBytes),
|
||||
// Either
|
||||
"cio" =>
|
||||
// Ok(Self::Cio),
|
||||
{
|
||||
Err(ParseError::Unimplemented(s.to_string()))
|
||||
}
|
||||
"direct" =>
|
||||
// Ok(Self::Direct),
|
||||
{
|
||||
if cfg!(target_os = "linux") {
|
||||
Ok(Self::Direct)
|
||||
} else {
|
||||
Err(ParseError::Unimplemented(s.to_string()))
|
||||
}
|
||||
}
|
||||
"directory" =>
|
||||
// Ok(Self::Directory),
|
||||
{
|
||||
if cfg!(target_os = "linux") {
|
||||
Ok(Self::Directory)
|
||||
} else {
|
||||
Err(ParseError::Unimplemented(s.to_string()))
|
||||
}
|
||||
}
|
||||
"dsync" =>
|
||||
// Ok(Self::Dsync),
|
||||
{
|
||||
if cfg!(target_os = "linux") {
|
||||
Ok(Self::Dsync)
|
||||
} else {
|
||||
Err(ParseError::Unimplemented(s.to_string()))
|
||||
}
|
||||
}
|
||||
"sync" =>
|
||||
// Ok(Self::Sync),
|
||||
{
|
||||
if cfg!(target_os = "linux") {
|
||||
Ok(Self::Sync)
|
||||
} else {
|
||||
Err(ParseError::Unimplemented(s.to_string()))
|
||||
}
|
||||
}
|
||||
"nocache" =>
|
||||
// Ok(Self::NoCache),
|
||||
{
|
||||
Err(ParseError::Unimplemented(s.to_string()))
|
||||
}
|
||||
"nonblock" =>
|
||||
// Ok(Self::NonBlock),
|
||||
{
|
||||
if cfg!(target_os = "linux") {
|
||||
Ok(Self::NonBlock)
|
||||
} else {
|
||||
Err(ParseError::Unimplemented(s.to_string()))
|
||||
}
|
||||
}
|
||||
"noatime" =>
|
||||
// Ok(Self::NoATime),
|
||||
{
|
||||
if cfg!(target_os = "linux") {
|
||||
Ok(Self::NoATime)
|
||||
} else {
|
||||
Err(ParseError::Unimplemented(s.to_string()))
|
||||
}
|
||||
}
|
||||
"noctty" =>
|
||||
// Ok(Self::NoCtty),
|
||||
{
|
||||
if cfg!(target_os = "linux") {
|
||||
Ok(Self::NoCtty)
|
||||
} else {
|
||||
Err(ParseError::Unimplemented(s.to_string()))
|
||||
}
|
||||
}
|
||||
"nofollow" =>
|
||||
// Ok(Self::NoFollow),
|
||||
{
|
||||
if cfg!(target_os = "linux") {
|
||||
Ok(Self::NoFollow)
|
||||
} else {
|
||||
Err(ParseError::Unimplemented(s.to_string()))
|
||||
}
|
||||
}
|
||||
"nolinks" =>
|
||||
// Ok(Self::NoLinks),
|
||||
{
|
||||
Err(ParseError::Unimplemented(s.to_string()))
|
||||
}
|
||||
"binary" =>
|
||||
// Ok(Self::Binary),
|
||||
{
|
||||
Err(ParseError::Unimplemented(s.to_string()))
|
||||
}
|
||||
"text" =>
|
||||
// Ok(Self::Text),
|
||||
{
|
||||
Err(ParseError::Unimplemented(s.to_string()))
|
||||
}
|
||||
// Output only
|
||||
"append" => Ok(Self::Append),
|
||||
"seek_bytes" => Ok(Self::SeekBytes),
|
||||
_ => Err(ParseError::FlagNoMatch(String::from(s))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::str::FromStr for StatusLevel {
|
||||
type Err = ParseError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"none" => Ok(StatusLevel::None),
|
||||
"noxfer" => Ok(StatusLevel::Noxfer),
|
||||
"progress" => Ok(StatusLevel::Progress),
|
||||
_ => Err(ParseError::StatusLevelNotRecognized(s.to_string())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse bytes using str::parse, then map error if needed.
|
||||
fn parse_bytes_only(s: &str) -> Result<usize, ParseError> {
|
||||
s.parse()
|
||||
.map_err(|_| ParseError::MultiplierStringParseFailure(s.to_string()))
|
||||
}
|
||||
|
||||
/// Parse byte and multiplier like 512, 5KiB, or 1G.
|
||||
/// Uses uucore::parse_size, and adds the 'w' and 'c' suffixes which are mentioned
|
||||
/// in dd's info page.
|
||||
fn parse_bytes_with_opt_multiplier(s: &str) -> Result<usize, ParseError> {
|
||||
if let Some(idx) = s.rfind('c') {
|
||||
parse_bytes_only(&s[..idx])
|
||||
} else if let Some(idx) = s.rfind('w') {
|
||||
let partial = parse_bytes_only(&s[..idx])?;
|
||||
|
||||
partial
|
||||
.checked_mul(2)
|
||||
.ok_or_else(|| ParseError::MultiplierStringOverflow(s.to_string()))
|
||||
} else {
|
||||
uucore::parse_size::parse_size(s).map_err(|e| match e {
|
||||
uucore::parse_size::ParseSizeError::ParseFailure(s) => {
|
||||
ParseError::MultiplierStringParseFailure(s)
|
||||
}
|
||||
uucore::parse_size::ParseSizeError::SizeTooBig(s) => {
|
||||
ParseError::MultiplierStringOverflow(s)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_ibs(matches: &Matches) -> Result<usize, ParseError> {
|
||||
if let Some(mixed_str) = matches.value_of(options::BS) {
|
||||
parse_bytes_with_opt_multiplier(mixed_str)
|
||||
} else if let Some(mixed_str) = matches.value_of(options::IBS) {
|
||||
parse_bytes_with_opt_multiplier(mixed_str)
|
||||
} else {
|
||||
Ok(512)
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_cbs(matches: &Matches) -> Result<Option<usize>, ParseError> {
|
||||
if let Some(s) = matches.value_of(options::CBS) {
|
||||
let bytes = parse_bytes_with_opt_multiplier(s)?;
|
||||
Ok(Some(bytes))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_status_level(matches: &Matches) -> Result<Option<StatusLevel>, ParseError> {
|
||||
match matches.value_of(options::STATUS) {
|
||||
Some(s) => {
|
||||
let st = s.parse()?;
|
||||
Ok(Some(st))
|
||||
}
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_obs(matches: &Matches) -> Result<usize, ParseError> {
|
||||
if let Some(mixed_str) = matches.value_of("bs") {
|
||||
parse_bytes_with_opt_multiplier(mixed_str)
|
||||
} else if let Some(mixed_str) = matches.value_of("obs") {
|
||||
parse_bytes_with_opt_multiplier(mixed_str)
|
||||
} else {
|
||||
Ok(512)
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_ctable(fmt: Option<ConvFlag>, case: Option<ConvFlag>) -> Option<&'static ConversionTable> {
|
||||
fn parse_conv_and_case_table(
|
||||
fmt: &ConvFlag,
|
||||
case: &ConvFlag,
|
||||
) -> Option<&'static ConversionTable> {
|
||||
match (fmt, case) {
|
||||
(ConvFlag::FmtAtoE, ConvFlag::UCase) => Some(&ASCII_TO_EBCDIC_LCASE_TO_UCASE),
|
||||
(ConvFlag::FmtAtoE, ConvFlag::LCase) => Some(&ASCII_TO_EBCDIC_UCASE_TO_LCASE),
|
||||
(ConvFlag::FmtEtoA, ConvFlag::UCase) => Some(&EBCDIC_TO_ASCII_LCASE_TO_UCASE),
|
||||
(ConvFlag::FmtEtoA, ConvFlag::LCase) => Some(&EBCDIC_TO_ASCII_UCASE_TO_LCASE),
|
||||
(ConvFlag::FmtAtoI, ConvFlag::UCase) => Some(&ASCII_TO_IBM_UCASE_TO_LCASE),
|
||||
(ConvFlag::FmtAtoI, ConvFlag::LCase) => Some(&ASCII_TO_IBM_LCASE_TO_UCASE),
|
||||
(_, _) => None,
|
||||
}
|
||||
}
|
||||
fn parse_conv_table_only(fmt: &ConvFlag) -> Option<&'static ConversionTable> {
|
||||
match fmt {
|
||||
ConvFlag::FmtAtoE => Some(&ASCII_TO_EBCDIC),
|
||||
ConvFlag::FmtEtoA => Some(&EBCDIC_TO_ASCII),
|
||||
ConvFlag::FmtAtoI => Some(&ASCII_TO_IBM),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
// ------------------------------------------------------------------------
|
||||
match (fmt, case) {
|
||||
// Both [ascii | ebcdic | ibm] and [lcase | ucase] specified
|
||||
(Some(fmt), Some(case)) => parse_conv_and_case_table(&fmt, &case),
|
||||
// Only [ascii | ebcdic | ibm] specified
|
||||
(Some(fmt), None) => parse_conv_table_only(&fmt),
|
||||
// Only [lcase | ucase] specified
|
||||
(None, Some(ConvFlag::UCase)) => Some(&ASCII_LCASE_TO_UCASE),
|
||||
(None, Some(ConvFlag::LCase)) => Some(&ASCII_UCASE_TO_LCASE),
|
||||
// ST else...
|
||||
(_, _) => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_flag_list<T: std::str::FromStr<Err = ParseError>>(
|
||||
tag: &str,
|
||||
matches: &Matches,
|
||||
) -> Result<Vec<T>, ParseError> {
|
||||
let mut flags = Vec::new();
|
||||
|
||||
if let Some(comma_str) = matches.value_of(tag) {
|
||||
for s in comma_str.split(',') {
|
||||
let flag = s.parse()?;
|
||||
flags.push(flag);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(flags)
|
||||
}
|
||||
|
||||
/// Parse Conversion Options (Input Variety)
|
||||
/// Construct and validate a IConvFlags
|
||||
pub fn parse_conv_flag_input(matches: &Matches) -> Result<IConvFlags, ParseError> {
|
||||
let mut iconvflags = IConvFlags::default();
|
||||
let mut fmt = None;
|
||||
let mut case = None;
|
||||
let mut is_sync = false;
|
||||
|
||||
let flags = parse_flag_list(options::CONV, matches)?;
|
||||
let cbs = parse_cbs(matches)?;
|
||||
|
||||
for flag in flags {
|
||||
match flag {
|
||||
ConvFlag::FmtEtoA => {
|
||||
if fmt.is_some() {
|
||||
return Err(ParseError::MultipleFmtTable);
|
||||
} else {
|
||||
fmt = Some(flag);
|
||||
}
|
||||
}
|
||||
ConvFlag::FmtAtoE => {
|
||||
if fmt.is_some() {
|
||||
return Err(ParseError::MultipleFmtTable);
|
||||
} else {
|
||||
fmt = Some(flag);
|
||||
}
|
||||
}
|
||||
ConvFlag::FmtAtoI => {
|
||||
if fmt.is_some() {
|
||||
return Err(ParseError::MultipleFmtTable);
|
||||
} else {
|
||||
fmt = Some(flag);
|
||||
}
|
||||
}
|
||||
ConvFlag::UCase => {
|
||||
if case.is_some() {
|
||||
return Err(ParseError::MultipleUCaseLCase);
|
||||
} else {
|
||||
case = Some(flag)
|
||||
}
|
||||
}
|
||||
ConvFlag::LCase => {
|
||||
if case.is_some() {
|
||||
return Err(ParseError::MultipleUCaseLCase);
|
||||
} else {
|
||||
case = Some(flag)
|
||||
}
|
||||
}
|
||||
ConvFlag::Block => match (cbs, iconvflags.unblock) {
|
||||
(Some(cbs), None) => iconvflags.block = Some(cbs),
|
||||
(None, _) => return Err(ParseError::BlockUnblockWithoutCBS),
|
||||
(_, Some(_)) => return Err(ParseError::MultipleBlockUnblock),
|
||||
},
|
||||
ConvFlag::Unblock => match (cbs, iconvflags.block) {
|
||||
(Some(cbs), None) => iconvflags.unblock = Some(cbs),
|
||||
(None, _) => return Err(ParseError::BlockUnblockWithoutCBS),
|
||||
(_, Some(_)) => return Err(ParseError::MultipleBlockUnblock),
|
||||
},
|
||||
ConvFlag::Swab => iconvflags.swab = true,
|
||||
ConvFlag::Sync => is_sync = true,
|
||||
ConvFlag::NoError => iconvflags.noerror = true,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
// The final conversion table depends on both
|
||||
// fmt (eg. ASCII -> EBCDIC)
|
||||
// case (eg. UCASE -> LCASE)
|
||||
// So the final value can't be set until all flags are parsed.
|
||||
let ctable = parse_ctable(fmt, case);
|
||||
|
||||
// The final value of sync depends on block/unblock
|
||||
// block implies sync with ' '
|
||||
// unblock implies sync with 0
|
||||
// So the final value can't be set until all flags are parsed.
|
||||
let sync = if is_sync && (iconvflags.block.is_some() || iconvflags.unblock.is_some()) {
|
||||
Some(b' ')
|
||||
} else if is_sync {
|
||||
Some(0u8)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Ok(IConvFlags {
|
||||
ctable,
|
||||
sync,
|
||||
..iconvflags
|
||||
})
|
||||
}
|
||||
|
||||
/// Parse Conversion Options (Output Variety)
|
||||
/// Construct and validate a OConvFlags
|
||||
pub fn parse_conv_flag_output(matches: &Matches) -> Result<OConvFlags, ParseError> {
|
||||
let mut oconvflags = OConvFlags::default();
|
||||
|
||||
let flags = parse_flag_list(options::CONV, matches)?;
|
||||
|
||||
for flag in flags {
|
||||
match flag {
|
||||
ConvFlag::Sparse => oconvflags.sparse = true,
|
||||
ConvFlag::Excl => {
|
||||
if !oconvflags.nocreat {
|
||||
oconvflags.excl = true;
|
||||
} else {
|
||||
return Err(ParseError::MultipleExclNoCreat);
|
||||
}
|
||||
}
|
||||
ConvFlag::NoCreat => {
|
||||
if !oconvflags.excl {
|
||||
oconvflags.nocreat = true;
|
||||
} else {
|
||||
return Err(ParseError::MultipleExclNoCreat);
|
||||
}
|
||||
}
|
||||
ConvFlag::NoTrunc => oconvflags.notrunc = true,
|
||||
ConvFlag::FDataSync => oconvflags.fdatasync = true,
|
||||
ConvFlag::FSync => oconvflags.fsync = true,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(oconvflags)
|
||||
}
|
||||
|
||||
/// Parse IFlags struct from CL-input
|
||||
pub fn parse_iflags(matches: &Matches) -> Result<IFlags, ParseError> {
|
||||
let mut iflags = IFlags::default();
|
||||
|
||||
let flags = parse_flag_list(options::IFLAG, matches)?;
|
||||
|
||||
for flag in flags {
|
||||
match flag {
|
||||
Flag::Cio => iflags.cio = true,
|
||||
Flag::Direct => iflags.direct = true,
|
||||
Flag::Directory => iflags.directory = true,
|
||||
Flag::Dsync => iflags.dsync = true,
|
||||
Flag::Sync => iflags.sync = true,
|
||||
Flag::NoCache => iflags.nocache = true,
|
||||
Flag::NonBlock => iflags.nonblock = true,
|
||||
Flag::NoATime => iflags.noatime = true,
|
||||
Flag::NoCtty => iflags.noctty = true,
|
||||
Flag::NoFollow => iflags.nofollow = true,
|
||||
Flag::NoLinks => iflags.nolinks = true,
|
||||
Flag::Binary => iflags.binary = true,
|
||||
Flag::Text => iflags.text = true,
|
||||
Flag::FullBlock => iflags.fullblock = true,
|
||||
Flag::CountBytes => iflags.count_bytes = true,
|
||||
Flag::SkipBytes => iflags.skip_bytes = true,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(iflags)
|
||||
}
|
||||
|
||||
/// Parse OFlags struct from CL-input
|
||||
pub fn parse_oflags(matches: &Matches) -> Result<OFlags, ParseError> {
|
||||
let mut oflags = OFlags::default();
|
||||
|
||||
let flags = parse_flag_list(options::OFLAG, matches)?;
|
||||
|
||||
for flag in flags {
|
||||
match flag {
|
||||
Flag::Append => oflags.append = true,
|
||||
Flag::Cio => oflags.cio = true,
|
||||
Flag::Direct => oflags.direct = true,
|
||||
Flag::Directory => oflags.directory = true,
|
||||
Flag::Dsync => oflags.dsync = true,
|
||||
Flag::Sync => oflags.sync = true,
|
||||
Flag::NoCache => oflags.nocache = true,
|
||||
Flag::NonBlock => oflags.nonblock = true,
|
||||
Flag::NoATime => oflags.noatime = true,
|
||||
Flag::NoCtty => oflags.noctty = true,
|
||||
Flag::NoFollow => oflags.nofollow = true,
|
||||
Flag::NoLinks => oflags.nolinks = true,
|
||||
Flag::Binary => oflags.binary = true,
|
||||
Flag::Text => oflags.text = true,
|
||||
Flag::SeekBytes => oflags.seek_bytes = true,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(oflags)
|
||||
}
|
||||
|
||||
/// Parse the amount of the input file to skip.
|
||||
pub fn parse_skip_amt(
|
||||
ibs: &usize,
|
||||
iflags: &IFlags,
|
||||
matches: &Matches,
|
||||
) -> Result<Option<usize>, ParseError> {
|
||||
if let Some(amt) = matches.value_of(options::SKIP) {
|
||||
let n = parse_bytes_with_opt_multiplier(amt)?;
|
||||
if iflags.skip_bytes {
|
||||
Ok(Some(n))
|
||||
} else {
|
||||
Ok(Some(ibs * n))
|
||||
}
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse the amount of the output file to seek.
|
||||
pub fn parse_seek_amt(
|
||||
obs: &usize,
|
||||
oflags: &OFlags,
|
||||
matches: &Matches,
|
||||
) -> Result<Option<usize>, ParseError> {
|
||||
if let Some(amt) = matches.value_of(options::SEEK) {
|
||||
let n = parse_bytes_with_opt_multiplier(amt)?;
|
||||
if oflags.seek_bytes {
|
||||
Ok(Some(n))
|
||||
} else {
|
||||
Ok(Some(obs * n))
|
||||
}
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse the value of count=N and the type of N implied by iflags
|
||||
pub fn parse_count(iflags: &IFlags, matches: &Matches) -> Result<Option<CountType>, ParseError> {
|
||||
if let Some(amt) = matches.value_of(options::COUNT) {
|
||||
let n = parse_bytes_with_opt_multiplier(amt)?;
|
||||
if iflags.count_bytes {
|
||||
Ok(Some(CountType::Bytes(n)))
|
||||
} else {
|
||||
Ok(Some(CountType::Reads(n)))
|
||||
}
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse whether the args indicate the input is not ascii
|
||||
pub fn parse_input_non_ascii(matches: &Matches) -> Result<bool, ParseError> {
|
||||
if let Some(conv_opts) = matches.value_of(options::CONV) {
|
||||
Ok(conv_opts.contains("ascii"))
|
||||
} else {
|
||||
Ok(false)
|
||||
}
|
||||
}
|
587
src/uu/dd/src/parseargs/unit_tests.rs
Normal file
587
src/uu/dd/src/parseargs/unit_tests.rs
Normal file
|
@ -0,0 +1,587 @@
|
|||
// 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
|
||||
|
||||
use super::*;
|
||||
|
||||
use crate::StatusLevel;
|
||||
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
#[test]
|
||||
fn unimplemented_flags_should_error_non_linux() {
|
||||
let mut succeeded = Vec::new();
|
||||
|
||||
// The following flags are only implemented in linux
|
||||
for &flag in &[
|
||||
"direct",
|
||||
"directory",
|
||||
"dsync",
|
||||
"sync",
|
||||
"nonblock",
|
||||
"noatime",
|
||||
"noctty",
|
||||
"nofollow",
|
||||
] {
|
||||
let args = vec![
|
||||
String::from("dd"),
|
||||
format!("--iflag={}", flag),
|
||||
format!("--oflag={}", flag),
|
||||
];
|
||||
let matches = uu_app().get_matches_from_safe(args).unwrap();
|
||||
|
||||
if parse_iflags(&matches).is_ok() {
|
||||
succeeded.push(format!("iflag={}", flag));
|
||||
}
|
||||
if parse_oflags(&matches).is_ok() {
|
||||
succeeded.push(format!("oflag={}", flag));
|
||||
}
|
||||
}
|
||||
|
||||
assert!(
|
||||
succeeded.is_empty(),
|
||||
"The following flags did not panic as expected: {:?}",
|
||||
succeeded
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unimplemented_flags_should_error() {
|
||||
let mut succeeded = Vec::new();
|
||||
|
||||
// The following flags are not implemented
|
||||
for &flag in &["cio", "nocache", "nolinks", "text", "binary"] {
|
||||
let args = vec![
|
||||
String::from("dd"),
|
||||
format!("--iflag={}", flag),
|
||||
format!("--oflag={}", flag),
|
||||
];
|
||||
let matches = uu_app().get_matches_from_safe(args).unwrap();
|
||||
|
||||
if parse_iflags(&matches).is_ok() {
|
||||
succeeded.push(format!("iflag={}", flag))
|
||||
}
|
||||
if parse_oflags(&matches).is_ok() {
|
||||
succeeded.push(format!("oflag={}", flag))
|
||||
}
|
||||
}
|
||||
|
||||
assert!(
|
||||
succeeded.is_empty(),
|
||||
"The following flags did not panic as expected: {:?}",
|
||||
succeeded
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_status_level_absent() {
|
||||
let args = vec![
|
||||
String::from("dd"),
|
||||
String::from("--if=foo.file"),
|
||||
String::from("--of=bar.file"),
|
||||
];
|
||||
|
||||
let matches = uu_app().get_matches_from_safe(args).unwrap();
|
||||
let st = parse_status_level(&matches).unwrap();
|
||||
|
||||
assert_eq!(st, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_status_level_none() {
|
||||
let args = vec![
|
||||
String::from("dd"),
|
||||
String::from("--status=none"),
|
||||
String::from("--if=foo.file"),
|
||||
String::from("--of=bar.file"),
|
||||
];
|
||||
|
||||
let matches = uu_app().get_matches_from_safe(args).unwrap();
|
||||
let st = parse_status_level(&matches).unwrap().unwrap();
|
||||
|
||||
assert_eq!(st, StatusLevel::None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_all_top_level_args_no_leading_dashes() {
|
||||
let args = vec![
|
||||
String::from("dd"),
|
||||
String::from("if=foo.file"),
|
||||
String::from("of=bar.file"),
|
||||
String::from("ibs=10"),
|
||||
String::from("obs=10"),
|
||||
String::from("cbs=1"),
|
||||
String::from("bs=100"),
|
||||
String::from("count=2"),
|
||||
String::from("skip=2"),
|
||||
String::from("seek=2"),
|
||||
String::from("status=progress"),
|
||||
String::from("conv=ascii,ucase"),
|
||||
String::from("iflag=count_bytes,skip_bytes"),
|
||||
String::from("oflag=append,seek_bytes"),
|
||||
];
|
||||
let args = args
|
||||
.into_iter()
|
||||
.fold(Vec::new(), append_dashes_if_not_present);
|
||||
|
||||
let matches = uu_app().get_matches_from_safe(args).unwrap();
|
||||
|
||||
assert_eq!(100, parse_ibs(&matches).unwrap());
|
||||
assert_eq!(100, parse_obs(&matches).unwrap());
|
||||
assert_eq!(1, parse_cbs(&matches).unwrap().unwrap());
|
||||
assert_eq!(
|
||||
CountType::Bytes(2),
|
||||
parse_count(
|
||||
&IFlags {
|
||||
count_bytes: true,
|
||||
..IFlags::default()
|
||||
},
|
||||
&matches
|
||||
)
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
200,
|
||||
parse_skip_amt(&100, &IFlags::default(), &matches)
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
200,
|
||||
parse_seek_amt(&100, &OFlags::default(), &matches)
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
StatusLevel::Progress,
|
||||
parse_status_level(&matches).unwrap().unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
IConvFlags {
|
||||
ctable: Some(&EBCDIC_TO_ASCII_LCASE_TO_UCASE),
|
||||
..IConvFlags::default()
|
||||
},
|
||||
parse_conv_flag_input(&matches).unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
OConvFlags::default(),
|
||||
parse_conv_flag_output(&matches).unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
IFlags {
|
||||
count_bytes: true,
|
||||
skip_bytes: true,
|
||||
..IFlags::default()
|
||||
},
|
||||
parse_iflags(&matches).unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
OFlags {
|
||||
append: true,
|
||||
seek_bytes: true,
|
||||
..OFlags::default()
|
||||
},
|
||||
parse_oflags(&matches).unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_all_top_level_args_with_leading_dashes() {
|
||||
let args = vec![
|
||||
String::from("dd"),
|
||||
String::from("--if=foo.file"),
|
||||
String::from("--of=bar.file"),
|
||||
String::from("--ibs=10"),
|
||||
String::from("--obs=10"),
|
||||
String::from("--cbs=1"),
|
||||
String::from("--bs=100"),
|
||||
String::from("--count=2"),
|
||||
String::from("--skip=2"),
|
||||
String::from("--seek=2"),
|
||||
String::from("--status=progress"),
|
||||
String::from("--conv=ascii,ucase"),
|
||||
String::from("--iflag=count_bytes,skip_bytes"),
|
||||
String::from("--oflag=append,seek_bytes"),
|
||||
];
|
||||
let args = args
|
||||
.into_iter()
|
||||
.fold(Vec::new(), append_dashes_if_not_present);
|
||||
|
||||
let matches = uu_app().get_matches_from_safe(args).unwrap();
|
||||
|
||||
assert_eq!(100, parse_ibs(&matches).unwrap());
|
||||
assert_eq!(100, parse_obs(&matches).unwrap());
|
||||
assert_eq!(1, parse_cbs(&matches).unwrap().unwrap());
|
||||
assert_eq!(
|
||||
CountType::Bytes(2),
|
||||
parse_count(
|
||||
&IFlags {
|
||||
count_bytes: true,
|
||||
..IFlags::default()
|
||||
},
|
||||
&matches
|
||||
)
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
200,
|
||||
parse_skip_amt(&100, &IFlags::default(), &matches)
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
200,
|
||||
parse_seek_amt(&100, &OFlags::default(), &matches)
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
StatusLevel::Progress,
|
||||
parse_status_level(&matches).unwrap().unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
IConvFlags {
|
||||
ctable: Some(&EBCDIC_TO_ASCII_LCASE_TO_UCASE),
|
||||
..IConvFlags::default()
|
||||
},
|
||||
parse_conv_flag_input(&matches).unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
OConvFlags::default(),
|
||||
parse_conv_flag_output(&matches).unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
IFlags {
|
||||
count_bytes: true,
|
||||
skip_bytes: true,
|
||||
..IFlags::default()
|
||||
},
|
||||
parse_iflags(&matches).unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
OFlags {
|
||||
append: true,
|
||||
seek_bytes: true,
|
||||
..OFlags::default()
|
||||
},
|
||||
parse_oflags(&matches).unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_status_level_progress() {
|
||||
let args = vec![
|
||||
String::from("dd"),
|
||||
String::from("--if=foo.file"),
|
||||
String::from("--of=bar.file"),
|
||||
String::from("--status=progress"),
|
||||
];
|
||||
|
||||
let matches = uu_app().get_matches_from_safe(args).unwrap();
|
||||
let st = parse_status_level(&matches).unwrap().unwrap();
|
||||
|
||||
assert_eq!(st, StatusLevel::Progress);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_status_level_noxfer() {
|
||||
let args = vec![
|
||||
String::from("dd"),
|
||||
String::from("--if=foo.file"),
|
||||
String::from("--status=noxfer"),
|
||||
String::from("--of=bar.file"),
|
||||
];
|
||||
|
||||
let matches = uu_app().get_matches_from_safe(args).unwrap();
|
||||
let st = parse_status_level(&matches).unwrap().unwrap();
|
||||
|
||||
assert_eq!(st, StatusLevel::Noxfer);
|
||||
}
|
||||
|
||||
// ----- IConvFlags/Output -----
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn icf_ctable_error() {
|
||||
let args = vec![String::from("dd"), String::from("--conv=ascii,ebcdic,ibm")];
|
||||
|
||||
let matches = uu_app().get_matches_from_safe(args).unwrap();
|
||||
|
||||
let _ = parse_conv_flag_input(&matches).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn icf_case_error() {
|
||||
let args = vec![String::from("dd"), String::from("--conv=ucase,lcase")];
|
||||
|
||||
let matches = uu_app().get_matches_from_safe(args).unwrap();
|
||||
|
||||
let _ = parse_conv_flag_input(&matches).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn icf_block_error() {
|
||||
let args = vec![String::from("dd"), String::from("--conv=block,unblock")];
|
||||
|
||||
let matches = uu_app().get_matches_from_safe(args).unwrap();
|
||||
|
||||
let _ = parse_conv_flag_input(&matches).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn icf_creat_error() {
|
||||
let args = vec![String::from("dd"), String::from("--conv=excl,nocreat")];
|
||||
|
||||
let matches = uu_app().get_matches_from_safe(args).unwrap();
|
||||
|
||||
let _ = parse_conv_flag_output(&matches).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_icf_token_ibm() {
|
||||
let exp = vec![ConvFlag::FmtAtoI];
|
||||
|
||||
let args = vec![String::from("dd"), String::from("--conv=ibm")];
|
||||
let matches = uu_app().get_matches_from_safe(args).unwrap();
|
||||
|
||||
let act = parse_flag_list::<ConvFlag>("conv", &matches).unwrap();
|
||||
|
||||
assert_eq!(exp.len(), act.len());
|
||||
for cf in &exp {
|
||||
assert!(exp.contains(cf));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_icf_tokens_elu() {
|
||||
let exp = vec![ConvFlag::FmtEtoA, ConvFlag::LCase, ConvFlag::Unblock];
|
||||
|
||||
let args = vec![
|
||||
String::from("dd"),
|
||||
String::from("--conv=ebcdic,lcase,unblock"),
|
||||
];
|
||||
let matches = uu_app().get_matches_from_safe(args).unwrap();
|
||||
let act = parse_flag_list::<ConvFlag>("conv", &matches).unwrap();
|
||||
|
||||
assert_eq!(exp.len(), act.len());
|
||||
for cf in &exp {
|
||||
assert!(exp.contains(cf));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_icf_tokens_remaining() {
|
||||
let exp = vec![
|
||||
ConvFlag::FmtAtoE,
|
||||
ConvFlag::UCase,
|
||||
ConvFlag::Block,
|
||||
ConvFlag::Sparse,
|
||||
ConvFlag::Swab,
|
||||
ConvFlag::Sync,
|
||||
ConvFlag::NoError,
|
||||
ConvFlag::Excl,
|
||||
ConvFlag::NoCreat,
|
||||
ConvFlag::NoTrunc,
|
||||
ConvFlag::NoError,
|
||||
ConvFlag::FDataSync,
|
||||
ConvFlag::FSync,
|
||||
];
|
||||
|
||||
let args = vec![
|
||||
String::from("dd"),
|
||||
String::from("--conv=ascii,ucase,block,sparse,swab,sync,noerror,excl,nocreat,notrunc,noerror,fdatasync,fsync"),
|
||||
];
|
||||
let matches = uu_app().get_matches_from_safe(args).unwrap();
|
||||
|
||||
let act = parse_flag_list::<ConvFlag>("conv", &matches).unwrap();
|
||||
|
||||
assert_eq!(exp.len(), act.len());
|
||||
for cf in &exp {
|
||||
assert!(exp.contains(cf));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_iflag_tokens() {
|
||||
let exp = vec![
|
||||
Flag::FullBlock,
|
||||
Flag::CountBytes,
|
||||
Flag::SkipBytes,
|
||||
Flag::Append,
|
||||
Flag::SeekBytes,
|
||||
];
|
||||
|
||||
let args = vec![
|
||||
String::from("dd"),
|
||||
String::from("--iflag=fullblock,count_bytes,skip_bytes,append,seek_bytes"),
|
||||
];
|
||||
let matches = uu_app().get_matches_from_safe(args).unwrap();
|
||||
|
||||
let act = parse_flag_list::<Flag>("iflag", &matches).unwrap();
|
||||
|
||||
assert_eq!(exp.len(), act.len());
|
||||
for cf in &exp {
|
||||
assert!(exp.contains(cf));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_oflag_tokens() {
|
||||
let exp = vec![
|
||||
Flag::FullBlock,
|
||||
Flag::CountBytes,
|
||||
Flag::SkipBytes,
|
||||
Flag::Append,
|
||||
Flag::SeekBytes,
|
||||
];
|
||||
|
||||
let args = vec![
|
||||
String::from("dd"),
|
||||
String::from("--oflag=fullblock,count_bytes,skip_bytes,append,seek_bytes"),
|
||||
];
|
||||
let matches = uu_app().get_matches_from_safe(args).unwrap();
|
||||
|
||||
let act = parse_flag_list::<Flag>("oflag", &matches).unwrap();
|
||||
|
||||
assert_eq!(exp.len(), act.len());
|
||||
for cf in &exp {
|
||||
assert!(exp.contains(cf));
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
#[test]
|
||||
fn parse_iflag_tokens_linux() {
|
||||
let exp = vec![
|
||||
Flag::Direct,
|
||||
Flag::Directory,
|
||||
Flag::Dsync,
|
||||
Flag::Sync,
|
||||
Flag::NonBlock,
|
||||
Flag::NoATime,
|
||||
Flag::NoCtty,
|
||||
Flag::NoFollow,
|
||||
];
|
||||
|
||||
let args = vec![
|
||||
String::from("dd"),
|
||||
String::from("--iflag=direct,directory,dsync,sync,nonblock,noatime,noctty,nofollow"),
|
||||
];
|
||||
let matches = uu_app().get_matches_from_safe(args).unwrap();
|
||||
|
||||
let act = parse_flag_list::<Flag>("iflag", &matches).unwrap();
|
||||
|
||||
assert_eq!(exp.len(), act.len());
|
||||
for cf in &exp {
|
||||
assert!(exp.contains(cf));
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
#[test]
|
||||
fn parse_oflag_tokens_linux() {
|
||||
let exp = vec![
|
||||
Flag::Direct,
|
||||
Flag::Directory,
|
||||
Flag::Dsync,
|
||||
Flag::Sync,
|
||||
Flag::NonBlock,
|
||||
Flag::NoATime,
|
||||
Flag::NoCtty,
|
||||
Flag::NoFollow,
|
||||
];
|
||||
|
||||
let args = vec![
|
||||
String::from("dd"),
|
||||
String::from("--oflag=direct,directory,dsync,sync,nonblock,noatime,noctty,nofollow"),
|
||||
];
|
||||
let matches = uu_app().get_matches_from_safe(args).unwrap();
|
||||
|
||||
let act = parse_flag_list::<Flag>("oflag", &matches).unwrap();
|
||||
|
||||
assert_eq!(exp.len(), act.len());
|
||||
for cf in &exp {
|
||||
assert!(exp.contains(cf));
|
||||
}
|
||||
}
|
||||
|
||||
// ----- Multiplier Strings etc. -----
|
||||
macro_rules! test_byte_parser (
|
||||
( $test_name:ident, $bs_str:expr, $bs:expr ) =>
|
||||
{
|
||||
#[allow(non_snake_case)]
|
||||
#[test]
|
||||
fn $test_name()
|
||||
{
|
||||
// let bs_str = String::from($bs_str);
|
||||
assert_eq!($bs, parse_bytes_with_opt_multiplier($bs_str).unwrap())
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
test_byte_parser!(test_bytes_n, "765", 765);
|
||||
test_byte_parser!(test_bytes_c, "13c", 13);
|
||||
|
||||
test_byte_parser!(test_bytes_w, "1w", 2);
|
||||
|
||||
test_byte_parser!(test_bytes_b, "1b", 512);
|
||||
|
||||
test_byte_parser!(test_bytes_k, "1kB", 1000);
|
||||
test_byte_parser!(test_bytes_K, "1K", 1024);
|
||||
test_byte_parser!(test_bytes_Ki, "1KiB", 1024);
|
||||
|
||||
test_byte_parser!(test_bytes_MB, "2MB", 2 * 1000 * 1000);
|
||||
test_byte_parser!(test_bytes_M, "2M", 2 * 1024 * 1024);
|
||||
test_byte_parser!(test_bytes_Mi, "2MiB", 2 * 1024 * 1024);
|
||||
|
||||
test_byte_parser!(test_bytes_GB, "3GB", 3 * 1000 * 1000 * 1000);
|
||||
test_byte_parser!(test_bytes_G, "3G", 3 * 1024 * 1024 * 1024);
|
||||
test_byte_parser!(test_bytes_Gi, "3GiB", 3 * 1024 * 1024 * 1024);
|
||||
|
||||
#[cfg(target_pointer_width = "64")]
|
||||
#[cfg(test)]
|
||||
mod test_64bit_arch {
|
||||
use super::*;
|
||||
|
||||
test_byte_parser!(test_bytes_TB, "4TB", 4 * 1000 * 1000 * 1000 * 1000);
|
||||
test_byte_parser!(test_bytes_T, "4T", 4 * 1024 * 1024 * 1024 * 1024);
|
||||
test_byte_parser!(test_bytes_Ti, "4TiB", 4 * 1024 * 1024 * 1024 * 1024);
|
||||
|
||||
test_byte_parser!(test_bytes_PB, "5PB", 5 * 1000 * 1000 * 1000 * 1000 * 1000);
|
||||
test_byte_parser!(test_bytes_P, "5P", 5 * 1024 * 1024 * 1024 * 1024 * 1024);
|
||||
test_byte_parser!(test_bytes_Pi, "5PiB", 5 * 1024 * 1024 * 1024 * 1024 * 1024);
|
||||
|
||||
test_byte_parser!(
|
||||
test_bytes_EB,
|
||||
"6EB",
|
||||
6 * 1000 * 1000 * 1000 * 1000 * 1000 * 1000
|
||||
);
|
||||
test_byte_parser!(
|
||||
test_bytes_E,
|
||||
"6E",
|
||||
6 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024
|
||||
);
|
||||
test_byte_parser!(
|
||||
test_bytes_Ei,
|
||||
"6EiB",
|
||||
6 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn test_overflow_panic() {
|
||||
let bs_str = format!("{}KiB", usize::MAX);
|
||||
|
||||
parse_bytes_with_opt_multiplier(&bs_str).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn test_neg_panic() {
|
||||
let bs_str = format!("{}", -1);
|
||||
|
||||
parse_bytes_with_opt_multiplier(&bs_str).unwrap();
|
||||
}
|
Binary file not shown.
16
src/uu/dd/test-resources/dd-block-cbs16-win.test
Normal file
16
src/uu/dd/test-resources/dd-block-cbs16-win.test
Normal file
|
@ -0,0 +1,16 @@
|
|||
0
|
||||
01
|
||||
012
|
||||
0123
|
||||
01234
|
||||
012345
|
||||
0123456
|
||||
01234567
|
||||
012345678
|
||||
0123456789
|
||||
0123456789a
|
||||
0123456789ab
|
||||
0123456789abc
|
||||
0123456789abcd
|
||||
0123456789abcde
|
||||
0123456789abcdef
|
1
src/uu/dd/test-resources/dd-block-cbs16.spec
Normal file
1
src/uu/dd/test-resources/dd-block-cbs16.spec
Normal file
|
@ -0,0 +1 @@
|
|||
0 01 012 0123 01234 012345 0123456 01234567 012345678 0123456789 0123456789a 0123456789ab 0123456789abc 0123456789abcd 0123456789abcde 0123456789abcdef
|
16
src/uu/dd/test-resources/dd-block-cbs16.test
Normal file
16
src/uu/dd/test-resources/dd-block-cbs16.test
Normal file
|
@ -0,0 +1,16 @@
|
|||
0
|
||||
01
|
||||
012
|
||||
0123
|
||||
01234
|
||||
012345
|
||||
0123456
|
||||
01234567
|
||||
012345678
|
||||
0123456789
|
||||
0123456789a
|
||||
0123456789ab
|
||||
0123456789abc
|
||||
0123456789abcd
|
||||
0123456789abcde
|
||||
0123456789abcdef
|
1
src/uu/dd/test-resources/dd-block-cbs8.spec
Normal file
1
src/uu/dd/test-resources/dd-block-cbs8.spec
Normal file
|
@ -0,0 +1 @@
|
|||
0 01 012 0123 01234 012345 0123456 012345670123456701234567012345670123456701234567012345670123456701234567
|
|
@ -0,0 +1 @@
|
|||
pre post
|
|
@ -0,0 +1,4 @@
|
|||
pre
|
||||
|
||||
|
||||
post
|
4
src/uu/dd/test-resources/dd-block-consecutive-nl.test
Normal file
4
src/uu/dd/test-resources/dd-block-consecutive-nl.test
Normal file
|
@ -0,0 +1,4 @@
|
|||
pre
|
||||
|
||||
|
||||
post
|
16
src/uu/dd/test-resources/dd-unblock-cbs16-win.spec
Normal file
16
src/uu/dd/test-resources/dd-unblock-cbs16-win.spec
Normal file
|
@ -0,0 +1,16 @@
|
|||
0
|
||||
01
|
||||
012
|
||||
0123
|
||||
01234
|
||||
012345
|
||||
0123456
|
||||
01234567
|
||||
012345678
|
||||
0123456789
|
||||
0123456789a
|
||||
0123456789ab
|
||||
0123456789abc
|
||||
0123456789abcd
|
||||
0123456789abcde
|
||||
0123456789abcdef
|
16
src/uu/dd/test-resources/dd-unblock-cbs16.spec
Normal file
16
src/uu/dd/test-resources/dd-unblock-cbs16.spec
Normal file
|
@ -0,0 +1,16 @@
|
|||
0
|
||||
01
|
||||
012
|
||||
0123
|
||||
01234
|
||||
012345
|
||||
0123456
|
||||
01234567
|
||||
012345678
|
||||
0123456789
|
||||
0123456789a
|
||||
0123456789ab
|
||||
0123456789abc
|
||||
0123456789abcd
|
||||
0123456789abcde
|
||||
0123456789abcdef
|
1
src/uu/dd/test-resources/dd-unblock-cbs16.test
Normal file
1
src/uu/dd/test-resources/dd-unblock-cbs16.test
Normal file
|
@ -0,0 +1 @@
|
|||
0 01 012 0123 01234 012345 0123456 01234567 012345678 0123456789 0123456789a 0123456789ab 0123456789abc 0123456789abcd 0123456789abcde 0123456789abcdef
|
32
src/uu/dd/test-resources/dd-unblock-cbs8-win.spec
Normal file
32
src/uu/dd/test-resources/dd-unblock-cbs8-win.spec
Normal file
|
@ -0,0 +1,32 @@
|
|||
0
|
||||
|
||||
01
|
||||
|
||||
012
|
||||
|
||||
0123
|
||||
|
||||
01234
|
||||
|
||||
012345
|
||||
|
||||
0123456
|
||||
|
||||
01234567
|
||||
|
||||
01234567
|
||||
8
|
||||
01234567
|
||||
89
|
||||
01234567
|
||||
89a
|
||||
01234567
|
||||
89ab
|
||||
01234567
|
||||
89abc
|
||||
01234567
|
||||
89abcd
|
||||
01234567
|
||||
89abcde
|
||||
01234567
|
||||
89abcdef
|
32
src/uu/dd/test-resources/dd-unblock-cbs8.spec
Normal file
32
src/uu/dd/test-resources/dd-unblock-cbs8.spec
Normal file
|
@ -0,0 +1,32 @@
|
|||
0
|
||||
|
||||
01
|
||||
|
||||
012
|
||||
|
||||
0123
|
||||
|
||||
01234
|
||||
|
||||
012345
|
||||
|
||||
0123456
|
||||
|
||||
01234567
|
||||
|
||||
01234567
|
||||
8
|
||||
01234567
|
||||
89
|
||||
01234567
|
||||
89a
|
||||
01234567
|
||||
89ab
|
||||
01234567
|
||||
89abc
|
||||
01234567
|
||||
89abcd
|
||||
01234567
|
||||
89abcde
|
||||
01234567
|
||||
89abcdef
|
BIN
src/uu/dd/test-resources/deadbeef-16.spec
Normal file
BIN
src/uu/dd/test-resources/deadbeef-16.spec
Normal file
Binary file not shown.
1
src/uu/dd/test-resources/deadbeef-16.test
Normal file
1
src/uu/dd/test-resources/deadbeef-16.test
Normal file
|
@ -0,0 +1 @@
|
|||
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
File diff suppressed because one or more lines are too long
BIN
src/uu/dd/test-resources/gnudd-conv-atoe-seq-byte-values.spec
Normal file
BIN
src/uu/dd/test-resources/gnudd-conv-atoe-seq-byte-values.spec
Normal file
Binary file not shown.
BIN
src/uu/dd/test-resources/gnudd-conv-atoibm-seq-byte-values.spec
Normal file
BIN
src/uu/dd/test-resources/gnudd-conv-atoibm-seq-byte-values.spec
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
src/uu/dd/test-resources/gnudd-conv-etoa-seq-byte-values.spec
Normal file
BIN
src/uu/dd/test-resources/gnudd-conv-etoa-seq-byte-values.spec
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
src/uu/dd/test-resources/gnudd-conv-ltou-seq-byte-values.spec
Normal file
BIN
src/uu/dd/test-resources/gnudd-conv-ltou-seq-byte-values.spec
Normal file
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue