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:
name: gnu-result
path: gnu-result.json
- name: Download the result
uses: dawidd6/action-download-artifact@v2
with:
workflow: GnuTests.yml
name: gnu-result
repo: uutils/coreutils
branch: master
path: dl
- name: Download the log
uses: dawidd6/action-download-artifact@v2
with:
workflow: GnuTests.yml
name: test-report
repo: uutils/coreutils
branch: master
path: dl
- name: Compare failing tests against master
shell: bash
run: |
OLD_FAILING=$(sed -n "s/^FAIL: \([[:print:]]\+\).*/\1/p" dl/test-suite.log | sort)
NEW_FAILING=$(sed -n "s/^FAIL: \([[:print:]]\+\).*/\1/p" gnu/tests/test-suite.log | sort)
for LINE in $OLD_FAILING
do
if ! grep -Fxq $LINE<<<"$NEW_FAILING"; then
echo "::warning ::Congrats! The gnu test $LINE is now passing!"
fi
done
for LINE in $NEW_FAILING
do
if ! grep -Fxq $LINE<<<"$OLD_FAILING"
then
echo "::error ::GNU test failed: $LINE. $LINE is passing on 'master'. Maybe you have to rebase?"
fi
done
- name: Compare against master results
shell: bash
run: |
mv dl/gnu-result.json master-gnu-result.json
python uutils/util/compare_gnu_result.py

View file

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

View file

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

128
Cargo.lock generated
View file

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

View file

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

View file

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

View file

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

View file

