1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2025-08-01 05:27:45 +00:00

Merge branch 'master' of github.com:backwaterred/coreutils into dedup-dd-mideb

This commit is contained in:
Tyler 2021-08-18 17:11:58 -07:00
commit 20c63caa0c
76 changed files with 3162 additions and 994 deletions

View file

@ -90,3 +90,42 @@ jobs:
with: with:
name: gnu-result name: gnu-result
path: gnu-result.json 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

View file

@ -63,6 +63,7 @@ abspath
addprefix addprefix
addsuffix addsuffix
endef endef
findstring
firstword firstword
ifeq ifeq
ifneq ifneq
@ -89,5 +90,9 @@ markdownlint
rerast rerast
rollup rollup
sed sed
selinuxenabled
wslpath wslpath
xargs xargs
# * directories
sbin

View file

@ -68,6 +68,7 @@ splitn
trunc trunc
# * uutils # * uutils
basenc
chcon chcon
chgrp chgrp
chmod chmod
@ -106,6 +107,7 @@ whoami
# * vars/errno # * vars/errno
errno errno
EEXIST EEXIST
ENODATA
ENOENT ENOENT
ENOSYS ENOSYS
EPERM EPERM
@ -118,7 +120,9 @@ fcntl
vmsplice vmsplice
# * vars/libc # * vars/libc
COMFOLLOW
FILENO FILENO
FTSENT
HOSTSIZE HOSTSIZE
IDSIZE IDSIZE
IFBLK IFBLK
@ -151,6 +155,7 @@ SIGTERM
SYS_fdatasync SYS_fdatasync
SYS_syncfs SYS_syncfs
USERSIZE USERSIZE
accpath
addrinfo addrinfo
addrlen addrlen
blocksize blocksize
@ -174,15 +179,18 @@ inode
inodes inodes
isatty isatty
lchown lchown
pathlen
setgid setgid
setgroups setgroups
settime settime
setuid setuid
socktype socktype
statfs statfs
statp
statvfs statvfs
strcmp strcmp
strerror strerror
strlen
syncfs syncfs
umask umask
waitpid waitpid
@ -274,6 +282,13 @@ winerror
winnt winnt
winsock winsock
# * vars/selinux
freecon
getfilecon
lgetfilecon
lsetfilecon
setfilecon
# * vars/uucore # * vars/uucore
optflag optflag
optflagmulti optflagmulti

128
Cargo.lock generated
View file

