mirror of
https://github.com/uutils/coreutils
synced 2024-11-17 10:18:11 +00:00
Merge branch 'master' of github.com:backwaterred/coreutils into dedup-dd-mideb
This commit is contained in:
commit
20c63caa0c
76 changed files with 3162 additions and 994 deletions
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
|
||||
|
|
|
@ -63,6 +63,7 @@ abspath
|
|||
addprefix
|
||||
addsuffix
|
||||
endef
|
||||
findstring
|
||||
firstword
|
||||
ifeq
|
||||
ifneq
|
||||
|
@ -89,5 +90,9 @@ markdownlint
|
|||
rerast
|
||||
rollup
|
||||
sed
|
||||
selinuxenabled
|
||||
wslpath
|
||||
xargs
|
||||
|
||||
# * directories
|
||||
sbin
|
||||
|
|
|
@ -68,6 +68,7 @@ splitn
|
|||
trunc
|
||||
|
||||
# * uutils
|
||||
basenc
|
||||
chcon
|
||||
chgrp
|
||||
chmod
|
||||
|
@ -106,6 +107,7 @@ whoami
|
|||
# * vars/errno
|
||||
errno
|
||||
EEXIST
|
||||
ENODATA
|
||||
ENOENT
|
||||
ENOSYS
|
||||
EPERM
|
||||
|
@ -118,7 +120,9 @@ fcntl
|
|||
vmsplice
|
||||
|
||||
# * vars/libc
|
||||
COMFOLLOW
|
||||
FILENO
|
||||
FTSENT
|
||||
HOSTSIZE
|
||||
IDSIZE
|
||||
IFBLK
|
||||
|
@ -151,6 +155,7 @@ SIGTERM
|
|||
SYS_fdatasync
|
||||
SYS_syncfs
|
||||
USERSIZE
|
||||
accpath
|
||||
addrinfo
|
||||
addrlen
|
||||
blocksize
|
||||
|
@ -174,15 +179,18 @@ inode
|
|||
inodes
|
||||
isatty
|
||||
lchown
|
||||
pathlen
|
||||
setgid
|
||||
setgroups
|
||||
settime
|
||||
setuid
|
||||
socktype
|
||||
statfs
|
||||
statp
|
||||
statvfs
|
||||
strcmp
|
||||
strerror
|
||||
strlen
|
||||
syncfs
|
||||
umask
|
||||
waitpid
|
||||
|
@ -274,6 +282,13 @@ winerror
|
|||
winnt
|
||||
winsock
|
||||
|
||||
# * vars/selinux
|
||||
freecon
|
||||
getfilecon
|
||||
lgetfilecon
|
||||
lsetfilecon
|
||||
setfilecon
|
||||
|
||||
# * vars/uucore
|
||||
optflag
|
||||
optflagmulti
|
||||
|
|
128
Cargo.lock
generated
128
Cargo.lock
generated
|
@ -17,6 +17,12 @@ dependencies = [
|
|||
"memchr 2.4.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aliasable"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd"
|
||||
|
||||
[[package]]
|
||||
name = "ansi_term"
|
||||
version = "0.11.0"
|
||||
|
@ -308,7 +314,9 @@ dependencies = [
|
|||
"uu_base32",
|
||||
"uu_base64",
|
||||
"uu_basename",
|
||||
"uu_basenc",
|
||||
"uu_cat",
|
||||
"uu_chcon",
|
||||
"uu_chgrp",
|
||||
"uu_chmod",
|
||||
"uu_chown",
|
||||
|
@ -587,6 +595,16 @@ dependencies = [
|
|||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ctrlc"
|
||||
version = "3.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "232295399409a8b7ae41276757b5a1cc21032848d42bff2352261f958b3ca29a"
|
||||
dependencies = [
|
||||
"nix 0.20.0",
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "custom_derive"
|
||||
version = "0.1.7"
|
||||
|
@ -595,9 +613,29 @@ checksum = "ef8ae57c4978a2acd8b869ce6b9ca1dfe817bff704c220209fdef2c0b75a01b9"
|
|||
|
||||
[[package]]
|
||||
name = "data-encoding"
|
||||
version = "2.1.2"
|
||||
version = "2.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f4f47ca1860a761136924ddd2422ba77b2ea54fe8cc75b9040804a0d9d32ad97"
|
||||
checksum = "3ee2393c4a91429dffb4bedf19f4d6abf27d8a732c8ce4980305d782e5426d57"
|
||||
|
||||
[[package]]
|
||||
name = "data-encoding-macro"
|
||||
version = "0.1.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "86927b7cd2fe88fa698b87404b287ab98d1a0063a34071d92e575b72d3029aca"
|
||||
dependencies = [
|
||||
"data-encoding",
|
||||
"data-encoding-macro-internal",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "data-encoding-macro-internal"
|
||||
version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a5bbed42daaa95e780b60a50546aa345b8413a1e46f9a40a12907d3598f038db"
|
||||
dependencies = [
|
||||
"data-encoding",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "diff"
|
||||
|
@ -675,13 +713,13 @@ checksum = "31a7a908b8f32538a2143e59a6e4e2508988832d5d4d6f7c156b3cbc762643a5"
|
|||
|
||||
[[package]]
|
||||
name = "filetime"
|
||||
version = "0.2.14"
|
||||
version = "0.2.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d34cfa13a63ae058bfa601fe9e313bbdb3746427c1459185464ce0fcf62e1e8"
|
||||
checksum = "975ccf83d8d9d0d84682850a38c8169027be83368805971cc4f238c2b245bc98"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"libc",
|
||||
"redox_syscall 0.2.9",
|
||||
"redox_syscall",
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
|
@ -697,6 +735,16 @@ version = "1.2.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2022715d62ab30faffd124d40b76f4134a550a87792276512b18d63272333394"
|
||||
|
||||
[[package]]
|
||||
name = "fts-sys"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d31ec9f1580e270ee49a1fae7b102f54514142d9be2d4aa363c361363d65cac9"
|
||||
dependencies = [
|
||||
"bindgen",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fuchsia-cprng"
|
||||
version = "0.1.1"
|
||||
|
@ -1139,19 +1187,20 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "ouroboros"
|
||||
version = "0.9.5"
|
||||
version = "0.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fbeff60e3e37407a80ead3e9458145b456e978c4068cddbfea6afb48572962ca"
|
||||
checksum = "84236d64f1718c387232287cf036eb6632a5ecff226f4ff9dccb8c2b79ba0bde"
|
||||
dependencies = [
|
||||
"aliasable",
|
||||
"ouroboros_macro",
|
||||
"stable_deref_trait",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ouroboros_macro"
|
||||
version = "0.9.5"
|
||||
version = "0.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "03f2cb802b5bdfdf52f1ffa0b54ce105e4d346e91990dd571f86c91321ad49e2"
|
||||
checksum = "f463857a6eb96c0136b1d56e56c718350cef30412ec065b48294799a088bca68"
|
||||
dependencies = [
|
||||
"Inflector",
|
||||
"proc-macro-error",
|
||||
|
@ -1189,7 +1238,7 @@ dependencies = [
|
|||
"cfg-if 1.0.0",
|
||||
"instant",
|
||||
"libc",
|
||||
"redox_syscall 0.2.9",
|
||||
"redox_syscall",
|
||||
"smallvec 1.6.1",
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
@ -1483,15 +1532,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.1.57"
|
||||
version = "0.2.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce"
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.2.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5ab49abadf3f9e1c4bc499e8845e152ad87d2ad2d30371841171169e9d75feee"
|
||||
checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
]
|
||||
|
@ -1502,7 +1545,7 @@ version = "0.1.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8440d8acb4fd3d277125b4bd01a6f38aee8d814b3b5fc09b3f2b825d37d3fe8f"
|
||||
dependencies = [
|
||||
"redox_syscall 0.2.9",
|
||||
"redox_syscall",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1588,9 +1631,9 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
|
|||
|
||||
[[package]]
|
||||
name = "selinux"
|
||||
version = "0.1.3"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bd525eeb189eb26c8471463186bba87644e3d8a9c7ae392adaf9ec45ede574bc"
|
||||
checksum = "1aa2f705dd871c2eb90888bb2d44b13218b34f5c7318c3971df62f799d0143eb"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"libc",
|
||||
|
@ -1761,7 +1804,7 @@ dependencies = [
|
|||
"cfg-if 1.0.0",
|
||||
"libc",
|
||||
"rand 0.8.4",
|
||||
"redox_syscall 0.2.9",
|
||||
"redox_syscall",
|
||||
"remove_dir_all",
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
@ -1802,7 +1845,7 @@ checksum = "077185e2eac69c3f8379a4298e1e07cd36beb962290d4a51199acf0fdc10607e"
|
|||
dependencies = [
|
||||
"libc",
|
||||
"numtoa",
|
||||
"redox_syscall 0.2.9",
|
||||
"redox_syscall",
|
||||
"redox_termios",
|
||||
]
|
||||
|
||||
|
@ -1959,6 +2002,16 @@ dependencies = [
|
|||
"uucore_procs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "uu_basenc"
|
||||
version = "0.0.7"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"uu_base32",
|
||||
"uucore",
|
||||
"uucore_procs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "uu_cat"
|
||||
version = "0.0.7"
|
||||
|
@ -1970,6 +2023,20 @@ dependencies = [
|
|||
"unix_socket",
|
||||
"uucore",
|
||||
"uucore_procs",
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "uu_chcon"
|
||||
version = "0.0.7"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"fts-sys",
|
||||
"libc",
|
||||
"selinux",
|
||||
"thiserror",
|
||||
"uucore",
|
||||
"uucore_procs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -2427,7 +2494,7 @@ dependencies = [
|
|||
"clap",
|
||||
"crossterm",
|
||||
"nix 0.13.1",
|
||||
"redox_syscall 0.1.57",
|
||||
"redox_syscall",
|
||||
"redox_termios",
|
||||
"unicode-segmentation",
|
||||
"unicode-width",
|
||||
|
@ -2697,6 +2764,7 @@ dependencies = [
|
|||
"binary-heap-plus",
|
||||
"clap",
|
||||
"compare",
|
||||
"ctrlc",
|
||||
"fnv",
|
||||
"itertools 0.10.1",
|
||||
"memchr 2.4.0",
|
||||
|
@ -2785,7 +2853,7 @@ dependencies = [
|
|||
"clap",
|
||||
"libc",
|
||||
"nix 0.20.0",
|
||||
"redox_syscall 0.1.57",
|
||||
"redox_syscall",
|
||||
"uucore",
|
||||
"uucore_procs",
|
||||
"winapi 0.3.9",
|
||||
|
@ -2808,7 +2876,7 @@ version = "0.0.7"
|
|||
dependencies = [
|
||||
"clap",
|
||||
"libc",
|
||||
"redox_syscall 0.1.57",
|
||||
"redox_syscall",
|
||||
"uucore",
|
||||
"uucore_procs",
|
||||
]
|
||||
|
@ -2990,6 +3058,7 @@ version = "0.0.9"
|
|||
dependencies = [
|
||||
"clap",
|
||||
"data-encoding",
|
||||
"data-encoding-macro",
|
||||
"dns-lookup",
|
||||
"dunce",
|
||||
"getopts",
|
||||
|
@ -3002,6 +3071,7 @@ dependencies = [
|
|||
"time",
|
||||
"wild",
|
||||
"winapi 0.3.9",
|
||||
"z85",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -3129,3 +3199,9 @@ checksum = "244c3741f4240ef46274860397c7c74e50eb23624996930e484c16679633a54c"
|
|||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "z85"
|
||||
version = "3.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5ac8b56e4f9906a4ef5412875e9ce448364023335cec645fd457ecf51d4f2781"
|
||||
|
|
14
Cargo.toml
14
Cargo.toml
|
@ -35,6 +35,7 @@ feat_common_core = [
|
|||
"base32",
|
||||
"base64",
|
||||
"basename",
|
||||
"basenc",
|
||||
"cat",
|
||||
"cksum",
|
||||
"comm",
|
||||
|
@ -145,7 +146,7 @@ feat_os_unix_musl = [
|
|||
# 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 = ["id/selinux", "selinux"]
|
||||
feat_selinux = ["id/selinux", "selinux", "feat_require_selinux"]
|
||||
## 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
|
||||
|
@ -185,6 +186,10 @@ feat_require_unix_utmpx = [
|
|||
"users",
|
||||
"who",
|
||||
]
|
||||
# "feat_require_selinux" == set of utilities depending on SELinux.
|
||||
feat_require_selinux = [
|
||||
"chcon",
|
||||
]
|
||||
## (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 = [
|
||||
|
@ -212,9 +217,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 = [
|
||||
|
@ -237,7 +241,7 @@ 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" }
|
||||
selinux = { version="0.1.3", optional = true }
|
||||
selinux = { version="0.2.1", optional = true }
|
||||
# * uutils
|
||||
uu_test = { optional=true, version="0.0.7", package="uu_test", path="src/uu/test" }
|
||||
#
|
||||
|
@ -245,7 +249,9 @@ arch = { optional=true, version="0.0.7", package="uu_arch", path="src/uu/arc
|
|||
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" }
|
||||
basenc = { optional=true, version="0.0.7", package="uu_basenc", path="src/uu/basenc" }
|
||||
cat = { optional=true, version="0.0.7", package="uu_cat", path="src/uu/cat" }
|
||||
chcon = { optional=true, version="0.0.7", package="uu_chcon", path="src/uu/chcon" }
|
||||
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" }
|
||||
|
|
20
GNUmakefile
20
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,17 @@ UNIX_PROGS := \
|
|||
users \
|
||||
who
|
||||
|
||||
SELINUX_PROGS := \
|
||||
chcon
|
||||
|
||||
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 +175,7 @@ TEST_PROGS := \
|
|||
base64 \
|
||||
basename \
|
||||
cat \
|
||||
chcon \
|
||||
chgrp \
|
||||
chmod \
|
||||
chown \
|
||||
|
@ -228,6 +245,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
|
||||
|
|
36
README.md
36
README.md
|
@ -365,24 +365,26 @@ To contribute to uutils, please see [CONTRIBUTING](CONTRIBUTING.md).
|
|||
|
||||
| Done | Semi-Done | To Do |
|
||||
|-----------|-----------|--------|
|
||||
| arch | cp | chcon |
|
||||
| base32 | date | runcon |
|
||||
| base64 | dd | stty |
|
||||
| arch | cp | runcon |
|
||||
| base32 | date | stty |
|
||||
| base64 | dd | |
|
||||
| basename | df | |
|
||||
| cat | expr | |
|
||||
| chgrp | install | |
|
||||
| chmod | join | |
|
||||
| chown | ls | |
|
||||
| chroot | more | |
|
||||
| cksum | numfmt | |
|
||||
| comm | od (`--strings` and 128-bit data types missing) |
|
||||
| csplit | pr | |
|
||||
| cut | printf | |
|
||||
| dircolors | sort | |
|
||||
| dirname | split | |
|
||||
| du | tac | |
|
||||
| echo | tail | |
|
||||
| env | test | |
|
||||
| basenc | expr | |
|
||||
| cat | install | |
|
||||
| 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 | | |
|
||||
| false | | |
|
||||
|
|
|
@ -34,7 +34,7 @@ pub mod options {
|
|||
}
|
||||
|
||||
impl Config {
|
||||
fn from(app_name: &str, options: clap::ArgMatches) -> Result<Config, String> {
|
||||
pub fn from(app_name: &str, options: &clap::ArgMatches) -> Result<Config, String> {
|
||||
let file: Option<String> = match options.values_of(options::FILE) {
|
||||
Some(mut values) => {
|
||||
let name = values.next().unwrap();
|
||||
|
@ -85,7 +85,7 @@ pub fn parse_base_cmd_args(
|
|||
let arg_list = args
|
||||
.collect_str(InvalidEncodingHandling::ConvertLossy)
|
||||
.accept_any();
|
||||
Config::from(name, app.get_matches_from(arg_list))
|
||||
Config::from(name, &app.get_matches_from(arg_list))
|
||||
}
|
||||
|
||||
pub fn base_app<'a>(name: &str, version: &'a str, about: &'a str) -> App<'static, 'a> {
|
||||
|
@ -145,8 +145,18 @@ pub fn handle_input<R: Read>(
|
|||
}
|
||||
|
||||
if !decode {
|
||||
let encoded = data.encode();
|
||||
wrap_print(&data, encoded);
|
||||
match data.encode() {
|
||||
Ok(s) => {
|
||||
wrap_print(&data, s);
|
||||
}
|
||||
Err(_) => {
|
||||
eprintln!(
|
||||
"{}: error: invalid input (length must be multiple of 4 characters)",
|
||||
name
|
||||
);
|
||||
exit!(1)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
match data.decode() {
|
||||
Ok(s) => {
|
||||
|
|
25
src/uu/basenc/Cargo.toml
Normal file
25
src/uu/basenc/Cargo.toml
Normal file
|
@ -0,0 +1,25 @@
|
|||
[package]
|
||||
name = "uu_basenc"
|
||||
version = "0.0.7"
|
||||
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.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"}
|
||||
|
||||
[[bin]]
|
||||
name = "basenc"
|
||||
path = "src/main.rs"
|
95
src/uu/basenc/src/basenc.rs
Normal file
95
src/uu/basenc/src/basenc.rs
Normal file
|
@ -0,0 +1,95 @@
|
|||
// 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
|
||||
|
||||
#[macro_use]
|
||||
extern crate uucore;
|
||||
|
||||
use clap::{crate_version, App, Arg};
|
||||
use uu_base32::base_common::{self, Config};
|
||||
|
||||
use uucore::{encoding::Format, 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.
|
||||
";
|
||||
|
||||
static BASE_CMD_PARSE_ERROR: i32 = 1;
|
||||
|
||||
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 get_usage() -> String {
|
||||
format!("{0} [OPTION]... [FILE]", executable!())
|
||||
}
|
||||
|
||||
pub fn uu_app() -> App<'static, 'static> {
|
||||
let mut app = base_common::base_app(executable!(), crate_version!(), 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) -> (Config, Format) {
|
||||
let usage = get_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))
|
||||
.unwrap_or_else(|| {
|
||||
show_usage_error!("missing encoding type");
|
||||
std::process::exit(1)
|
||||
})
|
||||
.1;
|
||||
(
|
||||
Config::from("basenc", &matches).unwrap_or_else(|s| crash!(BASE_CMD_PARSE_ERROR, "{}", s)),
|
||||
format,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn uumain(args: impl uucore::Args) -> i32 {
|
||||
let name = executable!();
|
||||
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,
|
||||
name,
|
||||
);
|
||||
|
||||
0
|
||||
}
|
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);
|
|
@ -23,9 +23,10 @@ uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_p
|
|||
|
||||
[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"
|
||||
|
|
|
@ -20,12 +20,16 @@ use clap::{crate_version, App, Arg};
|
|||
use std::fs::{metadata, File};
|
||||
use std::io::{self, Read, Write};
|
||||
use thiserror::Error;
|
||||
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};
|
||||
use std::os::unix::io::RawFd;
|
||||
|
||||
/// Unix domain socket support
|
||||
#[cfg(unix)]
|
||||
|
@ -58,6 +62,8 @@ enum CatError {
|
|||
},
|
||||
#[error("Is a directory")]
|
||||
IsDirectory,
|
||||
#[error("input file is output file")]
|
||||
OutputIsInput,
|
||||
}
|
||||
|
||||
type CatResult<T> = Result<T, CatError>;
|
||||
|
@ -122,6 +128,12 @@ 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,
|
||||
}
|
||||
|
||||
/// Represents an open file handle, stream, or other device
|
||||
|
@ -164,7 +176,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,13 +230,7 @@ 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> {
|
||||
|
@ -301,7 +308,13 @@ 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 {
|
||||
|
@ -328,6 +341,10 @@ 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(),
|
||||
|
@ -339,23 +356,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, 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{}: ", executable!());
|
||||
|
||||
Err(uucore::error::USimpleError::new(
|
||||
error_messages.len() as i32,
|
||||
error_messages.join(&line_joiner),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -423,7 +469,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 +479,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 +499,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 +519,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;
|
||||
}
|
||||
// print suitable end of line
|
||||
writer.write_all(options.end_of_line().as_bytes())?;
|
||||
if handle.is_interactive {
|
||||
writer.flush()?;
|
||||
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;
|
||||
}
|
||||
state.at_line_start = true;
|
||||
pos += offset;
|
||||
pos += offset + 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -482,17 +542,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 +562,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();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -538,11 +605,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)]
|
||||
|
|
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.7"
|
||||
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"
|
750
src/uu/chcon/src/chcon.rs
Normal file
750
src/uu/chcon/src/chcon.rs
Normal file
|
@ -0,0 +1,750 @@
|
|||
// spell-checker:ignore (vars) RFILE
|
||||
|
||||
#![allow(clippy::upper_case_acronyms)]
|
||||
|
||||
use uucore::{executable, 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...",
|
||||
executable!()
|
||||
)
|
||||
}
|
||||
|
||||
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.to_string_lossy());
|
||||
return libc::EXIT_FAILURE;
|
||||
}
|
||||
};
|
||||
|
||||
if SecurityContext::from_c_str(&c_context, false).check() == Some(false) {
|
||||
show_error!("Invalid security context '{}'.", context.to_string_lossy());
|
||||
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(executable!())
|
||||
.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,
|
||||
dereference: 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, dereference.
|
||||
let dereference = !matches.is_present(options::dereference::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,
|
||||
dereference,
|
||||
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: {}",
|
||||
executable!(),
|
||||
file_full_name.to_string_lossy()
|
||||
);
|
||||
}
|
||||
|
||||
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.to_string_lossy(),
|
||||
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.display()
|
||||
)
|
||||
}
|
||||
|
||||
#[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)),
|
||||
}
|
||||
}
|
||||
}
|
71
src/uu/chcon/src/errors.rs
Normal file
71
src/uu/chcon/src/errors.rs
Normal file
|
@ -0,0 +1,71 @@
|
|||
use std::ffi::OsString;
|
||||
use std::fmt::Write;
|
||||
use std::io;
|
||||
|
||||
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.to_string_lossy())]
|
||||
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);
|
|
@ -161,12 +161,9 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
Verbosity::Normal
|
||||
};
|
||||
|
||||
let dest_gid: u32;
|
||||
if let Some(file) = matches.value_of(options::REFERENCE) {
|
||||
let dest_gid = if let Some(file) = matches.value_of(options::REFERENCE) {
|
||||
match fs::metadata(&file) {
|
||||
Ok(meta) => {
|
||||
dest_gid = meta.gid();
|
||||
}
|
||||
Ok(meta) => Some(meta.gid()),
|
||||
Err(e) => {
|
||||
show_error!("failed to get attributes of '{}': {}", file, e);
|
||||
return 1;
|
||||
|
@ -174,16 +171,18 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
}
|
||||
} else {
|
||||
let group = matches.value_of(options::ARG_GROUP).unwrap_or_default();
|
||||
match entries::grp2gid(group) {
|
||||
Ok(g) => {
|
||||
dest_gid = g;
|
||||
}
|
||||
_ => {
|
||||
show_error!("invalid group: {}", group);
|
||||
return 1;
|
||||
if group.is_empty() {
|
||||
None
|
||||
} else {
|
||||
match entries::grp2gid(group) {
|
||||
Ok(g) => Some(g),
|
||||
_ => {
|
||||
show_error!("invalid group: {}", group);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let executor = Chgrper {
|
||||
bit_flag,
|
||||
|
@ -278,7 +277,7 @@ pub fn uu_app() -> App<'static, 'static> {
|
|||
}
|
||||
|
||||
struct Chgrper {
|
||||
dest_gid: gid_t,
|
||||
dest_gid: Option<gid_t>,
|
||||
bit_flag: u8,
|
||||
verbosity: Verbosity,
|
||||
files: Vec<String>,
|
||||
|
@ -364,7 +363,9 @@ impl Chgrper {
|
|||
self.verbosity.clone(),
|
||||
) {
|
||||
Ok(n) => {
|
||||
show_error!("{}", n);
|
||||
if !n.is_empty() {
|
||||
show_error!("{}", n);
|
||||
}
|
||||
0
|
||||
}
|
||||
Err(e) => {
|
||||
|
|
|
@ -14,7 +14,7 @@ use uucore::fs::resolve_relative_path;
|
|||
use uucore::libc::{gid_t, uid_t};
|
||||
use uucore::perms::{wrap_chown, Verbosity};
|
||||
|
||||
use uucore::error::{FromIo, UError, UResult, USimpleError};
|
||||
use uucore::error::{FromIo, UResult, USimpleError};
|
||||
|
||||
use clap::{crate_version, App, Arg};
|
||||
|
||||
|
@ -107,10 +107,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
|||
if recursive {
|
||||
if bit_flag == FTS_PHYSICAL {
|
||||
if derefer == 1 {
|
||||
return Err(USimpleError::new(
|
||||
1,
|
||||
"-R --dereference requires -H or -L".to_string(),
|
||||
));
|
||||
return Err(USimpleError::new(1, "-R --dereference requires -H or -L"));
|
||||
}
|
||||
derefer = 0;
|
||||
}
|
||||
|
@ -324,7 +321,7 @@ impl Chowner {
|
|||
ret |= self.traverse(f);
|
||||
}
|
||||
if ret != 0 {
|
||||
return Err(UError::from(ret));
|
||||
return Err(ret.into());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
@ -485,10 +482,7 @@ mod test {
|
|||
|
||||
#[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!(format!("{}", parse_spec("::").err().unwrap()).starts_with("invalid group: "));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -15,7 +15,7 @@ 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};
|
||||
|
@ -67,10 +67,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 {
|
||||
|
@ -357,7 +359,13 @@ fn set_system_datetime(_date: DateTime<Utc>) -> i32 {
|
|||
1
|
||||
}
|
||||
|
||||
#[cfg(all(unix, not(target_os = "macos")))]
|
||||
#[cfg(target_os = "redox")]
|
||||
fn set_system_datetime(_date: DateTime<Utc>) -> i32 {
|
||||
eprintln!("date: 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
|
||||
|
|
|
@ -19,13 +19,15 @@ byte-unit = "4.0"
|
|||
clap = { version = "2.33", features = [ "wrap_help" ] }
|
||||
gcd = "2.0"
|
||||
libc = "0.2"
|
||||
signal-hook = "0.3.9"
|
||||
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"
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
#[macro_use]
|
||||
extern crate uucore;
|
||||
use uucore::error::UCustomError;
|
||||
use uucore::error::UError;
|
||||
use uucore::error::UResult;
|
||||
#[cfg(unix)]
|
||||
use uucore::fsext::statfs_fn;
|
||||
|
@ -274,7 +274,7 @@ impl Display for DfError {
|
|||
|
||||
impl Error for DfError {}
|
||||
|
||||
impl UCustomError for DfError {
|
||||
impl UError for DfError {
|
||||
fn code(&self) -> i32 {
|
||||
match self {
|
||||
DfError::InvalidBaseValue(_) => 1,
|
||||
|
|
|
@ -79,7 +79,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
|||
print!("{}", separator);
|
||||
}
|
||||
} else {
|
||||
return Err(UUsageError::new(1, "missing operand".to_string()));
|
||||
return Err(UUsageError::new(1, "missing operand"));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
|
|
@ -32,7 +32,7 @@ use std::path::PathBuf;
|
|||
use std::str::FromStr;
|
||||
use std::time::{Duration, UNIX_EPOCH};
|
||||
use std::{error::Error, fmt::Display};
|
||||
use uucore::error::{UCustomError, UResult};
|
||||
use uucore::error::{UError, UResult};
|
||||
use uucore::parse_size::{parse_size, ParseSizeError};
|
||||
use uucore::InvalidEncodingHandling;
|
||||
#[cfg(windows)]
|
||||
|
@ -438,7 +438,7 @@ Try '{} --help' for more information.",
|
|||
|
||||
impl Error for DuError {}
|
||||
|
||||
impl UCustomError for DuError {
|
||||
impl UError for DuError {
|
||||
fn code(&self) -> i32 {
|
||||
match self {
|
||||
Self::InvalidMaxDepthArg(_) => 1,
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
extern crate uucore;
|
||||
|
||||
use std::error::Error;
|
||||
use std::fmt::Write as FmtWrite;
|
||||
use std::io::{self, stdin, stdout, BufRead, Write};
|
||||
|
||||
mod factor;
|
||||
|
@ -28,21 +29,29 @@ mod options {
|
|||
pub static NUMBER: &str = "NUMBER";
|
||||
}
|
||||
|
||||
fn print_factors_str(num_str: &str, w: &mut impl io::Write) -> Result<(), Box<dyn Error>> {
|
||||
num_str
|
||||
.parse::<u64>()
|
||||
.map_err(|e| e.into())
|
||||
.and_then(|x| writeln!(w, "{}:{}", x, factor(x)).map_err(|e| e.into()))
|
||||
fn print_factors_str(
|
||||
num_str: &str,
|
||||
w: &mut io::BufWriter<impl io::Write>,
|
||||
factors_buffer: &mut String,
|
||||
) -> Result<(), Box<dyn Error>> {
|
||||
num_str.parse::<u64>().map_err(|e| e.into()).and_then(|x| {
|
||||
factors_buffer.clear();
|
||||
writeln!(factors_buffer, "{}:{}", x, factor(x))?;
|
||||
w.write_all(factors_buffer.as_bytes())?;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
pub fn uumain(args: impl uucore::Args) -> i32 {
|
||||
let matches = uu_app().get_matches_from(args);
|
||||
let stdout = stdout();
|
||||
let mut w = io::BufWriter::new(stdout.lock());
|
||||
// We use a smaller buffer here to pass a gnu test. 4KiB appears to be the default pipe size for bash.
|
||||
let mut w = io::BufWriter::with_capacity(4 * 1024, stdout.lock());
|
||||
let mut factors_buffer = String::new();
|
||||
|
||||
if let Some(values) = matches.values_of(options::NUMBER) {
|
||||
for number in values {
|
||||
if let Err(e) = print_factors_str(number, &mut w) {
|
||||
if let Err(e) = print_factors_str(number, &mut w, &mut factors_buffer) {
|
||||
show_warning!("{}: {}", number, e);
|
||||
}
|
||||
}
|
||||
|
@ -51,7 +60,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
|
||||
for line in stdin.lock().lines() {
|
||||
for number in line.unwrap().split_whitespace() {
|
||||
if let Err(e) = print_factors_str(number, &mut w) {
|
||||
if let Err(e) = print_factors_str(number, &mut w, &mut factors_buffer) {
|
||||
show_warning!("{}: {}", number, e);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,13 +9,12 @@
|
|||
extern crate uucore;
|
||||
|
||||
use clap::App;
|
||||
use uucore::error::{UError, UResult};
|
||||
use uucore::executable;
|
||||
use uucore::{error::UResult, executable};
|
||||
|
||||
#[uucore_procs::gen_uumain]
|
||||
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||
uu_app().get_matches_from(args);
|
||||
Err(UError::from(1))
|
||||
Err(1.into())
|
||||
}
|
||||
|
||||
pub fn uu_app() -> App<'static, 'static> {
|
||||
|
|
|
@ -18,7 +18,7 @@ path = "src/id.rs"
|
|||
clap = { version = "2.33", features = ["wrap_help"] }
|
||||
uucore = { version=">=0.0.9", package="uucore", path="../../uucore", features=["entries", "process"] }
|
||||
uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_procs" }
|
||||
selinux = { version="0.1.3", optional = true }
|
||||
selinux = { version="0.2.1", optional = true }
|
||||
|
||||
[[bin]]
|
||||
name = "id"
|
||||
|
|
|
@ -40,10 +40,10 @@
|
|||
extern crate uucore;
|
||||
|
||||
use clap::{crate_version, App, Arg};
|
||||
#[cfg(all(target_os = "linux", feature = "selinux"))]
|
||||
use selinux;
|
||||
use std::ffi::CStr;
|
||||
use uucore::entries::{self, Group, Locate, Passwd};
|
||||
use uucore::error::UResult;
|
||||
use uucore::error::{set_exit_code, USimpleError};
|
||||
pub use uucore::libc;
|
||||
use uucore::libc::{getlogin, uid_t};
|
||||
use uucore::process::{getegid, geteuid, getgid, getuid};
|
||||
|
@ -123,10 +123,10 @@ struct State {
|
|||
// 1000 10 968 975
|
||||
// +++ exited with 0 +++
|
||||
user_specified: bool,
|
||||
exit_code: i32,
|
||||
}
|
||||
|
||||
pub fn uumain(args: impl uucore::Args) -> i32 {
|
||||
#[uucore_procs::gen_uumain]
|
||||
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||
let usage = get_usage();
|
||||
let after_help = get_description();
|
||||
|
||||
|
@ -161,7 +161,6 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
},
|
||||
user_specified: !users.is_empty(),
|
||||
ids: None,
|
||||
exit_code: 0,
|
||||
};
|
||||
|
||||
let default_format = {
|
||||
|
@ -170,14 +169,23 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
};
|
||||
|
||||
if (state.nflag || state.rflag) && default_format && !state.cflag {
|
||||
crash!(1, "cannot print only names or real IDs in default format");
|
||||
return Err(USimpleError::new(
|
||||
1,
|
||||
"cannot print only names or real IDs in default format",
|
||||
));
|
||||
}
|
||||
if state.zflag && default_format && !state.cflag {
|
||||
// NOTE: GNU test suite "id/zero.sh" needs this stderr output:
|
||||
crash!(1, "option --zero not permitted in default format");
|
||||
return Err(USimpleError::new(
|
||||
1,
|
||||
"option --zero not permitted in default format",
|
||||
));
|
||||
}
|
||||
if state.user_specified && state.cflag {
|
||||
crash!(1, "cannot print security context when user specified");
|
||||
return Err(USimpleError::new(
|
||||
1,
|
||||
"cannot print security context when user specified",
|
||||
));
|
||||
}
|
||||
|
||||
let delimiter = {
|
||||
|
@ -204,11 +212,14 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
print!("{}{}", String::from_utf8_lossy(bytes), line_ending);
|
||||
} else {
|
||||
// print error because `cflag` was explicitly requested
|
||||
crash!(1, "can't get process context");
|
||||
return Err(USimpleError::new(1, "can't get process context"));
|
||||
}
|
||||
return state.exit_code;
|
||||
return Ok(());
|
||||
} else {
|
||||
crash!(1, "--context (-Z) works only on an SELinux-enabled kernel");
|
||||
return Err(USimpleError::new(
|
||||
1,
|
||||
"--context (-Z) works only on an SELinux-enabled kernel",
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -220,7 +231,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
Ok(p) => Some(p),
|
||||
Err(_) => {
|
||||
show_error!("'{}': no such user", users[i]);
|
||||
state.exit_code = 1;
|
||||
set_exit_code(1);
|
||||
if i + 1 >= users.len() {
|
||||
break;
|
||||
} else {
|
||||
|
@ -234,17 +245,17 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
if matches.is_present(options::OPT_PASSWORD) {
|
||||
// BSD's `id` ignores all but the first specified user
|
||||
pline(possible_pw.map(|v| v.uid()));
|
||||
return state.exit_code;
|
||||
return Ok(());
|
||||
};
|
||||
if matches.is_present(options::OPT_HUMAN_READABLE) {
|
||||
// BSD's `id` ignores all but the first specified user
|
||||
pretty(possible_pw);
|
||||
return state.exit_code;
|
||||
return Ok(());
|
||||
}
|
||||
if matches.is_present(options::OPT_AUDIT) {
|
||||
// BSD's `id` ignores specified users
|
||||
auditid();
|
||||
return state.exit_code;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let (uid, gid) = possible_pw.map(|p| (p.uid(), p.gid())).unwrap_or((
|
||||
|
@ -264,7 +275,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
if state.nflag {
|
||||
entries::gid2grp(gid).unwrap_or_else(|_| {
|
||||
show_error!("cannot find name for group ID {}", gid);
|
||||
state.exit_code = 1;
|
||||
set_exit_code(1);
|
||||
gid.to_string()
|
||||
})
|
||||
} else {
|
||||
|
@ -279,7 +290,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
if state.nflag {
|
||||
entries::uid2usr(uid).unwrap_or_else(|_| {
|
||||
show_error!("cannot find name for user ID {}", uid);
|
||||
state.exit_code = 1;
|
||||
set_exit_code(1);
|
||||
uid.to_string()
|
||||
})
|
||||
} else {
|
||||
|
@ -304,7 +315,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
if state.nflag {
|
||||
entries::gid2grp(id).unwrap_or_else(|_| {
|
||||
show_error!("cannot find name for group ID {}", id);
|
||||
state.exit_code = 1;
|
||||
set_exit_code(1);
|
||||
id.to_string()
|
||||
})
|
||||
} else {
|
||||
|
@ -332,7 +343,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
}
|
||||
}
|
||||
|
||||
state.exit_code
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn uu_app() -> App<'static, 'static> {
|
||||
|
@ -560,7 +571,7 @@ fn id_print(state: &mut State, groups: Vec<u32>) {
|
|||
uid,
|
||||
entries::uid2usr(uid).unwrap_or_else(|_| {
|
||||
show_error!("cannot find name for user ID {}", uid);
|
||||
state.exit_code = 1;
|
||||
set_exit_code(1);
|
||||
uid.to_string()
|
||||
})
|
||||
);
|
||||
|
@ -569,7 +580,7 @@ fn id_print(state: &mut State, groups: Vec<u32>) {
|
|||
gid,
|
||||
entries::gid2grp(gid).unwrap_or_else(|_| {
|
||||
show_error!("cannot find name for group ID {}", gid);
|
||||
state.exit_code = 1;
|
||||
set_exit_code(1);
|
||||
gid.to_string()
|
||||
})
|
||||
);
|
||||
|
@ -579,7 +590,7 @@ fn id_print(state: &mut State, groups: Vec<u32>) {
|
|||
euid,
|
||||
entries::uid2usr(euid).unwrap_or_else(|_| {
|
||||
show_error!("cannot find name for user ID {}", euid);
|
||||
state.exit_code = 1;
|
||||
set_exit_code(1);
|
||||
euid.to_string()
|
||||
})
|
||||
);
|
||||
|
@ -590,7 +601,7 @@ fn id_print(state: &mut State, groups: Vec<u32>) {
|
|||
euid,
|
||||
entries::gid2grp(egid).unwrap_or_else(|_| {
|
||||
show_error!("cannot find name for group ID {}", egid);
|
||||
state.exit_code = 1;
|
||||
set_exit_code(1);
|
||||
egid.to_string()
|
||||
})
|
||||
);
|
||||
|
@ -604,7 +615,7 @@ fn id_print(state: &mut State, groups: Vec<u32>) {
|
|||
gr,
|
||||
entries::gid2grp(gr).unwrap_or_else(|_| {
|
||||
show_error!("cannot find name for group ID {}", gr);
|
||||
state.exit_code = 1;
|
||||
set_exit_code(1);
|
||||
gr.to_string()
|
||||
})
|
||||
))
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
// * For the full copyright and license information, please view the LICENSE file
|
||||
// * that was distributed with this source code.
|
||||
|
||||
// spell-checker:ignore (ToDO) rwxr sourcepath targetpath
|
||||
// spell-checker:ignore (ToDO) rwxr sourcepath targetpath Isnt uioerror
|
||||
|
||||
mod mode;
|
||||
|
||||
|
@ -17,15 +17,17 @@ use file_diff::diff;
|
|||
use filetime::{set_file_times, FileTime};
|
||||
use uucore::backup_control::{self, BackupMode};
|
||||
use uucore::entries::{grp2gid, usr2uid};
|
||||
use uucore::error::{FromIo, UError, UIoError, UResult, USimpleError};
|
||||
use uucore::perms::{wrap_chgrp, wrap_chown, Verbosity};
|
||||
|
||||
use libc::{getegid, geteuid};
|
||||
use std::error::Error;
|
||||
use std::fmt::{Debug, Display};
|
||||
use std::fs;
|
||||
use std::fs::File;
|
||||
use std::os::unix::fs::MetadataExt;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::Command;
|
||||
use std::result::Result;
|
||||
|
||||
const DEFAULT_MODE: u32 = 0o755;
|
||||
const DEFAULT_STRIP_PROGRAM: &str = "strip";
|
||||
|
@ -47,6 +49,87 @@ pub struct Behavior {
|
|||
target_dir: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum InstallError {
|
||||
Unimplemented(String),
|
||||
DirNeedsArg(),
|
||||
CreateDirFailed(PathBuf, std::io::Error),
|
||||
ChmodFailed(PathBuf),
|
||||
InvalidTarget(PathBuf),
|
||||
TargetDirIsntDir(PathBuf),
|
||||
BackupFailed(PathBuf, PathBuf, std::io::Error),
|
||||
InstallFailed(PathBuf, PathBuf, std::io::Error),
|
||||
StripProgramFailed(String),
|
||||
MetadataFailed(std::io::Error),
|
||||
NoSuchUser(String),
|
||||
NoSuchGroup(String),
|
||||
OmittingDirectory(PathBuf),
|
||||
}
|
||||
|
||||
impl UError for InstallError {
|
||||
fn code(&self) -> i32 {
|
||||
match self {
|
||||
InstallError::Unimplemented(_) => 2,
|
||||
_ => 1,
|
||||
}
|
||||
}
|
||||
|
||||
fn usage(&self) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for InstallError {}
|
||||
|
||||
impl Display for InstallError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
use InstallError as IE;
|
||||
match self {
|
||||
IE::Unimplemented(opt) => write!(f, "Unimplemented feature: {}", opt),
|
||||
IE::DirNeedsArg() => write!(
|
||||
f,
|
||||
"{} with -d requires at least one argument.",
|
||||
executable!()
|
||||
),
|
||||
IE::CreateDirFailed(dir, e) => {
|
||||
Display::fmt(&uio_error!(e, "failed to create {}", dir.display()), f)
|
||||
}
|
||||
IE::ChmodFailed(file) => write!(f, "failed to chmod {}", file.display()),
|
||||
IE::InvalidTarget(target) => write!(
|
||||
f,
|
||||
"invalid target {}: No such file or directory",
|
||||
target.display()
|
||||
),
|
||||
IE::TargetDirIsntDir(target) => {
|
||||
write!(f, "target '{}' is not a directory", target.display())
|
||||
}
|
||||
IE::BackupFailed(from, to, e) => Display::fmt(
|
||||
&uio_error!(
|
||||
e,
|
||||
"cannot backup '{}' to '{}'",
|
||||
from.display(),
|
||||
to.display()
|
||||
),
|
||||
f,
|
||||
),
|
||||
IE::InstallFailed(from, to, e) => Display::fmt(
|
||||
&uio_error!(
|
||||
e,
|
||||
"cannot install '{}' to '{}'",
|
||||
from.display(),
|
||||
to.display()
|
||||
),
|
||||
f,
|
||||
),
|
||||
IE::StripProgramFailed(msg) => write!(f, "strip program failed: {}", msg),
|
||||
IE::MetadataFailed(e) => Display::fmt(&uio_error!(e, ""), f),
|
||||
IE::NoSuchUser(user) => write!(f, "no such user: {}", user),
|
||||
IE::NoSuchGroup(group) => write!(f, "no such group: {}", group),
|
||||
IE::OmittingDirectory(dir) => write!(f, "omitting directory '{}'", dir.display()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Eq, PartialEq)]
|
||||
pub enum MainFunction {
|
||||
/// Create directories
|
||||
|
@ -97,7 +180,8 @@ fn get_usage() -> String {
|
|||
///
|
||||
/// Returns a program return code.
|
||||
///
|
||||
pub fn uumain(args: impl uucore::Args) -> i32 {
|
||||
#[uucore_procs::gen_uumain]
|
||||
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||
let usage = get_usage();
|
||||
|
||||
let matches = uu_app().usage(&usage[..]).get_matches_from(args);
|
||||
|
@ -107,17 +191,9 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
.map(|v| v.map(ToString::to_string).collect())
|
||||
.unwrap_or_default();
|
||||
|
||||
if let Err(s) = check_unimplemented(&matches) {
|
||||
show_error!("Unimplemented feature: {}", s);
|
||||
return 2;
|
||||
}
|
||||
check_unimplemented(&matches)?;
|
||||
|
||||
let behavior = match behavior(&matches) {
|
||||
Ok(x) => x,
|
||||
Err(ret) => {
|
||||
return ret;
|
||||
}
|
||||
};
|
||||
let behavior = behavior(&matches)?;
|
||||
|
||||
match behavior.main_function {
|
||||
MainFunction::Directory => directory(paths, behavior),
|
||||
|
@ -269,13 +345,13 @@ pub fn uu_app() -> App<'static, 'static> {
|
|||
/// Error datum is a string of the unimplemented argument.
|
||||
///
|
||||
///
|
||||
fn check_unimplemented<'a>(matches: &ArgMatches) -> Result<(), &'a str> {
|
||||
fn check_unimplemented(matches: &ArgMatches) -> UResult<()> {
|
||||
if matches.is_present(OPT_NO_TARGET_DIRECTORY) {
|
||||
Err("--no-target-directory, -T")
|
||||
Err(InstallError::Unimplemented(String::from("--no-target-directory, -T")).into())
|
||||
} else if matches.is_present(OPT_PRESERVE_CONTEXT) {
|
||||
Err("--preserve-context, -P")
|
||||
Err(InstallError::Unimplemented(String::from("--preserve-context, -P")).into())
|
||||
} else if matches.is_present(OPT_CONTEXT) {
|
||||
Err("--context, -Z")
|
||||
Err(InstallError::Unimplemented(String::from("--context, -Z")).into())
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
|
@ -289,7 +365,7 @@ fn check_unimplemented<'a>(matches: &ArgMatches) -> Result<(), &'a str> {
|
|||
///
|
||||
/// In event of failure, returns an integer intended as a program return code.
|
||||
///
|
||||
fn behavior(matches: &ArgMatches) -> Result<Behavior, i32> {
|
||||
fn behavior(matches: &ArgMatches) -> UResult<Behavior> {
|
||||
let main_function = if matches.is_present(OPT_DIRECTORY) {
|
||||
MainFunction::Directory
|
||||
} else {
|
||||
|
@ -314,10 +390,7 @@ fn behavior(matches: &ArgMatches) -> Result<Behavior, i32> {
|
|||
matches.value_of(OPT_BACKUP),
|
||||
);
|
||||
let backup_mode = match backup_mode {
|
||||
Err(err) => {
|
||||
show_usage_error!("{}", err);
|
||||
return Err(1);
|
||||
}
|
||||
Err(err) => return Err(USimpleError::new(1, err)),
|
||||
Ok(mode) => mode,
|
||||
};
|
||||
|
||||
|
@ -349,45 +422,46 @@ fn behavior(matches: &ArgMatches) -> Result<Behavior, i32> {
|
|||
/// GNU man pages describe this functionality as creating 'all components of
|
||||
/// the specified directories'.
|
||||
///
|
||||
/// Returns an integer intended as a program return code.
|
||||
/// Returns a Result type with the Err variant containing the error message.
|
||||
///
|
||||
fn directory(paths: Vec<String>, b: Behavior) -> i32 {
|
||||
fn directory(paths: Vec<String>, b: Behavior) -> UResult<()> {
|
||||
if paths.is_empty() {
|
||||
println!("{} with -d requires at least one argument.", executable!());
|
||||
1
|
||||
Err(InstallError::DirNeedsArg().into())
|
||||
} else {
|
||||
let mut all_successful = true;
|
||||
|
||||
for path in paths.iter().map(Path::new) {
|
||||
// if the path already exist, don't try to create it again
|
||||
if !path.exists() {
|
||||
// Differently than the primary functionality (MainFunction::Standard), the directory
|
||||
// functionality should create all ancestors (or components) of a directory regardless
|
||||
// of the presence of the "-D" flag.
|
||||
// NOTE: the GNU "install" sets the expected mode only for the target directory. All
|
||||
// created ancestor directories will have the default mode. Hence it is safe to use
|
||||
// fs::create_dir_all and then only modify the target's dir mode.
|
||||
if let Err(e) = fs::create_dir_all(path) {
|
||||
show_error!("{}: {}", path.display(), e);
|
||||
all_successful = false;
|
||||
// Differently than the primary functionality
|
||||
// (MainFunction::Standard), the directory functionality should
|
||||
// create all ancestors (or components) of a directory
|
||||
// regardless of the presence of the "-D" flag.
|
||||
//
|
||||
// NOTE: the GNU "install" sets the expected mode only for the
|
||||
// target directory. All created ancestor directories will have
|
||||
// the default mode. Hence it is safe to use fs::create_dir_all
|
||||
// and then only modify the target's dir mode.
|
||||
if let Err(e) =
|
||||
fs::create_dir_all(path).map_err_context(|| format!("{}", path.display()))
|
||||
{
|
||||
show!(e);
|
||||
continue;
|
||||
}
|
||||
|
||||
if b.verbose {
|
||||
show_error!("creating directory '{}'", path.display());
|
||||
println!("creating directory '{}'", path.display());
|
||||
}
|
||||
}
|
||||
|
||||
if mode::chmod(path, b.mode()).is_err() {
|
||||
all_successful = false;
|
||||
// Error messages are printed by the mode::chmod function!
|
||||
uucore::error::set_exit_code(1);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if all_successful {
|
||||
0
|
||||
} else {
|
||||
1
|
||||
}
|
||||
// If the exit code was set, or show! has been called at least once
|
||||
// (which sets the exit code as well), function execution will end after
|
||||
// this return.
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -401,9 +475,9 @@ fn is_new_file_path(path: &Path) -> bool {
|
|||
|
||||
/// Perform an install, given a list of paths and behavior.
|
||||
///
|
||||
/// Returns an integer intended as a program return code.
|
||||
/// Returns a Result type with the Err variant containing the error message.
|
||||
///
|
||||
fn standard(mut paths: Vec<String>, b: Behavior) -> i32 {
|
||||
fn standard(mut paths: Vec<String>, b: Behavior) -> UResult<()> {
|
||||
let target: PathBuf = b
|
||||
.target_dir
|
||||
.clone()
|
||||
|
@ -418,25 +492,19 @@ fn standard(mut paths: Vec<String>, b: Behavior) -> i32 {
|
|||
if let Some(parent) = target.parent() {
|
||||
if !parent.exists() && b.create_leading {
|
||||
if let Err(e) = fs::create_dir_all(parent) {
|
||||
show_error!("failed to create {}: {}", parent.display(), e);
|
||||
return 1;
|
||||
return Err(InstallError::CreateDirFailed(parent.to_path_buf(), e).into());
|
||||
}
|
||||
|
||||
if mode::chmod(parent, b.mode()).is_err() {
|
||||
show_error!("failed to chmod {}", parent.display());
|
||||
return 1;
|
||||
return Err(InstallError::ChmodFailed(parent.to_path_buf()).into());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if target.is_file() || is_new_file_path(&target) {
|
||||
copy_file_to_file(&sources[0], &target, &b)
|
||||
copy(&sources[0], &target, &b)
|
||||
} else {
|
||||
show_error!(
|
||||
"invalid target {}: No such file or directory",
|
||||
target.display()
|
||||
);
|
||||
1
|
||||
Err(InstallError::InvalidTarget(target).into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -444,34 +512,30 @@ fn standard(mut paths: Vec<String>, b: Behavior) -> i32 {
|
|||
/// Copy some files into a directory.
|
||||
///
|
||||
/// Prints verbose information and error messages.
|
||||
/// Returns an integer intended as a program return code.
|
||||
/// Returns a Result type with the Err variant containing the error message.
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// _files_ must all exist as non-directories.
|
||||
/// _target_dir_ must be a directory.
|
||||
///
|
||||
fn copy_files_into_dir(files: &[PathBuf], target_dir: &Path, b: &Behavior) -> i32 {
|
||||
fn copy_files_into_dir(files: &[PathBuf], target_dir: &Path, b: &Behavior) -> UResult<()> {
|
||||
if !target_dir.is_dir() {
|
||||
show_error!("target '{}' is not a directory", target_dir.display());
|
||||
return 1;
|
||||
return Err(InstallError::TargetDirIsntDir(target_dir.to_path_buf()).into());
|
||||
}
|
||||
|
||||
let mut all_successful = true;
|
||||
for sourcepath in files.iter() {
|
||||
if !sourcepath.exists() {
|
||||
show_error!(
|
||||
"cannot stat '{}': No such file or directory",
|
||||
sourcepath.display()
|
||||
let err = UIoError::new(
|
||||
std::io::ErrorKind::NotFound,
|
||||
format!("cannot stat '{}'", sourcepath.display()),
|
||||
);
|
||||
|
||||
all_successful = false;
|
||||
show!(err);
|
||||
continue;
|
||||
}
|
||||
|
||||
if sourcepath.is_dir() {
|
||||
show_error!("omitting directory '{}'", sourcepath.display());
|
||||
all_successful = false;
|
||||
let err = InstallError::OmittingDirectory(sourcepath.to_path_buf());
|
||||
show!(err);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -479,37 +543,18 @@ fn copy_files_into_dir(files: &[PathBuf], target_dir: &Path, b: &Behavior) -> i3
|
|||
let filename = sourcepath.components().last().unwrap();
|
||||
targetpath.push(filename);
|
||||
|
||||
if copy(sourcepath, &targetpath, b).is_err() {
|
||||
all_successful = false;
|
||||
}
|
||||
}
|
||||
if all_successful {
|
||||
0
|
||||
} else {
|
||||
1
|
||||
}
|
||||
}
|
||||
|
||||
/// Copy a file to another file.
|
||||
///
|
||||
/// Prints verbose information and error messages.
|
||||
/// Returns an integer intended as a program return code.
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// _file_ must exist as a non-directory.
|
||||
/// _target_ must be a non-directory
|
||||
///
|
||||
fn copy_file_to_file(file: &Path, target: &Path, b: &Behavior) -> i32 {
|
||||
if copy(file, target, b).is_err() {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
show_if_err!(copy(sourcepath, &targetpath, b));
|
||||
}
|
||||
// If the exit code was set, or show! has been called at least once
|
||||
// (which sets the exit code as well), function execution will end after
|
||||
// this return.
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Copy one file to a new location, changing metadata.
|
||||
///
|
||||
/// Returns a Result type with the Err variant containing the error message.
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// _from_ must exist as a non-directory.
|
||||
|
@ -520,8 +565,8 @@ fn copy_file_to_file(file: &Path, target: &Path, b: &Behavior) -> i32 {
|
|||
/// If the copy system call fails, we print a verbose error and return an empty error value.
|
||||
///
|
||||
#[allow(clippy::cognitive_complexity)]
|
||||
fn copy(from: &Path, to: &Path, b: &Behavior) -> Result<(), ()> {
|
||||
if b.compare && !need_copy(from, to, b) {
|
||||
fn copy(from: &Path, to: &Path, b: &Behavior) -> UResult<()> {
|
||||
if b.compare && !need_copy(from, to, b)? {
|
||||
return Ok(());
|
||||
}
|
||||
// Declare the path here as we may need it for the verbose output below.
|
||||
|
@ -536,13 +581,12 @@ fn copy(from: &Path, to: &Path, b: &Behavior) -> Result<(), ()> {
|
|||
if let Some(ref backup_path) = backup_path {
|
||||
// TODO!!
|
||||
if let Err(err) = fs::rename(to, backup_path) {
|
||||
show_error!(
|
||||
"install: cannot backup file '{}' to '{}': {}",
|
||||
to.display(),
|
||||
backup_path.display(),
|
||||
err
|
||||
);
|
||||
return Err(());
|
||||
return Err(InstallError::BackupFailed(
|
||||
to.to_path_buf(),
|
||||
backup_path.to_path_buf(),
|
||||
err,
|
||||
)
|
||||
.into());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -552,52 +596,41 @@ fn copy(from: &Path, to: &Path, b: &Behavior) -> Result<(), ()> {
|
|||
* https://github.com/rust-lang/rust/issues/79390
|
||||
*/
|
||||
if let Err(err) = File::create(to) {
|
||||
show_error!(
|
||||
"install: cannot install '{}' to '{}': {}",
|
||||
from.display(),
|
||||
to.display(),
|
||||
err
|
||||
return Err(
|
||||
InstallError::InstallFailed(from.to_path_buf(), to.to_path_buf(), err).into(),
|
||||
);
|
||||
return Err(());
|
||||
}
|
||||
} else if let Err(err) = fs::copy(from, to) {
|
||||
show_error!(
|
||||
"cannot install '{}' to '{}': {}",
|
||||
from.display(),
|
||||
to.display(),
|
||||
err
|
||||
);
|
||||
return Err(());
|
||||
return Err(InstallError::InstallFailed(from.to_path_buf(), to.to_path_buf(), err).into());
|
||||
}
|
||||
|
||||
if b.strip && cfg!(not(windows)) {
|
||||
match Command::new(&b.strip_program).arg(to).output() {
|
||||
Ok(o) => {
|
||||
if !o.status.success() {
|
||||
crash!(
|
||||
1,
|
||||
"strip program failed: {}",
|
||||
String::from_utf8(o.stderr).unwrap_or_default()
|
||||
);
|
||||
return Err(InstallError::StripProgramFailed(
|
||||
String::from_utf8(o.stderr).unwrap_or_default(),
|
||||
)
|
||||
.into());
|
||||
}
|
||||
}
|
||||
Err(e) => crash!(1, "strip program execution failed: {}", e),
|
||||
Err(e) => return Err(InstallError::StripProgramFailed(e.to_string()).into()),
|
||||
}
|
||||
}
|
||||
|
||||
if mode::chmod(to, b.mode()).is_err() {
|
||||
return Err(());
|
||||
return Err(InstallError::ChmodFailed(to.to_path_buf()).into());
|
||||
}
|
||||
|
||||
if !b.owner.is_empty() {
|
||||
let meta = match fs::metadata(to) {
|
||||
Ok(meta) => meta,
|
||||
Err(f) => crash!(1, "{}", f.to_string()),
|
||||
Err(e) => return Err(InstallError::MetadataFailed(e).into()),
|
||||
};
|
||||
|
||||
let owner_id = match usr2uid(&b.owner) {
|
||||
Ok(g) => g,
|
||||
_ => crash!(1, "no such user: {}", b.owner),
|
||||
_ => return Err(InstallError::NoSuchUser(b.owner.clone()).into()),
|
||||
};
|
||||
let gid = meta.gid();
|
||||
match wrap_chown(
|
||||
|
@ -620,14 +653,14 @@ fn copy(from: &Path, to: &Path, b: &Behavior) -> Result<(), ()> {
|
|||
if !b.group.is_empty() {
|
||||
let meta = match fs::metadata(to) {
|
||||
Ok(meta) => meta,
|
||||
Err(f) => crash!(1, "{}", f.to_string()),
|
||||
Err(e) => return Err(InstallError::MetadataFailed(e).into()),
|
||||
};
|
||||
|
||||
let group_id = match grp2gid(&b.group) {
|
||||
Ok(g) => g,
|
||||
_ => crash!(1, "no such group: {}", b.group),
|
||||
_ => return Err(InstallError::NoSuchGroup(b.group.clone()).into()),
|
||||
};
|
||||
match wrap_chgrp(to, &meta, group_id, false, Verbosity::Normal) {
|
||||
match wrap_chgrp(to, &meta, Some(group_id), false, Verbosity::Normal) {
|
||||
Ok(n) => {
|
||||
if !n.is_empty() {
|
||||
show_error!("{}", n);
|
||||
|
@ -640,7 +673,7 @@ fn copy(from: &Path, to: &Path, b: &Behavior) -> Result<(), ()> {
|
|||
if b.preserve_timestamps {
|
||||
let meta = match fs::metadata(from) {
|
||||
Ok(meta) => meta,
|
||||
Err(f) => crash!(1, "{}", f.to_string()),
|
||||
Err(e) => return Err(InstallError::MetadataFailed(e).into()),
|
||||
};
|
||||
|
||||
let modified_time = FileTime::from_last_modification_time(&meta);
|
||||
|
@ -664,6 +697,7 @@ fn copy(from: &Path, to: &Path, b: &Behavior) -> Result<(), ()> {
|
|||
}
|
||||
|
||||
/// Return true if a file is necessary to copy. This is the case when:
|
||||
///
|
||||
/// - _from_ or _to_ is nonexistent;
|
||||
/// - either file has a sticky bit or set[ug]id bit, or the user specified one;
|
||||
/// - either file isn't a regular file;
|
||||
|
@ -679,14 +713,14 @@ fn copy(from: &Path, to: &Path, b: &Behavior) -> Result<(), ()> {
|
|||
///
|
||||
/// Crashes the program if a nonexistent owner or group is specified in _b_.
|
||||
///
|
||||
fn need_copy(from: &Path, to: &Path, b: &Behavior) -> bool {
|
||||
fn need_copy(from: &Path, to: &Path, b: &Behavior) -> UResult<bool> {
|
||||
let from_meta = match fs::metadata(from) {
|
||||
Ok(meta) => meta,
|
||||
Err(_) => return true,
|
||||
Err(_) => return Ok(true),
|
||||
};
|
||||
let to_meta = match fs::metadata(to) {
|
||||
Ok(meta) => meta,
|
||||
Err(_) => return true,
|
||||
Err(_) => return Ok(true),
|
||||
};
|
||||
|
||||
// setuid || setgid || sticky
|
||||
|
@ -696,15 +730,15 @@ fn need_copy(from: &Path, to: &Path, b: &Behavior) -> bool {
|
|||
|| from_meta.mode() & extra_mode != 0
|
||||
|| to_meta.mode() & extra_mode != 0
|
||||
{
|
||||
return true;
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
if !from_meta.is_file() || !to_meta.is_file() {
|
||||
return true;
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
if from_meta.len() != to_meta.len() {
|
||||
return true;
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
// TODO: if -P (#1809) and from/to contexts mismatch, return true.
|
||||
|
@ -712,31 +746,31 @@ fn need_copy(from: &Path, to: &Path, b: &Behavior) -> bool {
|
|||
if !b.owner.is_empty() {
|
||||
let owner_id = match usr2uid(&b.owner) {
|
||||
Ok(id) => id,
|
||||
_ => crash!(1, "no such user: {}", b.owner),
|
||||
_ => return Err(InstallError::NoSuchUser(b.owner.clone()).into()),
|
||||
};
|
||||
if owner_id != to_meta.uid() {
|
||||
return true;
|
||||
return Ok(true);
|
||||
}
|
||||
} else if !b.group.is_empty() {
|
||||
let group_id = match grp2gid(&b.group) {
|
||||
Ok(id) => id,
|
||||
_ => crash!(1, "no such group: {}", b.group),
|
||||
_ => return Err(InstallError::NoSuchGroup(b.group.clone()).into()),
|
||||
};
|
||||
if group_id != to_meta.gid() {
|
||||
return true;
|
||||
return Ok(true);
|
||||
}
|
||||
} else {
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
unsafe {
|
||||
if to_meta.uid() != geteuid() || to_meta.gid() != getegid() {
|
||||
return true;
|
||||
return Ok(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !diff(from.to_str().unwrap(), to.to_str().unwrap()) {
|
||||
return true;
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
false
|
||||
Ok(false)
|
||||
}
|
||||
|
|
|
@ -19,7 +19,6 @@ static NAME: &str = "join";
|
|||
|
||||
#[derive(Copy, Clone, PartialEq)]
|
||||
enum FileNum {
|
||||
None,
|
||||
File1,
|
||||
File2,
|
||||
}
|
||||
|
@ -41,7 +40,8 @@ enum CheckOrder {
|
|||
struct Settings {
|
||||
key1: usize,
|
||||
key2: usize,
|
||||
print_unpaired: FileNum,
|
||||
print_unpaired1: bool,
|
||||
print_unpaired2: bool,
|
||||
print_joined: bool,
|
||||
ignore_case: bool,
|
||||
separator: Sep,
|
||||
|
@ -57,7 +57,8 @@ impl Default for Settings {
|
|||
Settings {
|
||||
key1: 0,
|
||||
key2: 0,
|
||||
print_unpaired: FileNum::None,
|
||||
print_unpaired1: false,
|
||||
print_unpaired2: false,
|
||||
print_joined: true,
|
||||
ignore_case: false,
|
||||
separator: Sep::Whitespaces,
|
||||
|
@ -243,7 +244,7 @@ impl<'a> State<'a> {
|
|||
name: &'a str,
|
||||
stdin: &'a Stdin,
|
||||
key: usize,
|
||||
print_unpaired: FileNum,
|
||||
print_unpaired: bool,
|
||||
) -> State<'a> {
|
||||
let f = if name == "-" {
|
||||
Box::new(stdin.lock()) as Box<dyn BufRead>
|
||||
|
@ -258,7 +259,7 @@ impl<'a> State<'a> {
|
|||
key,
|
||||
file_name: name,
|
||||
file_num,
|
||||
print_unpaired: print_unpaired == file_num,
|
||||
print_unpaired,
|
||||
lines: f.lines(),
|
||||
seq: Vec::new(),
|
||||
max_fields: None,
|
||||
|
@ -450,11 +451,19 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
|
||||
let mut settings: Settings = Default::default();
|
||||
|
||||
if let Some(value) = matches.value_of("v") {
|
||||
settings.print_unpaired = parse_file_number(value);
|
||||
let v_values = matches.values_of("v");
|
||||
if v_values.is_some() {
|
||||
settings.print_joined = false;
|
||||
} else if let Some(value) = matches.value_of("a") {
|
||||
settings.print_unpaired = parse_file_number(value);
|
||||
}
|
||||
|
||||
let unpaired = v_values
|
||||
.unwrap_or_default()
|
||||
.chain(matches.values_of("a").unwrap_or_default());
|
||||
for file_num in unpaired {
|
||||
match parse_file_number(file_num) {
|
||||
FileNum::File1 => settings.print_unpaired1 = true,
|
||||
FileNum::File2 => settings.print_unpaired2 = true,
|
||||
}
|
||||
}
|
||||
|
||||
settings.ignore_case = matches.is_present("i");
|
||||
|
@ -520,7 +529,8 @@ When FILE1 or FILE2 (not both) is -, read standard input.",
|
|||
.arg(
|
||||
Arg::with_name("a")
|
||||
.short("a")
|
||||
.takes_value(true)
|
||||
.multiple(true)
|
||||
.number_of_values(1)
|
||||
.possible_values(&["1", "2"])
|
||||
.value_name("FILENUM")
|
||||
.help(
|
||||
|
@ -531,6 +541,9 @@ FILENUM is 1 or 2, corresponding to FILE1 or FILE2",
|
|||
.arg(
|
||||
Arg::with_name("v")
|
||||
.short("v")
|
||||
.multiple(true)
|
||||
.number_of_values(1)
|
||||
.possible_values(&["1", "2"])
|
||||
.value_name("FILENUM")
|
||||
.help("like -a FILENUM, but suppress joined output lines"),
|
||||
)
|
||||
|
@ -617,7 +630,7 @@ fn exec(file1: &str, file2: &str, settings: &Settings) -> i32 {
|
|||
file1,
|
||||
&stdin,
|
||||
settings.key1,
|
||||
settings.print_unpaired,
|
||||
settings.print_unpaired1,
|
||||
);
|
||||
|
||||
let mut state2 = State::new(
|
||||
|
@ -625,7 +638,7 @@ fn exec(file1: &str, file2: &str, settings: &Settings) -> i32 {
|
|||
file2,
|
||||
&stdin,
|
||||
settings.key2,
|
||||
settings.print_unpaired,
|
||||
settings.print_unpaired2,
|
||||
);
|
||||
|
||||
let input = Input::new(
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
extern crate uucore;
|
||||
|
||||
use clap::{crate_version, App, Arg};
|
||||
use uucore::error::{UCustomError, UResult};
|
||||
use uucore::error::{UError, UResult};
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::error::Error;
|
||||
|
@ -79,7 +79,7 @@ impl Display for LnError {
|
|||
|
||||
impl Error for LnError {}
|
||||
|
||||
impl UCustomError for LnError {
|
||||
impl UError for LnError {
|
||||
fn code(&self) -> i32 {
|
||||
match self {
|
||||
Self::TargetIsDirectory(_) => 1,
|
||||
|
|
|
@ -39,7 +39,7 @@ use std::{
|
|||
time::Duration,
|
||||
};
|
||||
use term_grid::{Cell, Direction, Filling, Grid, GridOptions};
|
||||
use uucore::error::{set_exit_code, FromIo, UCustomError, UResult};
|
||||
use uucore::error::{set_exit_code, FromIo, UError, UResult};
|
||||
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
#[cfg(unix)]
|
||||
|
@ -127,13 +127,15 @@ pub mod options {
|
|||
pub static IGNORE: &str = "ignore";
|
||||
}
|
||||
|
||||
const DEFAULT_TERM_WIDTH: u16 = 80;
|
||||
|
||||
#[derive(Debug)]
|
||||
enum LsError {
|
||||
InvalidLineWidth(String),
|
||||
NoMetadata(PathBuf),
|
||||
}
|
||||
|
||||
impl UCustomError for LsError {
|
||||
impl UError for LsError {
|
||||
fn code(&self) -> i32 {
|
||||
match self {
|
||||
LsError::InvalidLineWidth(_) => 2,
|
||||
|
@ -229,7 +231,7 @@ struct Config {
|
|||
inode: bool,
|
||||
color: Option<LsColors>,
|
||||
long: LongFormat,
|
||||
width: Option<u16>,
|
||||
width: u16,
|
||||
quoting_style: QuotingStyle,
|
||||
indicator_style: IndicatorStyle,
|
||||
time_style: TimeStyle,
|
||||
|
@ -258,16 +260,20 @@ impl Config {
|
|||
// below should never happen as clap already restricts the values.
|
||||
_ => unreachable!("Invalid field for --format"),
|
||||
},
|
||||
options::FORMAT,
|
||||
Some(options::FORMAT),
|
||||
)
|
||||
} else if options.is_present(options::format::LONG) {
|
||||
(Format::Long, options::format::LONG)
|
||||
(Format::Long, Some(options::format::LONG))
|
||||
} else if options.is_present(options::format::ACROSS) {
|
||||
(Format::Across, options::format::ACROSS)
|
||||
(Format::Across, Some(options::format::ACROSS))
|
||||
} else if options.is_present(options::format::COMMAS) {
|
||||
(Format::Commas, options::format::COMMAS)
|
||||
(Format::Commas, Some(options::format::COMMAS))
|
||||
} else if options.is_present(options::format::COLUMNS) {
|
||||
(Format::Columns, Some(options::format::COLUMNS))
|
||||
} else if atty::is(atty::Stream::Stdout) {
|
||||
(Format::Columns, None)
|
||||
} else {
|
||||
(Format::Columns, options::format::COLUMNS)
|
||||
(Format::OneLine, None)
|
||||
};
|
||||
|
||||
// The -o, -n and -g options are tricky. They cannot override with each
|
||||
|
@ -286,9 +292,8 @@ impl Config {
|
|||
// options, but manually whether they have an index that's greater than
|
||||
// the other format options. If so, we set the appropriate format.
|
||||
if format != Format::Long {
|
||||
let idx = options
|
||||
.indices_of(opt)
|
||||
.map(|x| x.max().unwrap())
|
||||
let idx = opt
|
||||
.and_then(|opt| options.indices_of(opt).map(|x| x.max().unwrap()))
|
||||
.unwrap_or(0);
|
||||
if [
|
||||
options::format::LONG_NO_OWNER,
|
||||
|
@ -399,10 +404,25 @@ impl Config {
|
|||
|
||||
let width = match options.value_of(options::WIDTH) {
|
||||
Some(x) => match x.parse::<u16>() {
|
||||
Ok(u) => Some(u),
|
||||
Ok(u) => u,
|
||||
Err(_) => return Err(LsError::InvalidLineWidth(x.into()).into()),
|
||||
},
|
||||
None => termsize::get().map(|s| s.cols),
|
||||
None => match termsize::get() {
|
||||
Some(size) => size.cols,
|
||||
None => match std::env::var("COLUMNS") {
|
||||
Ok(columns) => match columns.parse() {
|
||||
Ok(columns) => columns,
|
||||
Err(_) => {
|
||||
show_error!(
|
||||
"ignoring invalid width in environment variable COLUMNS: '{}'",
|
||||
columns
|
||||
);
|
||||
DEFAULT_TERM_WIDTH
|
||||
}
|
||||
},
|
||||
Err(_) => DEFAULT_TERM_WIDTH,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
#[allow(clippy::needless_bool)]
|
||||
|
@ -1411,15 +1431,10 @@ fn display_items(items: &[PathData], config: &Config, out: &mut BufWriter<Stdout
|
|||
} else {
|
||||
let names = items.iter().filter_map(|i| display_file_name(i, config));
|
||||
|
||||
match (&config.format, config.width) {
|
||||
(Format::Columns, Some(width)) => {
|
||||
display_grid(names, width, Direction::TopToBottom, out)
|
||||
}
|
||||
(Format::Across, Some(width)) => {
|
||||
display_grid(names, width, Direction::LeftToRight, out)
|
||||
}
|
||||
(Format::Commas, width_opt) => {
|
||||
let term_width = width_opt.unwrap_or(1);
|
||||
match config.format {
|
||||
Format::Columns => display_grid(names, config.width, Direction::TopToBottom, out),
|
||||
Format::Across => display_grid(names, config.width, Direction::LeftToRight, out),
|
||||
Format::Commas => {
|
||||
let mut current_col = 0;
|
||||
let mut names = names;
|
||||
if let Some(name) = names.next() {
|
||||
|
@ -1428,7 +1443,8 @@ fn display_items(items: &[PathData], config: &Config, out: &mut BufWriter<Stdout
|
|||
}
|
||||
for name in names {
|
||||
let name_width = name.width as u16;
|
||||
if current_col + name_width + 1 > term_width {
|
||||
// If the width is 0 we print one single line
|
||||
if config.width != 0 && current_col + name_width + 1 > config.width {
|
||||
current_col = name_width + 2;
|
||||
let _ = write!(out, ",\n{}", name.contents);
|
||||
} else {
|
||||
|
@ -1480,22 +1496,37 @@ fn display_grid(
|
|||
direction: Direction,
|
||||
out: &mut BufWriter<Stdout>,
|
||||
) {
|
||||
let mut grid = Grid::new(GridOptions {
|
||||
filling: Filling::Spaces(2),
|
||||
direction,
|
||||
});
|
||||
|
||||
for name in names {
|
||||
grid.add(name);
|
||||
}
|
||||
|
||||
match grid.fit_into_width(width as usize) {
|
||||
Some(output) => {
|
||||
let _ = write!(out, "{}", output);
|
||||
if width == 0 {
|
||||
// If the width is 0 we print one single line
|
||||
let mut printed_something = false;
|
||||
for name in names {
|
||||
if printed_something {
|
||||
let _ = write!(out, " ");
|
||||
}
|
||||
printed_something = true;
|
||||
let _ = write!(out, "{}", name.contents);
|
||||
}
|
||||
// Width is too small for the grid, so we fit it in one column
|
||||
None => {
|
||||
let _ = write!(out, "{}", grid.fit_into_columns(1));
|
||||
if printed_something {
|
||||
let _ = writeln!(out);
|
||||
}
|
||||
} else {
|
||||
let mut grid = Grid::new(GridOptions {
|
||||
filling: Filling::Spaces(2),
|
||||
direction,
|
||||
});
|
||||
|
||||
for name in names {
|
||||
grid.add(name);
|
||||
}
|
||||
|
||||
match grid.fit_into_width(width as usize) {
|
||||
Some(output) => {
|
||||
let _ = write!(out, "{}", output);
|
||||
}
|
||||
// Width is too small for the grid, so we fit it in one column
|
||||
None => {
|
||||
let _ = write!(out, "{}", grid.fit_into_columns(1));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1590,7 +1621,7 @@ fn display_uname(metadata: &Metadata, config: &Config) -> String {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[cfg(all(unix, not(target_os = "redox")))]
|
||||
fn cached_gid2grp(gid: u32) -> String {
|
||||
lazy_static! {
|
||||
static ref GID_CACHE: Mutex<HashMap<u32, String>> = Mutex::new(HashMap::new());
|
||||
|
@ -1603,7 +1634,7 @@ fn cached_gid2grp(gid: u32) -> String {
|
|||
.clone()
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[cfg(all(unix, not(target_os = "redox")))]
|
||||
fn display_group(metadata: &Metadata, config: &Config) -> String {
|
||||
if config.long.numeric_uid_gid {
|
||||
metadata.gid().to_string()
|
||||
|
@ -1612,6 +1643,11 @@ fn display_group(metadata: &Metadata, config: &Config) -> String {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "redox")]
|
||||
fn display_group(metadata: &Metadata, config: &Config) -> String {
|
||||
metadata.gid().to_string()
|
||||
}
|
||||
|
||||
#[cfg(not(unix))]
|
||||
fn display_uname(_metadata: &Metadata, _config: &Config) -> String {
|
||||
"somebody".to_string()
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
extern crate uucore;
|
||||
|
||||
use clap::{crate_version, App, Arg};
|
||||
use uucore::error::{FromIo, UCustomError, UResult};
|
||||
use uucore::error::{FromIo, UError, UResult};
|
||||
|
||||
use std::env;
|
||||
use std::error::Error;
|
||||
|
@ -49,7 +49,7 @@ enum MkTempError {
|
|||
InvalidTemplate(String),
|
||||
}
|
||||
|
||||
impl UCustomError for MkTempError {}
|
||||
impl UError for MkTempError {}
|
||||
|
||||
impl Error for MkTempError {}
|
||||
|
||||
|
|
|
@ -25,7 +25,7 @@ unicode-segmentation = "1.7.1"
|
|||
|
||||
[target.'cfg(target_os = "redox")'.dependencies]
|
||||
redox_termios = "0.1"
|
||||
redox_syscall = "0.1"
|
||||
redox_syscall = "0.2"
|
||||
|
||||
[target.'cfg(all(unix, not(target_os = "fuchsia")))'.dependencies]
|
||||
nix = "<=0.13"
|
||||
|
|
|
@ -10,21 +10,13 @@
|
|||
#[macro_use]
|
||||
extern crate uucore;
|
||||
|
||||
use libc::{c_char, c_int, execvp};
|
||||
use libc::{c_char, c_int, execvp, PRIO_PROCESS};
|
||||
use std::ffi::CString;
|
||||
use std::io::Error;
|
||||
use std::ptr;
|
||||
|
||||
use clap::{crate_version, App, AppSettings, Arg};
|
||||
|
||||
// XXX: PRIO_PROCESS is 0 on at least FreeBSD and Linux. Don't know about Mac OS X.
|
||||
const PRIO_PROCESS: c_int = 0;
|
||||
|
||||
extern "C" {
|
||||
fn getpriority(which: c_int, who: c_int) -> c_int;
|
||||
fn setpriority(which: c_int, who: c_int, prio: c_int) -> c_int;
|
||||
}
|
||||
|
||||
pub mod options {
|
||||
pub static ADJUSTMENT: &str = "adjustment";
|
||||
pub static COMMAND: &str = "COMMAND";
|
||||
|
@ -50,7 +42,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
|
||||
let mut niceness = unsafe {
|
||||
nix::errno::Errno::clear();
|
||||
getpriority(PRIO_PROCESS, 0)
|
||||
libc::getpriority(PRIO_PROCESS, 0)
|
||||
};
|
||||
if Error::last_os_error().raw_os_error().unwrap() != 0 {
|
||||
show_error!("getpriority: {}", Error::last_os_error());
|
||||
|
@ -84,7 +76,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
|||
};
|
||||
|
||||
niceness += adjustment;
|
||||
if unsafe { setpriority(PRIO_PROCESS, 0, niceness) } == -1 {
|
||||
if unsafe { libc::setpriority(PRIO_PROCESS, 0, niceness) } == -1 {
|
||||
show_warning!("setpriority: {}", Error::last_os_error());
|
||||
}
|
||||
|
||||
|
|
|
@ -435,6 +435,7 @@ pub fn uu_app() -> clap::App<'static, 'static> {
|
|||
.long(options::FORMAT)
|
||||
.help("select output format or formats")
|
||||
.multiple(true)
|
||||
.number_of_values(1)
|
||||
.value_name("TYPE"),
|
||||
)
|
||||
.arg(
|
||||
|
|
|
@ -16,7 +16,7 @@ path = "src/pr.rs"
|
|||
|
||||
[dependencies]
|
||||
clap = { version = "2.33", features = ["wrap_help"] }
|
||||
uucore = { version=">=0.0.7", package="uucore", path="../../uucore", features=["utmpx", "entries"] }
|
||||
uucore = { version=">=0.0.7", package="uucore", path="../../uucore", features=["entries"] }
|
||||
uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_procs" }
|
||||
getopts = "0.2.21"
|
||||
time = "0.1.41"
|
||||
|
|
|
@ -18,10 +18,11 @@ path = "src/sort.rs"
|
|||
binary-heap-plus = "0.4.1"
|
||||
clap = { version = "2.33", features = ["wrap_help"] }
|
||||
compare = "0.1.0"
|
||||
ctrlc = { version = "3.0", features = ["termination"] }
|
||||
fnv = "1.0.7"
|
||||
itertools = "0.10.0"
|
||||
memchr = "2.4.0"
|
||||
ouroboros = "0.9.3"
|
||||
ouroboros = "0.10.1"
|
||||
rand = "0.7"
|
||||
rayon = "1.5"
|
||||
tempfile = "3"
|
||||
|
|
|
@ -13,7 +13,6 @@
|
|||
|
||||
use std::cmp::Ordering;
|
||||
use std::io::Write;
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
use std::{
|
||||
io::Read,
|
||||
|
@ -29,14 +28,13 @@ use crate::merge::ClosedTmpFile;
|
|||
use crate::merge::WriteableCompressedTmpFile;
|
||||
use crate::merge::WriteablePlainTmpFile;
|
||||
use crate::merge::WriteableTmpFile;
|
||||
use crate::tmp_dir::TmpDirWrapper;
|
||||
use crate::Output;
|
||||
use crate::SortError;
|
||||
use crate::{
|
||||
chunks::{self, Chunk},
|
||||
compare_by, merge, sort_by, GlobalSettings,
|
||||
};
|
||||
use crate::{print_sorted, Line};
|
||||
use tempfile::TempDir;
|
||||
|
||||
const START_BUFFER_SIZE: usize = 8_000;
|
||||
|
||||
|
@ -45,6 +43,7 @@ pub fn ext_sort(
|
|||
files: &mut impl Iterator<Item = UResult<Box<dyn Read + Send>>>,
|
||||
settings: &GlobalSettings,
|
||||
output: Output,
|
||||
tmp_dir: &mut TmpDirWrapper,
|
||||
) -> UResult<()> {
|
||||
let (sorted_sender, sorted_receiver) = std::sync::mpsc::sync_channel(1);
|
||||
let (recycled_sender, recycled_receiver) = std::sync::mpsc::sync_channel(1);
|
||||
|
@ -59,6 +58,7 @@ pub fn ext_sort(
|
|||
sorted_receiver,
|
||||
recycled_sender,
|
||||
output,
|
||||
tmp_dir,
|
||||
)
|
||||
} else {
|
||||
reader_writer::<_, WriteablePlainTmpFile>(
|
||||
|
@ -67,6 +67,7 @@ pub fn ext_sort(
|
|||
sorted_receiver,
|
||||
recycled_sender,
|
||||
output,
|
||||
tmp_dir,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -80,6 +81,7 @@ fn reader_writer<
|
|||
receiver: Receiver<Chunk>,
|
||||
sender: SyncSender<Chunk>,
|
||||
output: Output,
|
||||
tmp_dir: &mut TmpDirWrapper,
|
||||
) -> UResult<()> {
|
||||
let separator = if settings.zero_terminated {
|
||||
b'\0'
|
||||
|
@ -92,7 +94,7 @@ fn reader_writer<
|
|||
let buffer_size = settings.buffer_size / 10;
|
||||
let read_result: ReadResult<Tmp> = read_write_loop(
|
||||
files,
|
||||
&settings.tmp_dir,
|
||||
tmp_dir,
|
||||
separator,
|
||||
buffer_size,
|
||||
settings,
|
||||
|
@ -100,12 +102,11 @@ fn reader_writer<
|
|||
sender,
|
||||
)?;
|
||||
match read_result {
|
||||
ReadResult::WroteChunksToFile { tmp_files, tmp_dir } => {
|
||||
let tmp_dir_size = tmp_files.len();
|
||||
ReadResult::WroteChunksToFile { tmp_files } => {
|
||||
let merger = merge::merge_with_file_limit::<_, _, Tmp>(
|
||||
tmp_files.into_iter().map(|c| c.reopen()),
|
||||
settings,
|
||||
Some((tmp_dir, tmp_dir_size)),
|
||||
tmp_dir,
|
||||
)?;
|
||||
merger.write_all(settings, output)?;
|
||||
}
|
||||
|
@ -176,15 +177,12 @@ enum ReadResult<I: WriteableTmpFile> {
|
|||
/// The input fits into two chunks, which were kept in memory.
|
||||
SortedTwoChunks([Chunk; 2]),
|
||||
/// The input was read into multiple chunks, which were written to auxiliary files.
|
||||
WroteChunksToFile {
|
||||
tmp_files: Vec<I::Closed>,
|
||||
tmp_dir: TempDir,
|
||||
},
|
||||
WroteChunksToFile { tmp_files: Vec<I::Closed> },
|
||||
}
|
||||
/// The function that is executed on the reader/writer thread.
|
||||
fn read_write_loop<I: WriteableTmpFile>(
|
||||
mut files: impl Iterator<Item = UResult<Box<dyn Read + Send>>>,
|
||||
tmp_dir_parent: &Path,
|
||||
tmp_dir: &mut TmpDirWrapper,
|
||||
separator: u8,
|
||||
buffer_size: usize,
|
||||
settings: &GlobalSettings,
|
||||
|
@ -228,32 +226,24 @@ fn read_write_loop<I: WriteableTmpFile>(
|
|||
}
|
||||
}
|
||||
|
||||
let tmp_dir = tempfile::Builder::new()
|
||||
.prefix("uutils_sort")
|
||||
.tempdir_in(tmp_dir_parent)
|
||||
.map_err(|_| SortError::TmpDirCreationFailed)?;
|
||||
|
||||
let mut sender_option = Some(sender);
|
||||
let mut file_number = 0;
|
||||
let mut tmp_files = vec![];
|
||||
loop {
|
||||
let mut chunk = match receiver.recv() {
|
||||
Ok(it) => it,
|
||||
_ => {
|
||||
return Ok(ReadResult::WroteChunksToFile { tmp_files, tmp_dir });
|
||||
return Ok(ReadResult::WroteChunksToFile { tmp_files });
|
||||
}
|
||||
};
|
||||
|
||||
let tmp_file = write::<I>(
|
||||
&mut chunk,
|
||||
tmp_dir.path().join(file_number.to_string()),
|
||||
tmp_dir.next_file_path()?,
|
||||
settings.compress_prog.as_deref(),
|
||||
separator,
|
||||
)?;
|
||||
tmp_files.push(tmp_file);
|
||||
|
||||
file_number += 1;
|
||||
|
||||
let recycled_chunk = chunk.recycle();
|
||||
|
||||
if let Some(sender) = &sender_option {
|
||||
|
|
|
@ -22,45 +22,41 @@ use std::{
|
|||
|
||||
use compare::Compare;
|
||||
use itertools::Itertools;
|
||||
use tempfile::TempDir;
|
||||
use uucore::error::UResult;
|
||||
|
||||
use crate::{
|
||||
chunks::{self, Chunk, RecycledChunk},
|
||||
compare_by, open, GlobalSettings, Output, SortError,
|
||||
compare_by, open,
|
||||
tmp_dir::TmpDirWrapper,
|
||||
GlobalSettings, Output, SortError,
|
||||
};
|
||||
|
||||
/// If the output file occurs in the input files as well, copy the contents of the output file
|
||||
/// and replace its occurrences in the inputs with that copy.
|
||||
fn replace_output_file_in_input_files(
|
||||
files: &mut [OsString],
|
||||
settings: &GlobalSettings,
|
||||
output: Option<&str>,
|
||||
) -> UResult<Option<(TempDir, usize)>> {
|
||||
let mut copy: Option<(TempDir, PathBuf)> = None;
|
||||
tmp_dir: &mut TmpDirWrapper,
|
||||
) -> UResult<()> {
|
||||
let mut copy: Option<PathBuf> = None;
|
||||
if let Some(Ok(output_path)) = output.map(|path| Path::new(path).canonicalize()) {
|
||||
for file in files {
|
||||
if let Ok(file_path) = Path::new(file).canonicalize() {
|
||||
if file_path == output_path {
|
||||
if let Some((_dir, copy)) = © {
|
||||
if let Some(copy) = © {
|
||||
*file = copy.clone().into_os_string();
|
||||
} else {
|
||||
let tmp_dir = tempfile::Builder::new()
|
||||
.prefix("uutils_sort")
|
||||
.tempdir_in(&settings.tmp_dir)
|
||||
.map_err(|_| SortError::TmpDirCreationFailed)?;
|
||||
let copy_path = tmp_dir.path().join("0");
|
||||
let copy_path = tmp_dir.next_file_path()?;
|
||||
std::fs::copy(file_path, ©_path)
|
||||
.map_err(|error| SortError::OpenTmpFileFailed { error })?;
|
||||
*file = copy_path.clone().into_os_string();
|
||||
copy = Some((tmp_dir, copy_path))
|
||||
copy = Some(copy_path)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// if we created a TempDir its size must be one.
|
||||
Ok(copy.map(|(dir, _copy)| (dir, 1)))
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Merge pre-sorted `Box<dyn Read>`s.
|
||||
|
@ -71,8 +67,9 @@ pub fn merge<'a>(
|
|||
files: &mut [OsString],
|
||||
settings: &'a GlobalSettings,
|
||||
output: Option<&str>,
|
||||
tmp_dir: &mut TmpDirWrapper,
|
||||
) -> UResult<FileMerger<'a>> {
|
||||
let tmp_dir = replace_output_file_in_input_files(files, settings, output)?;
|
||||
replace_output_file_in_input_files(files, output, tmp_dir)?;
|
||||
if settings.compress_prog.is_none() {
|
||||
merge_with_file_limit::<_, _, WriteablePlainTmpFile>(
|
||||
files
|
||||
|
@ -94,26 +91,16 @@ pub fn merge<'a>(
|
|||
|
||||
// Merge already sorted `MergeInput`s.
|
||||
pub fn merge_with_file_limit<
|
||||
'a,
|
||||
M: MergeInput + 'static,
|
||||
F: ExactSizeIterator<Item = UResult<M>>,
|
||||
Tmp: WriteableTmpFile + 'static,
|
||||
>(
|
||||
files: F,
|
||||
settings: &GlobalSettings,
|
||||
tmp_dir: Option<(TempDir, usize)>,
|
||||
) -> UResult<FileMerger> {
|
||||
settings: &'a GlobalSettings,
|
||||
tmp_dir: &mut TmpDirWrapper,
|
||||
) -> UResult<FileMerger<'a>> {
|
||||
if files.len() > settings.merge_batch_size {
|
||||
// If we did not get a tmp_dir, create one.
|
||||
let (tmp_dir, mut tmp_dir_size) = match tmp_dir {
|
||||
Some(x) => x,
|
||||
None => (
|
||||
tempfile::Builder::new()
|
||||
.prefix("uutils_sort")
|
||||
.tempdir_in(&settings.tmp_dir)
|
||||
.map_err(|_| SortError::TmpDirCreationFailed)?,
|
||||
0,
|
||||
),
|
||||
};
|
||||
let mut remaining_files = files.len();
|
||||
let batches = files.chunks(settings.merge_batch_size);
|
||||
let mut batches = batches.into_iter();
|
||||
|
@ -122,11 +109,8 @@ pub fn merge_with_file_limit<
|
|||
// Work around the fact that `Chunks` is not an `ExactSizeIterator`.
|
||||
remaining_files = remaining_files.saturating_sub(settings.merge_batch_size);
|
||||
let merger = merge_without_limit(batches.next().unwrap(), settings)?;
|
||||
let mut tmp_file = Tmp::create(
|
||||
tmp_dir.path().join(tmp_dir_size.to_string()),
|
||||
settings.compress_prog.as_deref(),
|
||||
)?;
|
||||
tmp_dir_size += 1;
|
||||
let mut tmp_file =
|
||||
Tmp::create(tmp_dir.next_file_path()?, settings.compress_prog.as_deref())?;
|
||||
merger.write_all_to(settings, tmp_file.as_write())?;
|
||||
temporary_files.push(tmp_file.finished_writing()?);
|
||||
}
|
||||
|
@ -139,7 +123,7 @@ pub fn merge_with_file_limit<
|
|||
dyn FnMut(Tmp::Closed) -> UResult<<Tmp::Closed as ClosedTmpFile>::Reopened>,
|
||||
>),
|
||||
settings,
|
||||
Some((tmp_dir, tmp_dir_size)),
|
||||
tmp_dir,
|
||||
)
|
||||
} else {
|
||||
merge_without_limit(files, settings)
|
||||
|
|
|
@ -22,6 +22,7 @@ mod custom_str_cmp;
|
|||
mod ext_sort;
|
||||
mod merge;
|
||||
mod numeric_str_cmp;
|
||||
mod tmp_dir;
|
||||
|
||||
use chunks::LineData;
|
||||
use clap::{crate_version, App, Arg};
|
||||
|
@ -44,11 +45,13 @@ use std::path::Path;
|
|||
use std::path::PathBuf;
|
||||
use std::str::Utf8Error;
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
use uucore::error::{set_exit_code, UCustomError, UResult, USimpleError, UUsageError};
|
||||
use uucore::error::{set_exit_code, UError, UResult, USimpleError, UUsageError};
|
||||
use uucore::parse_size::{parse_size, ParseSizeError};
|
||||
use uucore::version_cmp::version_cmp;
|
||||
use uucore::InvalidEncodingHandling;
|
||||
|
||||
use crate::tmp_dir::TmpDirWrapper;
|
||||
|
||||
const NAME: &str = "sort";
|
||||
const ABOUT: &str = "Display sorted concatenation of all FILE(s).";
|
||||
|
||||
|
@ -161,7 +164,7 @@ enum SortError {
|
|||
|
||||
impl Error for SortError {}
|
||||
|
||||
impl UCustomError for SortError {
|
||||
impl UError for SortError {
|
||||
fn code(&self) -> i32 {
|
||||
match self {
|
||||
SortError::Disorder { .. } => 1,
|
||||
|
@ -317,7 +320,6 @@ pub struct GlobalSettings {
|
|||
threads: String,
|
||||
zero_terminated: bool,
|
||||
buffer_size: usize,
|
||||
tmp_dir: PathBuf,
|
||||
compress_prog: Option<String>,
|
||||
merge_batch_size: usize,
|
||||
precomputed: Precomputed,
|
||||
|
@ -400,7 +402,6 @@ impl Default for GlobalSettings {
|
|||
threads: String::new(),
|
||||
zero_terminated: false,
|
||||
buffer_size: DEFAULT_BUF_SIZE,
|
||||
tmp_dir: PathBuf::new(),
|
||||
compress_prog: None,
|
||||
merge_batch_size: 32,
|
||||
precomputed: Precomputed {
|
||||
|
@ -1178,10 +1179,12 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
|||
})
|
||||
})?;
|
||||
|
||||
settings.tmp_dir = matches
|
||||
.value_of(options::TMP_DIR)
|
||||
.map(PathBuf::from)
|
||||
.unwrap_or_else(env::temp_dir);
|
||||
let mut tmp_dir = TmpDirWrapper::new(
|
||||
matches
|
||||
.value_of(options::TMP_DIR)
|
||||
.map(PathBuf::from)
|
||||
.unwrap_or_else(env::temp_dir),
|
||||
);
|
||||
|
||||
settings.compress_prog = matches.value_of(options::COMPRESS_PROG).map(String::from);
|
||||
|
||||
|
@ -1235,7 +1238,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
|||
if separator.len() != 1 {
|
||||
return Err(UUsageError::new(
|
||||
2,
|
||||
"separator must be exactly one character long".into(),
|
||||
"separator must be exactly one character long",
|
||||
));
|
||||
}
|
||||
settings.separator = Some(separator.chars().next().unwrap())
|
||||
|
@ -1280,7 +1283,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
|||
|
||||
settings.init_precomputed();
|
||||
|
||||
exec(&mut files, &settings, output)
|
||||
exec(&mut files, &settings, output, &mut tmp_dir)
|
||||
}
|
||||
|
||||
pub fn uu_app() -> App<'static, 'static> {
|
||||
|
@ -1503,19 +1506,24 @@ pub fn uu_app() -> App<'static, 'static> {
|
|||
)
|
||||
}
|
||||
|
||||
fn exec(files: &mut [OsString], settings: &GlobalSettings, output: Output) -> UResult<()> {
|
||||
fn exec(
|
||||
files: &mut [OsString],
|
||||
settings: &GlobalSettings,
|
||||
output: Output,
|
||||
tmp_dir: &mut TmpDirWrapper,
|
||||
) -> UResult<()> {
|
||||
if settings.merge {
|
||||
let file_merger = merge::merge(files, settings, output.as_output_name())?;
|
||||
let file_merger = merge::merge(files, settings, output.as_output_name(), tmp_dir)?;
|
||||
file_merger.write_all(settings, output)
|
||||
} else if settings.check {
|
||||
if files.len() > 1 {
|
||||
Err(UUsageError::new(2, "only one file allowed with -c".into()))
|
||||
Err(UUsageError::new(2, "only one file allowed with -c"))
|
||||
} else {
|
||||
check::check(files.first().unwrap(), settings)
|
||||
}
|
||||
} else {
|
||||
let mut lines = files.iter().map(open);
|
||||
ext_sort(&mut lines, settings, output)
|
||||
ext_sort(&mut lines, settings, output, tmp_dir)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
80
src/uu/sort/src/tmp_dir.rs
Normal file
80
src/uu/sort/src/tmp_dir.rs
Normal file
|
@ -0,0 +1,80 @@
|
|||
use std::{
|
||||
path::{Path, PathBuf},
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
use tempfile::TempDir;
|
||||
use uucore::error::{UResult, USimpleError};
|
||||
|
||||
use crate::SortError;
|
||||
|
||||
/// A wrapper around TempDir that may only exist once in a process.
|
||||
///
|
||||
/// `TmpDirWrapper` handles the allocation of new temporary files in this temporary directory and
|
||||
/// deleting the whole directory when `SIGINT` is received. Creating a second `TmpDirWrapper` will
|
||||
/// fail because `ctrlc::set_handler()` fails when there's already a handler.
|
||||
/// The directory is only created once the first file is requested.
|
||||
pub struct TmpDirWrapper {
|
||||
temp_dir: Option<TempDir>,
|
||||
parent_path: PathBuf,
|
||||
size: usize,
|
||||
lock: Arc<Mutex<()>>,
|
||||
}
|
||||
|
||||
impl TmpDirWrapper {
|
||||
pub fn new(path: PathBuf) -> Self {
|
||||
Self {
|
||||
parent_path: path,
|
||||
size: 0,
|
||||
temp_dir: None,
|
||||
lock: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
fn init_tmp_dir(&mut self) -> UResult<()> {
|
||||
assert!(self.temp_dir.is_none());
|
||||
assert_eq!(self.size, 0);
|
||||
self.temp_dir = Some(
|
||||
tempfile::Builder::new()
|
||||
.prefix("uutils_sort")
|
||||
.tempdir_in(&self.parent_path)
|
||||
.map_err(|_| SortError::TmpDirCreationFailed)?,
|
||||
);
|
||||
|
||||
let path = self.temp_dir.as_ref().unwrap().path().to_owned();
|
||||
let lock = self.lock.clone();
|
||||
ctrlc::set_handler(move || {
|
||||
// Take the lock so that `next_file_path` returns no new file path.
|
||||
let _lock = lock.lock().unwrap();
|
||||
if let Err(e) = remove_tmp_dir(&path) {
|
||||
show_error!("failed to delete temporary directory: {}", e);
|
||||
}
|
||||
std::process::exit(2)
|
||||
})
|
||||
.map_err(|e| USimpleError::new(2, format!("failed to set up signal handler: {}", e)))
|
||||
}
|
||||
|
||||
pub fn next_file_path(&mut self) -> UResult<PathBuf> {
|
||||
if self.temp_dir.is_none() {
|
||||
self.init_tmp_dir()?;
|
||||
}
|
||||
|
||||
let _lock = self.lock.lock().unwrap();
|
||||
let file_name = self.size.to_string();
|
||||
self.size += 1;
|
||||
Ok(self.temp_dir.as_ref().unwrap().path().join(file_name))
|
||||
}
|
||||
}
|
||||
|
||||
/// Remove the directory at `path` by deleting its child files and then itself.
|
||||
/// Errors while deleting child files are ignored.
|
||||
fn remove_tmp_dir(path: &Path) -> std::io::Result<()> {
|
||||
if let Ok(read_dir) = std::fs::read_dir(&path) {
|
||||
for file in read_dir.flatten() {
|
||||
// if we fail to delete the file here it was probably deleted by another thread
|
||||
// in the meantime, but that's ok.
|
||||
let _ = std::fs::remove_file(file.path());
|
||||
}
|
||||
}
|
||||
std::fs::remove_dir(path)
|
||||
}
|
|
@ -22,7 +22,7 @@ uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_p
|
|||
winapi = { version="0.3", features=["fileapi", "handleapi", "processthreadsapi", "synchapi", "winbase"] }
|
||||
|
||||
[target.'cfg(target_os = "redox")'.dependencies]
|
||||
redox_syscall = "0.1"
|
||||
redox_syscall = "0.2"
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
nix = "0.20"
|
||||
|
|
|
@ -14,14 +14,8 @@ pub use self::unix::{stdin_is_pipe_or_fifo, supports_pid_checks, Pid, ProcessChe
|
|||
#[cfg(windows)]
|
||||
pub use self::windows::{supports_pid_checks, Pid, ProcessChecker};
|
||||
|
||||
#[cfg(target_os = "redox")]
|
||||
pub use self::redox::{supports_pid_checks, Pid, ProcessChecker};
|
||||
|
||||
#[cfg(unix)]
|
||||
mod unix;
|
||||
|
||||
#[cfg(windows)]
|
||||
mod windows;
|
||||
|
||||
#[cfg(target_os = "redox")]
|
||||
mod redox;
|
||||
|
|
|
@ -1,25 +0,0 @@
|
|||
// spell-checker:ignore (ToDO) ENOSYS EPERM
|
||||
|
||||
use self::syscall::{Error, ENOSYS, EPERM};
|
||||
|
||||
pub type Pid = usize;
|
||||
|
||||
pub struct ProcessChecker {
|
||||
pid: self::Pid,
|
||||
}
|
||||
|
||||
impl ProcessChecker {
|
||||
pub fn new(process_id: self::Pid) -> ProcessChecker {
|
||||
ProcessChecker { pid: process_id }
|
||||
}
|
||||
|
||||
// Borrowing mutably to be aligned with Windows implementation
|
||||
pub fn is_dead(&mut self) -> bool {
|
||||
let res = syscall::kill(self.pid, 0);
|
||||
res != Ok(0) && res != Err(Error::new(EPERM))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn supports_pid_checks(pid: self::Pid) -> bool {
|
||||
true
|
||||
}
|
|
@ -21,7 +21,7 @@ uucore = { version=">=0.0.9", package="uucore", path="../../uucore" }
|
|||
uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_procs" }
|
||||
|
||||
[target.'cfg(target_os = "redox")'.dependencies]
|
||||
redox_syscall = "0.1"
|
||||
redox_syscall = "0.2"
|
||||
|
||||
[[bin]]
|
||||
name = "test"
|
||||
|
|
|
@ -17,7 +17,7 @@ use clap::{crate_version, App, Arg, ArgGroup};
|
|||
use filetime::*;
|
||||
use std::fs::{self, File};
|
||||
use std::path::Path;
|
||||
use uucore::error::{FromIo, UResult, USimpleError};
|
||||
use uucore::error::{FromIo, UError, UResult, USimpleError};
|
||||
|
||||
static ABOUT: &str = "Update the access and modification times of each FILE to the current time.";
|
||||
pub mod options {
|
||||
|
|
|
@ -16,7 +16,7 @@ edition = "2018"
|
|||
path="src/lib/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
dns-lookup = "1.0.5"
|
||||
dns-lookup = { version="1.0.5", optional=true }
|
||||
dunce = "1.0.0"
|
||||
getopts = "<= 0.2.21"
|
||||
wild = "2.0.4"
|
||||
|
@ -27,7 +27,9 @@ nix = { version="<= 0.13", optional=true }
|
|||
platform-info = { version="<= 0.1", optional=true }
|
||||
time = { version="<= 0.1.43", optional=true }
|
||||
# * "problem" dependencies (pinned)
|
||||
data-encoding = { version="~2.1", optional=true } ## data-encoding: require v2.1; but v2.2.0 breaks the build for MinSRV v1.31.0
|
||||
data-encoding = { version="2.1", optional=true }
|
||||
data-encoding-macro = { version="0.1.12", optional=true }
|
||||
z85 = { version="3.0.3", optional=true }
|
||||
libc = { version="0.2.15, <= 0.2.85", optional=true } ## libc: initial utmp support added in v0.2.15; but v0.2.68 breaks the build for MinSRV v1.31.0
|
||||
|
||||
[dev-dependencies]
|
||||
|
@ -43,7 +45,7 @@ termion = "1.5"
|
|||
[features]
|
||||
default = []
|
||||
# * non-default features
|
||||
encoding = ["data-encoding", "thiserror"]
|
||||
encoding = ["data-encoding", "data-encoding-macro", "z85", "thiserror"]
|
||||
entries = ["libc"]
|
||||
fs = ["libc"]
|
||||
fsext = ["libc", "time"]
|
||||
|
@ -53,6 +55,6 @@ process = ["libc"]
|
|||
ringbuffer = []
|
||||
signals = []
|
||||
utf8 = []
|
||||
utmpx = ["time", "libc"]
|
||||
utmpx = ["time", "libc", "dns-lookup"]
|
||||
wide = []
|
||||
zero-copy = ["nix", "libc", "lazy_static", "platform-info"]
|
||||
|
|
|
@ -29,6 +29,7 @@ pub mod signals;
|
|||
#[cfg(all(
|
||||
unix,
|
||||
not(target_os = "fuchsia"),
|
||||
not(target_os = "redox"),
|
||||
not(target_env = "musl"),
|
||||
feature = "utmpx"
|
||||
))]
|
||||
|
|
|
@ -5,45 +5,95 @@
|
|||
// For the full copyright and license information, please view the LICENSE
|
||||
// file that was distributed with this source code.
|
||||
|
||||
// spell-checker:ignore (strings) ABCDEFGHIJKLMNOPQRSTUVWXYZ
|
||||
// spell-checker:ignore (strings) ABCDEFGHIJKLMNOPQRSTUVWXYZ ABCDEFGHIJKLMNOPQRSTUV
|
||||
// spell-checker:ignore (encodings) lsbf msbf hexupper
|
||||
|
||||
extern crate data_encoding;
|
||||
|
||||
use self::data_encoding::{DecodeError, BASE32, BASE64};
|
||||
use data_encoding::{self, BASE32, BASE64};
|
||||
|
||||
use std::io::{self, Read, Write};
|
||||
|
||||
use data_encoding::{Encoding, BASE32HEX, BASE64URL, HEXUPPER};
|
||||
use data_encoding_macro::new_encoding;
|
||||
#[cfg(feature = "thiserror")]
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum EncodingError {
|
||||
pub enum DecodeError {
|
||||
#[error("{}", _0)]
|
||||
Decode(#[from] DecodeError),
|
||||
Decode(#[from] data_encoding::DecodeError),
|
||||
#[error("{}", _0)]
|
||||
DecodeZ85(#[from] z85::DecodeError),
|
||||
#[error("{}", _0)]
|
||||
Io(#[from] io::Error),
|
||||
}
|
||||
|
||||
pub type DecodeResult = Result<Vec<u8>, EncodingError>;
|
||||
pub enum EncodeError {
|
||||
Z85InputLenNotMultipleOf4,
|
||||
}
|
||||
|
||||
pub type DecodeResult = Result<Vec<u8>, DecodeError>;
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum Format {
|
||||
Base32,
|
||||
Base64,
|
||||
Base64Url,
|
||||
Base32,
|
||||
Base32Hex,
|
||||
Base16,
|
||||
Base2Lsbf,
|
||||
Base2Msbf,
|
||||
Z85,
|
||||
}
|
||||
use self::Format::*;
|
||||
|
||||
pub fn encode(f: Format, input: &[u8]) -> String {
|
||||
match f {
|
||||
const BASE2LSBF: Encoding = new_encoding! {
|
||||
symbols: "01",
|
||||
bit_order: LeastSignificantFirst,
|
||||
};
|
||||
const BASE2MSBF: Encoding = new_encoding! {
|
||||
symbols: "01",
|
||||
bit_order: MostSignificantFirst,
|
||||
};
|
||||
|
||||
pub fn encode(f: Format, input: &[u8]) -> Result<String, EncodeError> {
|
||||
Ok(match f {
|
||||
Base32 => BASE32.encode(input),
|
||||
Base64 => BASE64.encode(input),
|
||||
}
|
||||
Base64Url => BASE64URL.encode(input),
|
||||
Base32Hex => BASE32HEX.encode(input),
|
||||
Base16 => HEXUPPER.encode(input),
|
||||
Base2Lsbf => BASE2LSBF.encode(input),
|
||||
Base2Msbf => BASE2MSBF.encode(input),
|
||||
Z85 => {
|
||||
// According to the spec we should not accept inputs whose len is not a multiple of 4.
|
||||
// However, the z85 crate implements a padded encoding and accepts such inputs. We have to manually check for them.
|
||||
if input.len() % 4 != 0 {
|
||||
return Err(EncodeError::Z85InputLenNotMultipleOf4);
|
||||
} else {
|
||||
z85::encode(input)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn decode(f: Format, input: &[u8]) -> DecodeResult {
|
||||
Ok(match f {
|
||||
Base32 => BASE32.decode(input)?,
|
||||
Base64 => BASE64.decode(input)?,
|
||||
Base64Url => BASE64URL.decode(input)?,
|
||||
Base32Hex => BASE32HEX.decode(input)?,
|
||||
Base16 => HEXUPPER.decode(input)?,
|
||||
Base2Lsbf => BASE2LSBF.decode(input)?,
|
||||
Base2Msbf => BASE2MSBF.decode(input)?,
|
||||
Z85 => {
|
||||
// The z85 crate implements a padded encoding by using a leading '#' which is otherwise not allowed.
|
||||
// We manually check for a leading '#' and return an error ourselves.
|
||||
if input.starts_with(&[b'#']) {
|
||||
return Err(z85::DecodeError::InvalidByte(0, b'#').into());
|
||||
} else {
|
||||
z85::decode(input)?
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -65,6 +115,12 @@ impl<R: Read> Data<R> {
|
|||
alphabet: match format {
|
||||
Base32 => b"ABCDEFGHIJKLMNOPQRSTUVWXYZ234567=",
|
||||
Base64 => b"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789=+/",
|
||||
Base64Url => b"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789=_-",
|
||||
Base32Hex => b"0123456789ABCDEFGHIJKLMNOPQRSTUV=",
|
||||
Base16 => b"0123456789ABCDEF",
|
||||
Base2Lsbf => b"01",
|
||||
Base2Msbf => b"01",
|
||||
Z85 => b"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ.-:+=^!/*?&<>()[]{}@%$#",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -90,7 +146,7 @@ impl<R: Read> Data<R> {
|
|||
decode(self.format, &buf)
|
||||
}
|
||||
|
||||
pub fn encode(&mut self) -> String {
|
||||
pub fn encode(&mut self) -> Result<String, EncodeError> {
|
||||
let mut buf: Vec<u8> = vec![];
|
||||
self.input.read_to_end(&mut buf).unwrap();
|
||||
encode(self.format, buf.as_slice())
|
||||
|
|
|
@ -37,7 +37,9 @@
|
|||
#[cfg(any(target_os = "freebsd", target_vendor = "apple"))]
|
||||
use libc::time_t;
|
||||
use libc::{c_char, c_int, gid_t, uid_t};
|
||||
use libc::{getgrgid, getgrnam, getgroups, getpwnam, getpwuid, group, passwd};
|
||||
#[cfg(not(target_os = "redox"))]
|
||||
use libc::{getgrgid, getgrnam, getgroups};
|
||||
use libc::{getpwnam, getpwuid, group, passwd};
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::ffi::{CStr, CString};
|
||||
|
@ -65,6 +67,7 @@ extern "C" {
|
|||
/// > supplementary group IDs for the process is returned. This allows
|
||||
/// > the caller to determine the size of a dynamically allocated list
|
||||
/// > to be used in a further call to getgroups().
|
||||
#[cfg(not(target_os = "redox"))]
|
||||
pub fn get_groups() -> IOResult<Vec<gid_t>> {
|
||||
let ngroups = unsafe { getgroups(0, ptr::null_mut()) };
|
||||
if ngroups == -1 {
|
||||
|
@ -104,7 +107,7 @@ pub fn get_groups() -> IOResult<Vec<gid_t>> {
|
|||
/// > groups is the same (in the mathematical sense of ``set''). (The
|
||||
/// > history of a process and its parents could affect the details of
|
||||
/// > the result.)
|
||||
#[cfg(all(unix, feature = "process"))]
|
||||
#[cfg(all(unix, not(target_os = "redox"), feature = "process"))]
|
||||
pub fn get_groups_gnu(arg_id: Option<u32>) -> IOResult<Vec<gid_t>> {
|
||||
let groups = get_groups()?;
|
||||
let egid = arg_id.unwrap_or_else(crate::features::process::getegid);
|
||||
|
@ -319,6 +322,7 @@ macro_rules! f {
|
|||
}
|
||||
|
||||
f!(getpwnam, getpwuid, uid_t, Passwd);
|
||||
#[cfg(not(target_os = "redox"))]
|
||||
f!(getgrnam, getgrgid, gid_t, Group);
|
||||
|
||||
#[inline]
|
||||
|
@ -326,6 +330,7 @@ pub fn uid2usr(id: uid_t) -> IOResult<String> {
|
|||
Passwd::locate(id).map(|p| p.name().into_owned())
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "redox"))]
|
||||
#[inline]
|
||||
pub fn gid2grp(id: gid_t) -> IOResult<String> {
|
||||
Group::locate(id).map(|p| p.name().into_owned())
|
||||
|
@ -336,6 +341,7 @@ pub fn usr2uid(name: &str) -> IOResult<uid_t> {
|
|||
Passwd::locate(name).map(|p| p.uid())
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "redox"))]
|
||||
#[inline]
|
||||
pub fn grp2gid(name: &str) -> IOResult<gid_t> {
|
||||
Group::locate(name).map(|p| p.gid())
|
||||
|
|
|
@ -15,8 +15,6 @@ use libc::{
|
|||
use std::borrow::Cow;
|
||||
use std::env;
|
||||
use std::fs;
|
||||
#[cfg(target_os = "redox")]
|
||||
use std::io;
|
||||
use std::io::Result as IOResult;
|
||||
use std::io::{Error, ErrorKind};
|
||||
#[cfg(any(unix, target_os = "redox"))]
|
||||
|
|
|
@ -94,7 +94,8 @@ pub use libc::statfs as StatFs;
|
|||
target_os = "netbsd",
|
||||
target_os = "openbsd",
|
||||
target_os = "bitrig",
|
||||
target_os = "dragonfly"
|
||||
target_os = "dragonfly",
|
||||
target_os = "redox"
|
||||
))]
|
||||
pub use libc::statvfs as StatFs;
|
||||
|
||||
|
@ -110,7 +111,8 @@ pub use libc::statfs as statfs_fn;
|
|||
target_os = "netbsd",
|
||||
target_os = "openbsd",
|
||||
target_os = "bitrig",
|
||||
target_os = "dragonfly"
|
||||
target_os = "dragonfly",
|
||||
target_os = "redox"
|
||||
))]
|
||||
pub use libc::statvfs as statfs_fn;
|
||||
|
||||
|
@ -438,6 +440,11 @@ pub fn read_fs_list() -> Vec<MountInfo> {
|
|||
}
|
||||
mounts
|
||||
}
|
||||
#[cfg(target_os = "redox")]
|
||||
{
|
||||
// No method to read mounts, yet
|
||||
Vec::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
|
|
@ -35,12 +35,6 @@ pub fn parse_symbolic(
|
|||
#[cfg(unix)]
|
||||
use libc::umask;
|
||||
|
||||
#[cfg(target_os = "redox")]
|
||||
unsafe fn umask(_mask: u32) -> u32 {
|
||||
// XXX Redox does not currently have umask
|
||||
0
|
||||
}
|
||||
|
||||
let (mask, pos) = parse_levels(mode);
|
||||
if pos == mode.len() {
|
||||
return Err(format!("invalid mode ({})", mode));
|
||||
|
|
|
@ -49,13 +49,14 @@ fn chgrp<P: AsRef<Path>>(path: P, gid: gid_t, follow: bool) -> IOResult<()> {
|
|||
pub fn wrap_chgrp<P: AsRef<Path>>(
|
||||
path: P,
|
||||
meta: &Metadata,
|
||||
dest_gid: gid_t,
|
||||
dest_gid: Option<gid_t>,
|
||||
follow: bool,
|
||||
verbosity: Verbosity,
|
||||
) -> Result<String, String> {
|
||||
use self::Verbosity::*;
|
||||
let path = path.as_ref();
|
||||
let mut out: String = String::new();
|
||||
let dest_gid = dest_gid.unwrap_or_else(|| meta.gid());
|
||||
|
||||
if let Err(e) = chgrp(path, dest_gid, follow) {
|
||||
match verbosity {
|
||||
|
|
|
@ -65,6 +65,7 @@ pub use crate::features::signals;
|
|||
#[cfg(all(
|
||||
unix,
|
||||
not(target_os = "fuchsia"),
|
||||
not(target_os = "redox"),
|
||||
not(target_env = "musl"),
|
||||
feature = "utmpx"
|
||||
))]
|
||||
|
|
|
@ -6,11 +6,11 @@
|
|||
//! This module provides types to reconcile these exit codes with idiomatic Rust error
|
||||
//! handling. This has a couple advantages over manually using [`std::process::exit`]:
|
||||
//! 1. It enables the use of `?`, `map_err`, `unwrap_or`, etc. in `uumain`.
|
||||
//! 1. It encourages the use of `UResult`/`Result` in functions in the utils.
|
||||
//! 1. It encourages the use of [`UResult`]/[`Result`] in functions in the utils.
|
||||
//! 1. The error messages are largely standardized across utils.
|
||||
//! 1. Standardized error messages can be created from external result types
|
||||
//! (i.e. [`std::io::Result`] & `clap::ClapResult`).
|
||||
//! 1. `set_exit_code` takes away the burden of manually tracking exit codes for non-fatal errors.
|
||||
//! 1. [`set_exit_code`] takes away the burden of manually tracking exit codes for non-fatal errors.
|
||||
//!
|
||||
//! # Usage
|
||||
//! The signature of a typical util should be:
|
||||
|
@ -19,7 +19,7 @@
|
|||
//! ...
|
||||
//! }
|
||||
//! ```
|
||||
//! [`UResult`] is a simple wrapper around [`Result`] with a custom error type: [`UError`]. The
|
||||
//! [`UResult`] is a simple wrapper around [`Result`] with a custom error trait: [`UError`]. The
|
||||
//! most important difference with types implementing [`std::error::Error`] is that [`UError`]s
|
||||
//! can specify the exit code of the program when they are returned from `uumain`:
|
||||
//! * When `Ok` is returned, the code set with [`set_exit_code`] is used as exit code. If
|
||||
|
@ -41,13 +41,15 @@
|
|||
//! [`set_exit_code`]. See the documentation on that function for more information.
|
||||
//!
|
||||
//! # Guidelines
|
||||
//! * Use common errors where possible.
|
||||
//! * Add variants to [`UCommonError`] if an error appears in multiple utils.
|
||||
//! * Use error types from `uucore` where possible.
|
||||
//! * Add error types to `uucore` if an error appears in multiple utils.
|
||||
//! * Prefer proper custom error types over [`ExitCode`] and [`USimpleError`].
|
||||
//! * [`USimpleError`] may be used in small utils with simple error handling.
|
||||
//! * Using [`ExitCode`] is not recommended but can be useful for converting utils to use
|
||||
//! [`UResult`].
|
||||
|
||||
// spell-checker:ignore uioerror
|
||||
|
||||
use std::{
|
||||
error::Error,
|
||||
fmt::{Display, Formatter},
|
||||
|
@ -85,115 +87,10 @@ pub fn set_exit_code(code: i32) {
|
|||
EXIT_CODE.store(code, Ordering::SeqCst);
|
||||
}
|
||||
|
||||
/// Should be returned by all utils.
|
||||
///
|
||||
/// Two additional methods are implemented on [`UResult`] on top of the normal [`Result`] methods:
|
||||
/// `map_err_code` & `map_err_code_message`.
|
||||
///
|
||||
/// These methods are used to convert [`UCommonError`]s into errors with a custom error code and
|
||||
/// message.
|
||||
pub type UResult<T> = Result<T, UError>;
|
||||
/// Result type that should be returned by all utils.
|
||||
pub type UResult<T> = Result<T, Box<dyn UError>>;
|
||||
|
||||
trait UResultTrait<T> {
|
||||
fn map_err_code(self, mapper: fn(&UCommonError) -> Option<i32>) -> Self;
|
||||
fn map_err_code_and_message(self, mapper: fn(&UCommonError) -> Option<(i32, String)>) -> Self;
|
||||
}
|
||||
|
||||
impl<T> UResultTrait<T> for UResult<T> {
|
||||
fn map_err_code(self, mapper: fn(&UCommonError) -> Option<i32>) -> Self {
|
||||
if let Err(UError::Common(error)) = self {
|
||||
if let Some(code) = mapper(&error) {
|
||||
Err(UCommonErrorWithCode { code, error }.into())
|
||||
} else {
|
||||
Err(error.into())
|
||||
}
|
||||
} else {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
fn map_err_code_and_message(self, mapper: fn(&UCommonError) -> Option<(i32, String)>) -> Self {
|
||||
if let Err(UError::Common(ref error)) = self {
|
||||
if let Some((code, message)) = mapper(error) {
|
||||
return Err(USimpleError { code, message }.into());
|
||||
}
|
||||
}
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// The error type of [`UResult`].
|
||||
///
|
||||
/// `UError::Common` errors are defined in [`uucore`](crate) while `UError::Custom` errors are
|
||||
/// defined by the utils.
|
||||
/// ```
|
||||
/// use uucore::error::USimpleError;
|
||||
/// let err = USimpleError::new(1, "Error!!".into());
|
||||
/// assert_eq!(1, err.code());
|
||||
/// assert_eq!(String::from("Error!!"), format!("{}", err));
|
||||
/// ```
|
||||
pub enum UError {
|
||||
Common(UCommonError),
|
||||
Custom(Box<dyn UCustomError>),
|
||||
}
|
||||
|
||||
impl UError {
|
||||
/// The error code of [`UResult`]
|
||||
///
|
||||
/// This function defines the error code associated with an instance of
|
||||
/// [`UResult`]. To associate error codes for self-defined instances of
|
||||
/// `UResult::Custom` (i.e. [`UCustomError`]), implement the
|
||||
/// [`code`-function there](UCustomError::code).
|
||||
pub fn code(&self) -> i32 {
|
||||
match self {
|
||||
UError::Common(e) => e.code(),
|
||||
UError::Custom(e) => e.code(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether to print usage help for a [`UResult`]
|
||||
///
|
||||
/// Defines if a variant of [`UResult`] should print a short usage message
|
||||
/// below the error. The usage message is printed when this function returns
|
||||
/// `true`. To do this for self-defined instances of `UResult::Custom` (i.e.
|
||||
/// [`UCustomError`]), implement the [`usage`-function
|
||||
/// there](UCustomError::usage).
|
||||
pub fn usage(&self) -> bool {
|
||||
match self {
|
||||
UError::Common(e) => e.usage(),
|
||||
UError::Custom(e) => e.usage(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<UCommonError> for UError {
|
||||
fn from(v: UCommonError) -> Self {
|
||||
UError::Common(v)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<i32> for UError {
|
||||
fn from(v: i32) -> Self {
|
||||
UError::Custom(Box::new(ExitCode(v)))
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: UCustomError + 'static> From<E> for UError {
|
||||
fn from(v: E) -> Self {
|
||||
UError::Custom(Box::new(v) as Box<dyn UCustomError>)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for UError {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
UError::Common(e) => e.fmt(f),
|
||||
UError::Custom(e) => e.fmt(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Custom errors defined by the utils.
|
||||
/// Custom errors defined by the utils and `uucore`.
|
||||
///
|
||||
/// All errors should implement [`std::error::Error`], [`std::fmt::Display`] and
|
||||
/// [`std::fmt::Debug`] and have an additional `code` method that specifies the
|
||||
|
@ -202,7 +99,7 @@ impl Display for UError {
|
|||
/// An example of a custom error from `ls`:
|
||||
///
|
||||
/// ```
|
||||
/// use uucore::error::{UCustomError, UResult};
|
||||
/// use uucore::error::{UError, UResult};
|
||||
/// use std::{
|
||||
/// error::Error,
|
||||
/// fmt::{Display, Debug},
|
||||
|
@ -215,7 +112,7 @@ impl Display for UError {
|
|||
/// NoMetadata(PathBuf),
|
||||
/// }
|
||||
///
|
||||
/// impl UCustomError for LsError {
|
||||
/// impl UError for LsError {
|
||||
/// fn code(&self) -> i32 {
|
||||
/// match self {
|
||||
/// LsError::InvalidLineWidth(_) => 2,
|
||||
|
@ -246,12 +143,12 @@ impl Display for UError {
|
|||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// The call to `into()` is required to convert the [`UCustomError`] to an
|
||||
/// instance of [`UError`].
|
||||
/// The call to `into()` is required to convert the `LsError` to
|
||||
/// [`Box<dyn UError>`]. The implementation for `From` is provided automatically.
|
||||
///
|
||||
/// A crate like [`quick_error`](https://crates.io/crates/quick-error) might
|
||||
/// also be used, but will still require an `impl` for the `code` method.
|
||||
pub trait UCustomError: Error + Send {
|
||||
pub trait UError: Error + Send {
|
||||
/// Error code of a custom error.
|
||||
///
|
||||
/// Set a return value for each variant of an enum-type to associate an
|
||||
|
@ -261,7 +158,7 @@ pub trait UCustomError: Error + Send {
|
|||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use uucore::error::{UCustomError};
|
||||
/// use uucore::error::{UError};
|
||||
/// use std::{
|
||||
/// error::Error,
|
||||
/// fmt::{Display, Debug},
|
||||
|
@ -275,7 +172,7 @@ pub trait UCustomError: Error + Send {
|
|||
/// Bing(),
|
||||
/// }
|
||||
///
|
||||
/// impl UCustomError for MyError {
|
||||
/// impl UError for MyError {
|
||||
/// fn code(&self) -> i32 {
|
||||
/// match self {
|
||||
/// MyError::Foo(_) => 2,
|
||||
|
@ -312,7 +209,7 @@ pub trait UCustomError: Error + Send {
|
|||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use uucore::error::{UCustomError};
|
||||
/// use uucore::error::{UError};
|
||||
/// use std::{
|
||||
/// error::Error,
|
||||
/// fmt::{Display, Debug},
|
||||
|
@ -326,7 +223,7 @@ pub trait UCustomError: Error + Send {
|
|||
/// Bing(),
|
||||
/// }
|
||||
///
|
||||
/// impl UCustomError for MyError {
|
||||
/// impl UError for MyError {
|
||||
/// fn usage(&self) -> bool {
|
||||
/// match self {
|
||||
/// // This will have a short usage help appended
|
||||
|
@ -355,47 +252,23 @@ pub trait UCustomError: Error + Send {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<Box<dyn UCustomError>> for i32 {
|
||||
fn from(e: Box<dyn UCustomError>) -> i32 {
|
||||
e.code()
|
||||
impl<T> From<T> for Box<dyn UError>
|
||||
where
|
||||
T: UError + 'static,
|
||||
{
|
||||
fn from(t: T) -> Box<dyn UError> {
|
||||
Box::new(t)
|
||||
}
|
||||
}
|
||||
|
||||
/// A [`UCommonError`] with an overridden exit code.
|
||||
/// A simple error type with an exit code and a message that implements [`UError`].
|
||||
///
|
||||
/// This exit code is returned instead of the default exit code for the [`UCommonError`]. This is
|
||||
/// typically created with the either the `UResult::map_err_code` or `UCommonError::with_code`
|
||||
/// method.
|
||||
#[derive(Debug)]
|
||||
pub struct UCommonErrorWithCode {
|
||||
code: i32,
|
||||
error: UCommonError,
|
||||
}
|
||||
|
||||
impl Error for UCommonErrorWithCode {}
|
||||
|
||||
impl Display for UCommonErrorWithCode {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
|
||||
self.error.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl UCustomError for UCommonErrorWithCode {
|
||||
fn code(&self) -> i32 {
|
||||
self.code
|
||||
}
|
||||
}
|
||||
|
||||
/// A simple error type with an exit code and a message that implements [`UCustomError`].
|
||||
///
|
||||
/// It is typically created with the `UResult::map_err_code_and_message` method. Alternatively, it
|
||||
/// can be constructed by manually:
|
||||
/// ```
|
||||
/// use uucore::error::{UResult, USimpleError};
|
||||
/// let err = USimpleError { code: 1, message: "error!".into()};
|
||||
/// let res: UResult<()> = Err(err.into());
|
||||
/// // or using the `new` method:
|
||||
/// let res: UResult<()> = Err(USimpleError::new(1, "error!".into()));
|
||||
/// let res: UResult<()> = Err(USimpleError::new(1, "error!"));
|
||||
/// ```
|
||||
#[derive(Debug)]
|
||||
pub struct USimpleError {
|
||||
|
@ -405,8 +278,11 @@ pub struct USimpleError {
|
|||
|
||||
impl USimpleError {
|
||||
#[allow(clippy::new_ret_no_self)]
|
||||
pub fn new(code: i32, message: String) -> UError {
|
||||
UError::Custom(Box::new(Self { code, message }))
|
||||
pub fn new<S: Into<String>>(code: i32, message: S) -> Box<dyn UError> {
|
||||
Box::new(Self {
|
||||
code,
|
||||
message: message.into(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -418,7 +294,7 @@ impl Display for USimpleError {
|
|||
}
|
||||
}
|
||||
|
||||
impl UCustomError for USimpleError {
|
||||
impl UError for USimpleError {
|
||||
fn code(&self) -> i32 {
|
||||
self.code
|
||||
}
|
||||
|
@ -432,8 +308,11 @@ pub struct UUsageError {
|
|||
|
||||
impl UUsageError {
|
||||
#[allow(clippy::new_ret_no_self)]
|
||||
pub fn new(code: i32, message: String) -> UError {
|
||||
UError::Custom(Box::new(Self { code, message }))
|
||||
pub fn new<S: Into<String>>(code: i32, message: S) -> Box<dyn UError> {
|
||||
Box::new(Self {
|
||||
code,
|
||||
message: message.into(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -445,7 +324,7 @@ impl Display for UUsageError {
|
|||
}
|
||||
}
|
||||
|
||||
impl UCustomError for UUsageError {
|
||||
impl UError for UUsageError {
|
||||
fn code(&self) -> i32 {
|
||||
self.code
|
||||
}
|
||||
|
@ -463,13 +342,13 @@ impl UCustomError for UUsageError {
|
|||
/// There are two ways to construct this type: with [`UIoError::new`] or by calling the
|
||||
/// [`FromIo::map_err_context`] method on a [`std::io::Result`] or [`std::io::Error`].
|
||||
/// ```
|
||||
/// use uucore::error::{FromIo, UResult, UIoError, UCommonError};
|
||||
/// use uucore::error::{FromIo, UResult, UIoError, UError};
|
||||
/// use std::fs::File;
|
||||
/// use std::path::Path;
|
||||
/// let path = Path::new("test.txt");
|
||||
///
|
||||
/// // Manual construction
|
||||
/// let e: UIoError = UIoError::new(
|
||||
/// let e: Box<dyn UError> = UIoError::new(
|
||||
/// std::io::ErrorKind::NotFound,
|
||||
/// format!("cannot access '{}'", path.display())
|
||||
/// );
|
||||
|
@ -485,22 +364,17 @@ pub struct UIoError {
|
|||
}
|
||||
|
||||
impl UIoError {
|
||||
pub fn new(kind: std::io::ErrorKind, context: String) -> Self {
|
||||
Self {
|
||||
context,
|
||||
#[allow(clippy::new_ret_no_self)]
|
||||
pub fn new<S: Into<String>>(kind: std::io::ErrorKind, context: S) -> Box<dyn UError> {
|
||||
Box::new(Self {
|
||||
context: context.into(),
|
||||
inner: std::io::Error::new(kind, ""),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn code(&self) -> i32 {
|
||||
1
|
||||
}
|
||||
|
||||
pub fn usage(&self) -> bool {
|
||||
false
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl UError for UIoError {}
|
||||
|
||||
impl Error for UIoError {}
|
||||
|
||||
impl Display for UIoError {
|
||||
|
@ -535,89 +409,102 @@ impl Display for UIoError {
|
|||
}
|
||||
}
|
||||
|
||||
/// Enables the conversion from `std::io::Error` to `UError` and from `std::io::Result` to
|
||||
/// `UResult`.
|
||||
/// Enables the conversion from [`std::io::Error`] to [`UError`] and from [`std::io::Result`] to
|
||||
/// [`UResult`].
|
||||
pub trait FromIo<T> {
|
||||
fn map_err_context(self, context: impl FnOnce() -> String) -> T;
|
||||
}
|
||||
|
||||
impl FromIo<UIoError> for std::io::Error {
|
||||
fn map_err_context(self, context: impl FnOnce() -> String) -> UIoError {
|
||||
UIoError {
|
||||
impl FromIo<Box<UIoError>> for std::io::Error {
|
||||
fn map_err_context(self, context: impl FnOnce() -> String) -> Box<UIoError> {
|
||||
Box::new(UIoError {
|
||||
context: (context)(),
|
||||
inner: self,
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> FromIo<UResult<T>> for std::io::Result<T> {
|
||||
fn map_err_context(self, context: impl FnOnce() -> String) -> UResult<T> {
|
||||
self.map_err(|e| UError::Common(UCommonError::Io(e.map_err_context(context))))
|
||||
self.map_err(|e| e.map_err_context(context) as Box<dyn UError>)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromIo<UIoError> for std::io::ErrorKind {
|
||||
fn map_err_context(self, context: impl FnOnce() -> String) -> UIoError {
|
||||
UIoError {
|
||||
impl FromIo<Box<UIoError>> for std::io::ErrorKind {
|
||||
fn map_err_context(self, context: impl FnOnce() -> String) -> Box<UIoError> {
|
||||
Box::new(UIoError {
|
||||
context: (context)(),
|
||||
inner: std::io::Error::new(self, ""),
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl From<UIoError> for UCommonError {
|
||||
fn from(e: UIoError) -> UCommonError {
|
||||
UCommonError::Io(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<UIoError> for UError {
|
||||
fn from(e: UIoError) -> UError {
|
||||
let common: UCommonError = e.into();
|
||||
common.into()
|
||||
}
|
||||
}
|
||||
|
||||
/// Common errors for utilities.
|
||||
/// Shorthand to construct [`UIoError`]-instances.
|
||||
///
|
||||
/// If identical errors appear across multiple utilities, they should be added here.
|
||||
#[derive(Debug)]
|
||||
pub enum UCommonError {
|
||||
Io(UIoError),
|
||||
// Clap(UClapError),
|
||||
}
|
||||
|
||||
impl UCommonError {
|
||||
pub fn with_code(self, code: i32) -> UCommonErrorWithCode {
|
||||
UCommonErrorWithCode { code, error: self }
|
||||
}
|
||||
|
||||
pub fn code(&self) -> i32 {
|
||||
1
|
||||
}
|
||||
|
||||
pub fn usage(&self) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl From<UCommonError> for i32 {
|
||||
fn from(common: UCommonError) -> i32 {
|
||||
match common {
|
||||
UCommonError::Io(e) => e.code(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for UCommonError {}
|
||||
|
||||
impl Display for UCommonError {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
|
||||
match self {
|
||||
UCommonError::Io(e) => e.fmt(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
/// This macro serves as a convenience call to quickly construct instances of
|
||||
/// [`UIoError`]. It takes:
|
||||
///
|
||||
/// - An instance of [`std::io::Error`]
|
||||
/// - A `format!`-compatible string and
|
||||
/// - An arbitrary number of arguments to the format string
|
||||
///
|
||||
/// In exactly this order. It is equivalent to the more verbose code seen in the
|
||||
/// example.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use uucore::error::UIoError;
|
||||
/// use uucore::uio_error;
|
||||
///
|
||||
/// let io_err = std::io::Error::new(
|
||||
/// std::io::ErrorKind::PermissionDenied, "fix me please!"
|
||||
/// );
|
||||
///
|
||||
/// let uio_err = UIoError::new(
|
||||
/// io_err.kind(),
|
||||
/// format!("Error code: {}", 2)
|
||||
/// );
|
||||
///
|
||||
/// let other_uio_err = uio_error!(io_err, "Error code: {}", 2);
|
||||
///
|
||||
/// // prints "fix me please!: Permission denied"
|
||||
/// println!("{}", uio_err);
|
||||
/// // prints "Error code: 2: Permission denied"
|
||||
/// println!("{}", other_uio_err);
|
||||
/// ```
|
||||
///
|
||||
/// The [`std::fmt::Display`] impl of [`UIoError`] will then ensure that an
|
||||
/// appropriate error message relating to the actual error kind of the
|
||||
/// [`std::io::Error`] is appended to whatever error message is defined in
|
||||
/// addition (as secondary argument).
|
||||
///
|
||||
/// If you want to show only the error message for the [`std::io::ErrorKind`]
|
||||
/// that's contained in [`UIoError`], pass the second argument as empty string:
|
||||
///
|
||||
/// ```
|
||||
/// use uucore::error::UIoError;
|
||||
/// use uucore::uio_error;
|
||||
///
|
||||
/// let io_err = std::io::Error::new(
|
||||
/// std::io::ErrorKind::PermissionDenied, "fix me please!"
|
||||
/// );
|
||||
///
|
||||
/// let other_uio_err = uio_error!(io_err, "");
|
||||
///
|
||||
/// // prints: ": Permission denied"
|
||||
/// println!("{}", other_uio_err);
|
||||
/// ```
|
||||
//#[macro_use]
|
||||
#[macro_export]
|
||||
macro_rules! uio_error(
|
||||
($err:expr, $($args:tt)+) => ({
|
||||
UIoError::new(
|
||||
$err.kind(),
|
||||
format!($($args)+)
|
||||
)
|
||||
})
|
||||
);
|
||||
|
||||
/// A special error type that does not print any message when returned from
|
||||
/// `uumain`. Especially useful for porting utilities to using [`UResult`].
|
||||
|
@ -636,6 +523,13 @@ impl Display for UCommonError {
|
|||
#[derive(Debug)]
|
||||
pub struct ExitCode(pub i32);
|
||||
|
||||
impl ExitCode {
|
||||
#[allow(clippy::new_ret_no_self)]
|
||||
pub fn new(code: i32) -> Box<dyn UError> {
|
||||
Box::new(Self(code))
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for ExitCode {}
|
||||
|
||||
impl Display for ExitCode {
|
||||
|
@ -644,8 +538,14 @@ impl Display for ExitCode {
|
|||
}
|
||||
}
|
||||
|
||||
impl UCustomError for ExitCode {
|
||||
impl UError for ExitCode {
|
||||
fn code(&self) -> i32 {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<i32> for Box<dyn UError> {
|
||||
fn from(i: i32) -> Self {
|
||||
ExitCode::new(i)
|
||||
}
|
||||
}
|
||||
|
|
16
tests/by-util/test_basenc.rs
Normal file
16
tests/by-util/test_basenc.rs
Normal file
|
@ -0,0 +1,16 @@
|
|||
use crate::common::util::*;
|
||||
|
||||
#[test]
|
||||
fn test_z85_not_padded() {
|
||||
// The z85 crate deviates from the standard in some cases; we have to catch those
|
||||
new_ucmd!()
|
||||
.args(&["--z85", "-d"])
|
||||
.pipe_in("##########")
|
||||
.fails()
|
||||
.stderr_only("basenc: error: invalid input");
|
||||
new_ucmd!()
|
||||
.args(&["--z85"])
|
||||
.pipe_in("123")
|
||||
.fails()
|
||||
.stderr_only("basenc: error: invalid input (length must be multiple of 4 characters)");
|
||||
}
|
|
@ -1,5 +1,4 @@
|
|||
use crate::common::util::*;
|
||||
#[cfg(unix)]
|
||||
use std::fs::OpenOptions;
|
||||
#[cfg(unix)]
|
||||
use std::io::Read;
|
||||
|
@ -274,6 +273,26 @@ fn test_stdin_show_ends() {
|
|||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn squeeze_all_files() {
|
||||
// empty lines at the end of a file are "squeezed" together with empty lines at the beginning
|
||||
let (at, mut ucmd) = at_and_ucmd!();
|
||||
at.write("input1", "a\n\n");
|
||||
at.write("input2", "\n\nb");
|
||||
ucmd.args(&["input1", "input2", "-s"])
|
||||
.succeeds()
|
||||
.stdout_only("a\n\nb");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_show_ends_crlf() {
|
||||
new_ucmd!()
|
||||
.arg("-E")
|
||||
.pipe_in("a\nb\r\n\rc\n\r\n\r")
|
||||
.succeeds()
|
||||
.stdout_only("a$\nb^M$\n\rc$\n^M$\n\r");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_stdin_show_all() {
|
||||
for same_param in &["-A", "--show-all"] {
|
||||
|
@ -443,3 +462,49 @@ fn test_domain_socket() {
|
|||
|
||||
thread.join().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_write_to_self_empty() {
|
||||
// it's ok if the input file is also the output file if it's empty
|
||||
let s = TestScenario::new(util_name!());
|
||||
let file_path = s.fixtures.plus("file.txt");
|
||||
|
||||
let file = OpenOptions::new()
|
||||
.create_new(true)
|
||||
.write(true)
|
||||
.append(true)
|
||||
.open(&file_path)
|
||||
.unwrap();
|
||||
|
||||
s.ucmd().set_stdout(file).arg(&file_path).succeeds();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_write_to_self() {
|
||||
let s = TestScenario::new(util_name!());
|
||||
let file_path = s.fixtures.plus("first_file");
|
||||
s.fixtures.write("second_file", "second_file_content.");
|
||||
|
||||
let file = OpenOptions::new()
|
||||
.create_new(true)
|
||||
.write(true)
|
||||
.append(true)
|
||||
.open(&file_path)
|
||||
.unwrap();
|
||||
|
||||
s.fixtures.append("first_file", "first_file_content.");
|
||||
|
||||
s.ucmd()
|
||||
.set_stdout(file)
|
||||
.arg("first_file")
|
||||
.arg("first_file")
|
||||
.arg("second_file")
|
||||
.fails()
|
||||
.code_is(2)
|
||||
.stderr_only("cat: first_file: input file is output file\ncat: first_file: input file is output file");
|
||||
|
||||
assert_eq!(
|
||||
s.fixtures.read("first_file"),
|
||||
"first_file_content.second_file_content."
|
||||
);
|
||||
}
|
||||
|
|
469
tests/by-util/test_chcon.rs
Normal file
469
tests/by-util/test_chcon.rs
Normal file
|
@ -0,0 +1,469 @@
|
|||
// spell-checker:ignore (jargon) xattributes
|
||||
|
||||
#![cfg(feature = "feat_selinux")]
|
||||
|
||||
use std::ffi::CString;
|
||||
use std::path::Path;
|
||||
use std::{io, iter, str};
|
||||
|
||||
use crate::common::util::*;
|
||||
|
||||
#[test]
|
||||
fn version() {
|
||||
new_ucmd!().arg("--version").succeeds();
|
||||
new_ucmd!().arg("-V").succeeds();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn help() {
|
||||
new_ucmd!().fails();
|
||||
new_ucmd!().arg("--help").succeeds();
|
||||
new_ucmd!().arg("-h").fails(); // -h is NOT --help, it is actually --no-dereference.
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reference_errors() {
|
||||
for args in &[
|
||||
&["--verbose", "--reference"] as &[&str],
|
||||
&["--verbose", "--reference=/dev/null"],
|
||||
&["--verbose", "--reference=/inexistent", "/dev/null"],
|
||||
] {
|
||||
new_ucmd!().args(args).fails();
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn recursive_errors() {
|
||||
for args in &[
|
||||
&["--verbose", "-P"] as &[&str],
|
||||
&["--verbose", "-H"],
|
||||
&["--verbose", "-L"],
|
||||
&["--verbose", "--recursive", "-P", "--dereference"],
|
||||
&["--verbose", "--recursive", "-H", "--no-dereference"],
|
||||
&["--verbose", "--recursive", "-L", "--no-dereference"],
|
||||
] {
|
||||
new_ucmd!().args(args).fails();
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn valid_context() {
|
||||
let (dir, mut cmd) = at_and_ucmd!();
|
||||
dir.touch("a.tmp");
|
||||
dir.symlink_file("a.tmp", "la.tmp");
|
||||
|
||||
let la_context = get_file_context(dir.plus("a.tmp")).unwrap();
|
||||
let new_la_context = "guest_u:object_r:etc_t:s0:c42";
|
||||
|
||||
cmd.args(&["--verbose", new_la_context])
|
||||
.arg(dir.plus("la.tmp"))
|
||||
.succeeds();
|
||||
assert_eq!(get_file_context(dir.plus("la.tmp")).unwrap(), la_context);
|
||||
assert_eq!(
|
||||
get_file_context(dir.plus("a.tmp")).unwrap().as_deref(),
|
||||
Some(new_la_context)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn valid_context_on_valid_symlink() {
|
||||
let (dir, mut cmd) = at_and_ucmd!();
|
||||
dir.touch("a.tmp");
|
||||
dir.symlink_file("a.tmp", "la.tmp");
|
||||
|
||||
let a_context = get_file_context(dir.plus("a.tmp")).unwrap();
|
||||
let new_la_context = "guest_u:object_r:etc_t:s0:c42";
|
||||
|
||||
cmd.args(&["--verbose", "--no-dereference", new_la_context])
|
||||
.arg(dir.plus("la.tmp"))
|
||||
.succeeds();
|
||||
assert_eq!(
|
||||
get_file_context(dir.plus("la.tmp")).unwrap().as_deref(),
|
||||
Some(new_la_context)
|
||||
);
|
||||
assert_eq!(get_file_context(dir.plus("a.tmp")).unwrap(), a_context);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn valid_context_on_broken_symlink() {
|
||||
let (dir, mut cmd) = at_and_ucmd!();
|
||||
dir.symlink_file("a.tmp", "la.tmp");
|
||||
|
||||
let new_la_context = "guest_u:object_r:etc_t:s0:c42";
|
||||
|
||||
cmd.args(&["--verbose", "--no-dereference", new_la_context])
|
||||
.arg(dir.plus("la.tmp"))
|
||||
.succeeds();
|
||||
assert_eq!(
|
||||
get_file_context(dir.plus("la.tmp")).unwrap().as_deref(),
|
||||
Some(new_la_context)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn valid_context_with_prior_xattributes() {
|
||||
let (dir, mut cmd) = at_and_ucmd!();
|
||||
dir.touch("a.tmp");
|
||||
|
||||
let a_context = get_file_context(dir.plus("a.tmp")).unwrap();
|
||||
if a_context.is_none() {
|
||||
set_file_context(dir.plus("a.tmp"), "unconfined_u:object_r:user_tmp_t:s0").unwrap();
|
||||
}
|
||||
let new_la_context = "guest_u:object_r:etc_t:s0:c42";
|
||||
|
||||
cmd.args(&["--verbose", new_la_context])
|
||||
.arg(dir.plus("a.tmp"))
|
||||
.succeeds();
|
||||
assert_eq!(
|
||||
get_file_context(dir.plus("a.tmp")).unwrap().as_deref(),
|
||||
Some(new_la_context)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn valid_context_directory() {
|
||||
let (dir, mut cmd) = at_and_ucmd!();
|
||||
dir.mkdir("a");
|
||||
dir.symlink_dir("a", "la");
|
||||
|
||||
let b_path = Path::new("a").join("b.txt");
|
||||
dir.touch(b_path.to_str().unwrap());
|
||||
|
||||
let la_context = get_file_context(dir.plus("la")).unwrap();
|
||||
let b_context = get_file_context(dir.plus(b_path.to_str().unwrap())).unwrap();
|
||||
|
||||
let new_la_context = "guest_u:object_r:etc_t:s0:c42";
|
||||
|
||||
cmd.args(&["--verbose", new_la_context])
|
||||
.arg(dir.plus("la"))
|
||||
.succeeds();
|
||||
assert_eq!(get_file_context(dir.plus("la")).unwrap(), la_context);
|
||||
assert_eq!(
|
||||
get_file_context(dir.plus("a")).unwrap().as_deref(),
|
||||
Some(new_la_context)
|
||||
);
|
||||
assert_eq!(
|
||||
get_file_context(dir.plus(b_path.to_str().unwrap())).unwrap(),
|
||||
b_context
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn valid_context_directory_recursive() {
|
||||
let (dir, mut cmd) = at_and_ucmd!();
|
||||
dir.mkdir("a");
|
||||
dir.symlink_dir("a", "la");
|
||||
|
||||
let b_path = Path::new("a").join("b.txt");
|
||||
dir.touch(b_path.to_str().unwrap());
|
||||
|
||||
let a_context = get_file_context(dir.plus("a")).unwrap();
|
||||
let b_context = get_file_context(dir.plus(b_path.to_str().unwrap())).unwrap();
|
||||
|
||||
let new_la_context = "guest_u:object_r:etc_t:s0:c42";
|
||||
|
||||
// -P (default): do not traverse any symbolic links.
|
||||
cmd.args(&["--verbose", "--recursive", new_la_context])
|
||||
.arg(dir.plus("la"))
|
||||
.succeeds();
|
||||
assert_eq!(
|
||||
get_file_context(dir.plus("la")).unwrap().as_deref(),
|
||||
Some(new_la_context)
|
||||
);
|
||||
assert_eq!(get_file_context(dir.plus("a")).unwrap(), a_context);
|
||||
assert_eq!(
|
||||
get_file_context(dir.plus(b_path.to_str().unwrap())).unwrap(),
|
||||
b_context
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn valid_context_directory_recursive_follow_args_dir_symlinks() {
|
||||
let (dir, mut cmd) = at_and_ucmd!();
|
||||
dir.mkdir("a");
|
||||
dir.symlink_dir("a", "la");
|
||||
|
||||
let b_path = Path::new("a").join("b.txt");
|
||||
dir.touch(b_path.to_str().unwrap());
|
||||
|
||||
let la_context = get_file_context(dir.plus("la")).unwrap();
|
||||
let new_la_context = "guest_u:object_r:etc_t:s0:c42";
|
||||
|
||||
/*
|
||||
let lc_path = Path::new("a").join("lc");
|
||||
dir.symlink_dir("c", lc_path.to_str().unwrap());
|
||||
assert_eq!(
|
||||
get_file_context(dir.plus(lc_path.to_str().unwrap())).unwrap(),
|
||||
None
|
||||
);
|
||||
*/
|
||||
|
||||
// -H: if a command line argument is a symbolic link to a directory, traverse it.
|
||||
cmd.args(&["--verbose", "--recursive", "-H", new_la_context])
|
||||
.arg(dir.plus("la"))
|
||||
.succeeds();
|
||||
assert_eq!(
|
||||
get_file_context(dir.plus("a")).unwrap().as_deref(),
|
||||
Some(new_la_context)
|
||||
);
|
||||
assert_eq!(
|
||||
get_file_context(dir.plus(b_path.to_str().unwrap()))
|
||||
.unwrap()
|
||||
.as_deref(),
|
||||
Some(new_la_context)
|
||||
);
|
||||
assert_eq!(get_file_context(dir.plus("la")).unwrap(), la_context);
|
||||
/*
|
||||
assert_eq!(
|
||||
get_file_context(dir.plus(lc_path.to_str().unwrap()))
|
||||
.unwrap()
|
||||
.as_deref(),
|
||||
Some(new_la_context)
|
||||
);
|
||||
*/
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn valid_context_directory_recursive_follow_all_symlinks() {
|
||||
let (dir, mut cmd) = at_and_ucmd!();
|
||||
dir.mkdir("a");
|
||||
dir.symlink_dir("a", "la");
|
||||
|
||||
let b_path = Path::new("a").join("b.txt");
|
||||
dir.touch(b_path.to_str().unwrap());
|
||||
|
||||
let c_path = Path::new("a").join("c");
|
||||
dir.touch(c_path.to_str().unwrap());
|
||||
|
||||
let lc_path = Path::new("a").join("lc");
|
||||
dir.symlink_dir(c_path.to_str().unwrap(), lc_path.to_str().unwrap());
|
||||
|
||||
let la_context = get_file_context(dir.plus("la")).unwrap();
|
||||
let lc_context = get_file_context(dir.plus(lc_path.to_str().unwrap())).unwrap();
|
||||
|
||||
let new_la_context = "guest_u:object_r:etc_t:s0:c42";
|
||||
|
||||
// -L: traverse every symbolic link to a directory encountered.
|
||||
cmd.args(&["--verbose", "--recursive", "-L", new_la_context])
|
||||
.arg(dir.plus("la"))
|
||||
.succeeds();
|
||||
assert_eq!(get_file_context(dir.plus("la")).unwrap(), la_context);
|
||||
assert_eq!(
|
||||
get_file_context(dir.plus("a")).unwrap().as_deref(),
|
||||
Some(new_la_context)
|
||||
);
|
||||
assert_eq!(
|
||||
get_file_context(dir.plus(b_path.to_str().unwrap()))
|
||||
.unwrap()
|
||||
.as_deref(),
|
||||
Some(new_la_context)
|
||||
);
|
||||
assert_eq!(
|
||||
get_file_context(dir.plus(lc_path.to_str().unwrap())).unwrap(),
|
||||
lc_context
|
||||
);
|
||||
assert_eq!(
|
||||
get_file_context(dir.plus(c_path.to_str().unwrap()))
|
||||
.unwrap()
|
||||
.as_deref(),
|
||||
Some(new_la_context)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn user_role_range_type() {
|
||||
let (dir, mut cmd) = at_and_ucmd!();
|
||||
|
||||
dir.touch("a.tmp");
|
||||
let a_context = get_file_context(dir.plus("a.tmp")).unwrap();
|
||||
if a_context.is_none() {
|
||||
set_file_context(dir.plus("a.tmp"), "unconfined_u:object_r:user_tmp_t:s0").unwrap();
|
||||
}
|
||||
|
||||
cmd.args(&[
|
||||
"--verbose",
|
||||
"--user=guest_u",
|
||||
"--role=object_r",
|
||||
"--type=etc_t",
|
||||
"--range=s0:c42",
|
||||
])
|
||||
.arg(dir.plus("a.tmp"))
|
||||
.succeeds();
|
||||
|
||||
assert_eq!(
|
||||
get_file_context(dir.plus("a.tmp")).unwrap().as_deref(),
|
||||
Some("guest_u:object_r:etc_t:s0:c42")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn user_change() {
|
||||
let (dir, mut cmd) = at_and_ucmd!();
|
||||
|
||||
dir.touch("a.tmp");
|
||||
let a_context = get_file_context(dir.plus("a.tmp")).unwrap();
|
||||
let new_a_context = if let Some(a_context) = a_context {
|
||||
let mut components: Vec<_> = a_context.split(':').collect();
|
||||
components[0] = "guest_u";
|
||||
components.join(":")
|
||||
} else {
|
||||
set_file_context(dir.plus("a.tmp"), "unconfined_u:object_r:user_tmp_t:s0").unwrap();
|
||||
String::from("guest_u:object_r:user_tmp_t:s0")
|
||||
};
|
||||
|
||||
cmd.args(&["--verbose", "--user=guest_u"])
|
||||
.arg(dir.plus("a.tmp"))
|
||||
.succeeds();
|
||||
assert_eq!(
|
||||
get_file_context(dir.plus("a.tmp")).unwrap(),
|
||||
Some(new_a_context)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn role_change() {
|
||||
let (dir, mut cmd) = at_and_ucmd!();
|
||||
|
||||
dir.touch("a.tmp");
|
||||
let a_context = get_file_context(dir.plus("a.tmp")).unwrap();
|
||||
let new_a_context = if let Some(a_context) = a_context {
|
||||
let mut components: Vec<_> = a_context.split(':').collect();
|
||||
components[1] = "system_r";
|
||||
components.join(":")
|
||||
} else {
|
||||
set_file_context(dir.plus("a.tmp"), "unconfined_u:object_r:user_tmp_t:s0").unwrap();
|
||||
String::from("unconfined_u:system_r:user_tmp_t:s0")
|
||||
};
|
||||
|
||||
cmd.args(&["--verbose", "--role=system_r"])
|
||||
.arg(dir.plus("a.tmp"))
|
||||
.succeeds();
|
||||
assert_eq!(
|
||||
get_file_context(dir.plus("a.tmp")).unwrap(),
|
||||
Some(new_a_context)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn type_change() {
|
||||
let (dir, mut cmd) = at_and_ucmd!();
|
||||
|
||||
dir.touch("a.tmp");
|
||||
let a_context = get_file_context(dir.plus("a.tmp")).unwrap();
|
||||
let new_a_context = if let Some(a_context) = a_context {
|
||||
let mut components: Vec<_> = a_context.split(':').collect();
|
||||
components[2] = "etc_t";
|
||||
components.join(":")
|
||||
} else {
|
||||
set_file_context(dir.plus("a.tmp"), "unconfined_u:object_r:user_tmp_t:s0").unwrap();
|
||||
String::from("unconfined_u:object_r:etc_t:s0")
|
||||
};
|
||||
|
||||
cmd.args(&["--verbose", "--type=etc_t"])
|
||||
.arg(dir.plus("a.tmp"))
|
||||
.succeeds();
|
||||
assert_eq!(
|
||||
get_file_context(dir.plus("a.tmp")).unwrap(),
|
||||
Some(new_a_context)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn range_change() {
|
||||
let (dir, mut cmd) = at_and_ucmd!();
|
||||
|
||||
dir.touch("a.tmp");
|
||||
let a_context = get_file_context(dir.plus("a.tmp")).unwrap();
|
||||
let new_a_context = if let Some(a_context) = a_context {
|
||||
a_context
|
||||
.split(':')
|
||||
.take(3)
|
||||
.chain(iter::once("s0:c42"))
|
||||
.collect::<Vec<_>>()
|
||||
.join(":")
|
||||
} else {
|
||||
set_file_context(dir.plus("a.tmp"), "unconfined_u:object_r:user_tmp_t:s0").unwrap();
|
||||
String::from("unconfined_u:object_r:user_tmp_t:s0:c42")
|
||||
};
|
||||
|
||||
cmd.args(&["--verbose", "--range=s0:c42"])
|
||||
.arg(dir.plus("a.tmp"))
|
||||
.succeeds();
|
||||
assert_eq!(
|
||||
get_file_context(dir.plus("a.tmp")).unwrap(),
|
||||
Some(new_a_context)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn valid_reference() {
|
||||
let (dir, mut cmd) = at_and_ucmd!();
|
||||
|
||||
dir.touch("a.tmp");
|
||||
let new_a_context = "guest_u:object_r:etc_t:s0:c42";
|
||||
set_file_context(dir.plus("a.tmp"), new_a_context).unwrap();
|
||||
|
||||
dir.touch("b.tmp");
|
||||
let b_context = get_file_context(dir.plus("b.tmp")).unwrap();
|
||||
assert_ne!(b_context.as_deref(), Some(new_a_context));
|
||||
|
||||
cmd.arg("--verbose")
|
||||
.arg(format!("--reference={}", dir.plus_as_string("a.tmp")))
|
||||
.arg(dir.plus("b.tmp"))
|
||||
.succeeds();
|
||||
assert_eq!(
|
||||
get_file_context(dir.plus("b.tmp")).unwrap().as_deref(),
|
||||
Some(new_a_context)
|
||||
);
|
||||
}
|
||||
|
||||
fn get_file_context(path: impl AsRef<Path>) -> Result<Option<String>, selinux::errors::Error> {
|
||||
let path = path.as_ref();
|
||||
match selinux::SecurityContext::of_path(path, false, false) {
|
||||
Err(r) => {
|
||||
println!("get_file_context failed: '{}': {}.", path.display(), &r);
|
||||
Err(r)
|
||||
}
|
||||
|
||||
Ok(None) => {
|
||||
println!(
|
||||
"get_file_context: '{}': No SELinux context defined.",
|
||||
path.display()
|
||||
);
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
Ok(Some(context)) => {
|
||||
let bytes = context
|
||||
.as_bytes()
|
||||
.splitn(2, |&b| b == 0_u8)
|
||||
.next()
|
||||
.unwrap_or_default();
|
||||
let context = String::from_utf8(bytes.into()).unwrap_or_default();
|
||||
println!("get_file_context: '{}' => '{}'.", context, path.display());
|
||||
Ok(Some(context))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn set_file_context(path: impl AsRef<Path>, context: &str) -> Result<(), selinux::errors::Error> {
|
||||
let c_context = CString::new(context.as_bytes()).map_err(|_r| selinux::errors::Error::IO {
|
||||
source: io::Error::from(io::ErrorKind::InvalidInput),
|
||||
operation: "CString::new",
|
||||
})?;
|
||||
|
||||
let path = path.as_ref();
|
||||
let r =
|
||||
selinux::SecurityContext::from_c_str(&c_context, false).set_for_path(path, false, false);
|
||||
if let Err(r) = &r {
|
||||
println!(
|
||||
"set_file_context failed: '{}' => '{}': {}.",
|
||||
context,
|
||||
path.display(),
|
||||
r
|
||||
)
|
||||
} else {
|
||||
println!("set_file_context: '{}' => '{}'.", context, path.display())
|
||||
}
|
||||
r
|
||||
}
|
|
@ -228,3 +228,26 @@ fn test_big_h() {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(target_os = "linux")]
|
||||
fn basic_succeeds() {
|
||||
let (at, mut ucmd) = at_and_ucmd!();
|
||||
let one_group = nix::unistd::getgroups().unwrap();
|
||||
// if there are no groups we can't run this test.
|
||||
if let Some(group) = one_group.first() {
|
||||
at.touch("f1");
|
||||
ucmd.arg(group.as_raw().to_string())
|
||||
.arg("f1")
|
||||
.succeeds()
|
||||
.no_stdout()
|
||||
.no_stderr();
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_no_change() {
|
||||
let (at, mut ucmd) = at_and_ucmd!();
|
||||
at.touch("file");
|
||||
ucmd.arg("").arg(at.plus("file")).succeeds();
|
||||
}
|
||||
|
|
|
@ -16,8 +16,6 @@ use std::os::windows::fs::symlink_file;
|
|||
use filetime::FileTime;
|
||||
#[cfg(target_os = "linux")]
|
||||
use rlimit::Resource;
|
||||
#[cfg(not(windows))]
|
||||
use std::env;
|
||||
#[cfg(target_os = "linux")]
|
||||
use std::fs as std_fs;
|
||||
#[cfg(target_os = "linux")]
|
||||
|
@ -743,20 +741,16 @@ fn test_cp_deref_folder_to_folder() {
|
|||
let scene = TestScenario::new(util_name!());
|
||||
let at = &scene.fixtures;
|
||||
|
||||
let cwd = env::current_dir().unwrap();
|
||||
let path_to_new_symlink = at.plus(TEST_COPY_FROM_FOLDER);
|
||||
|
||||
let path_to_new_symlink = at.subdir.join(TEST_COPY_FROM_FOLDER);
|
||||
|
||||
// Change the cwd to have a correct symlink
|
||||
assert!(env::set_current_dir(&path_to_new_symlink).is_ok());
|
||||
|
||||
#[cfg(not(windows))]
|
||||
let _r = fs::symlink(TEST_HELLO_WORLD_SOURCE, TEST_HELLO_WORLD_SOURCE_SYMLINK);
|
||||
#[cfg(windows)]
|
||||
let _r = symlink_file(TEST_HELLO_WORLD_SOURCE, TEST_HELLO_WORLD_SOURCE_SYMLINK);
|
||||
|
||||
// Back to the initial cwd (breaks the other tests)
|
||||
assert!(env::set_current_dir(&cwd).is_ok());
|
||||
at.symlink_file(
|
||||
&path_to_new_symlink
|
||||
.join(TEST_HELLO_WORLD_SOURCE)
|
||||
.to_string_lossy(),
|
||||
&path_to_new_symlink
|
||||
.join(TEST_HELLO_WORLD_SOURCE_SYMLINK)
|
||||
.to_string_lossy(),
|
||||
);
|
||||
|
||||
//using -P -R option
|
||||
scene
|
||||
|
@ -843,20 +837,16 @@ fn test_cp_no_deref_folder_to_folder() {
|
|||
let scene = TestScenario::new(util_name!());
|
||||
let at = &scene.fixtures;
|
||||
|
||||
let cwd = env::current_dir().unwrap();
|
||||
let path_to_new_symlink = at.plus(TEST_COPY_FROM_FOLDER);
|
||||
|
||||
let path_to_new_symlink = at.subdir.join(TEST_COPY_FROM_FOLDER);
|
||||
|
||||
// Change the cwd to have a correct symlink
|
||||
assert!(env::set_current_dir(&path_to_new_symlink).is_ok());
|
||||
|
||||
#[cfg(not(windows))]
|
||||
let _r = fs::symlink(TEST_HELLO_WORLD_SOURCE, TEST_HELLO_WORLD_SOURCE_SYMLINK);
|
||||
#[cfg(windows)]
|
||||
let _r = symlink_file(TEST_HELLO_WORLD_SOURCE, TEST_HELLO_WORLD_SOURCE_SYMLINK);
|
||||
|
||||
// Back to the initial cwd (breaks the other tests)
|
||||
assert!(env::set_current_dir(&cwd).is_ok());
|
||||
at.symlink_file(
|
||||
&path_to_new_symlink
|
||||
.join(TEST_HELLO_WORLD_SOURCE)
|
||||
.to_string_lossy(),
|
||||
&path_to_new_symlink
|
||||
.join(TEST_HELLO_WORLD_SOURCE_SYMLINK)
|
||||
.to_string_lossy(),
|
||||
);
|
||||
|
||||
//using -P -R option
|
||||
scene
|
||||
|
@ -969,10 +959,9 @@ fn test_cp_archive() {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(target_os = "unix")]
|
||||
#[cfg(unix)]
|
||||
fn test_cp_archive_recursive() {
|
||||
let (at, mut ucmd) = at_and_ucmd!();
|
||||
let cwd = env::current_dir().unwrap();
|
||||
|
||||
// creates
|
||||
// dir/1
|
||||
|
@ -988,26 +977,13 @@ fn test_cp_archive_recursive() {
|
|||
at.touch(&file_1.to_string_lossy());
|
||||
at.touch(&file_2.to_string_lossy());
|
||||
|
||||
// Change the cwd to have a correct symlink
|
||||
assert!(env::set_current_dir(&at.subdir.join(TEST_COPY_TO_FOLDER)).is_ok());
|
||||
|
||||
#[cfg(not(windows))]
|
||||
{
|
||||
let _r = fs::symlink("1", &file_1_link);
|
||||
let _r = fs::symlink("2", &file_2_link);
|
||||
}
|
||||
#[cfg(windows)]
|
||||
{
|
||||
let _r = symlink_file("1", &file_1_link);
|
||||
let _r = symlink_file("2", &file_2_link);
|
||||
}
|
||||
// Back to the initial cwd (breaks the other tests)
|
||||
assert!(env::set_current_dir(&cwd).is_ok());
|
||||
at.symlink_file("1", &file_1_link.to_string_lossy());
|
||||
at.symlink_file("2", &file_2_link.to_string_lossy());
|
||||
|
||||
ucmd.arg("--archive")
|
||||
.arg(TEST_COPY_TO_FOLDER)
|
||||
.arg(TEST_COPY_TO_FOLDER_NEW)
|
||||
.fails(); // fails for now
|
||||
.succeeds();
|
||||
|
||||
let scene2 = TestScenario::new("ls");
|
||||
let result = scene2
|
||||
|
@ -1025,18 +1001,6 @@ fn test_cp_archive_recursive() {
|
|||
.run();
|
||||
|
||||
println!("ls dest {}", result.stdout_str());
|
||||
assert!(at.file_exists(
|
||||
&at.subdir
|
||||
.join(TEST_COPY_TO_FOLDER_NEW)
|
||||
.join("1.link")
|
||||
.to_string_lossy()
|
||||
));
|
||||
assert!(at.file_exists(
|
||||
&at.subdir
|
||||
.join(TEST_COPY_TO_FOLDER_NEW)
|
||||
.join("2.link")
|
||||
.to_string_lossy()
|
||||
));
|
||||
assert!(at.file_exists(
|
||||
&at.subdir
|
||||
.join(TEST_COPY_TO_FOLDER_NEW)
|
||||
|
|
|
@ -8,7 +8,10 @@
|
|||
|
||||
// spell-checker:ignore (methods) hexdigest
|
||||
|
||||
use tempfile::TempDir;
|
||||
|
||||
use crate::common::util::*;
|
||||
use std::fs::OpenOptions;
|
||||
use std::time::SystemTime;
|
||||
|
||||
#[path = "../../src/uu/factor/sieve.rs"]
|
||||
|
@ -24,6 +27,43 @@ use self::sieve::Sieve;
|
|||
const NUM_PRIMES: usize = 10000;
|
||||
const NUM_TESTS: usize = 100;
|
||||
|
||||
#[test]
|
||||
fn test_parallel() {
|
||||
// factor should only flush the buffer at line breaks
|
||||
let n_integers = 100_000;
|
||||
let mut input_string = String::new();
|
||||
for i in 0..=n_integers {
|
||||
input_string.push_str(&(format!("{} ", i))[..]);
|
||||
}
|
||||
|
||||
let tmp_dir = TempDir::new().unwrap();
|
||||
let tmp_dir = AtPath::new(tmp_dir.path());
|
||||
tmp_dir.touch("output");
|
||||
let output = OpenOptions::new()
|
||||
.append(true)
|
||||
.open(tmp_dir.plus("output"))
|
||||
.unwrap();
|
||||
|
||||
for mut child in (0..10)
|
||||
.map(|_| {
|
||||
new_ucmd!()
|
||||
.set_stdout(output.try_clone().unwrap())
|
||||
.pipe_in(input_string.clone())
|
||||
.run_no_wait()
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
{
|
||||
assert_eq!(child.wait().unwrap().code().unwrap(), 0);
|
||||
}
|
||||
|
||||
let result = TestScenario::new(util_name!())
|
||||
.ccmd("sort")
|
||||
.arg(tmp_dir.plus("output"))
|
||||
.succeeds();
|
||||
let hash_check = sha1::Sha1::from(result.stdout()).hexdigest();
|
||||
assert_eq!(hash_check, "cc743607c0ff300ff575d92f4ff0c87d5660c393");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_first_100000_integers() {
|
||||
extern crate sha1;
|
||||
|
|
|
@ -94,6 +94,18 @@ fn unpaired_lines() {
|
|||
.arg("2")
|
||||
.succeeds()
|
||||
.stdout_only_fixture("unpaired_lines.expected");
|
||||
|
||||
new_ucmd!()
|
||||
.arg("fields_3.txt")
|
||||
.arg("fields_2.txt")
|
||||
.arg("-1")
|
||||
.arg("2")
|
||||
.arg("-a")
|
||||
.arg("1")
|
||||
.arg("-a")
|
||||
.arg("2")
|
||||
.succeeds()
|
||||
.stdout_only_fixture("unpaired_lines_outer.expected");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -107,6 +119,18 @@ fn suppress_joined() {
|
|||
.arg("2")
|
||||
.succeeds()
|
||||
.stdout_only_fixture("suppress_joined.expected");
|
||||
|
||||
new_ucmd!()
|
||||
.arg("fields_3.txt")
|
||||
.arg("fields_2.txt")
|
||||
.arg("-1")
|
||||
.arg("2")
|
||||
.arg("-a")
|
||||
.arg("1")
|
||||
.arg("-v")
|
||||
.arg("2")
|
||||
.succeeds()
|
||||
.stdout_only_fixture("suppress_joined_outer.expected");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -128,6 +128,7 @@ fn test_ls_width() {
|
|||
scene
|
||||
.ucmd()
|
||||
.args(&option.split(' ').collect::<Vec<_>>())
|
||||
.arg("-C")
|
||||
.succeeds()
|
||||
.stdout_only("test-width-1 test-width-2 test-width-3 test-width-4\n");
|
||||
}
|
||||
|
@ -136,30 +137,33 @@ fn test_ls_width() {
|
|||
scene
|
||||
.ucmd()
|
||||
.args(&option.split(' ').collect::<Vec<_>>())
|
||||
.arg("-C")
|
||||
.succeeds()
|
||||
.stdout_only("test-width-1 test-width-3\ntest-width-2 test-width-4\n");
|
||||
}
|
||||
|
||||
for option in &[
|
||||
"-w 25",
|
||||
"-w=25",
|
||||
"--width=25",
|
||||
"--width 25",
|
||||
"-w 0",
|
||||
"-w=0",
|
||||
"--width=0",
|
||||
"--width 0",
|
||||
] {
|
||||
for option in &["-w 25", "-w=25", "--width=25", "--width 25"] {
|
||||
scene
|
||||
.ucmd()
|
||||
.args(&option.split(' ').collect::<Vec<_>>())
|
||||
.arg("-C")
|
||||
.succeeds()
|
||||
.stdout_only("test-width-1\ntest-width-2\ntest-width-3\ntest-width-4\n");
|
||||
}
|
||||
|
||||
for option in &["-w 0", "-w=0", "--width=0", "--width 0"] {
|
||||
scene
|
||||
.ucmd()
|
||||
.args(&option.split(' ').collect::<Vec<_>>())
|
||||
.arg("-C")
|
||||
.succeeds()
|
||||
.stdout_only("test-width-1 test-width-2 test-width-3 test-width-4\n");
|
||||
}
|
||||
|
||||
scene
|
||||
.ucmd()
|
||||
.arg("-w=bad")
|
||||
.arg("-C")
|
||||
.fails()
|
||||
.stderr_contains("invalid line width");
|
||||
|
||||
|
@ -167,6 +171,7 @@ fn test_ls_width() {
|
|||
scene
|
||||
.ucmd()
|
||||
.args(&option.split(' ').collect::<Vec<_>>())
|
||||
.arg("-C")
|
||||
.fails()
|
||||
.stderr_only("ls: invalid line width: '1a'");
|
||||
}
|
||||
|
@ -184,16 +189,10 @@ fn test_ls_columns() {
|
|||
// Columns is the default
|
||||
let result = scene.ucmd().succeeds();
|
||||
|
||||
#[cfg(not(windows))]
|
||||
result.stdout_only("test-columns-1\ntest-columns-2\ntest-columns-3\ntest-columns-4\n");
|
||||
#[cfg(windows)]
|
||||
result.stdout_only("test-columns-1 test-columns-2 test-columns-3 test-columns-4\n");
|
||||
|
||||
for option in &["-C", "--format=columns"] {
|
||||
let result = scene.ucmd().arg(option).succeeds();
|
||||
#[cfg(not(windows))]
|
||||
result.stdout_only("test-columns-1\ntest-columns-2\ntest-columns-3\ntest-columns-4\n");
|
||||
#[cfg(windows)]
|
||||
result.stdout_only("test-columns-1 test-columns-2 test-columns-3 test-columns-4\n");
|
||||
}
|
||||
|
||||
|
@ -205,6 +204,38 @@ fn test_ls_columns() {
|
|||
.succeeds()
|
||||
.stdout_only("test-columns-1 test-columns-3\ntest-columns-2 test-columns-4\n");
|
||||
}
|
||||
|
||||
// On windows we are always able to get the terminal size, so we can't simulate falling back to the
|
||||
// environment variable.
|
||||
#[cfg(not(windows))]
|
||||
{
|
||||
for option in &["-C", "--format=columns"] {
|
||||
scene
|
||||
.ucmd()
|
||||
.env("COLUMNS", "40")
|
||||
.arg(option)
|
||||
.succeeds()
|
||||
.stdout_only("test-columns-1 test-columns-3\ntest-columns-2 test-columns-4\n");
|
||||
}
|
||||
|
||||
scene
|
||||
.ucmd()
|
||||
.env("COLUMNS", "garbage")
|
||||
.arg("-C")
|
||||
.succeeds()
|
||||
.stdout_is("test-columns-1 test-columns-2 test-columns-3 test-columns-4\n")
|
||||
.stderr_is("ls: ignoring invalid width in environment variable COLUMNS: 'garbage'");
|
||||
}
|
||||
scene
|
||||
.ucmd()
|
||||
.arg("-Cw0")
|
||||
.succeeds()
|
||||
.stdout_only("test-columns-1 test-columns-2 test-columns-3 test-columns-4\n");
|
||||
scene
|
||||
.ucmd()
|
||||
.arg("-mw0")
|
||||
.succeeds()
|
||||
.stdout_only("test-columns-1, test-columns-2, test-columns-3, test-columns-4\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -220,11 +251,7 @@ fn test_ls_across() {
|
|||
let result = scene.ucmd().arg(option).succeeds();
|
||||
// Because the test terminal has width 0, this is the same output as
|
||||
// the columns option.
|
||||
if cfg!(unix) {
|
||||
result.stdout_only("test-across-1\ntest-across-2\ntest-across-3\ntest-across-4\n");
|
||||
} else {
|
||||
result.stdout_only("test-across-1 test-across-2 test-across-3 test-across-4\n");
|
||||
}
|
||||
result.stdout_only("test-across-1 test-across-2 test-across-3 test-across-4\n");
|
||||
}
|
||||
|
||||
for option in &["-x", "--format=across"] {
|
||||
|
@ -250,11 +277,7 @@ fn test_ls_commas() {
|
|||
|
||||
for option in &["-m", "--format=commas"] {
|
||||
let result = scene.ucmd().arg(option).succeeds();
|
||||
if cfg!(unix) {
|
||||
result.stdout_only("test-commas-1,\ntest-commas-2,\ntest-commas-3,\ntest-commas-4\n");
|
||||
} else {
|
||||
result.stdout_only("test-commas-1, test-commas-2, test-commas-3, test-commas-4\n");
|
||||
}
|
||||
result.stdout_only("test-commas-1, test-commas-2, test-commas-3, test-commas-4\n");
|
||||
}
|
||||
|
||||
for option in &["-m", "--format=commas"] {
|
||||
|
@ -571,13 +594,11 @@ fn test_ls_sort_name() {
|
|||
at.touch("test-1");
|
||||
at.touch("test-2");
|
||||
|
||||
let sep = if cfg!(unix) { "\n" } else { " " };
|
||||
|
||||
scene
|
||||
.ucmd()
|
||||
.arg("--sort=name")
|
||||
.succeeds()
|
||||
.stdout_is(["test-1", "test-2", "test-3\n"].join(sep));
|
||||
.stdout_is("test-1\ntest-2\ntest-3\n");
|
||||
|
||||
let scene_dot = TestScenario::new(util_name!());
|
||||
let at = &scene_dot.fixtures;
|
||||
|
@ -591,7 +612,7 @@ fn test_ls_sort_name() {
|
|||
.arg("--sort=name")
|
||||
.arg("-A")
|
||||
.succeeds()
|
||||
.stdout_is([".a", ".b", "a", "b\n"].join(sep));
|
||||
.stdout_is(".a\n.b\na\nb\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -612,28 +633,16 @@ fn test_ls_order_size() {
|
|||
scene.ucmd().arg("-al").succeeds();
|
||||
|
||||
let result = scene.ucmd().arg("-S").succeeds();
|
||||
#[cfg(not(windows))]
|
||||
result.stdout_only("test-4\ntest-3\ntest-2\ntest-1\n");
|
||||
#[cfg(windows)]
|
||||
result.stdout_only("test-4 test-3 test-2 test-1\n");
|
||||
|
||||
let result = scene.ucmd().arg("-S").arg("-r").succeeds();
|
||||
#[cfg(not(windows))]
|
||||
result.stdout_only("test-1\ntest-2\ntest-3\ntest-4\n");
|
||||
#[cfg(windows)]
|
||||
result.stdout_only("test-1 test-2 test-3 test-4\n");
|
||||
|
||||
let result = scene.ucmd().arg("--sort=size").succeeds();
|
||||
#[cfg(not(windows))]
|
||||
result.stdout_only("test-4\ntest-3\ntest-2\ntest-1\n");
|
||||
#[cfg(windows)]
|
||||
result.stdout_only("test-4 test-3 test-2 test-1\n");
|
||||
|
||||
let result = scene.ucmd().arg("--sort=size").arg("-r").succeeds();
|
||||
#[cfg(not(windows))]
|
||||
result.stdout_only("test-1\ntest-2\ntest-3\ntest-4\n");
|
||||
#[cfg(windows)]
|
||||
result.stdout_only("test-1 test-2 test-3 test-4\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -755,9 +764,6 @@ fn test_ls_styles() {
|
|||
|
||||
at.touch("test2");
|
||||
let result = scene.ucmd().arg("--full-time").arg("-x").succeeds();
|
||||
#[cfg(not(windows))]
|
||||
assert_eq!(result.stdout_str(), "test\ntest2\n");
|
||||
#[cfg(windows)]
|
||||
assert_eq!(result.stdout_str(), "test test2\n");
|
||||
}
|
||||
|
||||
|
@ -794,28 +800,16 @@ fn test_ls_order_time() {
|
|||
|
||||
// ctime was changed at write, so the order is 4 3 2 1
|
||||
let result = scene.ucmd().arg("-t").succeeds();
|
||||
#[cfg(not(windows))]
|
||||
result.stdout_only("test-4\ntest-3\ntest-2\ntest-1\n");
|
||||
#[cfg(windows)]
|
||||
result.stdout_only("test-4 test-3 test-2 test-1\n");
|
||||
|
||||
let result = scene.ucmd().arg("--sort=time").succeeds();
|
||||
#[cfg(not(windows))]
|
||||
result.stdout_only("test-4\ntest-3\ntest-2\ntest-1\n");
|
||||
#[cfg(windows)]
|
||||
result.stdout_only("test-4 test-3 test-2 test-1\n");
|
||||
|
||||
let result = scene.ucmd().arg("-tr").succeeds();
|
||||
#[cfg(not(windows))]
|
||||
result.stdout_only("test-1\ntest-2\ntest-3\ntest-4\n");
|
||||
#[cfg(windows)]
|
||||
result.stdout_only("test-1 test-2 test-3 test-4\n");
|
||||
|
||||
let result = scene.ucmd().arg("--sort=time").arg("-r").succeeds();
|
||||
#[cfg(not(windows))]
|
||||
result.stdout_only("test-1\ntest-2\ntest-3\ntest-4\n");
|
||||
#[cfg(windows)]
|
||||
result.stdout_only("test-1 test-2 test-3 test-4\n");
|
||||
|
||||
// 3 was accessed last in the read
|
||||
// So the order should be 2 3 4 1
|
||||
|
@ -826,19 +820,11 @@ fn test_ls_order_time() {
|
|||
|
||||
// It seems to be dependent on the platform whether the access time is actually set
|
||||
if file3_access > file4_access {
|
||||
if cfg!(not(windows)) {
|
||||
result.stdout_only("test-3\ntest-4\ntest-2\ntest-1\n");
|
||||
} else {
|
||||
result.stdout_only("test-3 test-4 test-2 test-1\n");
|
||||
}
|
||||
result.stdout_only("test-3\ntest-4\ntest-2\ntest-1\n");
|
||||
} else {
|
||||
// Access time does not seem to be set on Windows and some other
|
||||
// systems so the order is 4 3 2 1
|
||||
if cfg!(not(windows)) {
|
||||
result.stdout_only("test-4\ntest-3\ntest-2\ntest-1\n");
|
||||
} else {
|
||||
result.stdout_only("test-4 test-3 test-2 test-1\n");
|
||||
}
|
||||
result.stdout_only("test-4\ntest-3\ntest-2\ntest-1\n");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -991,6 +977,7 @@ fn test_ls_color() {
|
|||
.ucmd()
|
||||
.arg("--color")
|
||||
.arg("-w=15")
|
||||
.arg("-C")
|
||||
.succeeds()
|
||||
.stdout_only(format!(
|
||||
"{} test-color\nb {}\n",
|
||||
|
@ -2009,11 +1996,7 @@ fn test_ls_path() {
|
|||
};
|
||||
scene.ucmd().arg(&abs_path).run().stdout_is(expected_stdout);
|
||||
|
||||
let expected_stdout = if cfg!(windows) {
|
||||
format!("{} {}\n", path, file1)
|
||||
} else {
|
||||
format!("{}\n{}\n", path, file1)
|
||||
};
|
||||
let expected_stdout = format!("{}\n{}\n", path, file1);
|
||||
scene
|
||||
.ucmd()
|
||||
.arg(file1)
|
||||
|
|
|
@ -46,6 +46,17 @@ fn test_file() {
|
|||
.succeeds()
|
||||
.no_stderr()
|
||||
.stdout_is(unindent(ALPHA_OUT));
|
||||
|
||||
// Ensure that default format matches `-t o2`, and that `-t` does not absorb file argument
|
||||
new_ucmd!()
|
||||
.arg("--endian=little")
|
||||
.arg("-t")
|
||||
.arg("o2")
|
||||
.arg(file.as_os_str())
|
||||
.succeeds()
|
||||
.no_stderr()
|
||||
.stdout_is(unindent(ALPHA_OUT));
|
||||
|
||||
let _ = remove_file(file);
|
||||
}
|
||||
|
||||
|
|
|
@ -68,41 +68,36 @@ fn test_with_numbering_option_with_number_width() {
|
|||
fn test_with_long_header_option() {
|
||||
let test_file_path = "test_one_page.log";
|
||||
let expected_test_file_path = "test_one_page_header.log.expected";
|
||||
let mut scenario = new_ucmd!();
|
||||
let value = file_last_modified_time(&scenario, test_file_path);
|
||||
let header = "new file";
|
||||
scenario
|
||||
.args(&["--header=new file", test_file_path])
|
||||
.succeeds()
|
||||
.stdout_is_templated_fixture(
|
||||
expected_test_file_path,
|
||||
&[("{last_modified_time}", &value), ("{header}", header)],
|
||||
);
|
||||
|
||||
new_ucmd!()
|
||||
.args(&["-h", header, test_file_path])
|
||||
.succeeds()
|
||||
.stdout_is_templated_fixture(
|
||||
expected_test_file_path,
|
||||
&[("{last_modified_time}", &value), ("{header}", header)],
|
||||
);
|
||||
for args in &[&["-h", header][..], &["--header=new file"][..]] {
|
||||
let mut scenario = new_ucmd!();
|
||||
let value = file_last_modified_time(&scenario, test_file_path);
|
||||
scenario
|
||||
.args(args)
|
||||
.arg(test_file_path)
|
||||
.succeeds()
|
||||
.stdout_is_templated_fixture(
|
||||
expected_test_file_path,
|
||||
&[("{last_modified_time}", &value), ("{header}", header)],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_with_double_space_option() {
|
||||
let test_file_path = "test_one_page.log";
|
||||
let expected_test_file_path = "test_one_page_double_line.log.expected";
|
||||
let mut scenario = new_ucmd!();
|
||||
let value = file_last_modified_time(&scenario, test_file_path);
|
||||
scenario
|
||||
.args(&["-d", test_file_path])
|
||||
.succeeds()
|
||||
.stdout_is_templated_fixture(expected_test_file_path, &[("{last_modified_time}", &value)]);
|
||||
|
||||
new_ucmd!()
|
||||
.args(&["--double-space", test_file_path])
|
||||
.succeeds()
|
||||
.stdout_is_templated_fixture(expected_test_file_path, &[("{last_modified_time}", &value)]);
|
||||
for &arg in &["-d", "--double-space"] {
|
||||
let mut scenario = new_ucmd!();
|
||||
let value = file_last_modified_time(&scenario, test_file_path);
|
||||
scenario
|
||||
.args(&[arg, test_file_path])
|
||||
.succeeds()
|
||||
.stdout_is_templated_fixture(
|
||||
expected_test_file_path,
|
||||
&[("{last_modified_time}", &value)],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -188,33 +183,28 @@ fn test_with_page_range() {
|
|||
let test_file_path = "test.log";
|
||||
let expected_test_file_path = "test_page_range_1.log.expected";
|
||||
let expected_test_file_path1 = "test_page_range_2.log.expected";
|
||||
let mut scenario = new_ucmd!();
|
||||
let value = file_last_modified_time(&scenario, test_file_path);
|
||||
scenario
|
||||
.args(&["--pages=15", test_file_path])
|
||||
.succeeds()
|
||||
.stdout_is_templated_fixture(expected_test_file_path, &[("{last_modified_time}", &value)]);
|
||||
|
||||
new_ucmd!()
|
||||
.args(&["+15", test_file_path])
|
||||
.succeeds()
|
||||
.stdout_is_templated_fixture(expected_test_file_path, &[("{last_modified_time}", &value)]);
|
||||
|
||||
new_ucmd!()
|
||||
.args(&["--pages=15:17", test_file_path])
|
||||
.succeeds()
|
||||
.stdout_is_templated_fixture(
|
||||
expected_test_file_path1,
|
||||
&[("{last_modified_time}", &value)],
|
||||
);
|
||||
|
||||
new_ucmd!()
|
||||
.args(&["+15:17", test_file_path])
|
||||
.succeeds()
|
||||
.stdout_is_templated_fixture(
|
||||
expected_test_file_path1,
|
||||
&[("{last_modified_time}", &value)],
|
||||
);
|
||||
for &arg in &["--pages=15", "+15"] {
|
||||
let mut scenario = new_ucmd!();
|
||||
let value = file_last_modified_time(&scenario, test_file_path);
|
||||
scenario
|
||||
.args(&[arg, test_file_path])
|
||||
.succeeds()
|
||||
.stdout_is_templated_fixture(
|
||||
expected_test_file_path,
|
||||
&[("{last_modified_time}", &value)],
|
||||
);
|
||||
}
|
||||
for &arg in &["--pages=15:17", "+15:17"] {
|
||||
let mut scenario = new_ucmd!();
|
||||
let value = file_last_modified_time(&scenario, test_file_path);
|
||||
scenario
|
||||
.args(&[arg, test_file_path])
|
||||
.succeeds()
|
||||
.stdout_is_templated_fixture(
|
||||
expected_test_file_path1,
|
||||
&[("{last_modified_time}", &value)],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -232,19 +222,17 @@ fn test_with_no_header_trailer_option() {
|
|||
#[test]
|
||||
fn test_with_page_length_option() {
|
||||
let test_file_path = "test.log";
|
||||
let expected_test_file_path = "test_page_length.log.expected";
|
||||
let expected_test_file_path1 = "test_page_length1.log.expected";
|
||||
let mut scenario = new_ucmd!();
|
||||
let value = file_last_modified_time(&scenario, test_file_path);
|
||||
scenario
|
||||
.args(&["--pages=2:3", "-l", "100", "-n", test_file_path])
|
||||
.succeeds()
|
||||
.stdout_is_templated_fixture(expected_test_file_path, &[("{last_modified_time}", &value)]);
|
||||
|
||||
new_ucmd!()
|
||||
.args(&["--pages=2:3", "-l", "5", "-n", test_file_path])
|
||||
.succeeds()
|
||||
.stdout_is_fixture(expected_test_file_path1);
|
||||
for (arg, expected) in &[
|
||||
("100", "test_page_length.log.expected"),
|
||||
("5", "test_page_length1.log.expected"),
|
||||
] {
|
||||
let mut scenario = new_ucmd!();
|
||||
let value = file_last_modified_time(&scenario, test_file_path);
|
||||
scenario
|
||||
.args(&["--pages=2:3", "-l", arg, "-n", test_file_path])
|
||||
.succeeds()
|
||||
.stdout_is_templated_fixture(expected, &[("{last_modified_time}", &value)]);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -277,17 +265,17 @@ fn test_with_stdin() {
|
|||
fn test_with_column() {
|
||||
let test_file_path = "column.log";
|
||||
let expected_test_file_path = "column.log.expected";
|
||||
let mut scenario = new_ucmd!();
|
||||
let value = file_last_modified_time(&scenario, test_file_path);
|
||||
scenario
|
||||
.args(&["--pages=3:5", "--column=3", "-n", test_file_path])
|
||||
.succeeds()
|
||||
.stdout_is_templated_fixture(expected_test_file_path, &[("{last_modified_time}", &value)]);
|
||||
|
||||
new_ucmd!()
|
||||
.args(&["--pages=3:5", "-3", "-n", test_file_path])
|
||||
.succeeds()
|
||||
.stdout_is_templated_fixture(expected_test_file_path, &[("{last_modified_time}", &value)]);
|
||||
for arg in &["-3", "--column=3"] {
|
||||
let mut scenario = new_ucmd!();
|
||||
let value = file_last_modified_time(&scenario, test_file_path);
|
||||
scenario
|
||||
.args(&["--pages=3:5", arg, "-n", test_file_path])
|
||||
.succeeds()
|
||||
.stdout_is_templated_fixture(
|
||||
expected_test_file_path,
|
||||
&[("{last_modified_time}", &value)],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -305,36 +293,17 @@ fn test_with_column_across_option() {
|
|||
#[test]
|
||||
fn test_with_column_across_option_and_column_separator() {
|
||||
let test_file_path = "column.log";
|
||||
let expected_test_file_path = "column_across_sep.log.expected";
|
||||
let expected_test_file_path1 = "column_across_sep1.log.expected";
|
||||
let mut scenario = new_ucmd!();
|
||||
let value = file_last_modified_time(&scenario, test_file_path);
|
||||
scenario
|
||||
.args(&[
|
||||
"--pages=3:5",
|
||||
"--column=3",
|
||||
"-s|",
|
||||
"-a",
|
||||
"-n",
|
||||
test_file_path,
|
||||
])
|
||||
.succeeds()
|
||||
.stdout_is_templated_fixture(expected_test_file_path, &[("{last_modified_time}", &value)]);
|
||||
|
||||
new_ucmd!()
|
||||
.args(&[
|
||||
"--pages=3:5",
|
||||
"--column=3",
|
||||
"-Sdivide",
|
||||
"-a",
|
||||
"-n",
|
||||
test_file_path,
|
||||
])
|
||||
.succeeds()
|
||||
.stdout_is_templated_fixture(
|
||||
expected_test_file_path1,
|
||||
&[("{last_modified_time}", &value)],
|
||||
);
|
||||
for (arg, expected) in &[
|
||||
("-s|", "column_across_sep.log.expected"),
|
||||
("-Sdivide", "column_across_sep1.log.expected"),
|
||||
] {
|
||||
let mut scenario = new_ucmd!();
|
||||
let value = file_last_modified_time(&scenario, test_file_path);
|
||||
scenario
|
||||
.args(&["--pages=3:5", "--column=3", arg, "-a", "-n", test_file_path])
|
||||
.succeeds()
|
||||
.stdout_is_templated_fixture(expected, &[("{last_modified_time}", &value)]);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -1091,3 +1091,30 @@ fn test_wrong_args_exit_code() {
|
|||
.status_code(2)
|
||||
.stderr_contains("--misspelled");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
fn test_tmp_files_deleted_on_sigint() {
|
||||
use std::{fs::read_dir, time::Duration};
|
||||
|
||||
use nix::{sys::signal, unistd::Pid};
|
||||
|
||||
let (at, mut ucmd) = at_and_ucmd!();
|
||||
at.mkdir("tmp_dir");
|
||||
ucmd.args(&[
|
||||
"ext_sort.txt",
|
||||
"--buffer-size=1", // with a small buffer size `sort` will be forced to create a temporary directory very soon.
|
||||
"--temporary-directory=tmp_dir",
|
||||
]);
|
||||
let mut child = ucmd.run_no_wait();
|
||||
// wait a short amount of time so that `sort` can create a temporary directory.
|
||||
std::thread::sleep(Duration::from_millis(100));
|
||||
// `sort` should have created a temporary directory.
|
||||
assert!(read_dir(at.plus("tmp_dir")).unwrap().next().is_some());
|
||||
// kill sort with SIGINT
|
||||
signal::kill(Pid::from_raw(child.id() as i32), signal::SIGINT).unwrap();
|
||||
// wait for `sort` to exit
|
||||
assert_eq!(child.wait().unwrap().code(), Some(2));
|
||||
// `sort` should have deleted the temporary directory again.
|
||||
assert!(read_dir(at.plus("tmp_dir")).unwrap().next().is_none());
|
||||
}
|
||||
|
|
1
tests/fixtures/join/fields_3.txt
vendored
1
tests/fixtures/join/fields_3.txt
vendored
|
@ -4,3 +4,4 @@ c 4 h
|
|||
f 5 i
|
||||
g 6 j
|
||||
h 7 k
|
||||
i 99 l
|
||||
|
|
4
tests/fixtures/join/suppress_joined_outer.expected
vendored
Normal file
4
tests/fixtures/join/suppress_joined_outer.expected
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
1 a
|
||||
8 h
|
||||
9 i
|
||||
99 i l
|
|
@ -4,3 +4,4 @@
|
|||
i 5 f
|
||||
j 6 g
|
||||
k 7 h
|
||||
l 99 i
|
||||
|
|
10
tests/fixtures/join/unpaired_lines_outer.expected
vendored
Normal file
10
tests/fixtures/join/unpaired_lines_outer.expected
vendored
Normal file
|
@ -0,0 +1,10 @@
|
|||
1 a
|
||||
2 a f b
|
||||
3 b g c
|
||||
4 c h d
|
||||
5 f i e
|
||||
6 g j f
|
||||
7 h k g
|
||||
8 h
|
||||
9 i
|
||||
99 i l
|
|
@ -96,7 +96,6 @@ sed -i 's|seq |/usr/bin/seq |' tests/misc/sort-discrim.sh
|
|||
|
||||
# Add specific timeout to tests that currently hang to limit time spent waiting
|
||||
sed -i 's|seq \$|/usr/bin/timeout 0.1 seq \$|' tests/misc/seq-precision.sh tests/misc/seq-long-double.sh
|
||||
sed -i 's|cat |/usr/bin/timeout 0.1 cat |' tests/misc/cat-self.sh
|
||||
|
||||
|
||||
# Remove dup of /usr/bin/ when executed several times
|
||||
|
@ -123,3 +122,14 @@ test -f "${BUILDDIR}/getlimits" || cp src/getlimits "${BUILDDIR}"
|
|||
# When decoding an invalid base32/64 string, gnu writes everything it was able to decode until
|
||||
# it hit the decode error, while we don't write anything if the input is invalid.
|
||||
sed -i "s/\(baddecode.*OUT=>\"\).*\"/\1\"/g" tests/misc/base64.pl
|
||||
sed -i "s/\(\(b2[ml]_[69]\|b32h_[56]\|z85_8\|z85_35\).*OUT=>\)[^}]*\(.*\)/\1\"\"\3/g" tests/misc/basenc.pl
|
||||
|
||||
# add "error: " to the expected error message
|
||||
sed -i "s/\$prog: invalid input/\$prog: error: invalid input/g" tests/misc/basenc.pl
|
||||
|
||||
# basenc: swap out error message for unexpected arg
|
||||
sed -i "s/ {ERR=>\"\$prog: foobar\\\\n\" \. \$try_help }/ {ERR=>\"error: Found argument '--foobar' which wasn't expected, or isn't valid in this context\n\nUSAGE:\n basenc [OPTION]... [FILE]\n\nFor more information try --help\n\"}]/" tests/misc/basenc.pl
|
||||
sed -i "s/ {ERR_SUBST=>\"s\/(unrecognized|unknown) option \[-' \]\*foobar\[' \]\*\/foobar\/\"}],//" tests/misc/basenc.pl
|
||||
|
||||
# Remove the check whether a util was built. Otherwise tests against utils like "arch" are not run.
|
||||
sed -i "s|require_built_ |# require_built_ |g" init.cfg
|
||||
|
|
32
util/compare_gnu_result.py
Normal file
32
util/compare_gnu_result.py
Normal file
|
@ -0,0 +1,32 @@
|
|||
#! /usr/bin/python
|
||||
|
||||
"""
|
||||
Compare the current results to the last results gathered from the master branch to highlight
|
||||
if a PR is making the results better/worse
|
||||
"""
|
||||
|
||||
import json
|
||||
import sys
|
||||
from os import environ
|
||||
|
||||
NEW = json.load(open("gnu-result.json"))
|
||||
OLD = json.load(open("master-gnu-result.json"))
|
||||
|
||||
# Extract the specific results from the dicts
|
||||
last = OLD[list(OLD.keys())[0]]
|
||||
current = NEW[list(NEW.keys())[0]]
|
||||
|
||||
|
||||
pass_d = int(current["pass"]) - int(last["pass"])
|
||||
fail_d = int(current["fail"]) - int(last["fail"])
|
||||
error_d = int(current["error"]) - int(last["error"])
|
||||
skip_d = int(current["skip"]) - int(last["skip"])
|
||||
|
||||
# Get an annotation to highlight changes
|
||||
print(
|
||||
f"::warning ::Changes from master: PASS {pass_d:+d} / FAIL {fail_d:+d} / ERROR {error_d:+d} / SKIP {skip_d:+d} "
|
||||
)
|
||||
|
||||
# If results are worse fail the job to draw attention
|
||||
if pass_d < 0:
|
||||
sys.exit(1)
|
Loading…
Reference in a new issue