@ -34,7 +34,7 @@ pub mod options {
}
impl Config {
fn from(app_name: &str, options: clap::ArgMatches) -> Result<Config, String> {
pub fn from(app_name: &str, options: &clap::ArgMatches) -> Result<Config, String> {
let file: Option<String> = match options.values_of(options::FILE) {
Some(mut values) => {
let name = values.next().unwrap();
@ -85,7 +85,7 @@ pub fn parse_base_cmd_args(
let arg_list = args
.collect_str(InvalidEncodingHandling::ConvertLossy)
.accept_any();
Config::from(name, app.get_matches_from(arg_list))
Config::from(name, &app.get_matches_from(arg_list))
}
pub fn base_app<'a>(name: &str, version: &'a str, about: &'a str) -> App<'static, 'a> {
@ -145,8 +145,18 @@ pub fn handle_input<R: Read>(
}
if !decode {
let encoded = data.encode();
wrap_print(&data, encoded);
match data.encode() {
Ok(s) => {
wrap_print(&data, s);
}
Err(_) => {
eprintln!(
"{}: error: invalid input (length must be multiple of 4 characters)",
name
);
exit!(1)
}
}
} else {
match data.decode() {
Ok(s) => {

25
src/uu/basenc/Cargo.toml Normal file
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]
unix_socket = "0.5.0"
nix = "0.20.0"
[target.'cfg(any(target_os = "linux", target_os = "android"))'.dependencies]
nix = "0.20"
[target.'cfg(windows)'.dependencies]
winapi-util = "0.1.5"
[[bin]]
name = "cat"

View file

@ -20,12 +20,16 @@ use clap::{crate_version, App, Arg};
use std::fs::{metadata, File};
use std::io::{self, Read, Write};
use thiserror::Error;
use uucore::error::UResult;
#[cfg(unix)]
use std::os::unix::io::AsRawFd;
/// Linux splice support
#[cfg(any(target_os = "linux", target_os = "android"))]
mod splice;
#[cfg(any(target_os = "linux", target_os = "android"))]
use std::os::unix::io::{AsRawFd, RawFd};
use std::os::unix::io::RawFd;
/// Unix domain socket support
#[cfg(unix)]
@ -58,6 +62,8 @@ enum CatError {
},
#[error("Is a directory")]
IsDirectory,
#[error("input file is output file")]
OutputIsInput,
}
type CatResult<T> = Result<T, CatError>;
@ -122,6 +128,12 @@ struct OutputState {
/// Whether the output cursor is at the beginning of a new line
at_line_start: bool,
/// Whether we skipped a \r, which still needs to be printed
skipped_carriage_return: bool,
/// Whether we have already printed a blank line
one_blank_kept: bool,
}
/// Represents an open file handle, stream, or other device
@ -164,7 +176,8 @@ mod options {
pub static SHOW_NONPRINTING: &str = "show-nonprinting";
}
pub fn uumain(args: impl uucore::Args) -> i32 {
#[uucore_procs::gen_uumain]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let args = args
.collect_str(InvalidEncodingHandling::Ignore)
.accept_any();
@ -217,13 +230,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
show_tabs,
squeeze_blank,
};
let success = cat_files(files, &options).is_ok();
if success {
0
} else {
1
}
cat_files(files, &options)
}
pub fn uu_app() -> App<'static, 'static> {
@ -301,7 +308,13 @@ fn cat_handle<R: Read>(
}
}
fn cat_path(path: &str, options: &OutputOptions, state: &mut OutputState) -> CatResult<()> {
fn cat_path(
path: &str,
options: &OutputOptions,
state: &mut OutputState,
#[cfg(unix)] out_info: &nix::sys::stat::FileStat,
#[cfg(windows)] out_info: &winapi_util::file::Information,
) -> CatResult<()> {
if path == "-" {
let stdin = io::stdin();
let mut handle = InputHandle {
@ -328,6 +341,10 @@ fn cat_path(path: &str, options: &OutputOptions, state: &mut OutputState) -> Cat
}
_ => {
let file = File::open(path)?;
#[cfg(any(windows, unix))]
if same_file(out_info, &file) {
return Err(CatError::OutputIsInput);
}
let mut handle = InputHandle {
#[cfg(any(target_os = "linux", target_os = "android"))]
file_descriptor: file.as_raw_fd(),
@ -339,23 +356,52 @@ fn cat_path(path: &str, options: &OutputOptions, state: &mut OutputState) -> Cat
}
}
fn cat_files(files: Vec<String>, options: &OutputOptions) -> Result<(), u32> {
let mut error_count = 0;
#[cfg(unix)]
fn same_file(a_info: &nix::sys::stat::FileStat, b: &File) -> bool {
let b_info = nix::sys::stat::fstat(b.as_raw_fd()).unwrap();
b_info.st_size != 0 && b_info.st_dev == a_info.st_dev && b_info.st_ino == a_info.st_ino
}
#[cfg(windows)]
fn same_file(a_info: &winapi_util::file::Information, b: &File) -> bool {
let b_info = winapi_util::file::information(b).unwrap();
b_info.file_size() != 0
&& b_info.volume_serial_number() == a_info.volume_serial_number()
&& b_info.file_index() == a_info.file_index()
}
fn cat_files(files: Vec<String>, options: &OutputOptions) -> UResult<()> {
#[cfg(windows)]
let out_info = winapi_util::file::information(&std::io::stdout()).unwrap();
#[cfg(unix)]
let out_info = nix::sys::stat::fstat(std::io::stdout().as_raw_fd()).unwrap();
let mut state = OutputState {
line_number: 1,
at_line_start: true,
skipped_carriage_return: false,
one_blank_kept: false,
};
let mut error_messages: Vec<String> = Vec::new();
for path in &files {
if let Err(err) = cat_path(path, options, &mut state) {
show_error!("{}: {}", path, err);
error_count += 1;
if let Err(err) = cat_path(path, options, &mut state, &out_info) {
error_messages.push(format!("{}: {}", path, err));
}
}
if error_count == 0 {
if state.skipped_carriage_return {
print!("\r");
}
if error_messages.is_empty() {
Ok(())
} else {
Err(error_count)
// each next line is expected to display "cat: …"
let line_joiner = format!("\n{}: ", executable!());
Err(uucore::error::USimpleError::new(
error_messages.len() as i32,
error_messages.join(&line_joiner),
))
}
}
@ -423,7 +469,6 @@ fn write_lines<R: Read>(
let mut in_buf = [0; 1024 * 31];
let stdout = io::stdout();
let mut writer = stdout.lock();
let mut one_blank_kept = false;
while let Ok(n) = handle.reader.read(&mut in_buf) {
if n == 0 {
@ -434,8 +479,13 @@ fn write_lines<R: Read>(
while pos < n {
// skip empty line_number enumerating them if needed
if in_buf[pos] == b'\n' {
if !state.at_line_start || !options.squeeze_blank || !one_blank_kept {
one_blank_kept = true;
// \r followed by \n is printed as ^M when show_ends is enabled, so that \r\n prints as ^M$
if state.skipped_carriage_return && options.show_ends {
writer.write_all(b"^M")?;
state.skipped_carriage_return = false;
}
if !state.at_line_start || !options.squeeze_blank || !state.one_blank_kept {
state.one_blank_kept = true;
if state.at_line_start && options.number == NumberingMode::All {
write!(&mut writer, "{0:6}\t", state.line_number)?;
state.line_number += 1;
@ -449,7 +499,12 @@ fn write_lines<R: Read>(
pos += 1;
continue;
}
one_blank_kept = false;
if state.skipped_carriage_return {
writer.write_all(b"\r")?;
state.skipped_carriage_return = false;
state.at_line_start = false;
}
state.one_blank_kept = false;
if state.at_line_start && options.number != NumberingMode::None {
write!(&mut writer, "{0:6}\t", state.line_number)?;
state.line_number += 1;
@ -464,17 +519,22 @@ fn write_lines<R: Read>(
write_to_end(&in_buf[pos..], &mut writer)
};
// end of buffer?
if offset == 0 {
if offset + pos == in_buf.len() {
state.at_line_start = false;
break;
}
// print suitable end of line
writer.write_all(options.end_of_line().as_bytes())?;
if handle.is_interactive {
writer.flush()?;
if in_buf[pos + offset] == b'\r' {
state.skipped_carriage_return = true;
} else {
assert_eq!(in_buf[pos + offset], b'\n');
// print suitable end of line
writer.write_all(options.end_of_line().as_bytes())?;
if handle.is_interactive {
writer.flush()?;
}
state.at_line_start = true;
}
state.at_line_start = true;
pos += offset;
pos += offset + 1;
}
}
@ -482,17 +542,19 @@ fn write_lines<R: Read>(
}
// write***_to_end methods
// Write all symbols till end of line or end of buffer is reached
// Return the (number of written symbols + 1) or 0 if the end of buffer is reached
// Write all symbols till \n or \r or end of buffer is reached
// We need to stop at \r because it may be written as ^M depending on the byte after and settings;
// however, write_nonprint_to_end doesn't need to stop at \r because it will always write \r as ^M.
// Return the number of written symbols
fn write_to_end<W: Write>(in_buf: &[u8], writer: &mut W) -> usize {
match in_buf.iter().position(|c| *c == b'\n') {
match in_buf.iter().position(|c| *c == b'\n' || *c == b'\r') {
Some(p) => {
writer.write_all(&in_buf[..p]).unwrap();
p + 1
p
}
None => {
writer.write_all(in_buf).unwrap();
0
in_buf.len()
}
}
}
@ -500,20 +562,25 @@ fn write_to_end<W: Write>(in_buf: &[u8], writer: &mut W) -> usize {
fn write_tab_to_end<W: Write>(mut in_buf: &[u8], writer: &mut W) -> usize {
let mut count = 0;
loop {
match in_buf.iter().position(|c| *c == b'\n' || *c == b'\t') {
match in_buf
.iter()
.position(|c| *c == b'\n' || *c == b'\t' || *c == b'\r')
{
Some(p) => {
writer.write_all(&in_buf[..p]).unwrap();
if in_buf[p] == b'\n' {
return count + p + 1;
} else {
return count + p;
} else if in_buf[p] == b'\t' {
writer.write_all(b"^I").unwrap();
in_buf = &in_buf[p + 1..];
count += p + 1;
} else {
return count + p;
}
}
None => {
writer.write_all(in_buf).unwrap();
return 0;
return in_buf.len();
}
};
}
@ -538,11 +605,7 @@ fn write_nonprint_to_end<W: Write>(in_buf: &[u8], writer: &mut W, tab: &[u8]) ->
.unwrap();
count += 1;
}
if count != in_buf.len() {
count + 1
} else {
0
}
count
}
#[cfg(test)]

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

View file

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

View file

@ -47,7 +47,7 @@ impl SplitName {
}),
Some(custom) => {
let spec =
Regex::new(r"(?P<ALL>%(?P<FLAG>[0#-])(?P<WIDTH>\d+)?(?P<TYPE>[diuoxX]))")
Regex::new(r"(?P<ALL>%((?P<FLAG>[0#-])(?P<WIDTH>\d+)?)?(?P<TYPE>[diuoxX]))")
.unwrap();
let mut captures_iter = spec.captures_iter(&custom);
let custom_fn: Box<dyn Fn(usize) -> String> = match captures_iter.next() {
@ -60,6 +60,21 @@ impl SplitName {
Some(m) => m.as_str().parse::<usize>().unwrap(),
};
match (captures.name("FLAG"), captures.name("TYPE")) {
(None, Some(ref t)) => match t.as_str() {
"d" | "i" | "u" => Box::new(move |n: usize| -> String {
format!("{}{}{}{}", prefix, before, n, after)
}),
"o" => Box::new(move |n: usize| -> String {
format!("{}{}{:o}{}", prefix, before, n, after)
}),
"x" => Box::new(move |n: usize| -> String {
format!("{}{}{:x}{}", prefix, before, n, after)
}),
"X" => Box::new(move |n: usize| -> String {
format!("{}{}{:X}{}", prefix, before, n, after)
}),
_ => return Err(CsplitError::SuffixFormatIncorrect),
},
(Some(ref f), Some(ref t)) => {
match (f.as_str(), t.as_str()) {
/*
@ -276,6 +291,12 @@ mod tests {
assert_eq!(split_name.get(2), "xx00002");
}
#[test]
fn no_padding_decimal() {
let split_name = SplitName::new(None, Some(String::from("cst-%d-")), None).unwrap();
assert_eq!(split_name.get(2), "xxcst-2-");
}
#[test]
fn zero_padding_decimal1() {
let split_name = SplitName::new(None, Some(String::from("cst-%03d-")), None).unwrap();

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -10,6 +10,7 @@
extern crate uucore;
use std::error::Error;
use std::fmt::Write as FmtWrite;
use std::io::{self, stdin, stdout, BufRead, Write};
mod factor;
@ -28,21 +29,29 @@ mod options {
pub static NUMBER: &str = "NUMBER";
}
fn print_factors_str(num_str: &str, w: &mut impl io::Write) -> Result<(), Box<dyn Error>> {
num_str
.parse::<u64>()
.map_err(|e| e.into())
.and_then(|x| writeln!(w, "{}:{}", x, factor(x)).map_err(|e| e.into()))
fn print_factors_str(
num_str: &str,
w: &mut io::BufWriter<impl io::Write>,
factors_buffer: &mut String,
) -> Result<(), Box<dyn Error>> {
num_str.parse::<u64>().map_err(|e| e.into()).and_then(|x| {
factors_buffer.clear();
writeln!(factors_buffer, "{}:{}", x, factor(x))?;
w.write_all(factors_buffer.as_bytes())?;
Ok(())
})
}
pub fn uumain(args: impl uucore::Args) -> i32 {
let matches = uu_app().get_matches_from(args);
let stdout = stdout();
let mut w = io::BufWriter::new(stdout.lock());
// We use a smaller buffer here to pass a gnu test. 4KiB appears to be the default pipe size for bash.
let mut w = io::BufWriter::with_capacity(4 * 1024, stdout.lock());
let mut factors_buffer = String::new();
if let Some(values) = matches.values_of(options::NUMBER) {
for number in values {
if let Err(e) = print_factors_str(number, &mut w) {
if let Err(e) = print_factors_str(number, &mut w, &mut factors_buffer) {
show_warning!("{}: {}", number, e);
}
}
@ -51,7 +60,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
for line in stdin.lock().lines() {
for number in line.unwrap().split_whitespace() {
if let Err(e) = print_factors_str(number, &mut w) {
if let Err(e) = print_factors_str(number, &mut w, &mut factors_buffer) {
show_warning!("{}: {}", number, e);
}
}

View file

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

View file

@ -18,7 +18,7 @@ path = "src/id.rs"
clap = { version = "2.33", features = ["wrap_help"] }
uucore = { version=">=0.0.9", package="uucore", path="../../uucore", features=["entries", "process"] }
uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_procs" }
selinux = { version="0.1.3", optional = true }
selinux = { version="0.2.1", optional = true }
[[bin]]
name = "id"

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -10,21 +10,13 @@
#[macro_use]
extern crate uucore;
use libc::{c_char, c_int, execvp};
use libc::{c_char, c_int, execvp, PRIO_PROCESS};
use std::ffi::CString;
use std::io::Error;
use std::ptr;
use clap::{crate_version, App, AppSettings, Arg};
// XXX: PRIO_PROCESS is 0 on at least FreeBSD and Linux. Don't know about Mac OS X.
const PRIO_PROCESS: c_int = 0;
extern "C" {
fn getpriority(which: c_int, who: c_int) -> c_int;
fn setpriority(which: c_int, who: c_int, prio: c_int) -> c_int;
}
pub mod options {
pub static ADJUSTMENT: &str = "adjustment";
pub static COMMAND: &str = "COMMAND";
@ -50,7 +42,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
let mut niceness = unsafe {
nix::errno::Errno::clear();
getpriority(PRIO_PROCESS, 0)
libc::getpriority(PRIO_PROCESS, 0)
};
if Error::last_os_error().raw_os_error().unwrap() != 0 {
show_error!("getpriority: {}", Error::last_os_error());
@ -84,7 +76,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
};
niceness += adjustment;
if unsafe { setpriority(PRIO_PROCESS, 0, niceness) } == -1 {
if unsafe { libc::setpriority(PRIO_PROCESS, 0, niceness) } == -1 {
show_warning!("setpriority: {}", Error::last_os_error());
}

View file

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

View file

@ -16,7 +16,7 @@ path = "src/pr.rs"
[dependencies]
clap = { version = "2.33", features = ["wrap_help"] }
uucore = { version=">=0.0.7", package="uucore", path="../../uucore", features=["utmpx", "entries"] }
uucore = { version=">=0.0.7", package="uucore", path="../../uucore", features=["entries"] }
uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_procs" }
getopts = "0.2.21"
time = "0.1.41"

View file

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

View file

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

View file

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

View file

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

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"] }
[target.'cfg(target_os = "redox")'.dependencies]
redox_syscall = "0.1"
redox_syscall = "0.2"
[target.'cfg(unix)'.dependencies]
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)]
pub use self::windows::{supports_pid_checks, Pid, ProcessChecker};
#[cfg(target_os = "redox")]
pub use self::redox::{supports_pid_checks, Pid, ProcessChecker};
#[cfg(unix)]
mod unix;
#[cfg(windows)]
mod windows;
#[cfg(target_os = "redox")]
mod redox;

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" }
[target.'cfg(target_os = "redox")'.dependencies]
redox_syscall = "0.1"
redox_syscall = "0.2"
[[bin]]
name = "test"

View file

@ -17,7 +17,7 @@ use clap::{crate_version, App, Arg, ArgGroup};
use filetime::*;
use std::fs::{self, File};
use std::path::Path;
use uucore::error::{FromIo, UResult, USimpleError};
use uucore::error::{FromIo, UError, UResult, USimpleError};
static ABOUT: &str = "Update the access and modification times of each FILE to the current time.";
pub mod options {

View file

@ -16,7 +16,7 @@ edition = "2018"
path="src/lib/lib.rs"
[dependencies]
dns-lookup = "1.0.5"
dns-lookup = { version="1.0.5", optional=true }
dunce = "1.0.0"
getopts = "<= 0.2.21"
wild = "2.0.4"
@ -27,7 +27,9 @@ nix = { version="<= 0.13", optional=true }
platform-info = { version="<= 0.1", optional=true }
time = { version="<= 0.1.43", optional=true }
# * "problem" dependencies (pinned)
data-encoding = { version="~2.1", optional=true } ## data-encoding: require v2.1; but v2.2.0 breaks the build for MinSRV v1.31.0
data-encoding = { version="2.1", optional=true }
data-encoding-macro = { version="0.1.12", optional=true }
z85 = { version="3.0.3", optional=true }
libc = { version="0.2.15, <= 0.2.85", optional=true } ## libc: initial utmp support added in v0.2.15; but v0.2.68 breaks the build for MinSRV v1.31.0
[dev-dependencies]
@ -43,7 +45,7 @@ termion = "1.5"
[features]
default = []
# * non-default features
encoding = ["data-encoding", "thiserror"]
encoding = ["data-encoding", "data-encoding-macro", "z85", "thiserror"]
entries = ["libc"]
fs = ["libc"]
fsext = ["libc", "time"]
@ -53,6 +55,6 @@ process = ["libc"]
ringbuffer = []
signals = []
utf8 = []
utmpx = ["time", "libc"]
utmpx = ["time", "libc", "dns-lookup"]
wide = []
zero-copy = ["nix", "libc", "lazy_static", "platform-info"]

View file

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

View file

@ -5,45 +5,95 @@
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
// spell-checker:ignore (strings) ABCDEFGHIJKLMNOPQRSTUVWXYZ
// spell-checker:ignore (strings) ABCDEFGHIJKLMNOPQRSTUVWXYZ ABCDEFGHIJKLMNOPQRSTUV
// spell-checker:ignore (encodings) lsbf msbf hexupper
extern crate data_encoding;
use self::data_encoding::{DecodeError, BASE32, BASE64};
use data_encoding::{self, BASE32, BASE64};
use std::io::{self, Read, Write};
use data_encoding::{Encoding, BASE32HEX, BASE64URL, HEXUPPER};
use data_encoding_macro::new_encoding;
#[cfg(feature = "thiserror")]
use thiserror::Error;
#[derive(Debug, Error)]
pub enum EncodingError {
pub enum DecodeError {
#[error("{}", _0)]
Decode(#[from] DecodeError),
Decode(#[from] data_encoding::DecodeError),
#[error("{}", _0)]
DecodeZ85(#[from] z85::DecodeError),
#[error("{}", _0)]
Io(#[from] io::Error),
}
pub type DecodeResult = Result<Vec<u8>, EncodingError>;
pub enum EncodeError {
Z85InputLenNotMultipleOf4,
}
pub type DecodeResult = Result<Vec<u8>, DecodeError>;
#[derive(Clone, Copy)]
pub enum Format {
Base32,
Base64,
Base64Url,
Base32,
Base32Hex,
Base16,
Base2Lsbf,
Base2Msbf,
Z85,
}
use self::Format::*;
pub fn encode(f: Format, input: &[u8]) -> String {
match f {
const BASE2LSBF: Encoding = new_encoding! {
symbols: "01",
bit_order: LeastSignificantFirst,
};
const BASE2MSBF: Encoding = new_encoding! {
symbols: "01",
bit_order: MostSignificantFirst,
};
pub fn encode(f: Format, input: &[u8]) -> Result<String, EncodeError> {
Ok(match f {
Base32 => BASE32.encode(input),
Base64 => BASE64.encode(input),
}
Base64Url => BASE64URL.encode(input),
Base32Hex => BASE32HEX.encode(input),
Base16 => HEXUPPER.encode(input),
Base2Lsbf => BASE2LSBF.encode(input),
Base2Msbf => BASE2MSBF.encode(input),
Z85 => {
// According to the spec we should not accept inputs whose len is not a multiple of 4.
// However, the z85 crate implements a padded encoding and accepts such inputs. We have to manually check for them.
if input.len() % 4 != 0 {
return Err(EncodeError::Z85InputLenNotMultipleOf4);
} else {
z85::encode(input)
}
}
})
}
pub fn decode(f: Format, input: &[u8]) -> DecodeResult {
Ok(match f {
Base32 => BASE32.decode(input)?,
Base64 => BASE64.decode(input)?,
Base64Url => BASE64URL.decode(input)?,
Base32Hex => BASE32HEX.decode(input)?,
Base16 => HEXUPPER.decode(input)?,
Base2Lsbf => BASE2LSBF.decode(input)?,
Base2Msbf => BASE2MSBF.decode(input)?,
Z85 => {
// The z85 crate implements a padded encoding by using a leading '#' which is otherwise not allowed.
// We manually check for a leading '#' and return an error ourselves.
if input.starts_with(&[b'#']) {
return Err(z85::DecodeError::InvalidByte(0, b'#').into());
} else {
z85::decode(input)?
}
}
})
}
@ -65,6 +115,12 @@ impl<R: Read> Data<R> {
alphabet: match format {
Base32 => b"ABCDEFGHIJKLMNOPQRSTUVWXYZ234567=",
Base64 => b"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789=+/",
Base64Url => b"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789=_-",
Base32Hex => b"0123456789ABCDEFGHIJKLMNOPQRSTUV=",
Base16 => b"0123456789ABCDEF",
Base2Lsbf => b"01",
Base2Msbf => b"01",
Z85 => b"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ.-:+=^!/*?&<>()[]{}@%$#",
},
}
}
@ -90,7 +146,7 @@ impl<R: Read> Data<R> {
decode(self.format, &buf)
}
pub fn encode(&mut self) -> String {
pub fn encode(&mut self) -> Result<String, EncodeError> {
let mut buf: Vec<u8> = vec![];
self.input.read_to_end(&mut buf).unwrap();
encode(self.format, buf.as_slice())

View file

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

View file

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

View file

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

View file

@ -35,12 +35,6 @@ pub fn parse_symbolic(
#[cfg(unix)]
use libc::umask;
#[cfg(target_os = "redox")]
unsafe fn umask(_mask: u32) -> u32 {
// XXX Redox does not currently have umask
0
}
let (mask, pos) = parse_levels(mode);
if pos == mode.len() {
return Err(format!("invalid mode ({})", mode));

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>>(
path: P,
meta: &Metadata,
dest_gid: gid_t,
dest_gid: Option<gid_t>,
follow: bool,
verbosity: Verbosity,
) -> Result<String, String> {
use self::Verbosity::*;
let path = path.as_ref();
let mut out: String = String::new();
let dest_gid = dest_gid.unwrap_or_else(|| meta.gid());
if let Err(e) = chgrp(path, dest_gid, follow) {
match verbosity {

View file

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

View file

@ -6,11 +6,11 @@
//! This module provides types to reconcile these exit codes with idiomatic Rust error
//! handling. This has a couple advantages over manually using [`std::process::exit`]:
//! 1. It enables the use of `?`, `map_err`, `unwrap_or`, etc. in `uumain`.
//! 1. It encourages the use of `UResult`/`Result` in functions in the utils.
//! 1. It encourages the use of [`UResult`]/[`Result`] in functions in the utils.
//! 1. The error messages are largely standardized across utils.
//! 1. Standardized error messages can be created from external result types
//! (i.e. [`std::io::Result`] & `clap::ClapResult`).
//! 1. `set_exit_code` takes away the burden of manually tracking exit codes for non-fatal errors.
//! 1. [`set_exit_code`] takes away the burden of manually tracking exit codes for non-fatal errors.
//!
//! # Usage
//! The signature of a typical util should be:
@ -19,7 +19,7 @@
//! ...
//! }
//! ```
//! [`UResult`] is a simple wrapper around [`Result`] with a custom error type: [`UError`]. The
//! [`UResult`] is a simple wrapper around [`Result`] with a custom error trait: [`UError`]. The
//! most important difference with types implementing [`std::error::Error`] is that [`UError`]s
//! can specify the exit code of the program when they are returned from `uumain`:
//! * When `Ok` is returned, the code set with [`set_exit_code`] is used as exit code. If
@ -41,13 +41,15 @@
//! [`set_exit_code`]. See the documentation on that function for more information.
//!
//! # Guidelines
//! * Use common errors where possible.
//! * Add variants to [`UCommonError`] if an error appears in multiple utils.
//! * Use error types from `uucore` where possible.
//! * Add error types to `uucore` if an error appears in multiple utils.
//! * Prefer proper custom error types over [`ExitCode`] and [`USimpleError`].
//! * [`USimpleError`] may be used in small utils with simple error handling.
//! * Using [`ExitCode`] is not recommended but can be useful for converting utils to use
//! [`UResult`].
// spell-checker:ignore uioerror
use std::{
error::Error,
fmt::{Display, Formatter},
@ -85,115 +87,10 @@ pub fn set_exit_code(code: i32) {
EXIT_CODE.store(code, Ordering::SeqCst);
}
/// Should be returned by all utils.
///
/// Two additional methods are implemented on [`UResult`] on top of the normal [`Result`] methods:
/// `map_err_code` & `map_err_code_message`.
///
/// These methods are used to convert [`UCommonError`]s into errors with a custom error code and
/// message.
pub type UResult<T> = Result<T, UError>;
/// Result type that should be returned by all utils.
pub type UResult<T> = Result<T, Box<dyn UError>>;
trait UResultTrait<T> {
fn map_err_code(self, mapper: fn(&UCommonError) -> Option<i32>) -> Self;
fn map_err_code_and_message(self, mapper: fn(&UCommonError) -> Option<(i32, String)>) -> Self;
}
impl<T> UResultTrait<T> for UResult<T> {
fn map_err_code(self, mapper: fn(&UCommonError) -> Option<i32>) -> Self {
if let Err(UError::Common(error)) = self {
if let Some(code) = mapper(&error) {
Err(UCommonErrorWithCode { code, error }.into())
} else {
Err(error.into())
}
} else {
self
}
}
fn map_err_code_and_message(self, mapper: fn(&UCommonError) -> Option<(i32, String)>) -> Self {
if let Err(UError::Common(ref error)) = self {
if let Some((code, message)) = mapper(error) {
return Err(USimpleError { code, message }.into());
}
}
self
}
}
/// The error type of [`UResult`].
///
/// `UError::Common` errors are defined in [`uucore`](crate) while `UError::Custom` errors are
/// defined by the utils.
/// ```
/// use uucore::error::USimpleError;
/// let err = USimpleError::new(1, "Error!!".into());
/// assert_eq!(1, err.code());
/// assert_eq!(String::from("Error!!"), format!("{}", err));
/// ```
pub enum UError {
Common(UCommonError),
Custom(Box<dyn UCustomError>),
}
impl UError {
/// The error code of [`UResult`]
///
/// This function defines the error code associated with an instance of
/// [`UResult`]. To associate error codes for self-defined instances of
/// `UResult::Custom` (i.e. [`UCustomError`]), implement the
/// [`code`-function there](UCustomError::code).
pub fn code(&self) -> i32 {
match self {
UError::Common(e) => e.code(),
UError::Custom(e) => e.code(),
}
}
/// Whether to print usage help for a [`UResult`]
///
/// Defines if a variant of [`UResult`] should print a short usage message
/// below the error. The usage message is printed when this function returns
/// `true`. To do this for self-defined instances of `UResult::Custom` (i.e.
/// [`UCustomError`]), implement the [`usage`-function
/// there](UCustomError::usage).
pub fn usage(&self) -> bool {
match self {
UError::Common(e) => e.usage(),
UError::Custom(e) => e.usage(),
}
}
}
impl From<UCommonError> for UError {
fn from(v: UCommonError) -> Self {
UError::Common(v)
}
}
impl From<i32> for UError {
fn from(v: i32) -> Self {
UError::Custom(Box::new(ExitCode(v)))
}
}
impl<E: UCustomError + 'static> From<E> for UError {
fn from(v: E) -> Self {
UError::Custom(Box::new(v) as Box<dyn UCustomError>)
}
}
impl Display for UError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
UError::Common(e) => e.fmt(f),
UError::Custom(e) => e.fmt(f),
}
}
}
/// Custom errors defined by the utils.
/// Custom errors defined by the utils and `uucore`.
///
/// All errors should implement [`std::error::Error`], [`std::fmt::Display`] and
/// [`std::fmt::Debug`] and have an additional `code` method that specifies the
@ -202,7 +99,7 @@ impl Display for UError {
/// An example of a custom error from `ls`:
///
/// ```
/// use uucore::error::{UCustomError, UResult};
/// use uucore::error::{UError, UResult};
/// use std::{
/// error::Error,
/// fmt::{Display, Debug},
@ -215,7 +112,7 @@ impl Display for UError {
/// NoMetadata(PathBuf),
/// }
///
/// impl UCustomError for LsError {
/// impl UError for LsError {
/// fn code(&self) -> i32 {
/// match self {
/// LsError::InvalidLineWidth(_) => 2,
@ -246,12 +143,12 @@ impl Display for UError {
/// }
/// ```
///
/// The call to `into()` is required to convert the [`UCustomError`] to an
/// instance of [`UError`].
/// The call to `into()` is required to convert the `LsError` to
/// [`Box<dyn UError>`]. The implementation for `From` is provided automatically.
///
/// A crate like [`quick_error`](https://crates.io/crates/quick-error) might
/// also be used, but will still require an `impl` for the `code` method.
pub trait UCustomError: Error + Send {
pub trait UError: Error + Send {
/// Error code of a custom error.
///
/// Set a return value for each variant of an enum-type to associate an
@ -261,7 +158,7 @@ pub trait UCustomError: Error + Send {
/// # Example
///
/// ```
/// use uucore::error::{UCustomError};
/// use uucore::error::{UError};
/// use std::{
/// error::Error,
/// fmt::{Display, Debug},
@ -275,7 +172,7 @@ pub trait UCustomError: Error + Send {
/// Bing(),
/// }
///
/// impl UCustomError for MyError {
/// impl UError for MyError {
/// fn code(&self) -> i32 {
/// match self {
/// MyError::Foo(_) => 2,
@ -312,7 +209,7 @@ pub trait UCustomError: Error + Send {
/// # Example
///
/// ```
/// use uucore::error::{UCustomError};
/// use uucore::error::{UError};
/// use std::{
/// error::Error,
/// fmt::{Display, Debug},
@ -326,7 +223,7 @@ pub trait UCustomError: Error + Send {
/// Bing(),
/// }
///
/// impl UCustomError for MyError {
/// impl UError for MyError {
/// fn usage(&self) -> bool {
/// match self {
/// // This will have a short usage help appended
@ -355,47 +252,23 @@ pub trait UCustomError: Error + Send {
}
}
impl From<Box<dyn UCustomError>> for i32 {
fn from(e: Box<dyn UCustomError>) -> i32 {
e.code()
impl<T> From<T> for Box<dyn UError>
where
T: UError + 'static,
{
fn from(t: T) -> Box<dyn UError> {
Box::new(t)
}
}
/// A [`UCommonError`] with an overridden exit code.
/// A simple error type with an exit code and a message that implements [`UError`].
///
/// This exit code is returned instead of the default exit code for the [`UCommonError`]. This is
/// typically created with the either the `UResult::map_err_code` or `UCommonError::with_code`
/// method.
#[derive(Debug)]
pub struct UCommonErrorWithCode {
code: i32,
error: UCommonError,
}
impl Error for UCommonErrorWithCode {}
impl Display for UCommonErrorWithCode {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
self.error.fmt(f)
}
}
impl UCustomError for UCommonErrorWithCode {
fn code(&self) -> i32 {
self.code
}
}
/// A simple error type with an exit code and a message that implements [`UCustomError`].
///
/// It is typically created with the `UResult::map_err_code_and_message` method. Alternatively, it
/// can be constructed by manually:
/// ```
/// use uucore::error::{UResult, USimpleError};
/// let err = USimpleError { code: 1, message: "error!".into()};
/// let res: UResult<()> = Err(err.into());
/// // or using the `new` method:
/// let res: UResult<()> = Err(USimpleError::new(1, "error!".into()));
/// let res: UResult<()> = Err(USimpleError::new(1, "error!"));
/// ```
#[derive(Debug)]
pub struct USimpleError {
@ -405,8 +278,11 @@ pub struct USimpleError {
impl USimpleError {
#[allow(clippy::new_ret_no_self)]
pub fn new(code: i32, message: String) -> UError {
UError::Custom(Box::new(Self { code, message }))
pub fn new<S: Into<String>>(code: i32, message: S) -> Box<dyn UError> {
Box::new(Self {
code,
message: message.into(),
})
}
}
@ -418,7 +294,7 @@ impl Display for USimpleError {
}
}
impl UCustomError for USimpleError {
impl UError for USimpleError {
fn code(&self) -> i32 {
self.code
}
@ -432,8 +308,11 @@ pub struct UUsageError {
impl UUsageError {
#[allow(clippy::new_ret_no_self)]
pub fn new(code: i32, message: String) -> UError {
UError::Custom(Box::new(Self { code, message }))
pub fn new<S: Into<String>>(code: i32, message: S) -> Box<dyn UError> {
Box::new(Self {
code,
message: message.into(),
})
}
}
@ -445,7 +324,7 @@ impl Display for UUsageError {
}
}
impl UCustomError for UUsageError {
impl UError for UUsageError {
fn code(&self) -> i32 {
self.code
}
@ -463,13 +342,13 @@ impl UCustomError for UUsageError {
/// There are two ways to construct this type: with [`UIoError::new`] or by calling the
/// [`FromIo::map_err_context`] method on a [`std::io::Result`] or [`std::io::Error`].
/// ```
/// use uucore::error::{FromIo, UResult, UIoError, UCommonError};
/// use uucore::error::{FromIo, UResult, UIoError, UError};
/// use std::fs::File;
/// use std::path::Path;
/// let path = Path::new("test.txt");
///
/// // Manual construction
/// let e: UIoError = UIoError::new(
/// let e: Box<dyn UError> = UIoError::new(
/// std::io::ErrorKind::NotFound,
/// format!("cannot access '{}'", path.display())
/// );
@ -485,22 +364,17 @@ pub struct UIoError {
}
impl UIoError {
pub fn new(kind: std::io::ErrorKind, context: String) -> Self {
Self {
context,
#[allow(clippy::new_ret_no_self)]
pub fn new<S: Into<String>>(kind: std::io::ErrorKind, context: S) -> Box<dyn UError> {
Box::new(Self {
context: context.into(),
inner: std::io::Error::new(kind, ""),
}
}
pub fn code(&self) -> i32 {
1
}
pub fn usage(&self) -> bool {
false
})
}
}
impl UError for UIoError {}
impl Error for UIoError {}
impl Display for UIoError {
@ -535,89 +409,102 @@ impl Display for UIoError {
}
}
/// Enables the conversion from `std::io::Error` to `UError` and from `std::io::Result` to
/// `UResult`.
/// Enables the conversion from [`std::io::Error`] to [`UError`] and from [`std::io::Result`] to
/// [`UResult`].
pub trait FromIo<T> {
fn map_err_context(self, context: impl FnOnce() -> String) -> T;
}
impl FromIo<UIoError> for std::io::Error {
fn map_err_context(self, context: impl FnOnce() -> String) -> UIoError {
UIoError {
impl FromIo<Box<UIoError>> for std::io::Error {
fn map_err_context(self, context: impl FnOnce() -> String) -> Box<UIoError> {
Box::new(UIoError {
context: (context)(),
inner: self,
}
})
}
}
impl<T> FromIo<UResult<T>> for std::io::Result<T> {
fn map_err_context(self, context: impl FnOnce() -> String) -> UResult<T> {
self.map_err(|e| UError::Common(UCommonError::Io(e.map_err_context(context))))
self.map_err(|e| e.map_err_context(context) as Box<dyn UError>)
}
}
impl FromIo<UIoError> for std::io::ErrorKind {
fn map_err_context(self, context: impl FnOnce() -> String) -> UIoError {
UIoError {
impl FromIo<Box<UIoError>> for std::io::ErrorKind {
fn map_err_context(self, context: impl FnOnce() -> String) -> Box<UIoError> {
Box::new(UIoError {
context: (context)(),
inner: std::io::Error::new(self, ""),
}
})
}
}
impl From<UIoError> for UCommonError {
fn from(e: UIoError) -> UCommonError {
UCommonError::Io(e)
}
}
impl From<UIoError> for UError {
fn from(e: UIoError) -> UError {
let common: UCommonError = e.into();
common.into()
}
}
/// Common errors for utilities.
/// Shorthand to construct [`UIoError`]-instances.
///
/// If identical errors appear across multiple utilities, they should be added here.
#[derive(Debug)]
pub enum UCommonError {
Io(UIoError),
// Clap(UClapError),
}
impl UCommonError {
pub fn with_code(self, code: i32) -> UCommonErrorWithCode {
UCommonErrorWithCode { code, error: self }
}
pub fn code(&self) -> i32 {
1
}
pub fn usage(&self) -> bool {
false
}
}
impl From<UCommonError> for i32 {
fn from(common: UCommonError) -> i32 {
match common {
UCommonError::Io(e) => e.code(),
}
}
}
impl Error for UCommonError {}
impl Display for UCommonError {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
match self {
UCommonError::Io(e) => e.fmt(f),
}
}
}
/// This macro serves as a convenience call to quickly construct instances of
/// [`UIoError`]. It takes:
///
/// - An instance of [`std::io::Error`]
/// - A `format!`-compatible string and
/// - An arbitrary number of arguments to the format string
///
/// In exactly this order. It is equivalent to the more verbose code seen in the
/// example.
///
/// # Examples
///
/// ```
/// use uucore::error::UIoError;
/// use uucore::uio_error;
///
/// let io_err = std::io::Error::new(
/// std::io::ErrorKind::PermissionDenied, "fix me please!"
/// );
///
/// let uio_err = UIoError::new(
/// io_err.kind(),
/// format!("Error code: {}", 2)
/// );
///
/// let other_uio_err = uio_error!(io_err, "Error code: {}", 2);
///
/// // prints "fix me please!: Permission denied"
/// println!("{}", uio_err);
/// // prints "Error code: 2: Permission denied"
/// println!("{}", other_uio_err);
/// ```
///
/// The [`std::fmt::Display`] impl of [`UIoError`] will then ensure that an
/// appropriate error message relating to the actual error kind of the
/// [`std::io::Error`] is appended to whatever error message is defined in
/// addition (as secondary argument).
///
/// If you want to show only the error message for the [`std::io::ErrorKind`]
/// that's contained in [`UIoError`], pass the second argument as empty string:
///
/// ```
/// use uucore::error::UIoError;
/// use uucore::uio_error;
///
/// let io_err = std::io::Error::new(
/// std::io::ErrorKind::PermissionDenied, "fix me please!"
/// );
///
/// let other_uio_err = uio_error!(io_err, "");
///
/// // prints: ": Permission denied"
/// println!("{}", other_uio_err);
/// ```
//#[macro_use]
#[macro_export]
macro_rules! uio_error(
($err:expr, $($args:tt)+) => ({
UIoError::new(
$err.kind(),
format!($($args)+)
)
})
);
/// A special error type that does not print any message when returned from
/// `uumain`. Especially useful for porting utilities to using [`UResult`].
@ -636,6 +523,13 @@ impl Display for UCommonError {
#[derive(Debug)]
pub struct ExitCode(pub i32);
impl ExitCode {
#[allow(clippy::new_ret_no_self)]
pub fn new(code: i32) -> Box<dyn UError> {
Box::new(Self(code))
}
}
impl Error for ExitCode {}
impl Display for ExitCode {
@ -644,8 +538,14 @@ impl Display for ExitCode {
}
}
impl UCustomError for ExitCode {
impl UError for ExitCode {
fn code(&self) -> i32 {
self.0
}
}
impl From<i32> for Box<dyn UError> {
fn from(i: i32) -> Self {
ExitCode::new(i)
}
}

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::*;
#[cfg(unix)]
use std::fs::OpenOptions;
#[cfg(unix)]
use std::io::Read;
@ -274,6 +273,26 @@ fn test_stdin_show_ends() {
}
}
#[test]
fn squeeze_all_files() {
// empty lines at the end of a file are "squeezed" together with empty lines at the beginning
let (at, mut ucmd) = at_and_ucmd!();
at.write("input1", "a\n\n");
at.write("input2", "\n\nb");
ucmd.args(&["input1", "input2", "-s"])
.succeeds()
.stdout_only("a\n\nb");
}
#[test]
fn test_show_ends_crlf() {
new_ucmd!()
.arg("-E")
.pipe_in("a\nb\r\n\rc\n\r\n\r")
.succeeds()
.stdout_only("a$\nb^M$\n\rc$\n^M$\n\r");
}
#[test]
fn test_stdin_show_all() {
for same_param in &["-A", "--show-all"] {
@ -443,3 +462,49 @@ fn test_domain_socket() {
thread.join().unwrap();
}
#[test]
fn test_write_to_self_empty() {
// it's ok if the input file is also the output file if it's empty
let s = TestScenario::new(util_name!());
let file_path = s.fixtures.plus("file.txt");
let file = OpenOptions::new()
.create_new(true)
.write(true)
.append(true)
.open(&file_path)
.unwrap();
s.ucmd().set_stdout(file).arg(&file_path).succeeds();
}
#[test]
fn test_write_to_self() {
let s = TestScenario::new(util_name!());
let file_path = s.fixtures.plus("first_file");
s.fixtures.write("second_file", "second_file_content.");
let file = OpenOptions::new()
.create_new(true)
.write(true)
.append(true)
.open(&file_path)
.unwrap();
s.fixtures.append("first_file", "first_file_content.");
s.ucmd()
.set_stdout(file)
.arg("first_file")
.arg("first_file")
.arg("second_file")
.fails()
.code_is(2)
.stderr_only("cat: first_file: input file is output file\ncat: first_file: input file is output file");
assert_eq!(
s.fixtures.read("first_file"),
"first_file_content.second_file_content."
);
}

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

View file

@ -8,7 +8,10 @@
// spell-checker:ignore (methods) hexdigest
use tempfile::TempDir;
use crate::common::util::*;
use std::fs::OpenOptions;
use std::time::SystemTime;
#[path = "../../src/uu/factor/sieve.rs"]
@ -24,6 +27,43 @@ use self::sieve::Sieve;
const NUM_PRIMES: usize = 10000;
const NUM_TESTS: usize = 100;
#[test]
fn test_parallel() {
// factor should only flush the buffer at line breaks
let n_integers = 100_000;
let mut input_string = String::new();
for i in 0..=n_integers {
input_string.push_str(&(format!("{} ", i))[..]);
}
let tmp_dir = TempDir::new().unwrap();
let tmp_dir = AtPath::new(tmp_dir.path());
tmp_dir.touch("output");
let output = OpenOptions::new()
.append(true)
.open(tmp_dir.plus("output"))
.unwrap();
for mut child in (0..10)
.map(|_| {
new_ucmd!()
.set_stdout(output.try_clone().unwrap())
.pipe_in(input_string.clone())
.run_no_wait()
})
.collect::<Vec<_>>()
{
assert_eq!(child.wait().unwrap().code().unwrap(), 0);
}
let result = TestScenario::new(util_name!())
.ccmd("sort")
.arg(tmp_dir.plus("output"))
.succeeds();
let hash_check = sha1::Sha1::from(result.stdout()).hexdigest();
assert_eq!(hash_check, "cc743607c0ff300ff575d92f4ff0c87d5660c393");
}
#[test]
fn test_first_100000_integers() {
extern crate sha1;

View file

@ -94,6 +94,18 @@ fn unpaired_lines() {
.arg("2")
.succeeds()
.stdout_only_fixture("unpaired_lines.expected");
new_ucmd!()
.arg("fields_3.txt")
.arg("fields_2.txt")
.arg("-1")
.arg("2")
.arg("-a")
.arg("1")
.arg("-a")
.arg("2")
.succeeds()
.stdout_only_fixture("unpaired_lines_outer.expected");
}
#[test]
@ -107,6 +119,18 @@ fn suppress_joined() {
.arg("2")
.succeeds()
.stdout_only_fixture("suppress_joined.expected");
new_ucmd!()
.arg("fields_3.txt")
.arg("fields_2.txt")
.arg("-1")
.arg("2")
.arg("-a")
.arg("1")
.arg("-v")
.arg("2")
.succeeds()
.stdout_only_fixture("suppress_joined_outer.expected");
}
#[test]

View file

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

View file

@ -46,6 +46,17 @@ fn test_file() {
.succeeds()
.no_stderr()
.stdout_is(unindent(ALPHA_OUT));
// Ensure that default format matches `-t o2`, and that `-t` does not absorb file argument
new_ucmd!()
.arg("--endian=little")
.arg("-t")
.arg("o2")
.arg(file.as_os_str())
.succeeds()
.no_stderr()
.stdout_is(unindent(ALPHA_OUT));
let _ = remove_file(file);
}

View file

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

View file

@ -1091,3 +1091,30 @@ fn test_wrong_args_exit_code() {
.status_code(2)
.stderr_contains("--misspelled");
}
#[test]
#[cfg(unix)]
fn test_tmp_files_deleted_on_sigint() {
use std::{fs::read_dir, time::Duration};
use nix::{sys::signal, unistd::Pid};
let (at, mut ucmd) = at_and_ucmd!();
at.mkdir("tmp_dir");
ucmd.args(&[
"ext_sort.txt",
"--buffer-size=1", // with a small buffer size `sort` will be forced to create a temporary directory very soon.
"--temporary-directory=tmp_dir",
]);
let mut child = ucmd.run_no_wait();
// wait a short amount of time so that `sort` can create a temporary directory.
std::thread::sleep(Duration::from_millis(100));
// `sort` should have created a temporary directory.
assert!(read_dir(at.plus("tmp_dir")).unwrap().next().is_some());
// kill sort with SIGINT
signal::kill(Pid::from_raw(child.id() as i32), signal::SIGINT).unwrap();
// wait for `sort` to exit
assert_eq!(child.wait().unwrap().code(), Some(2));
// `sort` should have deleted the temporary directory again.
assert!(read_dir(at.plus("tmp_dir")).unwrap().next().is_none());
}

View file

@ -4,3 +4,4 @@ c 4 h
f 5 i
g 6 j
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
j 6 g
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
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
@ -122,4 +121,15 @@ test -f "${BUILDDIR}/getlimits" || cp src/getlimits "${BUILDDIR}"
# When decoding an invalid base32/64 string, gnu writes everything it was able to decode until
# it hit the decode error, while we don't write anything if the input is invalid.
sed -i "s/\(baddecode.*OUT=>\"\).*\"/\1\"/g" tests/misc/base64.pl
sed -i "s/\(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)