@ -17,6 +17,12 @@ dependencies = [
"memchr 2.4.0", "memchr 2.4.0",
] ]
[[package]]
name = "aliasable"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd"
[[package]] [[package]]
name = "ansi_term" name = "ansi_term"
version = "0.11.0" version = "0.11.0"
@ -308,7 +314,9 @@ dependencies = [
"uu_base32", "uu_base32",
"uu_base64", "uu_base64",
"uu_basename", "uu_basename",
"uu_basenc",
"uu_cat", "uu_cat",
"uu_chcon",
"uu_chgrp", "uu_chgrp",
"uu_chmod", "uu_chmod",
"uu_chown", "uu_chown",
@ -587,6 +595,16 @@ dependencies = [
"syn", "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]] [[package]]
name = "custom_derive" name = "custom_derive"
version = "0.1.7" version = "0.1.7"
@ -595,9 +613,29 @@ checksum = "ef8ae57c4978a2acd8b869ce6b9ca1dfe817bff704c220209fdef2c0b75a01b9"
[[package]] [[package]]
name = "data-encoding" name = "data-encoding"
version = "2.1.2" version = "2.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" 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]] [[package]]
name = "diff" name = "diff"
@ -675,13 +713,13 @@ checksum = "31a7a908b8f32538a2143e59a6e4e2508988832d5d4d6f7c156b3cbc762643a5"
[[package]] [[package]]
name = "filetime" name = "filetime"
version = "0.2.14" version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d34cfa13a63ae058bfa601fe9e313bbdb3746427c1459185464ce0fcf62e1e8" checksum = "975ccf83d8d9d0d84682850a38c8169027be83368805971cc4f238c2b245bc98"
dependencies = [ dependencies = [
"cfg-if 1.0.0", "cfg-if 1.0.0",
"libc", "libc",
"redox_syscall 0.2.9", "redox_syscall",
"winapi 0.3.9", "winapi 0.3.9",
] ]
@ -697,6 +735,16 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2022715d62ab30faffd124d40b76f4134a550a87792276512b18d63272333394" 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]] [[package]]
name = "fuchsia-cprng" name = "fuchsia-cprng"
version = "0.1.1" version = "0.1.1"
@ -1139,19 +1187,20 @@ dependencies = [
[[package]] [[package]]
name = "ouroboros" name = "ouroboros"
version = "0.9.5" version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fbeff60e3e37407a80ead3e9458145b456e978c4068cddbfea6afb48572962ca" checksum = "84236d64f1718c387232287cf036eb6632a5ecff226f4ff9dccb8c2b79ba0bde"
dependencies = [ dependencies = [
"aliasable",
"ouroboros_macro", "ouroboros_macro",
"stable_deref_trait", "stable_deref_trait",
] ]
[[package]] [[package]]
name = "ouroboros_macro" name = "ouroboros_macro"
version = "0.9.5" version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03f2cb802b5bdfdf52f1ffa0b54ce105e4d346e91990dd571f86c91321ad49e2" checksum = "f463857a6eb96c0136b1d56e56c718350cef30412ec065b48294799a088bca68"
dependencies = [ dependencies = [
"Inflector", "Inflector",
"proc-macro-error", "proc-macro-error",
@ -1189,7 +1238,7 @@ dependencies = [
"cfg-if 1.0.0", "cfg-if 1.0.0",
"instant", "instant",
"libc", "libc",
"redox_syscall 0.2.9", "redox_syscall",
"smallvec 1.6.1", "smallvec 1.6.1",
"winapi 0.3.9", "winapi 0.3.9",
] ]
@ -1483,15 +1532,9 @@ dependencies = [
[[package]] [[package]]
name = "redox_syscall" name = "redox_syscall"
version = "0.1.57" version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff"
[[package]]
name = "redox_syscall"
version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ab49abadf3f9e1c4bc499e8845e152ad87d2ad2d30371841171169e9d75feee"
dependencies = [ dependencies = [
"bitflags", "bitflags",
] ]
@ -1502,7 +1545,7 @@ version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8440d8acb4fd3d277125b4bd01a6f38aee8d814b3b5fc09b3f2b825d37d3fe8f" checksum = "8440d8acb4fd3d277125b4bd01a6f38aee8d814b3b5fc09b3f2b825d37d3fe8f"
dependencies = [ dependencies = [
"redox_syscall 0.2.9", "redox_syscall",
] ]
[[package]] [[package]]
@ -1588,9 +1631,9 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]] [[package]]
name = "selinux" name = "selinux"
version = "0.1.3" version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd525eeb189eb26c8471463186bba87644e3d8a9c7ae392adaf9ec45ede574bc" checksum = "1aa2f705dd871c2eb90888bb2d44b13218b34f5c7318c3971df62f799d0143eb"
dependencies = [ dependencies = [
"bitflags", "bitflags",
"libc", "libc",
@ -1761,7 +1804,7 @@ dependencies = [
"cfg-if 1.0.0", "cfg-if 1.0.0",
"libc", "libc",
"rand 0.8.4", "rand 0.8.4",
"redox_syscall 0.2.9", "redox_syscall",
"remove_dir_all", "remove_dir_all",
"winapi 0.3.9", "winapi 0.3.9",
] ]
@ -1802,7 +1845,7 @@ checksum = "077185e2eac69c3f8379a4298e1e07cd36beb962290d4a51199acf0fdc10607e"
dependencies = [ dependencies = [
"libc", "libc",
"numtoa", "numtoa",
"redox_syscall 0.2.9", "redox_syscall",
"redox_termios", "redox_termios",
] ]
@ -1959,6 +2002,16 @@ dependencies = [
"uucore_procs", "uucore_procs",
] ]
[[package]]
name = "uu_basenc"
version = "0.0.7"
dependencies = [
"clap",
"uu_base32",
"uucore",
"uucore_procs",
]
[[package]] [[package]]
name = "uu_cat" name = "uu_cat"
version = "0.0.7" version = "0.0.7"
@ -1970,6 +2023,20 @@ dependencies = [
"unix_socket", "unix_socket",
"uucore", "uucore",
"uucore_procs", "uucore_procs",
"winapi-util",
]
[[package]]
name = "uu_chcon"
version = "0.0.7"
dependencies = [
"clap",
"fts-sys",
"libc",
"selinux",
"thiserror",
"uucore",
"uucore_procs",
] ]
[[package]] [[package]]
@ -2427,7 +2494,7 @@ dependencies = [
"clap", "clap",
"crossterm", "crossterm",
"nix 0.13.1", "nix 0.13.1",
"redox_syscall 0.1.57", "redox_syscall",
"redox_termios", "redox_termios",
"unicode-segmentation", "unicode-segmentation",
"unicode-width", "unicode-width",
@ -2697,6 +2764,7 @@ dependencies = [
"binary-heap-plus", "binary-heap-plus",
"clap", "clap",
"compare", "compare",
"ctrlc",
"fnv", "fnv",
"itertools 0.10.1", "itertools 0.10.1",
"memchr 2.4.0", "memchr 2.4.0",
@ -2785,7 +2853,7 @@ dependencies = [
"clap", "clap",
"libc", "libc",
"nix 0.20.0", "nix 0.20.0",
"redox_syscall 0.1.57", "redox_syscall",
"uucore", "uucore",
"uucore_procs", "uucore_procs",
"winapi 0.3.9", "winapi 0.3.9",
@ -2808,7 +2876,7 @@ version = "0.0.7"
dependencies = [ dependencies = [
"clap", "clap",
"libc", "libc",
"redox_syscall 0.1.57", "redox_syscall",
"uucore", "uucore",
"uucore_procs", "uucore_procs",
] ]
@ -2990,6 +3058,7 @@ version = "0.0.9"
dependencies = [ dependencies = [
"clap", "clap",
"data-encoding", "data-encoding",
"data-encoding-macro",
"dns-lookup", "dns-lookup",
"dunce", "dunce",
"getopts", "getopts",
@ -3002,6 +3071,7 @@ dependencies = [
"time", "time",
"wild", "wild",
"winapi 0.3.9", "winapi 0.3.9",
"z85",
] ]
[[package]] [[package]]
@ -3129,3 +3199,9 @@ checksum = "244c3741f4240ef46274860397c7c74e50eb23624996930e484c16679633a54c"
dependencies = [ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "z85"
version = "3.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ac8b56e4f9906a4ef5412875e9ce448364023335cec645fd457ecf51d4f2781"

View file

@ -35,6 +35,7 @@ feat_common_core = [
"base32", "base32",
"base64", "base64",
"basename", "basename",
"basenc",
"cat", "cat",
"cksum", "cksum",
"comm", "comm",
@ -145,7 +146,7 @@ feat_os_unix_musl = [
# NOTE: # NOTE:
# The selinux(-sys) crate requires `libselinux` headers and shared library to be accessible in the C toolchain at compile time. # The selinux(-sys) crate requires `libselinux` headers and shared library to be accessible in the C toolchain at compile time.
# Running a uutils compiled with `feat_selinux` requires an SELinux enabled Kernel at run time. # Running a uutils compiled with `feat_selinux` requires an SELinux enabled Kernel at run time.
feat_selinux = ["id/selinux", "selinux"] feat_selinux = ["id/selinux", "selinux", "feat_require_selinux"]
## feature sets with requirements (restricting cross-platform availability) ## feature sets with requirements (restricting cross-platform availability)
# #
# ** NOTE: these `feat_require_...` sets should be minimized as much as possible to encourage cross-platform availability of utilities # ** NOTE: these `feat_require_...` sets should be minimized as much as possible to encourage cross-platform availability of utilities
@ -185,6 +186,10 @@ feat_require_unix_utmpx = [
"users", "users",
"who", "who",
] ]
# "feat_require_selinux" == set of utilities depending on SELinux.
feat_require_selinux = [
"chcon",
]
## (alternate/newer/smaller platforms) feature sets ## (alternate/newer/smaller platforms) feature sets
# "feat_os_unix_fuchsia" == set of utilities which can be built/run on the "Fuchsia" OS (refs: <https://fuchsia.dev>; <https://en.wikipedia.org/wiki/Google_Fuchsia>) # "feat_os_unix_fuchsia" == set of utilities which can be built/run on the "Fuchsia" OS (refs: <https://fuchsia.dev>; <https://en.wikipedia.org/wiki/Google_Fuchsia>)
feat_os_unix_fuchsia = [ feat_os_unix_fuchsia = [
@ -212,9 +217,8 @@ feat_os_unix_fuchsia = [
feat_os_unix_redox = [ feat_os_unix_redox = [
"feat_common_core", "feat_common_core",
# #
"uname",
"chmod", "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" == slightly restricted set of utilities which can be built/run on early windows platforms (eg, "WinXP")
feat_os_windows_legacy = [ feat_os_windows_legacy = [
@ -237,7 +241,7 @@ clap = { version = "2.33", features = ["wrap_help"] }
lazy_static = { version="1.3" } lazy_static = { version="1.3" }
textwrap = { version="=0.11.0", features=["term_size"] } # !maint: [2020-05-10; rivy] unstable crate using undocumented features; pinned currently, will review textwrap = { version="=0.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" } 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 # * uutils
uu_test = { optional=true, version="0.0.7", package="uu_test", path="src/uu/test" } uu_test = { optional=true, version="0.0.7", package="uu_test", path="src/uu/test" }
# #
@ -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" } 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" } 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" } 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" } 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" } 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" } 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" } chown = { optional=true, version="0.0.7", package="uu_chown", path="src/uu/chown" }

View file

@ -46,6 +46,15 @@ BUSYBOX_ROOT := $(BASEDIR)/tmp
BUSYBOX_VER := 1.32.1 BUSYBOX_VER := 1.32.1
BUSYBOX_SRC := $(BUSYBOX_ROOT)/busybox-$(BUSYBOX_VER) 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 # Possible programs
PROGS := \ PROGS := \
base32 \ base32 \
@ -147,10 +156,17 @@ UNIX_PROGS := \
users \ users \
who who
SELINUX_PROGS := \
chcon
ifneq ($(OS),Windows_NT) ifneq ($(OS),Windows_NT)
PROGS := $(PROGS) $(UNIX_PROGS) PROGS := $(PROGS) $(UNIX_PROGS)
endif endif
ifeq ($(SELINUX_ENABLED),1)
PROGS := $(PROGS) $(SELINUX_PROGS)
endif
UTILS ?= $(PROGS) UTILS ?= $(PROGS)
# Programs with usable tests # Programs with usable tests
@ -159,6 +175,7 @@ TEST_PROGS := \
base64 \ base64 \
basename \ basename \
cat \ cat \
chcon \
chgrp \ chgrp \
chmod \ chmod \
chown \ chown \
@ -228,6 +245,9 @@ TEST_SPEC_FEATURE :=
ifneq ($(SPEC),) ifneq ($(SPEC),)
TEST_NO_FAIL_FAST :=--no-fail-fast TEST_NO_FAIL_FAST :=--no-fail-fast
TEST_SPEC_FEATURE := test_unimplemented TEST_SPEC_FEATURE := test_unimplemented
else ifeq ($(SELINUX_ENABLED),1)
TEST_NO_FAIL_FAST :=
TEST_SPEC_FEATURE := feat_selinux
endif endif
define TEST_BUSYBOX define TEST_BUSYBOX

View file

@ -365,24 +365,26 @@ To contribute to uutils, please see [CONTRIBUTING](CONTRIBUTING.md).
| Done | Semi-Done | To Do | | Done | Semi-Done | To Do |
|-----------|-----------|--------| |-----------|-----------|--------|
| arch | cp | chcon | | arch | cp | runcon |
| base32 | date | runcon | | base32 | date | stty |
| base64 | dd | stty | | base64 | dd | |
| basename | df | | | basename | df | |
| cat | expr | | | basenc | expr | |
| chgrp | install | | | cat | install | |
| chmod | join | | | chcon | join | |
| chown | ls | | | chgrp | ls | |
| chroot | more | | | chmod | more | |
| cksum | numfmt | | | chown | numfmt | |
| comm | od (`--strings` and 128-bit data types missing) | | chroot | od (`--strings` and 128-bit data types missing) | |
| csplit | pr | | | cksum | pr | |
| cut | printf | | | comm | printf | |
| dircolors | sort | | | csplit | sort | |
| dirname | split | | | cut | split | |
| du | tac | | | dircolors | tac | |
| echo | tail | | | dirname | tail | |
| env | test | | | du | test | |
| echo | | |
| env | | |
| expand | | | | expand | | |
| factor | | | | factor | | |
| false | | | | false | | |

View file

@ -34,7 +34,7 @@ pub mod options {
} }
impl Config { 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) { let file: Option<String> = match options.values_of(options::FILE) {
Some(mut values) => { Some(mut values) => {
let name = values.next().unwrap(); let name = values.next().unwrap();
@ -85,7 +85,7 @@ pub fn parse_base_cmd_args(
let arg_list = args let arg_list = args
.collect_str(InvalidEncodingHandling::ConvertLossy) .collect_str(InvalidEncodingHandling::ConvertLossy)
.accept_any(); .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> { 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 { if !decode {
let encoded = data.encode(); match data.encode() {
wrap_print(&data, encoded); Ok(s) => {
wrap_print(&data, s);
}
Err(_) => {
eprintln!(
"{}: error: invalid input (length must be multiple of 4 characters)",
name
);
exit!(1)
}
}
} else { } else {
match data.decode() { match data.decode() {
Ok(s) => { Ok(s) => {

25
src/uu/basenc/Cargo.toml Normal file
View 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"

View 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
}

View file

@ -0,0 +1 @@
uucore_procs::main!(uu_basenc);

View file

@ -23,9 +23,10 @@ uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_p
[target.'cfg(unix)'.dependencies] [target.'cfg(unix)'.dependencies]
unix_socket = "0.5.0" unix_socket = "0.5.0"
nix = "0.20.0"
[target.'cfg(any(target_os = "linux", target_os = "android"))'.dependencies] [target.'cfg(windows)'.dependencies]
nix = "0.20" winapi-util = "0.1.5"
[[bin]] [[bin]]
name = "cat" name = "cat"

View file

@ -20,12 +20,16 @@ use clap::{crate_version, App, Arg};
use std::fs::{metadata, File}; use std::fs::{metadata, File};
use std::io::{self, Read, Write}; use std::io::{self, Read, Write};
use thiserror::Error; use thiserror::Error;
use uucore::error::UResult;
#[cfg(unix)]
use std::os::unix::io::AsRawFd;
/// Linux splice support /// Linux splice support
#[cfg(any(target_os = "linux", target_os = "android"))] #[cfg(any(target_os = "linux", target_os = "android"))]
mod splice; mod splice;
#[cfg(any(target_os = "linux", target_os = "android"))] #[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 /// Unix domain socket support
#[cfg(unix)] #[cfg(unix)]
@ -58,6 +62,8 @@ enum CatError {
}, },
#[error("Is a directory")] #[error("Is a directory")]
IsDirectory, IsDirectory,
#[error("input file is output file")]
OutputIsInput,
} }
type CatResult<T> = Result<T, CatError>; type CatResult<T> = Result<T, CatError>;
@ -122,6 +128,12 @@ struct OutputState {
/// Whether the output cursor is at the beginning of a new line /// Whether the output cursor is at the beginning of a new line
at_line_start: bool, 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 /// Represents an open file handle, stream, or other device
@ -164,7 +176,8 @@ mod options {
pub static SHOW_NONPRINTING: &str = "show-nonprinting"; 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 let args = args
.collect_str(InvalidEncodingHandling::Ignore) .collect_str(InvalidEncodingHandling::Ignore)
.accept_any(); .accept_any();
@ -217,13 +230,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
show_tabs, show_tabs,
squeeze_blank, squeeze_blank,
}; };
let success = cat_files(files, &options).is_ok(); cat_files(files, &options)
if success {
0
} else {
1
}
} }
pub fn uu_app() -> App<'static, 'static> { 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 == "-" { if path == "-" {
let stdin = io::stdin(); let stdin = io::stdin();
let mut handle = InputHandle { let mut handle = InputHandle {
@ -328,6 +341,10 @@ fn cat_path(path: &str, options: &OutputOptions, state: &mut OutputState) -> Cat
} }
_ => { _ => {
let file = File::open(path)?; let file = File::open(path)?;
#[cfg(any(windows, unix))]
if same_file(out_info, &file) {
return Err(CatError::OutputIsInput);
}
let mut handle = InputHandle { let mut handle = InputHandle {
#[cfg(any(target_os = "linux", target_os = "android"))] #[cfg(any(target_os = "linux", target_os = "android"))]
file_descriptor: file.as_raw_fd(), 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> { #[cfg(unix)]
let mut error_count = 0; 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 { let mut state = OutputState {
line_number: 1, line_number: 1,
at_line_start: true, at_line_start: true,
skipped_carriage_return: false,
one_blank_kept: false,
}; };
let mut error_messages: Vec<String> = Vec::new();
for path in &files { for path in &files {
if let Err(err) = cat_path(path, options, &mut state) { if let Err(err) = cat_path(path, options, &mut state, &out_info) {
show_error!("{}: {}", path, err); error_messages.push(format!("{}: {}", path, err));
error_count += 1;
} }
} }
if error_count == 0 { if state.skipped_carriage_return {
print!("\r");
}
if error_messages.is_empty() {
Ok(()) Ok(())
} else { } 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 mut in_buf = [0; 1024 * 31];
let stdout = io::stdout(); let stdout = io::stdout();
let mut writer = stdout.lock(); let mut writer = stdout.lock();
let mut one_blank_kept = false;
while let Ok(n) = handle.reader.read(&mut in_buf) { while let Ok(n) = handle.reader.read(&mut in_buf) {
if n == 0 { if n == 0 {
@ -434,8 +479,13 @@ fn write_lines<R: Read>(
while pos < n { while pos < n {
// skip empty line_number enumerating them if needed // skip empty line_number enumerating them if needed
if in_buf[pos] == b'\n' { if in_buf[pos] == b'\n' {
if !state.at_line_start || !options.squeeze_blank || !one_blank_kept { // \r followed by \n is printed as ^M when show_ends is enabled, so that \r\n prints as ^M$
one_blank_kept = true; 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 { if state.at_line_start && options.number == NumberingMode::All {
write!(&mut writer, "{0:6}\t", state.line_number)?; write!(&mut writer, "{0:6}\t", state.line_number)?;
state.line_number += 1; state.line_number += 1;
@ -449,7 +499,12 @@ fn write_lines<R: Read>(
pos += 1; pos += 1;
continue; 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 { if state.at_line_start && options.number != NumberingMode::None {
write!(&mut writer, "{0:6}\t", state.line_number)?; write!(&mut writer, "{0:6}\t", state.line_number)?;
state.line_number += 1; state.line_number += 1;
@ -464,17 +519,22 @@ fn write_lines<R: Read>(
write_to_end(&in_buf[pos..], &mut writer) write_to_end(&in_buf[pos..], &mut writer)
}; };
// end of buffer? // end of buffer?
if offset == 0 { if offset + pos == in_buf.len() {
state.at_line_start = false; state.at_line_start = false;
break; break;
} }
// print suitable end of line if in_buf[pos + offset] == b'\r' {
writer.write_all(options.end_of_line().as_bytes())?; state.skipped_carriage_return = true;
if handle.is_interactive { } else {
writer.flush()?; 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 + 1;
pos += offset;
} }
} }
@ -482,17 +542,19 @@ fn write_lines<R: Read>(
} }
// write***_to_end methods // write***_to_end methods
// Write all symbols till end of line or end of buffer is reached // Write all symbols till \n or \r or end of buffer is reached
// Return the (number of written symbols + 1) or 0 if the 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 { 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) => { Some(p) => {
writer.write_all(&in_buf[..p]).unwrap(); writer.write_all(&in_buf[..p]).unwrap();
p + 1 p
} }
None => { None => {
writer.write_all(in_buf).unwrap(); 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 { fn write_tab_to_end<W: Write>(mut in_buf: &[u8], writer: &mut W) -> usize {
let mut count = 0; let mut count = 0;
loop { 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) => { Some(p) => {
writer.write_all(&in_buf[..p]).unwrap(); writer.write_all(&in_buf[..p]).unwrap();
if in_buf[p] == b'\n' { if in_buf[p] == b'\n' {
return count + p + 1; return count + p;
} else { } else if in_buf[p] == b'\t' {
writer.write_all(b"^I").unwrap(); writer.write_all(b"^I").unwrap();
in_buf = &in_buf[p + 1..]; in_buf = &in_buf[p + 1..];
count += p + 1; count += p + 1;
} else {
return count + p;
} }
} }
None => { None => {
writer.write_all(in_buf).unwrap(); 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(); .unwrap();
count += 1; count += 1;
} }
if count != in_buf.len() { count
count + 1
} else {
0
}
} }
#[cfg(test)] #[cfg(test)]

27
src/uu/chcon/Cargo.toml Normal file
View 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
View 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)),
}
}
}

View 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
View 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
View file

@ -0,0 +1 @@
uucore_procs::main!(uu_chcon);

View file

@ -161,12 +161,9 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
Verbosity::Normal Verbosity::Normal
}; };
let dest_gid: u32; let dest_gid = if let Some(file) = matches.value_of(options::REFERENCE) {
if let Some(file) = matches.value_of(options::REFERENCE) {
match fs::metadata(&file) { match fs::metadata(&file) {
Ok(meta) => { Ok(meta) => Some(meta.gid()),
dest_gid = meta.gid();
}
Err(e) => { Err(e) => {
show_error!("failed to get attributes of '{}': {}", file, e); show_error!("failed to get attributes of '{}': {}", file, e);
return 1; return 1;
@ -174,16 +171,18 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
} }
} else { } else {
let group = matches.value_of(options::ARG_GROUP).unwrap_or_default(); let group = matches.value_of(options::ARG_GROUP).unwrap_or_default();
match entries::grp2gid(group) { if group.is_empty() {
Ok(g) => { None
dest_gid = g; } else {
} match entries::grp2gid(group) {
_ => { Ok(g) => Some(g),
show_error!("invalid group: {}", group); _ => {
return 1; show_error!("invalid group: {}", group);
return 1;
}
} }
} }
} };
let executor = Chgrper { let executor = Chgrper {
bit_flag, bit_flag,
@ -278,7 +277,7 @@ pub fn uu_app() -> App<'static, 'static> {
} }
struct Chgrper { struct Chgrper {
dest_gid: gid_t, dest_gid: Option<gid_t>,
bit_flag: u8, bit_flag: u8,
verbosity: Verbosity, verbosity: Verbosity,
files: Vec<String>, files: Vec<String>,
@ -364,7 +363,9 @@ impl Chgrper {
self.verbosity.clone(), self.verbosity.clone(),
) { ) {
Ok(n) => { Ok(n) => {
show_error!("{}", n); if !n.is_empty() {
show_error!("{}", n);
}
0 0
} }
Err(e) => { Err(e) => {

View file

@ -14,7 +14,7 @@ use uucore::fs::resolve_relative_path;
use uucore::libc::{gid_t, uid_t}; use uucore::libc::{gid_t, uid_t};
use uucore::perms::{wrap_chown, Verbosity}; 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}; use clap::{crate_version, App, Arg};
@ -107,10 +107,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
if recursive { if recursive {
if bit_flag == FTS_PHYSICAL { if bit_flag == FTS_PHYSICAL {
if derefer == 1 { if derefer == 1 {
return Err(USimpleError::new( return Err(USimpleError::new(1, "-R --dereference requires -H or -L"));
1,
"-R --dereference requires -H or -L".to_string(),
));
} }
derefer = 0; derefer = 0;
} }
@ -324,7 +321,7 @@ impl Chowner {
ret |= self.traverse(f); ret |= self.traverse(f);
} }
if ret != 0 { if ret != 0 {
return Err(UError::from(ret)); return Err(ret.into());
} }
Ok(()) Ok(())
} }
@ -485,10 +482,7 @@ mod test {
#[test] #[test]
fn test_parse_spec() { fn test_parse_spec() {
assert_eq!(parse_spec(":"), Ok((None, None))); assert!(matches!(parse_spec(":"), Ok((None, None))));
assert!(parse_spec("::") assert!(format!("{}", parse_spec("::").err().unwrap()).starts_with("invalid group: "));
.err()
.unwrap()
.starts_with("invalid group: "));
} }
} }

View file

@ -47,7 +47,7 @@ impl SplitName {
}), }),
Some(custom) => { Some(custom) => {
let spec = 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(); .unwrap();
let mut captures_iter = spec.captures_iter(&custom); let mut captures_iter = spec.captures_iter(&custom);
let custom_fn: Box<dyn Fn(usize) -> String> = match captures_iter.next() { 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(), Some(m) => m.as_str().parse::<usize>().unwrap(),
}; };
match (captures.name("FLAG"), captures.name("TYPE")) { 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)) => { (Some(ref f), Some(ref t)) => {
match (f.as_str(), t.as_str()) { match (f.as_str(), t.as_str()) {
/* /*
@ -276,6 +291,12 @@ mod tests {
assert_eq!(split_name.get(2), "xx00002"); 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] #[test]
fn zero_padding_decimal1() { fn zero_padding_decimal1() {
let split_name = SplitName::new(None, Some(String::from("cst-%03d-")), None).unwrap(); let split_name = SplitName::new(None, Some(String::from("cst-%03d-")), None).unwrap();

View file

@ -15,7 +15,7 @@ use chrono::{DateTime, FixedOffset, Local, Offset, Utc};
#[cfg(windows)] #[cfg(windows)]
use chrono::{Datelike, Timelike}; use chrono::{Datelike, Timelike};
use clap::{crate_version, App, Arg}; 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 libc::{clock_settime, timespec, CLOCK_REALTIME};
use std::fs::File; use std::fs::File;
use std::io::{BufRead, BufReader}; 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. for date and time to the indicated precision.
Example: 2006-08-14 02:34:56-06:00"; 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"; static OPT_SET_HELP_STRING: &str = "set time described by STRING";
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
static OPT_SET_HELP_STRING: &str = "set time described by STRING (not available on mac yet)"; 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 /// Settings for this program, parsed from the command line
struct Settings { struct Settings {
@ -357,7 +359,13 @@ fn set_system_datetime(_date: DateTime<Utc>) -> i32 {
1 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). /// System call to set date (unix).
/// See here for more: /// See here for more:
/// https://doc.rust-lang.org/libc/i686-unknown-linux-gnu/libc/fn.clock_settime.html /// https://doc.rust-lang.org/libc/i686-unknown-linux-gnu/libc/fn.clock_settime.html

View file

@ -19,13 +19,15 @@ byte-unit = "4.0"
clap = { version = "2.33", features = [ "wrap_help" ] } clap = { version = "2.33", features = [ "wrap_help" ] }
gcd = "2.0" gcd = "2.0"
libc = "0.2" libc = "0.2"
signal-hook = "0.3.9"
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" } uucore = { version=">=0.0.8", package="uucore", path="../../uucore" }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
[dev-dependencies] [dev-dependencies]
tempfile = "^3" tempfile = "^3"
[target.'cfg(target_os = "linux")'.dependencies]
signal-hook = "0.3.9"
[[bin]] [[bin]]
name = "dd" name = "dd"
path = "src/main.rs" path = "src/main.rs"

View file

@ -8,7 +8,7 @@
#[macro_use] #[macro_use]
extern crate uucore; extern crate uucore;
use uucore::error::UCustomError; use uucore::error::UError;
use uucore::error::UResult; use uucore::error::UResult;
#[cfg(unix)] #[cfg(unix)]
use uucore::fsext::statfs_fn; use uucore::fsext::statfs_fn;
@ -274,7 +274,7 @@ impl Display for DfError {
impl Error for DfError {} impl Error for DfError {}
impl UCustomError for DfError { impl UError for DfError {
fn code(&self) -> i32 { fn code(&self) -> i32 {
match self { match self {
DfError::InvalidBaseValue(_) => 1, DfError::InvalidBaseValue(_) => 1,

View file

@ -79,7 +79,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
print!("{}", separator); print!("{}", separator);
} }
} else { } else {
return Err(UUsageError::new(1, "missing operand".to_string())); return Err(UUsageError::new(1, "missing operand"));
} }
Ok(()) Ok(())

View file

@ -32,7 +32,7 @@ use std::path::PathBuf;
use std::str::FromStr; use std::str::FromStr;
use std::time::{Duration, UNIX_EPOCH}; use std::time::{Duration, UNIX_EPOCH};
use std::{error::Error, fmt::Display}; use std::{error::Error, fmt::Display};
use uucore::error::{UCustomError, UResult}; use uucore::error::{UError, UResult};
use uucore::parse_size::{parse_size, ParseSizeError}; use uucore::parse_size::{parse_size, ParseSizeError};
use uucore::InvalidEncodingHandling; use uucore::InvalidEncodingHandling;
#[cfg(windows)] #[cfg(windows)]
@ -438,7 +438,7 @@ Try '{} --help' for more information.",
impl Error for DuError {} impl Error for DuError {}
impl UCustomError for DuError { impl UError for DuError {
fn code(&self) -> i32 { fn code(&self) -> i32 {
match self { match self {
Self::InvalidMaxDepthArg(_) => 1, Self::InvalidMaxDepthArg(_) => 1,

View file

@ -10,6 +10,7 @@
extern crate uucore; extern crate uucore;
use std::error::Error; use std::error::Error;
use std::fmt::Write as FmtWrite;
use std::io::{self, stdin, stdout, BufRead, Write}; use std::io::{self, stdin, stdout, BufRead, Write};
mod factor; mod factor;
@ -28,21 +29,29 @@ mod options {
pub static NUMBER: &str = "NUMBER"; pub static NUMBER: &str = "NUMBER";
} }
fn print_factors_str(num_str: &str, w: &mut impl io::Write) -> Result<(), Box<dyn Error>> { fn print_factors_str(
num_str num_str: &str,
.parse::<u64>() w: &mut io::BufWriter<impl io::Write>,
.map_err(|e| e.into()) factors_buffer: &mut String,
.and_then(|x| writeln!(w, "{}:{}", x, factor(x)).map_err(|e| e.into())) ) -> 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 { pub fn uumain(args: impl uucore::Args) -> i32 {
let matches = uu_app().get_matches_from(args); let matches = uu_app().get_matches_from(args);
let stdout = stdout(); 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) { if let Some(values) = matches.values_of(options::NUMBER) {
for number in values { for number in values {
if let Err(e) = print_factors_str(number, &mut w) { if let Err(e) = print_factors_str(number, &mut w, &mut factors_buffer) {
show_warning!("{}: {}", number, e); show_warning!("{}: {}", number, e);
} }
} }
@ -51,7 +60,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
for line in stdin.lock().lines() { for line in stdin.lock().lines() {
for number in line.unwrap().split_whitespace() { for number in line.unwrap().split_whitespace() {
if let Err(e) = print_factors_str(number, &mut w) { if let Err(e) = print_factors_str(number, &mut w, &mut factors_buffer) {
show_warning!("{}: {}", number, e); show_warning!("{}: {}", number, e);
} }
} }

View file

@ -9,13 +9,12 @@
extern crate uucore; extern crate uucore;
use clap::App; use clap::App;
use uucore::error::{UError, UResult}; use uucore::{error::UResult, executable};
use uucore::executable;
#[uucore_procs::gen_uumain] #[uucore_procs::gen_uumain]
pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uumain(args: impl uucore::Args) -> UResult<()> {
uu_app().get_matches_from(args); uu_app().get_matches_from(args);
Err(UError::from(1)) Err(1.into())
} }
pub fn uu_app() -> App<'static, 'static> { pub fn uu_app() -> App<'static, 'static> {

View file

@ -18,7 +18,7 @@ path = "src/id.rs"
clap = { version = "2.33", features = ["wrap_help"] } clap = { version = "2.33", features = ["wrap_help"] }
uucore = { version=">=0.0.9", package="uucore", path="../../uucore", features=["entries", "process"] } uucore = { version=">=0.0.9", package="uucore", path="../../uucore", features=["entries", "process"] }
uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_procs" } 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]] [[bin]]
name = "id" name = "id"

View file

@ -40,10 +40,10 @@
extern crate uucore; extern crate uucore;
use clap::{crate_version, App, Arg}; use clap::{crate_version, App, Arg};
#[cfg(all(target_os = "linux", feature = "selinux"))]
use selinux;
use std::ffi::CStr; use std::ffi::CStr;
use uucore::entries::{self, Group, Locate, Passwd}; use uucore::entries::{self, Group, Locate, Passwd};
use uucore::error::UResult;
use uucore::error::{set_exit_code, USimpleError};
pub use uucore::libc; pub use uucore::libc;
use uucore::libc::{getlogin, uid_t}; use uucore::libc::{getlogin, uid_t};
use uucore::process::{getegid, geteuid, getgid, getuid}; use uucore::process::{getegid, geteuid, getgid, getuid};
@ -123,10 +123,10 @@ struct State {
// 1000 10 968 975 // 1000 10 968 975
// +++ exited with 0 +++ // +++ exited with 0 +++
user_specified: bool, 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 usage = get_usage();
let after_help = get_description(); let after_help = get_description();
@ -161,7 +161,6 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
}, },
user_specified: !users.is_empty(), user_specified: !users.is_empty(),
ids: None, ids: None,
exit_code: 0,
}; };
let default_format = { let default_format = {
@ -170,14 +169,23 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
}; };
if (state.nflag || state.rflag) && default_format && !state.cflag { 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 { if state.zflag && default_format && !state.cflag {
// NOTE: GNU test suite "id/zero.sh" needs this stderr output: // 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 { 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 = { let delimiter = {
@ -204,11 +212,14 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
print!("{}{}", String::from_utf8_lossy(bytes), line_ending); print!("{}{}", String::from_utf8_lossy(bytes), line_ending);
} else { } else {
// print error because `cflag` was explicitly requested // 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 { } 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), Ok(p) => Some(p),
Err(_) => { Err(_) => {
show_error!("'{}': no such user", users[i]); show_error!("'{}': no such user", users[i]);
state.exit_code = 1; set_exit_code(1);
if i + 1 >= users.len() { if i + 1 >= users.len() {
break; break;
} else { } else {
@ -234,17 +245,17 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
if matches.is_present(options::OPT_PASSWORD) { if matches.is_present(options::OPT_PASSWORD) {
// BSD's `id` ignores all but the first specified user // BSD's `id` ignores all but the first specified user
pline(possible_pw.map(|v| v.uid())); pline(possible_pw.map(|v| v.uid()));
return state.exit_code; return Ok(());
}; };
if matches.is_present(options::OPT_HUMAN_READABLE) { if matches.is_present(options::OPT_HUMAN_READABLE) {
// BSD's `id` ignores all but the first specified user // BSD's `id` ignores all but the first specified user
pretty(possible_pw); pretty(possible_pw);
return state.exit_code; return Ok(());
} }
if matches.is_present(options::OPT_AUDIT) { if matches.is_present(options::OPT_AUDIT) {
// BSD's `id` ignores specified users // BSD's `id` ignores specified users
auditid(); auditid();
return state.exit_code; return Ok(());
} }
let (uid, gid) = possible_pw.map(|p| (p.uid(), p.gid())).unwrap_or(( 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 { if state.nflag {
entries::gid2grp(gid).unwrap_or_else(|_| { entries::gid2grp(gid).unwrap_or_else(|_| {
show_error!("cannot find name for group ID {}", gid); show_error!("cannot find name for group ID {}", gid);
state.exit_code = 1; set_exit_code(1);
gid.to_string() gid.to_string()
}) })
} else { } else {
@ -279,7 +290,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
if state.nflag { if state.nflag {
entries::uid2usr(uid).unwrap_or_else(|_| { entries::uid2usr(uid).unwrap_or_else(|_| {
show_error!("cannot find name for user ID {}", uid); show_error!("cannot find name for user ID {}", uid);
state.exit_code = 1; set_exit_code(1);
uid.to_string() uid.to_string()
}) })
} else { } else {
@ -304,7 +315,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
if state.nflag { if state.nflag {
entries::gid2grp(id).unwrap_or_else(|_| { entries::gid2grp(id).unwrap_or_else(|_| {
show_error!("cannot find name for group ID {}", id); show_error!("cannot find name for group ID {}", id);
state.exit_code = 1; set_exit_code(1);
id.to_string() id.to_string()
}) })
} else { } else {
@ -332,7 +343,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
} }
} }
state.exit_code Ok(())
} }
pub fn uu_app() -> App<'static, 'static> { pub fn uu_app() -> App<'static, 'static> {
@ -560,7 +571,7 @@ fn id_print(state: &mut State, groups: Vec<u32>) {
uid, uid,
entries::uid2usr(uid).unwrap_or_else(|_| { entries::uid2usr(uid).unwrap_or_else(|_| {
show_error!("cannot find name for user ID {}", uid); show_error!("cannot find name for user ID {}", uid);
state.exit_code = 1; set_exit_code(1);
uid.to_string() uid.to_string()
}) })
); );
@ -569,7 +580,7 @@ fn id_print(state: &mut State, groups: Vec<u32>) {
gid, gid,
entries::gid2grp(gid).unwrap_or_else(|_| { entries::gid2grp(gid).unwrap_or_else(|_| {
show_error!("cannot find name for group ID {}", gid); show_error!("cannot find name for group ID {}", gid);
state.exit_code = 1; set_exit_code(1);
gid.to_string() gid.to_string()
}) })
); );
@ -579,7 +590,7 @@ fn id_print(state: &mut State, groups: Vec<u32>) {
euid, euid,
entries::uid2usr(euid).unwrap_or_else(|_| { entries::uid2usr(euid).unwrap_or_else(|_| {
show_error!("cannot find name for user ID {}", euid); show_error!("cannot find name for user ID {}", euid);
state.exit_code = 1; set_exit_code(1);
euid.to_string() euid.to_string()
}) })
); );
@ -590,7 +601,7 @@ fn id_print(state: &mut State, groups: Vec<u32>) {
euid, euid,
entries::gid2grp(egid).unwrap_or_else(|_| { entries::gid2grp(egid).unwrap_or_else(|_| {
show_error!("cannot find name for group ID {}", egid); show_error!("cannot find name for group ID {}", egid);
state.exit_code = 1; set_exit_code(1);
egid.to_string() egid.to_string()
}) })
); );
@ -604,7 +615,7 @@ fn id_print(state: &mut State, groups: Vec<u32>) {
gr, gr,
entries::gid2grp(gr).unwrap_or_else(|_| { entries::gid2grp(gr).unwrap_or_else(|_| {
show_error!("cannot find name for group ID {}", gr); show_error!("cannot find name for group ID {}", gr);
state.exit_code = 1; set_exit_code(1);
gr.to_string() gr.to_string()
}) })
)) ))

View file

@ -5,7 +5,7 @@
// * For the full copyright and license information, please view the LICENSE file // * For the full copyright and license information, please view the LICENSE file
// * that was distributed with this source code. // * that was distributed with this source code.
// spell-checker:ignore (ToDO) rwxr sourcepath targetpath // spell-checker:ignore (ToDO) rwxr sourcepath targetpath Isnt uioerror
mod mode; mod mode;
@ -17,15 +17,17 @@ use file_diff::diff;
use filetime::{set_file_times, FileTime}; use filetime::{set_file_times, FileTime};
use uucore::backup_control::{self, BackupMode}; use uucore::backup_control::{self, BackupMode};
use uucore::entries::{grp2gid, usr2uid}; use uucore::entries::{grp2gid, usr2uid};
use uucore::error::{FromIo, UError, UIoError, UResult, USimpleError};
use uucore::perms::{wrap_chgrp, wrap_chown, Verbosity}; use uucore::perms::{wrap_chgrp, wrap_chown, Verbosity};
use libc::{getegid, geteuid}; use libc::{getegid, geteuid};
use std::error::Error;
use std::fmt::{Debug, Display};
use std::fs; use std::fs;
use std::fs::File; use std::fs::File;
use std::os::unix::fs::MetadataExt; use std::os::unix::fs::MetadataExt;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::process::Command; use std::process::Command;
use std::result::Result;
const DEFAULT_MODE: u32 = 0o755; const DEFAULT_MODE: u32 = 0o755;
const DEFAULT_STRIP_PROGRAM: &str = "strip"; const DEFAULT_STRIP_PROGRAM: &str = "strip";
@ -47,6 +49,87 @@ pub struct Behavior {
target_dir: Option<String>, 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)] #[derive(Clone, Eq, PartialEq)]
pub enum MainFunction { pub enum MainFunction {
/// Create directories /// Create directories
@ -97,7 +180,8 @@ fn get_usage() -> String {
/// ///
/// Returns a program return code. /// 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 usage = get_usage();
let matches = uu_app().usage(&usage[..]).get_matches_from(args); 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()) .map(|v| v.map(ToString::to_string).collect())
.unwrap_or_default(); .unwrap_or_default();
if let Err(s) = check_unimplemented(&matches) { check_unimplemented(&matches)?;
show_error!("Unimplemented feature: {}", s);
return 2;
}
let behavior = match behavior(&matches) { let behavior = behavior(&matches)?;
Ok(x) => x,
Err(ret) => {
return ret;
}
};
match behavior.main_function { match behavior.main_function {
MainFunction::Directory => directory(paths, behavior), 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. /// 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) { 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) { } 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) { } else if matches.is_present(OPT_CONTEXT) {
Err("--context, -Z") Err(InstallError::Unimplemented(String::from("--context, -Z")).into())
} else { } else {
Ok(()) 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. /// 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) { let main_function = if matches.is_present(OPT_DIRECTORY) {
MainFunction::Directory MainFunction::Directory
} else { } else {
@ -314,10 +390,7 @@ fn behavior(matches: &ArgMatches) -> Result<Behavior, i32> {
matches.value_of(OPT_BACKUP), matches.value_of(OPT_BACKUP),
); );
let backup_mode = match backup_mode { let backup_mode = match backup_mode {
Err(err) => { Err(err) => return Err(USimpleError::new(1, err)),
show_usage_error!("{}", err);
return Err(1);
}
Ok(mode) => mode, 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 /// GNU man pages describe this functionality as creating 'all components of
/// the specified directories'. /// 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() { if paths.is_empty() {
println!("{} with -d requires at least one argument.", executable!()); Err(InstallError::DirNeedsArg().into())
1
} else { } else {
let mut all_successful = true;
for path in paths.iter().map(Path::new) { for path in paths.iter().map(Path::new) {
// if the path already exist, don't try to create it again // if the path already exist, don't try to create it again
if !path.exists() { if !path.exists() {
// Differently than the primary functionality (MainFunction::Standard), the directory // Differently than the primary functionality
// functionality should create all ancestors (or components) of a directory regardless // (MainFunction::Standard), the directory functionality should
// of the presence of the "-D" flag. // create all ancestors (or components) of a directory
// NOTE: the GNU "install" sets the expected mode only for the target directory. All // regardless of the presence of the "-D" flag.
// 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. // NOTE: the GNU "install" sets the expected mode only for the
if let Err(e) = fs::create_dir_all(path) { // target directory. All created ancestor directories will have
show_error!("{}: {}", path.display(), e); // the default mode. Hence it is safe to use fs::create_dir_all
all_successful = false; // 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; continue;
} }
if b.verbose { if b.verbose {
show_error!("creating directory '{}'", path.display()); println!("creating directory '{}'", path.display());
} }
} }
if mode::chmod(path, b.mode()).is_err() { 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; continue;
} }
} }
if all_successful { // If the exit code was set, or show! has been called at least once
0 // (which sets the exit code as well), function execution will end after
} else { // this return.
1 Ok(())
}
} }
} }
@ -401,9 +475,9 @@ fn is_new_file_path(path: &Path) -> bool {
/// Perform an install, given a list of paths and behavior. /// 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 let target: PathBuf = b
.target_dir .target_dir
.clone() .clone()
@ -418,25 +492,19 @@ fn standard(mut paths: Vec<String>, b: Behavior) -> i32 {
if let Some(parent) = target.parent() { if let Some(parent) = target.parent() {
if !parent.exists() && b.create_leading { if !parent.exists() && b.create_leading {
if let Err(e) = fs::create_dir_all(parent) { if let Err(e) = fs::create_dir_all(parent) {
show_error!("failed to create {}: {}", parent.display(), e); return Err(InstallError::CreateDirFailed(parent.to_path_buf(), e).into());
return 1;
} }
if mode::chmod(parent, b.mode()).is_err() { if mode::chmod(parent, b.mode()).is_err() {
show_error!("failed to chmod {}", parent.display()); return Err(InstallError::ChmodFailed(parent.to_path_buf()).into());
return 1;
} }
} }
} }
if target.is_file() || is_new_file_path(&target) { if target.is_file() || is_new_file_path(&target) {
copy_file_to_file(&sources[0], &target, &b) copy(&sources[0], &target, &b)
} else { } else {
show_error!( Err(InstallError::InvalidTarget(target).into())
"invalid target {}: No such file or directory",
target.display()
);
1
} }
} }
} }
@ -444,34 +512,30 @@ fn standard(mut paths: Vec<String>, b: Behavior) -> i32 {
/// Copy some files into a directory. /// Copy some files into a directory.
/// ///
/// Prints verbose information and error messages. /// 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 /// # Parameters
/// ///
/// _files_ must all exist as non-directories. /// _files_ must all exist as non-directories.
/// _target_dir_ must be a directory. /// _target_dir_ must be a directory.
/// ///
fn copy_files_into_dir(files: &[PathBuf], target_dir: &Path, b: &Behavior) -> i32 { fn copy_files_into_dir(files: &[PathBuf], target_dir: &Path, b: &Behavior) -> UResult<()> {
if !target_dir.is_dir() { if !target_dir.is_dir() {
show_error!("target '{}' is not a directory", target_dir.display()); return Err(InstallError::TargetDirIsntDir(target_dir.to_path_buf()).into());
return 1;
} }
let mut all_successful = true;
for sourcepath in files.iter() { for sourcepath in files.iter() {
if !sourcepath.exists() { if !sourcepath.exists() {
show_error!( let err = UIoError::new(
"cannot stat '{}': No such file or directory", std::io::ErrorKind::NotFound,
sourcepath.display() format!("cannot stat '{}'", sourcepath.display()),
); );
show!(err);
all_successful = false;
continue; continue;
} }
if sourcepath.is_dir() { if sourcepath.is_dir() {
show_error!("omitting directory '{}'", sourcepath.display()); let err = InstallError::OmittingDirectory(sourcepath.to_path_buf());
all_successful = false; show!(err);
continue; continue;
} }
@ -479,37 +543,18 @@ fn copy_files_into_dir(files: &[PathBuf], target_dir: &Path, b: &Behavior) -> i3
let filename = sourcepath.components().last().unwrap(); let filename = sourcepath.components().last().unwrap();
targetpath.push(filename); targetpath.push(filename);
if copy(sourcepath, &targetpath, b).is_err() { show_if_err!(copy(sourcepath, &targetpath, b));
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
} }
// 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. /// Copy one file to a new location, changing metadata.
/// ///
/// Returns a Result type with the Err variant containing the error message.
///
/// # Parameters /// # Parameters
/// ///
/// _from_ must exist as a non-directory. /// _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. /// If the copy system call fails, we print a verbose error and return an empty error value.
/// ///
#[allow(clippy::cognitive_complexity)] #[allow(clippy::cognitive_complexity)]
fn copy(from: &Path, to: &Path, b: &Behavior) -> Result<(), ()> { fn copy(from: &Path, to: &Path, b: &Behavior) -> UResult<()> {
if b.compare && !need_copy(from, to, b) { if b.compare && !need_copy(from, to, b)? {
return Ok(()); return Ok(());
} }
// Declare the path here as we may need it for the verbose output below. // 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 { if let Some(ref backup_path) = backup_path {
// TODO!! // TODO!!
if let Err(err) = fs::rename(to, backup_path) { if let Err(err) = fs::rename(to, backup_path) {
show_error!( return Err(InstallError::BackupFailed(
"install: cannot backup file '{}' to '{}': {}", to.to_path_buf(),
to.display(), backup_path.to_path_buf(),
backup_path.display(), err,
err )
); .into());
return Err(());
} }
} }
} }
@ -552,52 +596,41 @@ fn copy(from: &Path, to: &Path, b: &Behavior) -> Result<(), ()> {
* https://github.com/rust-lang/rust/issues/79390 * https://github.com/rust-lang/rust/issues/79390
*/ */
if let Err(err) = File::create(to) { if let Err(err) = File::create(to) {
show_error!( return Err(
"install: cannot install '{}' to '{}': {}", InstallError::InstallFailed(from.to_path_buf(), to.to_path_buf(), err).into(),
from.display(),
to.display(),
err
); );
return Err(());
} }
} else if let Err(err) = fs::copy(from, to) { } else if let Err(err) = fs::copy(from, to) {
show_error!( return Err(InstallError::InstallFailed(from.to_path_buf(), to.to_path_buf(), err).into());
"cannot install '{}' to '{}': {}",
from.display(),
to.display(),
err
);
return Err(());
} }
if b.strip && cfg!(not(windows)) { if b.strip && cfg!(not(windows)) {
match Command::new(&b.strip_program).arg(to).output() { match Command::new(&b.strip_program).arg(to).output() {
Ok(o) => { Ok(o) => {
if !o.status.success() { if !o.status.success() {
crash!( return Err(InstallError::StripProgramFailed(
1, String::from_utf8(o.stderr).unwrap_or_default(),
"strip program failed: {}", )
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() { if mode::chmod(to, b.mode()).is_err() {
return Err(()); return Err(InstallError::ChmodFailed(to.to_path_buf()).into());
} }
if !b.owner.is_empty() { if !b.owner.is_empty() {
let meta = match fs::metadata(to) { let meta = match fs::metadata(to) {
Ok(meta) => meta, 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) { let owner_id = match usr2uid(&b.owner) {
Ok(g) => g, Ok(g) => g,
_ => crash!(1, "no such user: {}", b.owner), _ => return Err(InstallError::NoSuchUser(b.owner.clone()).into()),
}; };
let gid = meta.gid(); let gid = meta.gid();
match wrap_chown( match wrap_chown(
@ -620,14 +653,14 @@ fn copy(from: &Path, to: &Path, b: &Behavior) -> Result<(), ()> {
if !b.group.is_empty() { if !b.group.is_empty() {
let meta = match fs::metadata(to) { let meta = match fs::metadata(to) {
Ok(meta) => meta, 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) { let group_id = match grp2gid(&b.group) {
Ok(g) => g, 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) => { Ok(n) => {
if !n.is_empty() { if !n.is_empty() {
show_error!("{}", n); show_error!("{}", n);
@ -640,7 +673,7 @@ fn copy(from: &Path, to: &Path, b: &Behavior) -> Result<(), ()> {
if b.preserve_timestamps { if b.preserve_timestamps {
let meta = match fs::metadata(from) { let meta = match fs::metadata(from) {
Ok(meta) => meta, 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); 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: /// Return true if a file is necessary to copy. This is the case when:
///
/// - _from_ or _to_ is nonexistent; /// - _from_ or _to_ is nonexistent;
/// - either file has a sticky bit or set[ug]id bit, or the user specified one; /// - either file has a sticky bit or set[ug]id bit, or the user specified one;
/// - either file isn't a regular file; /// - 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_. /// 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) { let from_meta = match fs::metadata(from) {
Ok(meta) => meta, Ok(meta) => meta,
Err(_) => return true, Err(_) => return Ok(true),
}; };
let to_meta = match fs::metadata(to) { let to_meta = match fs::metadata(to) {
Ok(meta) => meta, Ok(meta) => meta,
Err(_) => return true, Err(_) => return Ok(true),
}; };
// setuid || setgid || sticky // setuid || setgid || sticky
@ -696,15 +730,15 @@ fn need_copy(from: &Path, to: &Path, b: &Behavior) -> bool {
|| from_meta.mode() & extra_mode != 0 || from_meta.mode() & extra_mode != 0
|| to_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() { if !from_meta.is_file() || !to_meta.is_file() {
return true; return Ok(true);
} }
if from_meta.len() != to_meta.len() { if from_meta.len() != to_meta.len() {
return true; return Ok(true);
} }
// TODO: if -P (#1809) and from/to contexts mismatch, return 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() { if !b.owner.is_empty() {
let owner_id = match usr2uid(&b.owner) { let owner_id = match usr2uid(&b.owner) {
Ok(id) => id, Ok(id) => id,
_ => crash!(1, "no such user: {}", b.owner), _ => return Err(InstallError::NoSuchUser(b.owner.clone()).into()),
}; };
if owner_id != to_meta.uid() { if owner_id != to_meta.uid() {
return true; return Ok(true);
} }
} else if !b.group.is_empty() { } else if !b.group.is_empty() {
let group_id = match grp2gid(&b.group) { let group_id = match grp2gid(&b.group) {
Ok(id) => id, Ok(id) => id,
_ => crash!(1, "no such group: {}", b.group), _ => return Err(InstallError::NoSuchGroup(b.group.clone()).into()),
}; };
if group_id != to_meta.gid() { if group_id != to_meta.gid() {
return true; return Ok(true);
} }
} else { } else {
#[cfg(not(target_os = "windows"))] #[cfg(not(target_os = "windows"))]
unsafe { unsafe {
if to_meta.uid() != geteuid() || to_meta.gid() != getegid() { if to_meta.uid() != geteuid() || to_meta.gid() != getegid() {
return true; return Ok(true);
} }
} }
} }
if !diff(from.to_str().unwrap(), to.to_str().unwrap()) { if !diff(from.to_str().unwrap(), to.to_str().unwrap()) {
return true; return Ok(true);
} }
false Ok(false)
} }

View file

@ -19,7 +19,6 @@ static NAME: &str = "join";
#[derive(Copy, Clone, PartialEq)] #[derive(Copy, Clone, PartialEq)]
enum FileNum { enum FileNum {
None,
File1, File1,
File2, File2,
} }
@ -41,7 +40,8 @@ enum CheckOrder {
struct Settings { struct Settings {
key1: usize, key1: usize,
key2: usize, key2: usize,
print_unpaired: FileNum, print_unpaired1: bool,
print_unpaired2: bool,
print_joined: bool, print_joined: bool,
ignore_case: bool, ignore_case: bool,
separator: Sep, separator: Sep,
@ -57,7 +57,8 @@ impl Default for Settings {
Settings { Settings {
key1: 0, key1: 0,
key2: 0, key2: 0,
print_unpaired: FileNum::None, print_unpaired1: false,
print_unpaired2: false,
print_joined: true, print_joined: true,
ignore_case: false, ignore_case: false,
separator: Sep::Whitespaces, separator: Sep::Whitespaces,
@ -243,7 +244,7 @@ impl<'a> State<'a> {
name: &'a str, name: &'a str,
stdin: &'a Stdin, stdin: &'a Stdin,
key: usize, key: usize,
print_unpaired: FileNum, print_unpaired: bool,
) -> State<'a> { ) -> State<'a> {
let f = if name == "-" { let f = if name == "-" {
Box::new(stdin.lock()) as Box<dyn BufRead> Box::new(stdin.lock()) as Box<dyn BufRead>
@ -258,7 +259,7 @@ impl<'a> State<'a> {
key, key,
file_name: name, file_name: name,
file_num, file_num,
print_unpaired: print_unpaired == file_num, print_unpaired,
lines: f.lines(), lines: f.lines(),
seq: Vec::new(), seq: Vec::new(),
max_fields: None, max_fields: None,
@ -450,11 +451,19 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
let mut settings: Settings = Default::default(); let mut settings: Settings = Default::default();
if let Some(value) = matches.value_of("v") { let v_values = matches.values_of("v");
settings.print_unpaired = parse_file_number(value); if v_values.is_some() {
settings.print_joined = false; 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"); settings.ignore_case = matches.is_present("i");
@ -520,7 +529,8 @@ When FILE1 or FILE2 (not both) is -, read standard input.",
.arg( .arg(
Arg::with_name("a") Arg::with_name("a")
.short("a") .short("a")
.takes_value(true) .multiple(true)
.number_of_values(1)
.possible_values(&["1", "2"]) .possible_values(&["1", "2"])
.value_name("FILENUM") .value_name("FILENUM")
.help( .help(
@ -531,6 +541,9 @@ FILENUM is 1 or 2, corresponding to FILE1 or FILE2",
.arg( .arg(
Arg::with_name("v") Arg::with_name("v")
.short("v") .short("v")
.multiple(true)
.number_of_values(1)
.possible_values(&["1", "2"])
.value_name("FILENUM") .value_name("FILENUM")
.help("like -a FILENUM, but suppress joined output lines"), .help("like -a FILENUM, but suppress joined output lines"),
) )
@ -617,7 +630,7 @@ fn exec(file1: &str, file2: &str, settings: &Settings) -> i32 {
file1, file1,
&stdin, &stdin,
settings.key1, settings.key1,
settings.print_unpaired, settings.print_unpaired1,
); );
let mut state2 = State::new( let mut state2 = State::new(
@ -625,7 +638,7 @@ fn exec(file1: &str, file2: &str, settings: &Settings) -> i32 {
file2, file2,
&stdin, &stdin,
settings.key2, settings.key2,
settings.print_unpaired, settings.print_unpaired2,
); );
let input = Input::new( let input = Input::new(

View file

@ -11,7 +11,7 @@
extern crate uucore; extern crate uucore;
use clap::{crate_version, App, Arg}; use clap::{crate_version, App, Arg};
use uucore::error::{UCustomError, UResult}; use uucore::error::{UError, UResult};
use std::borrow::Cow; use std::borrow::Cow;
use std::error::Error; use std::error::Error;
@ -79,7 +79,7 @@ impl Display for LnError {
impl Error for LnError {} impl Error for LnError {}
impl UCustomError for LnError { impl UError for LnError {
fn code(&self) -> i32 { fn code(&self) -> i32 {
match self { match self {
Self::TargetIsDirectory(_) => 1, Self::TargetIsDirectory(_) => 1,

View file

@ -39,7 +39,7 @@ use std::{
time::Duration, time::Duration,
}; };
use term_grid::{Cell, Direction, Filling, Grid, GridOptions}; use term_grid::{Cell, Direction, Filling, Grid, GridOptions};
use uucore::error::{set_exit_code, FromIo, UCustomError, UResult}; use uucore::error::{set_exit_code, FromIo, UError, UResult};
use unicode_width::UnicodeWidthStr; use unicode_width::UnicodeWidthStr;
#[cfg(unix)] #[cfg(unix)]
@ -127,13 +127,15 @@ pub mod options {
pub static IGNORE: &str = "ignore"; pub static IGNORE: &str = "ignore";
} }
const DEFAULT_TERM_WIDTH: u16 = 80;
#[derive(Debug)] #[derive(Debug)]
enum LsError { enum LsError {
InvalidLineWidth(String), InvalidLineWidth(String),
NoMetadata(PathBuf), NoMetadata(PathBuf),
} }
impl UCustomError for LsError { impl UError for LsError {
fn code(&self) -> i32 { fn code(&self) -> i32 {
match self { match self {
LsError::InvalidLineWidth(_) => 2, LsError::InvalidLineWidth(_) => 2,
@ -229,7 +231,7 @@ struct Config {
inode: bool, inode: bool,
color: Option<LsColors>, color: Option<LsColors>,
long: LongFormat, long: LongFormat,
width: Option<u16>, width: u16,
quoting_style: QuotingStyle, quoting_style: QuotingStyle,
indicator_style: IndicatorStyle, indicator_style: IndicatorStyle,
time_style: TimeStyle, time_style: TimeStyle,
@ -258,16 +260,20 @@ impl Config {
// below should never happen as clap already restricts the values. // below should never happen as clap already restricts the values.
_ => unreachable!("Invalid field for --format"), _ => unreachable!("Invalid field for --format"),
}, },
options::FORMAT, Some(options::FORMAT),
) )
} else if options.is_present(options::format::LONG) { } 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) { } 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) { } 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 { } else {
(Format::Columns, options::format::COLUMNS) (Format::OneLine, None)
}; };
// The -o, -n and -g options are tricky. They cannot override with each // 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 // options, but manually whether they have an index that's greater than
// the other format options. If so, we set the appropriate format. // the other format options. If so, we set the appropriate format.
if format != Format::Long { if format != Format::Long {
let idx = options let idx = opt
.indices_of(opt) .and_then(|opt| options.indices_of(opt).map(|x| x.max().unwrap()))
.map(|x| x.max().unwrap())
.unwrap_or(0); .unwrap_or(0);
if [ if [
options::format::LONG_NO_OWNER, options::format::LONG_NO_OWNER,
@ -399,10 +404,25 @@ impl Config {
let width = match options.value_of(options::WIDTH) { let width = match options.value_of(options::WIDTH) {
Some(x) => match x.parse::<u16>() { Some(x) => match x.parse::<u16>() {
Ok(u) => Some(u), Ok(u) => u,
Err(_) => return Err(LsError::InvalidLineWidth(x.into()).into()), 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)] #[allow(clippy::needless_bool)]
@ -1411,15 +1431,10 @@ fn display_items(items: &[PathData], config: &Config, out: &mut BufWriter<Stdout
} else { } else {
let names = items.iter().filter_map(|i| display_file_name(i, config)); let names = items.iter().filter_map(|i| display_file_name(i, config));
match (&config.format, config.width) { match config.format {
(Format::Columns, Some(width)) => { Format::Columns => display_grid(names, config.width, Direction::TopToBottom, out),
display_grid(names, width, Direction::TopToBottom, out) Format::Across => display_grid(names, config.width, Direction::LeftToRight, out),
} Format::Commas => {
(Format::Across, Some(width)) => {
display_grid(names, width, Direction::LeftToRight, out)
}
(Format::Commas, width_opt) => {
let term_width = width_opt.unwrap_or(1);
let mut current_col = 0; let mut current_col = 0;
let mut names = names; let mut names = names;
if let Some(name) = names.next() { 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 { for name in names {
let name_width = name.width as u16; 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; current_col = name_width + 2;
let _ = write!(out, ",\n{}", name.contents); let _ = write!(out, ",\n{}", name.contents);
} else { } else {
@ -1480,22 +1496,37 @@ fn display_grid(
direction: Direction, direction: Direction,
out: &mut BufWriter<Stdout>, out: &mut BufWriter<Stdout>,
) { ) {
let mut grid = Grid::new(GridOptions { if width == 0 {
filling: Filling::Spaces(2), // If the width is 0 we print one single line
direction, let mut printed_something = false;
}); for name in names {
if printed_something {
for name in names { let _ = write!(out, " ");
grid.add(name); }
} printed_something = true;
let _ = write!(out, "{}", name.contents);
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 if printed_something {
None => { let _ = writeln!(out);
let _ = write!(out, "{}", grid.fit_into_columns(1)); }
} 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 { fn cached_gid2grp(gid: u32) -> String {
lazy_static! { lazy_static! {
static ref GID_CACHE: Mutex<HashMap<u32, String>> = Mutex::new(HashMap::new()); static ref GID_CACHE: Mutex<HashMap<u32, String>> = Mutex::new(HashMap::new());
@ -1603,7 +1634,7 @@ fn cached_gid2grp(gid: u32) -> String {
.clone() .clone()
} }
#[cfg(unix)] #[cfg(all(unix, not(target_os = "redox")))]
fn display_group(metadata: &Metadata, config: &Config) -> String { fn display_group(metadata: &Metadata, config: &Config) -> String {
if config.long.numeric_uid_gid { if config.long.numeric_uid_gid {
metadata.gid().to_string() 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))] #[cfg(not(unix))]
fn display_uname(_metadata: &Metadata, _config: &Config) -> String { fn display_uname(_metadata: &Metadata, _config: &Config) -> String {
"somebody".to_string() "somebody".to_string()

View file

@ -12,7 +12,7 @@
extern crate uucore; extern crate uucore;
use clap::{crate_version, App, Arg}; use clap::{crate_version, App, Arg};
use uucore::error::{FromIo, UCustomError, UResult}; use uucore::error::{FromIo, UError, UResult};
use std::env; use std::env;
use std::error::Error; use std::error::Error;
@ -49,7 +49,7 @@ enum MkTempError {
InvalidTemplate(String), InvalidTemplate(String),
} }
impl UCustomError for MkTempError {} impl UError for MkTempError {}
impl Error for MkTempError {} impl Error for MkTempError {}

View file

@ -25,7 +25,7 @@ unicode-segmentation = "1.7.1"
[target.'cfg(target_os = "redox")'.dependencies] [target.'cfg(target_os = "redox")'.dependencies]
redox_termios = "0.1" redox_termios = "0.1"
redox_syscall = "0.1" redox_syscall = "0.2"
[target.'cfg(all(unix, not(target_os = "fuchsia")))'.dependencies] [target.'cfg(all(unix, not(target_os = "fuchsia")))'.dependencies]
nix = "<=0.13" nix = "<=0.13"

View file

@ -10,21 +10,13 @@
#[macro_use] #[macro_use]
extern crate uucore; 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::ffi::CString;
use std::io::Error; use std::io::Error;
use std::ptr; use std::ptr;
use clap::{crate_version, App, AppSettings, Arg}; 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 mod options {
pub static ADJUSTMENT: &str = "adjustment"; pub static ADJUSTMENT: &str = "adjustment";
pub static COMMAND: &str = "COMMAND"; pub static COMMAND: &str = "COMMAND";
@ -50,7 +42,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
let mut niceness = unsafe { let mut niceness = unsafe {
nix::errno::Errno::clear(); nix::errno::Errno::clear();
getpriority(PRIO_PROCESS, 0) libc::getpriority(PRIO_PROCESS, 0)
}; };
if Error::last_os_error().raw_os_error().unwrap() != 0 { if Error::last_os_error().raw_os_error().unwrap() != 0 {
show_error!("getpriority: {}", Error::last_os_error()); show_error!("getpriority: {}", Error::last_os_error());
@ -84,7 +76,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
}; };
niceness += adjustment; 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()); show_warning!("setpriority: {}", Error::last_os_error());
} }

View file

@ -435,6 +435,7 @@ pub fn uu_app() -> clap::App<'static, 'static> {
.long(options::FORMAT) .long(options::FORMAT)
.help("select output format or formats") .help("select output format or formats")
.multiple(true) .multiple(true)
.number_of_values(1)
.value_name("TYPE"), .value_name("TYPE"),
) )
.arg( .arg(

View file

@ -16,7 +16,7 @@ path = "src/pr.rs"
[dependencies] [dependencies]
clap = { version = "2.33", features = ["wrap_help"] } 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" } uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_procs" }
getopts = "0.2.21" getopts = "0.2.21"
time = "0.1.41" time = "0.1.41"

View file

@ -18,10 +18,11 @@ path = "src/sort.rs"
binary-heap-plus = "0.4.1" binary-heap-plus = "0.4.1"
clap = { version = "2.33", features = ["wrap_help"] } clap = { version = "2.33", features = ["wrap_help"] }
compare = "0.1.0" compare = "0.1.0"
ctrlc = { version = "3.0", features = ["termination"] }
fnv = "1.0.7" fnv = "1.0.7"
itertools = "0.10.0" itertools = "0.10.0"
memchr = "2.4.0" memchr = "2.4.0"
ouroboros = "0.9.3" ouroboros = "0.10.1"
rand = "0.7" rand = "0.7"
rayon = "1.5" rayon = "1.5"
tempfile = "3" tempfile = "3"

View file

@ -13,7 +13,6 @@
use std::cmp::Ordering; use std::cmp::Ordering;
use std::io::Write; use std::io::Write;
use std::path::Path;
use std::path::PathBuf; use std::path::PathBuf;
use std::{ use std::{
io::Read, io::Read,
@ -29,14 +28,13 @@ use crate::merge::ClosedTmpFile;
use crate::merge::WriteableCompressedTmpFile; use crate::merge::WriteableCompressedTmpFile;
use crate::merge::WriteablePlainTmpFile; use crate::merge::WriteablePlainTmpFile;
use crate::merge::WriteableTmpFile; use crate::merge::WriteableTmpFile;
use crate::tmp_dir::TmpDirWrapper;
use crate::Output; use crate::Output;
use crate::SortError;
use crate::{ use crate::{
chunks::{self, Chunk}, chunks::{self, Chunk},
compare_by, merge, sort_by, GlobalSettings, compare_by, merge, sort_by, GlobalSettings,
}; };
use crate::{print_sorted, Line}; use crate::{print_sorted, Line};
use tempfile::TempDir;
const START_BUFFER_SIZE: usize = 8_000; 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>>>, files: &mut impl Iterator<Item = UResult<Box<dyn Read + Send>>>,
settings: &GlobalSettings, settings: &GlobalSettings,
output: Output, output: Output,
tmp_dir: &mut TmpDirWrapper,
) -> UResult<()> { ) -> UResult<()> {
let (sorted_sender, sorted_receiver) = std::sync::mpsc::sync_channel(1); let (sorted_sender, sorted_receiver) = std::sync::mpsc::sync_channel(1);
let (recycled_sender, recycled_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, sorted_receiver,
recycled_sender, recycled_sender,
output, output,
tmp_dir,
) )
} else { } else {
reader_writer::<_, WriteablePlainTmpFile>( reader_writer::<_, WriteablePlainTmpFile>(
@ -67,6 +67,7 @@ pub fn ext_sort(
sorted_receiver, sorted_receiver,
recycled_sender, recycled_sender,
output, output,
tmp_dir,
) )
} }
} }
@ -80,6 +81,7 @@ fn reader_writer<
receiver: Receiver<Chunk>, receiver: Receiver<Chunk>,
sender: SyncSender<Chunk>, sender: SyncSender<Chunk>,
output: Output, output: Output,
tmp_dir: &mut TmpDirWrapper,
) -> UResult<()> { ) -> UResult<()> {
let separator = if settings.zero_terminated { let separator = if settings.zero_terminated {
b'\0' b'\0'
@ -92,7 +94,7 @@ fn reader_writer<
let buffer_size = settings.buffer_size / 10; let buffer_size = settings.buffer_size / 10;
let read_result: ReadResult<Tmp> = read_write_loop( let read_result: ReadResult<Tmp> = read_write_loop(
files, files,
&settings.tmp_dir, tmp_dir,
separator, separator,
buffer_size, buffer_size,
settings, settings,
@ -100,12 +102,11 @@ fn reader_writer<
sender, sender,
)?; )?;
match read_result { match read_result {
ReadResult::WroteChunksToFile { tmp_files, tmp_dir } => { ReadResult::WroteChunksToFile { tmp_files } => {
let tmp_dir_size = tmp_files.len();
let merger = merge::merge_with_file_limit::<_, _, Tmp>( let merger = merge::merge_with_file_limit::<_, _, Tmp>(
tmp_files.into_iter().map(|c| c.reopen()), tmp_files.into_iter().map(|c| c.reopen()),
settings, settings,
Some((tmp_dir, tmp_dir_size)), tmp_dir,
)?; )?;
merger.write_all(settings, output)?; merger.write_all(settings, output)?;
} }
@ -176,15 +177,12 @@ enum ReadResult<I: WriteableTmpFile> {
/// The input fits into two chunks, which were kept in memory. /// The input fits into two chunks, which were kept in memory.
SortedTwoChunks([Chunk; 2]), SortedTwoChunks([Chunk; 2]),
/// The input was read into multiple chunks, which were written to auxiliary files. /// The input was read into multiple chunks, which were written to auxiliary files.
WroteChunksToFile { WroteChunksToFile { tmp_files: Vec<I::Closed> },
tmp_files: Vec<I::Closed>,
tmp_dir: TempDir,
},
} }
/// The function that is executed on the reader/writer thread. /// The function that is executed on the reader/writer thread.
fn read_write_loop<I: WriteableTmpFile>( fn read_write_loop<I: WriteableTmpFile>(
mut files: impl Iterator<Item = UResult<Box<dyn Read + Send>>>, mut files: impl Iterator<Item = UResult<Box<dyn Read + Send>>>,
tmp_dir_parent: &Path, tmp_dir: &mut TmpDirWrapper,
separator: u8, separator: u8,
buffer_size: usize, buffer_size: usize,
settings: &GlobalSettings, 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 sender_option = Some(sender);
let mut file_number = 0;
let mut tmp_files = vec![]; let mut tmp_files = vec![];
loop { loop {
let mut chunk = match receiver.recv() { let mut chunk = match receiver.recv() {
Ok(it) => it, Ok(it) => it,
_ => { _ => {
return Ok(ReadResult::WroteChunksToFile { tmp_files, tmp_dir }); return Ok(ReadResult::WroteChunksToFile { tmp_files });
} }
}; };
let tmp_file = write::<I>( let tmp_file = write::<I>(
&mut chunk, &mut chunk,
tmp_dir.path().join(file_number.to_string()), tmp_dir.next_file_path()?,
settings.compress_prog.as_deref(), settings.compress_prog.as_deref(),
separator, separator,
)?; )?;
tmp_files.push(tmp_file); tmp_files.push(tmp_file);
file_number += 1;
let recycled_chunk = chunk.recycle(); let recycled_chunk = chunk.recycle();
if let Some(sender) = &sender_option { if let Some(sender) = &sender_option {

View file

@ -22,45 +22,41 @@ use std::{
use compare::Compare; use compare::Compare;
use itertools::Itertools; use itertools::Itertools;
use tempfile::TempDir;
use uucore::error::UResult; use uucore::error::UResult;
use crate::{ use crate::{
chunks::{self, Chunk, RecycledChunk}, 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 /// 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. /// and replace its occurrences in the inputs with that copy.
fn replace_output_file_in_input_files( fn replace_output_file_in_input_files(
files: &mut [OsString], files: &mut [OsString],
settings: &GlobalSettings,
output: Option<&str>, output: Option<&str>,
) -> UResult<Option<(TempDir, usize)>> { tmp_dir: &mut TmpDirWrapper,
let mut copy: Option<(TempDir, PathBuf)> = None; ) -> UResult<()> {
let mut copy: Option<PathBuf> = None;
if let Some(Ok(output_path)) = output.map(|path| Path::new(path).canonicalize()) { if let Some(Ok(output_path)) = output.map(|path| Path::new(path).canonicalize()) {
for file in files { for file in files {
if let Ok(file_path) = Path::new(file).canonicalize() { if let Ok(file_path) = Path::new(file).canonicalize() {
if file_path == output_path { if file_path == output_path {
if let Some((_dir, copy)) = &copy { if let Some(copy) = &copy {
*file = copy.clone().into_os_string(); *file = copy.clone().into_os_string();
} else { } else {
let tmp_dir = tempfile::Builder::new() let copy_path = tmp_dir.next_file_path()?;
.prefix("uutils_sort")
.tempdir_in(&settings.tmp_dir)
.map_err(|_| SortError::TmpDirCreationFailed)?;
let copy_path = tmp_dir.path().join("0");
std::fs::copy(file_path, &copy_path) std::fs::copy(file_path, &copy_path)
.map_err(|error| SortError::OpenTmpFileFailed { error })?; .map_err(|error| SortError::OpenTmpFileFailed { error })?;
*file = copy_path.clone().into_os_string(); *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(())
Ok(copy.map(|(dir, _copy)| (dir, 1)))
} }
/// Merge pre-sorted `Box<dyn Read>`s. /// Merge pre-sorted `Box<dyn Read>`s.
@ -71,8 +67,9 @@ pub fn merge<'a>(
files: &mut [OsString], files: &mut [OsString],
settings: &'a GlobalSettings, settings: &'a GlobalSettings,
output: Option<&str>, output: Option<&str>,
tmp_dir: &mut TmpDirWrapper,
) -> UResult<FileMerger<'a>> { ) -> 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() { if settings.compress_prog.is_none() {
merge_with_file_limit::<_, _, WriteablePlainTmpFile>( merge_with_file_limit::<_, _, WriteablePlainTmpFile>(
files files
@ -94,26 +91,16 @@ pub fn merge<'a>(
// Merge already sorted `MergeInput`s. // Merge already sorted `MergeInput`s.
pub fn merge_with_file_limit< pub fn merge_with_file_limit<
'a,
M: MergeInput + 'static, M: MergeInput + 'static,
F: ExactSizeIterator<Item = UResult<M>>, F: ExactSizeIterator<Item = UResult<M>>,
Tmp: WriteableTmpFile + 'static, Tmp: WriteableTmpFile + 'static,
>( >(
files: F, files: F,
settings: &GlobalSettings, settings: &'a GlobalSettings,
tmp_dir: Option<(TempDir, usize)>, tmp_dir: &mut TmpDirWrapper,
) -> UResult<FileMerger> { ) -> UResult<FileMerger<'a>> {
if files.len() > settings.merge_batch_size { 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 mut remaining_files = files.len();
let batches = files.chunks(settings.merge_batch_size); let batches = files.chunks(settings.merge_batch_size);
let mut batches = batches.into_iter(); 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`. // Work around the fact that `Chunks` is not an `ExactSizeIterator`.
remaining_files = remaining_files.saturating_sub(settings.merge_batch_size); remaining_files = remaining_files.saturating_sub(settings.merge_batch_size);
let merger = merge_without_limit(batches.next().unwrap(), settings)?; let merger = merge_without_limit(batches.next().unwrap(), settings)?;
let mut tmp_file = Tmp::create( let mut tmp_file =
tmp_dir.path().join(tmp_dir_size.to_string()), Tmp::create(tmp_dir.next_file_path()?, settings.compress_prog.as_deref())?;
settings.compress_prog.as_deref(),
)?;
tmp_dir_size += 1;
merger.write_all_to(settings, tmp_file.as_write())?; merger.write_all_to(settings, tmp_file.as_write())?;
temporary_files.push(tmp_file.finished_writing()?); 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>, dyn FnMut(Tmp::Closed) -> UResult<<Tmp::Closed as ClosedTmpFile>::Reopened>,
>), >),
settings, settings,
Some((tmp_dir, tmp_dir_size)), tmp_dir,
) )
} else { } else {
merge_without_limit(files, settings) merge_without_limit(files, settings)

View file

@ -22,6 +22,7 @@ mod custom_str_cmp;
mod ext_sort; mod ext_sort;
mod merge; mod merge;
mod numeric_str_cmp; mod numeric_str_cmp;
mod tmp_dir;
use chunks::LineData; use chunks::LineData;
use clap::{crate_version, App, Arg}; use clap::{crate_version, App, Arg};
@ -44,11 +45,13 @@ use std::path::Path;
use std::path::PathBuf; use std::path::PathBuf;
use std::str::Utf8Error; use std::str::Utf8Error;
use unicode_width::UnicodeWidthStr; 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::parse_size::{parse_size, ParseSizeError};
use uucore::version_cmp::version_cmp; use uucore::version_cmp::version_cmp;
use uucore::InvalidEncodingHandling; use uucore::InvalidEncodingHandling;
use crate::tmp_dir::TmpDirWrapper;
const NAME: &str = "sort"; const NAME: &str = "sort";
const ABOUT: &str = "Display sorted concatenation of all FILE(s)."; const ABOUT: &str = "Display sorted concatenation of all FILE(s).";
@ -161,7 +164,7 @@ enum SortError {
impl Error for SortError {} impl Error for SortError {}
impl UCustomError for SortError { impl UError for SortError {
fn code(&self) -> i32 { fn code(&self) -> i32 {
match self { match self {
SortError::Disorder { .. } => 1, SortError::Disorder { .. } => 1,
@ -317,7 +320,6 @@ pub struct GlobalSettings {
threads: String, threads: String,
zero_terminated: bool, zero_terminated: bool,
buffer_size: usize, buffer_size: usize,
tmp_dir: PathBuf,
compress_prog: Option<String>, compress_prog: Option<String>,
merge_batch_size: usize, merge_batch_size: usize,
precomputed: Precomputed, precomputed: Precomputed,
@ -400,7 +402,6 @@ impl Default for GlobalSettings {
threads: String::new(), threads: String::new(),
zero_terminated: false, zero_terminated: false,
buffer_size: DEFAULT_BUF_SIZE, buffer_size: DEFAULT_BUF_SIZE,
tmp_dir: PathBuf::new(),
compress_prog: None, compress_prog: None,
merge_batch_size: 32, merge_batch_size: 32,
precomputed: Precomputed { precomputed: Precomputed {
@ -1178,10 +1179,12 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
}) })
})?; })?;
settings.tmp_dir = matches let mut tmp_dir = TmpDirWrapper::new(
.value_of(options::TMP_DIR) matches
.map(PathBuf::from) .value_of(options::TMP_DIR)
.unwrap_or_else(env::temp_dir); .map(PathBuf::from)
.unwrap_or_else(env::temp_dir),
);
settings.compress_prog = matches.value_of(options::COMPRESS_PROG).map(String::from); 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 { if separator.len() != 1 {
return Err(UUsageError::new( return Err(UUsageError::new(
2, 2,
"separator must be exactly one character long".into(), "separator must be exactly one character long",
)); ));
} }
settings.separator = Some(separator.chars().next().unwrap()) settings.separator = Some(separator.chars().next().unwrap())
@ -1280,7 +1283,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
settings.init_precomputed(); settings.init_precomputed();
exec(&mut files, &settings, output) exec(&mut files, &settings, output, &mut tmp_dir)
} }
pub fn uu_app() -> App<'static, 'static> { 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 { 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) file_merger.write_all(settings, output)
} else if settings.check { } else if settings.check {
if files.len() > 1 { 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 { } else {
check::check(files.first().unwrap(), settings) check::check(files.first().unwrap(), settings)
} }
} else { } else {
let mut lines = files.iter().map(open); let mut lines = files.iter().map(open);
ext_sort(&mut lines, settings, output) ext_sort(&mut lines, settings, output, tmp_dir)
} }
} }

View 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)
}

View file

@ -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"] } winapi = { version="0.3", features=["fileapi", "handleapi", "processthreadsapi", "synchapi", "winbase"] }
[target.'cfg(target_os = "redox")'.dependencies] [target.'cfg(target_os = "redox")'.dependencies]
redox_syscall = "0.1" redox_syscall = "0.2"
[target.'cfg(unix)'.dependencies] [target.'cfg(unix)'.dependencies]
nix = "0.20" nix = "0.20"

View file

@ -14,14 +14,8 @@ pub use self::unix::{stdin_is_pipe_or_fifo, supports_pid_checks, Pid, ProcessChe
#[cfg(windows)] #[cfg(windows)]
pub use self::windows::{supports_pid_checks, Pid, ProcessChecker}; pub use self::windows::{supports_pid_checks, Pid, ProcessChecker};
#[cfg(target_os = "redox")]
pub use self::redox::{supports_pid_checks, Pid, ProcessChecker};
#[cfg(unix)] #[cfg(unix)]
mod unix; mod unix;
#[cfg(windows)] #[cfg(windows)]
mod windows; mod windows;
#[cfg(target_os = "redox")]
mod redox;

View file

@ -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
}

View file

@ -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" } uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_procs" }
[target.'cfg(target_os = "redox")'.dependencies] [target.'cfg(target_os = "redox")'.dependencies]
redox_syscall = "0.1" redox_syscall = "0.2"
[[bin]] [[bin]]
name = "test" name = "test"

View file

@ -17,7 +17,7 @@ use clap::{crate_version, App, Arg, ArgGroup};
use filetime::*; use filetime::*;
use std::fs::{self, File}; use std::fs::{self, File};
use std::path::Path; 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."; static ABOUT: &str = "Update the access and modification times of each FILE to the current time.";
pub mod options { pub mod options {

View file

@ -16,7 +16,7 @@ edition = "2018"
path="src/lib/lib.rs" path="src/lib/lib.rs"
[dependencies] [dependencies]
dns-lookup = "1.0.5" dns-lookup = { version="1.0.5", optional=true }
dunce = "1.0.0" dunce = "1.0.0"
getopts = "<= 0.2.21" getopts = "<= 0.2.21"
wild = "2.0.4" wild = "2.0.4"
@ -27,7 +27,9 @@ nix = { version="<= 0.13", optional=true }
platform-info = { version="<= 0.1", optional=true } platform-info = { version="<= 0.1", optional=true }
time = { version="<= 0.1.43", optional=true } time = { version="<= 0.1.43", optional=true }
# * "problem" dependencies (pinned) # * "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 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] [dev-dependencies]
@ -43,7 +45,7 @@ termion = "1.5"
[features] [features]
default = [] default = []
# * non-default features # * non-default features
encoding = ["data-encoding", "thiserror"] encoding = ["data-encoding", "data-encoding-macro", "z85", "thiserror"]
entries = ["libc"] entries = ["libc"]
fs = ["libc"] fs = ["libc"]
fsext = ["libc", "time"] fsext = ["libc", "time"]
@ -53,6 +55,6 @@ process = ["libc"]
ringbuffer = [] ringbuffer = []
signals = [] signals = []
utf8 = [] utf8 = []
utmpx = ["time", "libc"] utmpx = ["time", "libc", "dns-lookup"]
wide = [] wide = []
zero-copy = ["nix", "libc", "lazy_static", "platform-info"] zero-copy = ["nix", "libc", "lazy_static", "platform-info"]

View file

@ -29,6 +29,7 @@ pub mod signals;
#[cfg(all( #[cfg(all(
unix, unix,
not(target_os = "fuchsia"), not(target_os = "fuchsia"),
not(target_os = "redox"),
not(target_env = "musl"), not(target_env = "musl"),
feature = "utmpx" feature = "utmpx"
))] ))]

View file

@ -5,45 +5,95 @@
// For the full copyright and license information, please view the LICENSE // For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code. // file that was distributed with this source code.
// spell-checker:ignore (strings) ABCDEFGHIJKLMNOPQRSTUVWXYZ // spell-checker:ignore (strings) ABCDEFGHIJKLMNOPQRSTUVWXYZ ABCDEFGHIJKLMNOPQRSTUV
// spell-checker:ignore (encodings) lsbf msbf hexupper
extern crate data_encoding; use data_encoding::{self, BASE32, BASE64};
use self::data_encoding::{DecodeError, BASE32, BASE64};
use std::io::{self, Read, Write}; use std::io::{self, Read, Write};
use data_encoding::{Encoding, BASE32HEX, BASE64URL, HEXUPPER};
use data_encoding_macro::new_encoding;
#[cfg(feature = "thiserror")] #[cfg(feature = "thiserror")]
use thiserror::Error; use thiserror::Error;
#[derive(Debug, Error)] #[derive(Debug, Error)]
pub enum EncodingError { pub enum DecodeError {
#[error("{}", _0)] #[error("{}", _0)]
Decode(#[from] DecodeError), Decode(#[from] data_encoding::DecodeError),
#[error("{}", _0)]
DecodeZ85(#[from] z85::DecodeError),
#[error("{}", _0)] #[error("{}", _0)]
Io(#[from] io::Error), 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)] #[derive(Clone, Copy)]
pub enum Format { pub enum Format {
Base32,
Base64, Base64,
Base64Url,
Base32,
Base32Hex,
Base16,
Base2Lsbf,
Base2Msbf,
Z85,
} }
use self::Format::*; use self::Format::*;
pub fn encode(f: Format, input: &[u8]) -> String { const BASE2LSBF: Encoding = new_encoding! {
match f { 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), Base32 => BASE32.encode(input),
Base64 => BASE64.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 { pub fn decode(f: Format, input: &[u8]) -> DecodeResult {
Ok(match f { Ok(match f {
Base32 => BASE32.decode(input)?, Base32 => BASE32.decode(input)?,
Base64 => BASE64.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 { alphabet: match format {
Base32 => b"ABCDEFGHIJKLMNOPQRSTUVWXYZ234567=", Base32 => b"ABCDEFGHIJKLMNOPQRSTUVWXYZ234567=",
Base64 => b"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789=+/", 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) decode(self.format, &buf)
} }
pub fn encode(&mut self) -> String { pub fn encode(&mut self) -> Result<String, EncodeError> {
let mut buf: Vec<u8> = vec![]; let mut buf: Vec<u8> = vec![];
self.input.read_to_end(&mut buf).unwrap(); self.input.read_to_end(&mut buf).unwrap();
encode(self.format, buf.as_slice()) encode(self.format, buf.as_slice())

View file

@ -37,7 +37,9 @@
#[cfg(any(target_os = "freebsd", target_vendor = "apple"))] #[cfg(any(target_os = "freebsd", target_vendor = "apple"))]
use libc::time_t; use libc::time_t;
use libc::{c_char, c_int, gid_t, uid_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::borrow::Cow;
use std::ffi::{CStr, CString}; use std::ffi::{CStr, CString};
@ -65,6 +67,7 @@ extern "C" {
/// > supplementary group IDs for the process is returned. This allows /// > supplementary group IDs for the process is returned. This allows
/// > the caller to determine the size of a dynamically allocated list /// > the caller to determine the size of a dynamically allocated list
/// > to be used in a further call to getgroups(). /// > to be used in a further call to getgroups().
#[cfg(not(target_os = "redox"))]
pub fn get_groups() -> IOResult<Vec<gid_t>> { pub fn get_groups() -> IOResult<Vec<gid_t>> {
let ngroups = unsafe { getgroups(0, ptr::null_mut()) }; let ngroups = unsafe { getgroups(0, ptr::null_mut()) };
if ngroups == -1 { 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 /// > groups is the same (in the mathematical sense of ``set''). (The
/// > history of a process and its parents could affect the details of /// > history of a process and its parents could affect the details of
/// > the result.) /// > 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>> { pub fn get_groups_gnu(arg_id: Option<u32>) -> IOResult<Vec<gid_t>> {
let groups = get_groups()?; let groups = get_groups()?;
let egid = arg_id.unwrap_or_else(crate::features::process::getegid); let egid = arg_id.unwrap_or_else(crate::features::process::getegid);
@ -319,6 +322,7 @@ macro_rules! f {
} }
f!(getpwnam, getpwuid, uid_t, Passwd); f!(getpwnam, getpwuid, uid_t, Passwd);
#[cfg(not(target_os = "redox"))]
f!(getgrnam, getgrgid, gid_t, Group); f!(getgrnam, getgrgid, gid_t, Group);
#[inline] #[inline]
@ -326,6 +330,7 @@ pub fn uid2usr(id: uid_t) -> IOResult<String> {
Passwd::locate(id).map(|p| p.name().into_owned()) Passwd::locate(id).map(|p| p.name().into_owned())
} }
#[cfg(not(target_os = "redox"))]
#[inline] #[inline]
pub fn gid2grp(id: gid_t) -> IOResult<String> { pub fn gid2grp(id: gid_t) -> IOResult<String> {
Group::locate(id).map(|p| p.name().into_owned()) 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()) Passwd::locate(name).map(|p| p.uid())
} }
#[cfg(not(target_os = "redox"))]
#[inline] #[inline]
pub fn grp2gid(name: &str) -> IOResult<gid_t> { pub fn grp2gid(name: &str) -> IOResult<gid_t> {
Group::locate(name).map(|p| p.gid()) Group::locate(name).map(|p| p.gid())

View file

@ -15,8 +15,6 @@ use libc::{
use std::borrow::Cow; use std::borrow::Cow;
use std::env; use std::env;
use std::fs; use std::fs;
#[cfg(target_os = "redox")]
use std::io;
use std::io::Result as IOResult; use std::io::Result as IOResult;
use std::io::{Error, ErrorKind}; use std::io::{Error, ErrorKind};
#[cfg(any(unix, target_os = "redox"))] #[cfg(any(unix, target_os = "redox"))]

View file

@ -94,7 +94,8 @@ pub use libc::statfs as StatFs;
target_os = "netbsd", target_os = "netbsd",
target_os = "openbsd", target_os = "openbsd",
target_os = "bitrig", target_os = "bitrig",
target_os = "dragonfly" target_os = "dragonfly",
target_os = "redox"
))] ))]
pub use libc::statvfs as StatFs; pub use libc::statvfs as StatFs;
@ -110,7 +111,8 @@ pub use libc::statfs as statfs_fn;
target_os = "netbsd", target_os = "netbsd",
target_os = "openbsd", target_os = "openbsd",
target_os = "bitrig", target_os = "bitrig",
target_os = "dragonfly" target_os = "dragonfly",
target_os = "redox"
))] ))]
pub use libc::statvfs as statfs_fn; pub use libc::statvfs as statfs_fn;
@ -438,6 +440,11 @@ pub fn read_fs_list() -> Vec<MountInfo> {
} }
mounts mounts
} }
#[cfg(target_os = "redox")]
{
// No method to read mounts, yet
Vec::new()
}
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]

View file

@ -35,12 +35,6 @@ pub fn parse_symbolic(
#[cfg(unix)] #[cfg(unix)]
use libc::umask; 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); let (mask, pos) = parse_levels(mode);
if pos == mode.len() { if pos == mode.len() {
return Err(format!("invalid mode ({})", mode)); return Err(format!("invalid mode ({})", mode));

View file

@ -49,13 +49,14 @@ fn chgrp<P: AsRef<Path>>(path: P, gid: gid_t, follow: bool) -> IOResult<()> {
pub fn wrap_chgrp<P: AsRef<Path>>( pub fn wrap_chgrp<P: AsRef<Path>>(
path: P, path: P,
meta: &Metadata, meta: &Metadata,
dest_gid: gid_t, dest_gid: Option<gid_t>,
follow: bool, follow: bool,
verbosity: Verbosity, verbosity: Verbosity,
) -> Result<String, String> { ) -> Result<String, String> {
use self::Verbosity::*; use self::Verbosity::*;
let path = path.as_ref(); let path = path.as_ref();
let mut out: String = String::new(); 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) { if let Err(e) = chgrp(path, dest_gid, follow) {
match verbosity { match verbosity {

View file

@ -65,6 +65,7 @@ pub use crate::features::signals;
#[cfg(all( #[cfg(all(
unix, unix,
not(target_os = "fuchsia"), not(target_os = "fuchsia"),
not(target_os = "redox"),
not(target_env = "musl"), not(target_env = "musl"),
feature = "utmpx" feature = "utmpx"
))] ))]

View file

@ -6,11 +6,11 @@
//! This module provides types to reconcile these exit codes with idiomatic Rust error //! 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`]: //! 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 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. The error messages are largely standardized across utils.
//! 1. Standardized error messages can be created from external result types //! 1. Standardized error messages can be created from external result types
//! (i.e. [`std::io::Result`] & `clap::ClapResult`). //! (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 //! # Usage
//! The signature of a typical util should be: //! 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 //! 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`: //! 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 //! * 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. //! [`set_exit_code`]. See the documentation on that function for more information.
//! //!
//! # Guidelines //! # Guidelines
//! * Use common errors where possible. //! * Use error types from `uucore` where possible.
//! * Add variants to [`UCommonError`] if an error appears in multiple utils. //! * Add error types to `uucore` if an error appears in multiple utils.
//! * Prefer proper custom error types over [`ExitCode`] and [`USimpleError`]. //! * Prefer proper custom error types over [`ExitCode`] and [`USimpleError`].
//! * [`USimpleError`] may be used in small utils with simple error handling. //! * [`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 //! * Using [`ExitCode`] is not recommended but can be useful for converting utils to use
//! [`UResult`]. //! [`UResult`].
// spell-checker:ignore uioerror
use std::{ use std::{
error::Error, error::Error,
fmt::{Display, Formatter}, fmt::{Display, Formatter},
@ -85,115 +87,10 @@ pub fn set_exit_code(code: i32) {
EXIT_CODE.store(code, Ordering::SeqCst); EXIT_CODE.store(code, Ordering::SeqCst);
} }
/// Should be returned by all utils. /// Result type that should be returned by all utils.
/// pub type UResult<T> = Result<T, Box<dyn UError>>;
/// 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>;
trait UResultTrait<T> { /// Custom errors defined by the utils and `uucore`.
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.
/// ///
/// All errors should implement [`std::error::Error`], [`std::fmt::Display`] and /// All errors should implement [`std::error::Error`], [`std::fmt::Display`] and
/// [`std::fmt::Debug`] and have an additional `code` method that specifies the /// [`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`: /// An example of a custom error from `ls`:
/// ///
/// ``` /// ```
/// use uucore::error::{UCustomError, UResult}; /// use uucore::error::{UError, UResult};
/// use std::{ /// use std::{
/// error::Error, /// error::Error,
/// fmt::{Display, Debug}, /// fmt::{Display, Debug},
@ -215,7 +112,7 @@ impl Display for UError {
/// NoMetadata(PathBuf), /// NoMetadata(PathBuf),
/// } /// }
/// ///
/// impl UCustomError for LsError { /// impl UError for LsError {
/// fn code(&self) -> i32 { /// fn code(&self) -> i32 {
/// match self { /// match self {
/// LsError::InvalidLineWidth(_) => 2, /// LsError::InvalidLineWidth(_) => 2,
@ -246,12 +143,12 @@ impl Display for UError {
/// } /// }
/// ``` /// ```
/// ///
/// The call to `into()` is required to convert the [`UCustomError`] to an /// The call to `into()` is required to convert the `LsError` to
/// instance of [`UError`]. /// [`Box<dyn UError>`]. The implementation for `From` is provided automatically.
/// ///
/// A crate like [`quick_error`](https://crates.io/crates/quick-error) might /// 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. /// 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. /// Error code of a custom error.
/// ///
/// Set a return value for each variant of an enum-type to associate an /// Set a return value for each variant of an enum-type to associate an
@ -261,7 +158,7 @@ pub trait UCustomError: Error + Send {
/// # Example /// # Example
/// ///
/// ``` /// ```
/// use uucore::error::{UCustomError}; /// use uucore::error::{UError};
/// use std::{ /// use std::{
/// error::Error, /// error::Error,
/// fmt::{Display, Debug}, /// fmt::{Display, Debug},
@ -275,7 +172,7 @@ pub trait UCustomError: Error + Send {
/// Bing(), /// Bing(),
/// } /// }
/// ///
/// impl UCustomError for MyError { /// impl UError for MyError {
/// fn code(&self) -> i32 { /// fn code(&self) -> i32 {
/// match self { /// match self {
/// MyError::Foo(_) => 2, /// MyError::Foo(_) => 2,
@ -312,7 +209,7 @@ pub trait UCustomError: Error + Send {
/// # Example /// # Example
/// ///
/// ``` /// ```
/// use uucore::error::{UCustomError}; /// use uucore::error::{UError};
/// use std::{ /// use std::{
/// error::Error, /// error::Error,
/// fmt::{Display, Debug}, /// fmt::{Display, Debug},
@ -326,7 +223,7 @@ pub trait UCustomError: Error + Send {
/// Bing(), /// Bing(),
/// } /// }
/// ///
/// impl UCustomError for MyError { /// impl UError for MyError {
/// fn usage(&self) -> bool { /// fn usage(&self) -> bool {
/// match self { /// match self {
/// // This will have a short usage help appended /// // This will have a short usage help appended
@ -355,47 +252,23 @@ pub trait UCustomError: Error + Send {
} }
} }
impl From<Box<dyn UCustomError>> for i32 { impl<T> From<T> for Box<dyn UError>
fn from(e: Box<dyn UCustomError>) -> i32 { where
e.code() 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}; /// use uucore::error::{UResult, USimpleError};
/// let err = USimpleError { code: 1, message: "error!".into()}; /// let err = USimpleError { code: 1, message: "error!".into()};
/// let res: UResult<()> = Err(err.into()); /// let res: UResult<()> = Err(err.into());
/// // or using the `new` method: /// // or using the `new` method:
/// let res: UResult<()> = Err(USimpleError::new(1, "error!".into())); /// let res: UResult<()> = Err(USimpleError::new(1, "error!"));
/// ``` /// ```
#[derive(Debug)] #[derive(Debug)]
pub struct USimpleError { pub struct USimpleError {
@ -405,8 +278,11 @@ pub struct USimpleError {
impl USimpleError { impl USimpleError {
#[allow(clippy::new_ret_no_self)] #[allow(clippy::new_ret_no_self)]
pub fn new(code: i32, message: String) -> UError { pub fn new<S: Into<String>>(code: i32, message: S) -> Box<dyn UError> {
UError::Custom(Box::new(Self { code, message })) 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 { fn code(&self) -> i32 {
self.code self.code
} }
@ -432,8 +308,11 @@ pub struct UUsageError {
impl UUsageError { impl UUsageError {
#[allow(clippy::new_ret_no_self)] #[allow(clippy::new_ret_no_self)]
pub fn new(code: i32, message: String) -> UError { pub fn new<S: Into<String>>(code: i32, message: S) -> Box<dyn UError> {
UError::Custom(Box::new(Self { code, message })) 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 { fn code(&self) -> i32 {
self.code 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 /// 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`]. /// [`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::fs::File;
/// use std::path::Path; /// use std::path::Path;
/// let path = Path::new("test.txt"); /// let path = Path::new("test.txt");
/// ///
/// // Manual construction /// // Manual construction
/// let e: UIoError = UIoError::new( /// let e: Box<dyn UError> = UIoError::new(
/// std::io::ErrorKind::NotFound, /// std::io::ErrorKind::NotFound,
/// format!("cannot access '{}'", path.display()) /// format!("cannot access '{}'", path.display())
/// ); /// );
@ -485,22 +364,17 @@ pub struct UIoError {
} }
impl UIoError { impl UIoError {
pub fn new(kind: std::io::ErrorKind, context: String) -> Self { #[allow(clippy::new_ret_no_self)]
Self { pub fn new<S: Into<String>>(kind: std::io::ErrorKind, context: S) -> Box<dyn UError> {
context, Box::new(Self {
context: context.into(),
inner: std::io::Error::new(kind, ""), 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 Error for UIoError {}
impl Display 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 /// Enables the conversion from [`std::io::Error`] to [`UError`] and from [`std::io::Result`] to
/// `UResult`. /// [`UResult`].
pub trait FromIo<T> { pub trait FromIo<T> {
fn map_err_context(self, context: impl FnOnce() -> String) -> T; fn map_err_context(self, context: impl FnOnce() -> String) -> T;
} }
impl FromIo<UIoError> for std::io::Error { impl FromIo<Box<UIoError>> for std::io::Error {
fn map_err_context(self, context: impl FnOnce() -> String) -> UIoError { fn map_err_context(self, context: impl FnOnce() -> String) -> Box<UIoError> {
UIoError { Box::new(UIoError {
context: (context)(), context: (context)(),
inner: self, inner: self,
} })
} }
} }
impl<T> FromIo<UResult<T>> for std::io::Result<T> { impl<T> FromIo<UResult<T>> for std::io::Result<T> {
fn map_err_context(self, context: impl FnOnce() -> String) -> UResult<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 { impl FromIo<Box<UIoError>> for std::io::ErrorKind {
fn map_err_context(self, context: impl FnOnce() -> String) -> UIoError { fn map_err_context(self, context: impl FnOnce() -> String) -> Box<UIoError> {
UIoError { Box::new(UIoError {
context: (context)(), context: (context)(),
inner: std::io::Error::new(self, ""), inner: std::io::Error::new(self, ""),
} })
} }
} }
impl From<UIoError> for UCommonError { /// Shorthand to construct [`UIoError`]-instances.
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.
/// ///
/// If identical errors appear across multiple utilities, they should be added here. /// This macro serves as a convenience call to quickly construct instances of
#[derive(Debug)] /// [`UIoError`]. It takes:
pub enum UCommonError { ///
Io(UIoError), /// - An instance of [`std::io::Error`]
// Clap(UClapError), /// - A `format!`-compatible string and
} /// - An arbitrary number of arguments to the format string
///
impl UCommonError { /// In exactly this order. It is equivalent to the more verbose code seen in the
pub fn with_code(self, code: i32) -> UCommonErrorWithCode { /// example.
UCommonErrorWithCode { code, error: self } ///
} /// # Examples
///
pub fn code(&self) -> i32 { /// ```
1 /// use uucore::error::UIoError;
} /// use uucore::uio_error;
///
pub fn usage(&self) -> bool { /// let io_err = std::io::Error::new(
false /// std::io::ErrorKind::PermissionDenied, "fix me please!"
} /// );
} ///
/// let uio_err = UIoError::new(
impl From<UCommonError> for i32 { /// io_err.kind(),
fn from(common: UCommonError) -> i32 { /// format!("Error code: {}", 2)
match common { /// );
UCommonError::Io(e) => e.code(), ///
} /// let other_uio_err = uio_error!(io_err, "Error code: {}", 2);
} ///
} /// // prints "fix me please!: Permission denied"
/// println!("{}", uio_err);
impl Error for UCommonError {} /// // prints "Error code: 2: Permission denied"
/// println!("{}", other_uio_err);
impl Display for UCommonError { /// ```
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> { ///
match self { /// The [`std::fmt::Display`] impl of [`UIoError`] will then ensure that an
UCommonError::Io(e) => e.fmt(f), /// 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 /// A special error type that does not print any message when returned from
/// `uumain`. Especially useful for porting utilities to using [`UResult`]. /// `uumain`. Especially useful for porting utilities to using [`UResult`].
@ -636,6 +523,13 @@ impl Display for UCommonError {
#[derive(Debug)] #[derive(Debug)]
pub struct ExitCode(pub i32); 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 Error for ExitCode {}
impl Display 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 { fn code(&self) -> i32 {
self.0 self.0
} }
} }
impl From<i32> for Box<dyn UError> {
fn from(i: i32) -> Self {
ExitCode::new(i)
}
}

View 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)");
}

View file

@ -1,5 +1,4 @@
use crate::common::util::*; use crate::common::util::*;
#[cfg(unix)]
use std::fs::OpenOptions; use std::fs::OpenOptions;
#[cfg(unix)] #[cfg(unix)]
use std::io::Read; 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] #[test]
fn test_stdin_show_all() { fn test_stdin_show_all() {
for same_param in &["-A", "--show-all"] { for same_param in &["-A", "--show-all"] {
@ -443,3 +462,49 @@ fn test_domain_socket() {
thread.join().unwrap(); 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
View 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
}

View file

@ -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();
}

View file

@ -16,8 +16,6 @@ use std::os::windows::fs::symlink_file;
use filetime::FileTime; use filetime::FileTime;
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
use rlimit::Resource; use rlimit::Resource;
#[cfg(not(windows))]
use std::env;
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
use std::fs as std_fs; use std::fs as std_fs;
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
@ -743,20 +741,16 @@ fn test_cp_deref_folder_to_folder() {
let scene = TestScenario::new(util_name!()); let scene = TestScenario::new(util_name!());
let at = &scene.fixtures; 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); at.symlink_file(
&path_to_new_symlink
// Change the cwd to have a correct symlink .join(TEST_HELLO_WORLD_SOURCE)
assert!(env::set_current_dir(&path_to_new_symlink).is_ok()); .to_string_lossy(),
&path_to_new_symlink
#[cfg(not(windows))] .join(TEST_HELLO_WORLD_SOURCE_SYMLINK)
let _r = fs::symlink(TEST_HELLO_WORLD_SOURCE, TEST_HELLO_WORLD_SOURCE_SYMLINK); .to_string_lossy(),
#[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());
//using -P -R option //using -P -R option
scene scene
@ -843,20 +837,16 @@ fn test_cp_no_deref_folder_to_folder() {
let scene = TestScenario::new(util_name!()); let scene = TestScenario::new(util_name!());
let at = &scene.fixtures; 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); at.symlink_file(
&path_to_new_symlink
// Change the cwd to have a correct symlink .join(TEST_HELLO_WORLD_SOURCE)
assert!(env::set_current_dir(&path_to_new_symlink).is_ok()); .to_string_lossy(),
&path_to_new_symlink
#[cfg(not(windows))] .join(TEST_HELLO_WORLD_SOURCE_SYMLINK)
let _r = fs::symlink(TEST_HELLO_WORLD_SOURCE, TEST_HELLO_WORLD_SOURCE_SYMLINK); .to_string_lossy(),
#[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());
//using -P -R option //using -P -R option
scene scene
@ -969,10 +959,9 @@ fn test_cp_archive() {
} }
#[test] #[test]
#[cfg(target_os = "unix")] #[cfg(unix)]
fn test_cp_archive_recursive() { fn test_cp_archive_recursive() {
let (at, mut ucmd) = at_and_ucmd!(); let (at, mut ucmd) = at_and_ucmd!();
let cwd = env::current_dir().unwrap();
// creates // creates
// dir/1 // dir/1
@ -988,26 +977,13 @@ fn test_cp_archive_recursive() {
at.touch(&file_1.to_string_lossy()); at.touch(&file_1.to_string_lossy());
at.touch(&file_2.to_string_lossy()); at.touch(&file_2.to_string_lossy());
// Change the cwd to have a correct symlink at.symlink_file("1", &file_1_link.to_string_lossy());
assert!(env::set_current_dir(&at.subdir.join(TEST_COPY_TO_FOLDER)).is_ok()); at.symlink_file("2", &file_2_link.to_string_lossy());
#[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());
ucmd.arg("--archive") ucmd.arg("--archive")
.arg(TEST_COPY_TO_FOLDER) .arg(TEST_COPY_TO_FOLDER)
.arg(TEST_COPY_TO_FOLDER_NEW) .arg(TEST_COPY_TO_FOLDER_NEW)
.fails(); // fails for now .succeeds();
let scene2 = TestScenario::new("ls"); let scene2 = TestScenario::new("ls");
let result = scene2 let result = scene2
@ -1025,18 +1001,6 @@ fn test_cp_archive_recursive() {
.run(); .run();
println!("ls dest {}", result.stdout_str()); 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( assert!(at.file_exists(
&at.subdir &at.subdir
.join(TEST_COPY_TO_FOLDER_NEW) .join(TEST_COPY_TO_FOLDER_NEW)

View file

@ -8,7 +8,10 @@
// spell-checker:ignore (methods) hexdigest // spell-checker:ignore (methods) hexdigest
use tempfile::TempDir;
use crate::common::util::*; use crate::common::util::*;
use std::fs::OpenOptions;
use std::time::SystemTime; use std::time::SystemTime;
#[path = "../../src/uu/factor/sieve.rs"] #[path = "../../src/uu/factor/sieve.rs"]
@ -24,6 +27,43 @@ use self::sieve::Sieve;
const NUM_PRIMES: usize = 10000; const NUM_PRIMES: usize = 10000;
const NUM_TESTS: usize = 100; 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] #[test]
fn test_first_100000_integers() { fn test_first_100000_integers() {
extern crate sha1; extern crate sha1;

View file

@ -94,6 +94,18 @@ fn unpaired_lines() {
.arg("2") .arg("2")
.succeeds() .succeeds()
.stdout_only_fixture("unpaired_lines.expected"); .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] #[test]
@ -107,6 +119,18 @@ fn suppress_joined() {
.arg("2") .arg("2")
.succeeds() .succeeds()
.stdout_only_fixture("suppress_joined.expected"); .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] #[test]

View file

@ -128,6 +128,7 @@ fn test_ls_width() {
scene scene
.ucmd() .ucmd()
.args(&option.split(' ').collect::<Vec<_>>()) .args(&option.split(' ').collect::<Vec<_>>())
.arg("-C")
.succeeds() .succeeds()
.stdout_only("test-width-1 test-width-2 test-width-3 test-width-4\n"); .stdout_only("test-width-1 test-width-2 test-width-3 test-width-4\n");
} }
@ -136,30 +137,33 @@ fn test_ls_width() {
scene scene
.ucmd() .ucmd()
.args(&option.split(' ').collect::<Vec<_>>()) .args(&option.split(' ').collect::<Vec<_>>())
.arg("-C")
.succeeds() .succeeds()
.stdout_only("test-width-1 test-width-3\ntest-width-2 test-width-4\n"); .stdout_only("test-width-1 test-width-3\ntest-width-2 test-width-4\n");
} }
for option in &[ for option in &["-w 25", "-w=25", "--width=25", "--width 25"] {
"-w 25",
"-w=25",
"--width=25",
"--width 25",
"-w 0",
"-w=0",
"--width=0",
"--width 0",
] {
scene scene
.ucmd() .ucmd()
.args(&option.split(' ').collect::<Vec<_>>()) .args(&option.split(' ').collect::<Vec<_>>())
.arg("-C")
.succeeds() .succeeds()
.stdout_only("test-width-1\ntest-width-2\ntest-width-3\ntest-width-4\n"); .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 scene
.ucmd() .ucmd()
.arg("-w=bad") .arg("-w=bad")
.arg("-C")
.fails() .fails()
.stderr_contains("invalid line width"); .stderr_contains("invalid line width");
@ -167,6 +171,7 @@ fn test_ls_width() {
scene scene
.ucmd() .ucmd()
.args(&option.split(' ').collect::<Vec<_>>()) .args(&option.split(' ').collect::<Vec<_>>())
.arg("-C")
.fails() .fails()
.stderr_only("ls: invalid line width: '1a'"); .stderr_only("ls: invalid line width: '1a'");
} }
@ -184,16 +189,10 @@ fn test_ls_columns() {
// Columns is the default // Columns is the default
let result = scene.ucmd().succeeds(); let result = scene.ucmd().succeeds();
#[cfg(not(windows))]
result.stdout_only("test-columns-1\ntest-columns-2\ntest-columns-3\ntest-columns-4\n"); 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"] { for option in &["-C", "--format=columns"] {
let result = scene.ucmd().arg(option).succeeds(); 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"); 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() .succeeds()
.stdout_only("test-columns-1 test-columns-3\ntest-columns-2 test-columns-4\n"); .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] #[test]
@ -220,11 +251,7 @@ fn test_ls_across() {
let result = scene.ucmd().arg(option).succeeds(); let result = scene.ucmd().arg(option).succeeds();
// Because the test terminal has width 0, this is the same output as // Because the test terminal has width 0, this is the same output as
// the columns option. // the columns option.
if cfg!(unix) { result.stdout_only("test-across-1 test-across-2 test-across-3 test-across-4\n");
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");
}
} }
for option in &["-x", "--format=across"] { for option in &["-x", "--format=across"] {
@ -250,11 +277,7 @@ fn test_ls_commas() {
for option in &["-m", "--format=commas"] { for option in &["-m", "--format=commas"] {
let result = scene.ucmd().arg(option).succeeds(); let result = scene.ucmd().arg(option).succeeds();
if cfg!(unix) { result.stdout_only("test-commas-1, test-commas-2, test-commas-3, test-commas-4\n");
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");
}
} }
for option in &["-m", "--format=commas"] { for option in &["-m", "--format=commas"] {
@ -571,13 +594,11 @@ fn test_ls_sort_name() {
at.touch("test-1"); at.touch("test-1");
at.touch("test-2"); at.touch("test-2");
let sep = if cfg!(unix) { "\n" } else { " " };
scene scene
.ucmd() .ucmd()
.arg("--sort=name") .arg("--sort=name")
.succeeds() .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 scene_dot = TestScenario::new(util_name!());
let at = &scene_dot.fixtures; let at = &scene_dot.fixtures;
@ -591,7 +612,7 @@ fn test_ls_sort_name() {
.arg("--sort=name") .arg("--sort=name")
.arg("-A") .arg("-A")
.succeeds() .succeeds()
.stdout_is([".a", ".b", "a", "b\n"].join(sep)); .stdout_is(".a\n.b\na\nb\n");
} }
#[test] #[test]
@ -612,28 +633,16 @@ fn test_ls_order_size() {
scene.ucmd().arg("-al").succeeds(); scene.ucmd().arg("-al").succeeds();
let result = scene.ucmd().arg("-S").succeeds(); let result = scene.ucmd().arg("-S").succeeds();
#[cfg(not(windows))]
result.stdout_only("test-4\ntest-3\ntest-2\ntest-1\n"); 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(); let result = scene.ucmd().arg("-S").arg("-r").succeeds();
#[cfg(not(windows))]
result.stdout_only("test-1\ntest-2\ntest-3\ntest-4\n"); 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(); let result = scene.ucmd().arg("--sort=size").succeeds();
#[cfg(not(windows))]
result.stdout_only("test-4\ntest-3\ntest-2\ntest-1\n"); 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(); 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"); 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] #[test]
@ -755,9 +764,6 @@ fn test_ls_styles() {
at.touch("test2"); at.touch("test2");
let result = scene.ucmd().arg("--full-time").arg("-x").succeeds(); 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"); 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 // ctime was changed at write, so the order is 4 3 2 1
let result = scene.ucmd().arg("-t").succeeds(); let result = scene.ucmd().arg("-t").succeeds();
#[cfg(not(windows))]
result.stdout_only("test-4\ntest-3\ntest-2\ntest-1\n"); 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(); let result = scene.ucmd().arg("--sort=time").succeeds();
#[cfg(not(windows))]
result.stdout_only("test-4\ntest-3\ntest-2\ntest-1\n"); 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(); let result = scene.ucmd().arg("-tr").succeeds();
#[cfg(not(windows))]
result.stdout_only("test-1\ntest-2\ntest-3\ntest-4\n"); 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(); 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"); 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 // 3 was accessed last in the read
// So the order should be 2 3 4 1 // 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 // It seems to be dependent on the platform whether the access time is actually set
if file3_access > file4_access { if file3_access > file4_access {
if cfg!(not(windows)) { result.stdout_only("test-3\ntest-4\ntest-2\ntest-1\n");
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");
}
} else { } else {
// Access time does not seem to be set on Windows and some other // Access time does not seem to be set on Windows and some other
// systems so the order is 4 3 2 1 // 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");
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");
}
} }
} }
@ -991,6 +977,7 @@ fn test_ls_color() {
.ucmd() .ucmd()
.arg("--color") .arg("--color")
.arg("-w=15") .arg("-w=15")
.arg("-C")
.succeeds() .succeeds()
.stdout_only(format!( .stdout_only(format!(
"{} test-color\nb {}\n", "{} test-color\nb {}\n",
@ -2009,11 +1996,7 @@ fn test_ls_path() {
}; };
scene.ucmd().arg(&abs_path).run().stdout_is(expected_stdout); scene.ucmd().arg(&abs_path).run().stdout_is(expected_stdout);
let expected_stdout = if cfg!(windows) { let expected_stdout = format!("{}\n{}\n", path, file1);
format!("{} {}\n", path, file1)
} else {
format!("{}\n{}\n", path, file1)
};
scene scene
.ucmd() .ucmd()
.arg(file1) .arg(file1)

View file

@ -46,6 +46,17 @@ fn test_file() {
.succeeds() .succeeds()
.no_stderr() .no_stderr()
.stdout_is(unindent(ALPHA_OUT)); .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); let _ = remove_file(file);
} }

View file

@ -68,41 +68,36 @@ fn test_with_numbering_option_with_number_width() {
fn test_with_long_header_option() { fn test_with_long_header_option() {
let test_file_path = "test_one_page.log"; let test_file_path = "test_one_page.log";
let expected_test_file_path = "test_one_page_header.log.expected"; 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"; let header = "new file";
scenario for args in &[&["-h", header][..], &["--header=new file"][..]] {
.args(&["--header=new file", test_file_path]) let mut scenario = new_ucmd!();
.succeeds() let value = file_last_modified_time(&scenario, test_file_path);
.stdout_is_templated_fixture( scenario
expected_test_file_path, .args(args)
&[("{last_modified_time}", &value), ("{header}", header)], .arg(test_file_path)
); .succeeds()
.stdout_is_templated_fixture(
new_ucmd!() expected_test_file_path,
.args(&["-h", header, test_file_path]) &[("{last_modified_time}", &value), ("{header}", header)],
.succeeds() );
.stdout_is_templated_fixture( }
expected_test_file_path,
&[("{last_modified_time}", &value), ("{header}", header)],
);
} }
#[test] #[test]
fn test_with_double_space_option() { fn test_with_double_space_option() {
let test_file_path = "test_one_page.log"; let test_file_path = "test_one_page.log";
let expected_test_file_path = "test_one_page_double_line.log.expected"; let expected_test_file_path = "test_one_page_double_line.log.expected";
let mut scenario = new_ucmd!(); for &arg in &["-d", "--double-space"] {
let value = file_last_modified_time(&scenario, test_file_path); let mut scenario = new_ucmd!();
scenario let value = file_last_modified_time(&scenario, test_file_path);
.args(&["-d", test_file_path]) scenario
.succeeds() .args(&[arg, test_file_path])
.stdout_is_templated_fixture(expected_test_file_path, &[("{last_modified_time}", &value)]); .succeeds()
.stdout_is_templated_fixture(
new_ucmd!() expected_test_file_path,
.args(&["--double-space", test_file_path]) &[("{last_modified_time}", &value)],
.succeeds() );
.stdout_is_templated_fixture(expected_test_file_path, &[("{last_modified_time}", &value)]); }
} }
#[test] #[test]
@ -188,33 +183,28 @@ fn test_with_page_range() {
let test_file_path = "test.log"; let test_file_path = "test.log";
let expected_test_file_path = "test_page_range_1.log.expected"; let expected_test_file_path = "test_page_range_1.log.expected";
let expected_test_file_path1 = "test_page_range_2.log.expected"; let expected_test_file_path1 = "test_page_range_2.log.expected";
let mut scenario = new_ucmd!(); for &arg in &["--pages=15", "+15"] {
let value = file_last_modified_time(&scenario, test_file_path); let mut scenario = new_ucmd!();
scenario let value = file_last_modified_time(&scenario, test_file_path);
.args(&["--pages=15", test_file_path]) scenario
.succeeds() .args(&[arg, test_file_path])
.stdout_is_templated_fixture(expected_test_file_path, &[("{last_modified_time}", &value)]); .succeeds()
.stdout_is_templated_fixture(
new_ucmd!() expected_test_file_path,
.args(&["+15", test_file_path]) &[("{last_modified_time}", &value)],
.succeeds() );
.stdout_is_templated_fixture(expected_test_file_path, &[("{last_modified_time}", &value)]); }
for &arg in &["--pages=15:17", "+15:17"] {
new_ucmd!() let mut scenario = new_ucmd!();
.args(&["--pages=15:17", test_file_path]) let value = file_last_modified_time(&scenario, test_file_path);
.succeeds() scenario
.stdout_is_templated_fixture( .args(&[arg, test_file_path])
expected_test_file_path1, .succeeds()
&[("{last_modified_time}", &value)], .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)],
);
} }
#[test] #[test]
@ -232,19 +222,17 @@ fn test_with_no_header_trailer_option() {
#[test] #[test]
fn test_with_page_length_option() { fn test_with_page_length_option() {
let test_file_path = "test.log"; let test_file_path = "test.log";
let expected_test_file_path = "test_page_length.log.expected"; for (arg, expected) in &[
let expected_test_file_path1 = "test_page_length1.log.expected"; ("100", "test_page_length.log.expected"),
let mut scenario = new_ucmd!(); ("5", "test_page_length1.log.expected"),
let value = file_last_modified_time(&scenario, test_file_path); ] {
scenario let mut scenario = new_ucmd!();
.args(&["--pages=2:3", "-l", "100", "-n", test_file_path]) let value = file_last_modified_time(&scenario, test_file_path);
.succeeds() scenario
.stdout_is_templated_fixture(expected_test_file_path, &[("{last_modified_time}", &value)]); .args(&["--pages=2:3", "-l", arg, "-n", test_file_path])
.succeeds()
new_ucmd!() .stdout_is_templated_fixture(expected, &[("{last_modified_time}", &value)]);
.args(&["--pages=2:3", "-l", "5", "-n", test_file_path]) }
.succeeds()
.stdout_is_fixture(expected_test_file_path1);
} }
#[test] #[test]
@ -277,17 +265,17 @@ fn test_with_stdin() {
fn test_with_column() { fn test_with_column() {
let test_file_path = "column.log"; let test_file_path = "column.log";
let expected_test_file_path = "column.log.expected"; let expected_test_file_path = "column.log.expected";
let mut scenario = new_ucmd!(); for arg in &["-3", "--column=3"] {
let value = file_last_modified_time(&scenario, test_file_path); let mut scenario = new_ucmd!();
scenario let value = file_last_modified_time(&scenario, test_file_path);
.args(&["--pages=3:5", "--column=3", "-n", test_file_path]) scenario
.succeeds() .args(&["--pages=3:5", arg, "-n", test_file_path])
.stdout_is_templated_fixture(expected_test_file_path, &[("{last_modified_time}", &value)]); .succeeds()
.stdout_is_templated_fixture(
new_ucmd!() expected_test_file_path,
.args(&["--pages=3:5", "-3", "-n", test_file_path]) &[("{last_modified_time}", &value)],
.succeeds() );
.stdout_is_templated_fixture(expected_test_file_path, &[("{last_modified_time}", &value)]); }
} }
#[test] #[test]
@ -305,36 +293,17 @@ fn test_with_column_across_option() {
#[test] #[test]
fn test_with_column_across_option_and_column_separator() { fn test_with_column_across_option_and_column_separator() {
let test_file_path = "column.log"; let test_file_path = "column.log";
let expected_test_file_path = "column_across_sep.log.expected"; for (arg, expected) in &[
let expected_test_file_path1 = "column_across_sep1.log.expected"; ("-s|", "column_across_sep.log.expected"),
let mut scenario = new_ucmd!(); ("-Sdivide", "column_across_sep1.log.expected"),
let value = file_last_modified_time(&scenario, test_file_path); ] {
scenario let mut scenario = new_ucmd!();
.args(&[ let value = file_last_modified_time(&scenario, test_file_path);
"--pages=3:5", scenario
"--column=3", .args(&["--pages=3:5", "--column=3", arg, "-a", "-n", test_file_path])
"-s|", .succeeds()
"-a", .stdout_is_templated_fixture(expected, &[("{last_modified_time}", &value)]);
"-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)],
);
} }
#[test] #[test]

View file

@ -1091,3 +1091,30 @@ fn test_wrong_args_exit_code() {
.status_code(2) .status_code(2)
.stderr_contains("--misspelled"); .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());
}

View file

@ -4,3 +4,4 @@ c 4 h
f 5 i f 5 i
g 6 j g 6 j
h 7 k h 7 k
i 99 l

View file

@ -0,0 +1,4 @@
1 a
8 h
9 i
99 i l

View file

@ -4,3 +4,4 @@
i 5 f i 5 f
j 6 g j 6 g
k 7 h k 7 h
l 99 i

View 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

View file

@ -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 # 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|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 # 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 # 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. # 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/\(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

View 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)