1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2025-07-28 11:37:44 +00:00

Merge branch 'fix_getgrouplist' into id_zero_2351

This commit is contained in:
Jan Scheer 2021-06-16 19:20:34 +02:00
commit 88367c6fb4
18 changed files with 701 additions and 647 deletions

1
Cargo.lock generated
View file

@ -2597,6 +2597,7 @@ version = "0.0.6"
dependencies = [
"clap",
"libc",
"nix 0.20.0",
"uucore",
"uucore_procs",
]

View file

@ -28,6 +28,7 @@ use std::os::windows::io::AsRawHandle;
#[cfg(windows)]
use std::path::Path;
use std::path::PathBuf;
use std::str::FromStr;
use std::time::{Duration, UNIX_EPOCH};
use uucore::parse_size::{parse_size, ParseSizeError};
use uucore::InvalidEncodingHandling;
@ -56,6 +57,7 @@ mod options {
pub const BLOCK_SIZE_1M: &str = "m";
pub const SEPARATE_DIRS: &str = "S";
pub const SUMMARIZE: &str = "s";
pub const THRESHOLD: &str = "threshold";
pub const SI: &str = "si";
pub const TIME: &str = "time";
pub const TIME_STYLE: &str = "time-style";
@ -510,6 +512,17 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.long(options::ONE_FILE_SYSTEM)
.help("skip directories on different file systems")
)
.arg(
Arg::with_name(options::THRESHOLD)
.short("t")
.long(options::THRESHOLD)
.alias("th")
.value_name("SIZE")
.number_of_values(1)
.allow_hyphen_values(true)
.help("exclude entries smaller than SIZE if positive, \
or entries greater than SIZE if negative")
)
// .arg(
// Arg::with_name("")
// .short("x")
@ -586,6 +599,11 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
let block_size = u64::try_from(read_block_size(matches.value_of(options::BLOCK_SIZE))).unwrap();
let threshold = matches.value_of(options::THRESHOLD).map(|s| {
Threshold::from_str(s)
.unwrap_or_else(|e| crash!(1, "{}", format_error_message(e, s, options::THRESHOLD)))
});
let multiplier: u64 = if matches.is_present(options::SI) {
1000
} else {
@ -654,6 +672,11 @@ Try '{} --help' for more information.",
// See: http://linux.die.net/man/2/stat
stat.blocks * 512
};
if threshold.map_or(false, |threshold| threshold.should_exclude(size)) {
continue;
}
if matches.is_present(options::TIME) {
let tm = {
let secs = {
@ -720,6 +743,37 @@ Try '{} --help' for more information.",
0
}
#[derive(Clone, Copy)]
enum Threshold {
Lower(u64),
Upper(u64),
}
impl FromStr for Threshold {
type Err = ParseSizeError;
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
let offset = if s.starts_with(&['-', '+'][..]) { 1 } else { 0 };
let size = u64::try_from(parse_size(&s[offset..])?).unwrap();
if s.starts_with('-') {
Ok(Threshold::Upper(size))
} else {
Ok(Threshold::Lower(size))
}
}
}
impl Threshold {
fn should_exclude(&self, size: u64) -> bool {
match *self {
Threshold::Upper(threshold) => size > threshold,
Threshold::Lower(threshold) => size < threshold,
}
}
}
fn format_error_message(error: ParseSizeError, s: &str, option: &str) -> String {
// NOTE:
// GNU's du echos affected flag, -B or --block-size (-t or --threshold), depending user's selection

View file

@ -129,33 +129,24 @@ fn handle_obsolete(mut args: Vec<String>) -> (Vec<String>, Option<String>) {
}
fn table() {
let mut name_width = 0;
/* Compute the maximum width of a signal name. */
for s in &ALL_SIGNALS {
if s.name.len() > name_width {
name_width = s.name.len()
}
}
let name_width = ALL_SIGNALS.iter().map(|n| n.len()).max().unwrap();
for (idx, signal) in ALL_SIGNALS.iter().enumerate() {
print!("{0: >#2} {1: <#8}", idx + 1, signal.name);
//TODO: obtain max signal width here
print!("{0: >#2} {1: <#2$}", idx, signal, name_width + 2);
if (idx + 1) % 7 == 0 {
println!();
}
}
println!()
}
fn print_signal(signal_name_or_value: &str) {
for signal in &ALL_SIGNALS {
if signal.name == signal_name_or_value
|| (format!("SIG{}", signal.name)) == signal_name_or_value
{
println!("{}", signal.value);
for (value, &signal) in ALL_SIGNALS.iter().enumerate() {
if signal == signal_name_or_value || (format!("SIG{}", signal)) == signal_name_or_value {
println!("{}", value);
exit!(EXIT_OK as i32)
} else if signal_name_or_value == signal.value.to_string() {
println!("{}", signal.name);
} else if signal_name_or_value == value.to_string() {
println!("{}", signal);
exit!(EXIT_OK as i32)
}
}
@ -165,8 +156,8 @@ fn print_signal(signal_name_or_value: &str) {
fn print_signals() {
let mut pos = 0;
for (idx, signal) in ALL_SIGNALS.iter().enumerate() {
pos += signal.name.len();
print!("{}", signal.name);
pos += signal.len();
print!("{}", signal);
if idx > 0 && pos > 73 {
println!();
pos = 0;

View file

@ -46,8 +46,8 @@ use unicode_width::UnicodeWidthStr;
use uucore::parse_size::{parse_size, ParseSizeError};
use uucore::InvalidEncodingHandling;
static NAME: &str = "sort";
static ABOUT: &str = "Display sorted concatenation of all FILE(s).";
const NAME: &str = "sort";
const ABOUT: &str = "Display sorted concatenation of all FILE(s).";
const LONG_HELP_KEYS: &str = "The key format is FIELD[.CHAR][OPTIONS][,FIELD[.CHAR]][OPTIONS].
@ -59,49 +59,59 @@ If CHAR is set 0, it means the end of the field. CHAR defaults to 1 for the star
Valid options are: MbdfhnRrV. They override the global options for this key.";
static OPT_HUMAN_NUMERIC_SORT: &str = "human-numeric-sort";
static OPT_MONTH_SORT: &str = "month-sort";
static OPT_NUMERIC_SORT: &str = "numeric-sort";
static OPT_GENERAL_NUMERIC_SORT: &str = "general-numeric-sort";
static OPT_VERSION_SORT: &str = "version-sort";
mod options {
pub mod modes {
pub const SORT: &str = "sort";
static OPT_SORT: &str = "sort";
pub const HUMAN_NUMERIC: &str = "human-numeric-sort";
pub const MONTH: &str = "month-sort";
pub const NUMERIC: &str = "numeric-sort";
pub const GENERAL_NUMERIC: &str = "general-numeric-sort";
pub const VERSION: &str = "version-sort";
pub const RANDOM: &str = "random-sort";
static ALL_SORT_MODES: &[&str] = &[
OPT_GENERAL_NUMERIC_SORT,
OPT_HUMAN_NUMERIC_SORT,
OPT_MONTH_SORT,
OPT_NUMERIC_SORT,
OPT_VERSION_SORT,
OPT_RANDOM,
];
pub const ALL_SORT_MODES: [&str; 6] = [
GENERAL_NUMERIC,
HUMAN_NUMERIC,
MONTH,
NUMERIC,
VERSION,
RANDOM,
];
}
static OPT_DICTIONARY_ORDER: &str = "dictionary-order";
static OPT_MERGE: &str = "merge";
static OPT_CHECK: &str = "check";
static OPT_CHECK_SILENT: &str = "check-silent";
static OPT_DEBUG: &str = "debug";
static OPT_IGNORE_CASE: &str = "ignore-case";
static OPT_IGNORE_BLANKS: &str = "ignore-blanks";
static OPT_IGNORE_NONPRINTING: &str = "ignore-nonprinting";
static OPT_OUTPUT: &str = "output";
static OPT_REVERSE: &str = "reverse";
static OPT_STABLE: &str = "stable";
static OPT_UNIQUE: &str = "unique";
static OPT_KEY: &str = "key";
static OPT_SEPARATOR: &str = "field-separator";
static OPT_RANDOM: &str = "random-sort";
static OPT_ZERO_TERMINATED: &str = "zero-terminated";
static OPT_PARALLEL: &str = "parallel";
static OPT_FILES0_FROM: &str = "files0-from";
static OPT_BUF_SIZE: &str = "buffer-size";
static OPT_TMP_DIR: &str = "temporary-directory";
static OPT_COMPRESS_PROG: &str = "compress-program";
static OPT_BATCH_SIZE: &str = "batch-size";
pub mod check {
pub const CHECK: &str = "check";
pub const CHECK_SILENT: &str = "check-silent";
pub const SILENT: &str = "silent";
pub const QUIET: &str = "quiet";
pub const DIAGNOSE_FIRST: &str = "diagnose-first";
}
static ARG_FILES: &str = "files";
pub const DICTIONARY_ORDER: &str = "dictionary-order";
pub const MERGE: &str = "merge";
pub const DEBUG: &str = "debug";
pub const IGNORE_CASE: &str = "ignore-case";
pub const IGNORE_LEADING_BLANKS: &str = "ignore-leading-blanks";
pub const IGNORE_NONPRINTING: &str = "ignore-nonprinting";
pub const OUTPUT: &str = "output";
pub const REVERSE: &str = "reverse";
pub const STABLE: &str = "stable";
pub const UNIQUE: &str = "unique";
pub const KEY: &str = "key";
pub const SEPARATOR: &str = "field-separator";
pub const ZERO_TERMINATED: &str = "zero-terminated";
pub const PARALLEL: &str = "parallel";
pub const FILES0_FROM: &str = "files0-from";
pub const BUF_SIZE: &str = "buffer-size";
pub const TMP_DIR: &str = "temporary-directory";
pub const COMPRESS_PROG: &str = "compress-program";
pub const BATCH_SIZE: &str = "batch-size";
static DECIMAL_PT: char = '.';
pub const FILES: &str = "files";
}
const DECIMAL_PT: char = '.';
const NEGATIVE: char = '-';
const POSITIVE: char = '+';
@ -109,7 +119,7 @@ const POSITIVE: char = '+';
// Choosing a higher buffer size does not result in performance improvements
// (at least not on my machine). TODO: In the future, we should also take the amount of
// available memory into consideration, instead of relying on this constant only.
static DEFAULT_BUF_SIZE: usize = 1_000_000_000; // 1 GB
const DEFAULT_BUF_SIZE: usize = 1_000_000_000; // 1 GB
#[derive(Eq, Ord, PartialEq, PartialOrd, Clone, Copy, Debug)]
enum SortMode {
@ -140,7 +150,7 @@ impl SortMode {
pub struct GlobalSettings {
mode: SortMode,
debug: bool,
ignore_blanks: bool,
ignore_leading_blanks: bool,
ignore_case: bool,
dictionary_order: bool,
ignore_non_printing: bool,
@ -207,7 +217,7 @@ impl Default for GlobalSettings {
GlobalSettings {
mode: SortMode::Default,
debug: false,
ignore_blanks: false,
ignore_leading_blanks: false,
ignore_case: false,
dictionary_order: false,
ignore_non_printing: false,
@ -298,7 +308,7 @@ impl From<&GlobalSettings> for KeySettings {
fn from(settings: &GlobalSettings) -> Self {
Self {
mode: settings.mode,
ignore_blanks: settings.ignore_blanks,
ignore_blanks: settings.ignore_leading_blanks,
ignore_case: settings.ignore_case,
ignore_non_printing: settings.ignore_non_printing,
reverse: settings.reverse,
@ -505,7 +515,7 @@ impl<'a> Line<'a> {
&& !settings.stable
&& !settings.unique
&& (settings.dictionary_order
|| settings.ignore_blanks
|| settings.ignore_leading_blanks
|| settings.ignore_case
|| settings.ignore_non_printing
|| settings.mode != SortMode::Default
@ -669,9 +679,11 @@ impl FieldSelector {
// This would be ideal for a try block, I think. In the meantime this closure allows
// to use the `?` operator here.
Self::new(
KeyPosition::new(from, 1, global_settings.ignore_blanks)?,
to.map(|(to, _)| KeyPosition::new(to, 0, global_settings.ignore_blanks))
.transpose()?,
KeyPosition::new(from, 1, global_settings.ignore_leading_blanks)?,
to.map(|(to, _)| {
KeyPosition::new(to, 0, global_settings.ignore_leading_blanks)
})
.transpose()?,
KeySettings::from(global_settings),
)
})()
@ -887,9 +899,10 @@ With no FILE, or when FILE is -, read standard input.",
)
}
/// Creates an `Arg` that conflicts with all other sort modes.
fn make_sort_mode_arg<'a, 'b>(mode: &'a str, short: &'b str, help: &'b str) -> Arg<'a, 'b> {
let mut arg = Arg::with_name(mode).short(short).long(mode).help(help);
for possible_mode in ALL_SORT_MODES {
for possible_mode in &options::modes::ALL_SORT_MODES {
if *possible_mode != mode {
arg = arg.conflicts_with(possible_mode);
}
@ -909,8 +922,8 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.about(ABOUT)
.usage(&usage[..])
.arg(
Arg::with_name(OPT_SORT)
.long(OPT_SORT)
Arg::with_name(options::modes::SORT)
.long(options::modes::SORT)
.takes_value(true)
.possible_values(
&[
@ -922,199 +935,221 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
"random",
]
)
.conflicts_with_all(ALL_SORT_MODES)
.conflicts_with_all(&options::modes::ALL_SORT_MODES)
)
.arg(
make_sort_mode_arg(
OPT_HUMAN_NUMERIC_SORT,
options::modes::HUMAN_NUMERIC,
"h",
"compare according to human readable sizes, eg 1M > 100k"
),
)
.arg(
make_sort_mode_arg(
OPT_MONTH_SORT,
options::modes::MONTH,
"M",
"compare according to month name abbreviation"
),
)
.arg(
make_sort_mode_arg(
OPT_NUMERIC_SORT,
options::modes::NUMERIC,
"n",
"compare according to string numerical value"
),
)
.arg(
make_sort_mode_arg(
OPT_GENERAL_NUMERIC_SORT,
options::modes::GENERAL_NUMERIC,
"g",
"compare according to string general numerical value"
),
)
.arg(
make_sort_mode_arg(
OPT_VERSION_SORT,
options::modes::VERSION,
"V",
"Sort by SemVer version number, eg 1.12.2 > 1.1.2",
),
)
.arg(
make_sort_mode_arg(
OPT_RANDOM,
options::modes::RANDOM,
"R",
"shuffle in random order",
),
)
.arg(
Arg::with_name(OPT_DICTIONARY_ORDER)
Arg::with_name(options::DICTIONARY_ORDER)
.short("d")
.long(OPT_DICTIONARY_ORDER)
.long(options::DICTIONARY_ORDER)
.help("consider only blanks and alphanumeric characters")
.conflicts_with_all(&[OPT_NUMERIC_SORT, OPT_GENERAL_NUMERIC_SORT, OPT_HUMAN_NUMERIC_SORT, OPT_MONTH_SORT]),
.conflicts_with_all(
&[
options::modes::NUMERIC,
options::modes::GENERAL_NUMERIC,
options::modes::HUMAN_NUMERIC,
options::modes::MONTH,
]
),
)
.arg(
Arg::with_name(OPT_MERGE)
Arg::with_name(options::MERGE)
.short("m")
.long(OPT_MERGE)
.long(options::MERGE)
.help("merge already sorted files; do not sort"),
)
.arg(
Arg::with_name(OPT_CHECK)
Arg::with_name(options::check::CHECK)
.short("c")
.long(OPT_CHECK)
.long(options::check::CHECK)
.takes_value(true)
.require_equals(true)
.min_values(0)
.possible_values(&[
options::check::SILENT,
options::check::QUIET,
options::check::DIAGNOSE_FIRST,
])
.help("check for sorted input; do not sort"),
)
.arg(
Arg::with_name(OPT_CHECK_SILENT)
Arg::with_name(options::check::CHECK_SILENT)
.short("C")
.long(OPT_CHECK_SILENT)
.long(options::check::CHECK_SILENT)
.help("exit successfully if the given file is already sorted, and exit with status 1 otherwise."),
)
.arg(
Arg::with_name(OPT_IGNORE_CASE)
Arg::with_name(options::IGNORE_CASE)
.short("f")
.long(OPT_IGNORE_CASE)
.long(options::IGNORE_CASE)
.help("fold lower case to upper case characters"),
)
.arg(
Arg::with_name(OPT_IGNORE_NONPRINTING)
Arg::with_name(options::IGNORE_NONPRINTING)
.short("i")
.long(OPT_IGNORE_NONPRINTING)
.long(options::IGNORE_NONPRINTING)
.help("ignore nonprinting characters")
.conflicts_with_all(&[OPT_NUMERIC_SORT, OPT_GENERAL_NUMERIC_SORT, OPT_HUMAN_NUMERIC_SORT, OPT_MONTH_SORT]),
.conflicts_with_all(
&[
options::modes::NUMERIC,
options::modes::GENERAL_NUMERIC,
options::modes::HUMAN_NUMERIC,
options::modes::MONTH
]
),
)
.arg(
Arg::with_name(OPT_IGNORE_BLANKS)
Arg::with_name(options::IGNORE_LEADING_BLANKS)
.short("b")
.long(OPT_IGNORE_BLANKS)
.long(options::IGNORE_LEADING_BLANKS)
.help("ignore leading blanks when finding sort keys in each line"),
)
.arg(
Arg::with_name(OPT_OUTPUT)
Arg::with_name(options::OUTPUT)
.short("o")
.long(OPT_OUTPUT)
.long(options::OUTPUT)
.help("write output to FILENAME instead of stdout")
.takes_value(true)
.value_name("FILENAME"),
)
.arg(
Arg::with_name(OPT_REVERSE)
Arg::with_name(options::REVERSE)
.short("r")
.long(OPT_REVERSE)
.long(options::REVERSE)
.help("reverse the output"),
)
.arg(
Arg::with_name(OPT_STABLE)
Arg::with_name(options::STABLE)
.short("s")
.long(OPT_STABLE)
.long(options::STABLE)
.help("stabilize sort by disabling last-resort comparison"),
)
.arg(
Arg::with_name(OPT_UNIQUE)
Arg::with_name(options::UNIQUE)
.short("u")
.long(OPT_UNIQUE)
.long(options::UNIQUE)
.help("output only the first of an equal run"),
)
.arg(
Arg::with_name(OPT_KEY)
Arg::with_name(options::KEY)
.short("k")
.long(OPT_KEY)
.long(options::KEY)
.help("sort by a key")
.long_help(LONG_HELP_KEYS)
.multiple(true)
.takes_value(true),
)
.arg(
Arg::with_name(OPT_SEPARATOR)
Arg::with_name(options::SEPARATOR)
.short("t")
.long(OPT_SEPARATOR)
.long(options::SEPARATOR)
.help("custom separator for -k")
.takes_value(true))
.arg(
Arg::with_name(OPT_ZERO_TERMINATED)
Arg::with_name(options::ZERO_TERMINATED)
.short("z")
.long(OPT_ZERO_TERMINATED)
.long(options::ZERO_TERMINATED)
.help("line delimiter is NUL, not newline"),
)
.arg(
Arg::with_name(OPT_PARALLEL)
.long(OPT_PARALLEL)
Arg::with_name(options::PARALLEL)
.long(options::PARALLEL)
.help("change the number of threads running concurrently to NUM_THREADS")
.takes_value(true)
.value_name("NUM_THREADS"),
)
.arg(
Arg::with_name(OPT_BUF_SIZE)
Arg::with_name(options::BUF_SIZE)
.short("S")
.long(OPT_BUF_SIZE)
.long(options::BUF_SIZE)
.help("sets the maximum SIZE of each segment in number of sorted items")
.takes_value(true)
.value_name("SIZE"),
)
.arg(
Arg::with_name(OPT_TMP_DIR)
Arg::with_name(options::TMP_DIR)
.short("T")
.long(OPT_TMP_DIR)
.long(options::TMP_DIR)
.help("use DIR for temporaries, not $TMPDIR or /tmp")
.takes_value(true)
.value_name("DIR"),
)
.arg(
Arg::with_name(OPT_COMPRESS_PROG)
.long(OPT_COMPRESS_PROG)
Arg::with_name(options::COMPRESS_PROG)
.long(options::COMPRESS_PROG)
.help("compress temporary files with PROG, decompress with PROG -d")
.long_help("PROG has to take input from stdin and output to stdout")
.value_name("PROG")
)
.arg(
Arg::with_name(OPT_BATCH_SIZE)
.long(OPT_BATCH_SIZE)
Arg::with_name(options::BATCH_SIZE)
.long(options::BATCH_SIZE)
.help("Merge at most N_MERGE inputs at once.")
.value_name("N_MERGE")
)
.arg(
Arg::with_name(OPT_FILES0_FROM)
.long(OPT_FILES0_FROM)
Arg::with_name(options::FILES0_FROM)
.long(options::FILES0_FROM)
.help("read input from the files specified by NUL-terminated NUL_FILES")
.takes_value(true)
.value_name("NUL_FILES")
.multiple(true),
)
.arg(
Arg::with_name(OPT_DEBUG)
.long(OPT_DEBUG)
Arg::with_name(options::DEBUG)
.long(options::DEBUG)
.help("underline the parts of the line that are actually used for sorting"),
)
.arg(Arg::with_name(ARG_FILES).multiple(true).takes_value(true))
.arg(Arg::with_name(options::FILES).multiple(true).takes_value(true))
.get_matches_from(args);
settings.debug = matches.is_present(OPT_DEBUG);
settings.debug = matches.is_present(options::DEBUG);
// check whether user specified a zero terminated list of files for input, otherwise read files from args
let mut files: Vec<String> = if matches.is_present(OPT_FILES0_FROM) {
let mut files: Vec<String> = if matches.is_present(options::FILES0_FROM) {
let files0_from: Vec<String> = matches
.values_of(OPT_FILES0_FROM)
.values_of(options::FILES0_FROM)
.map(|v| v.map(ToString::to_string).collect())
.unwrap_or_default();
@ -1133,82 +1168,93 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
files
} else {
matches
.values_of(ARG_FILES)
.values_of(options::FILES)
.map(|v| v.map(ToString::to_string).collect())
.unwrap_or_default()
};
settings.mode = if matches.is_present(OPT_HUMAN_NUMERIC_SORT)
|| matches.value_of(OPT_SORT) == Some("human-numeric")
settings.mode = if matches.is_present(options::modes::HUMAN_NUMERIC)
|| matches.value_of(options::modes::SORT) == Some("human-numeric")
{
SortMode::HumanNumeric
} else if matches.is_present(OPT_MONTH_SORT) || matches.value_of(OPT_SORT) == Some("month") {
} else if matches.is_present(options::modes::MONTH)
|| matches.value_of(options::modes::SORT) == Some("month")
{
SortMode::Month
} else if matches.is_present(OPT_GENERAL_NUMERIC_SORT)
|| matches.value_of(OPT_SORT) == Some("general-numeric")
} else if matches.is_present(options::modes::GENERAL_NUMERIC)
|| matches.value_of(options::modes::SORT) == Some("general-numeric")
{
SortMode::GeneralNumeric
} else if matches.is_present(OPT_NUMERIC_SORT) || matches.value_of(OPT_SORT) == Some("numeric")
} else if matches.is_present(options::modes::NUMERIC)
|| matches.value_of(options::modes::SORT) == Some("numeric")
{
SortMode::Numeric
} else if matches.is_present(OPT_VERSION_SORT) || matches.value_of(OPT_SORT) == Some("version")
} else if matches.is_present(options::modes::VERSION)
|| matches.value_of(options::modes::SORT) == Some("version")
{
SortMode::Version
} else if matches.is_present(OPT_RANDOM) || matches.value_of(OPT_SORT) == Some("random") {
} else if matches.is_present(options::modes::RANDOM)
|| matches.value_of(options::modes::SORT) == Some("random")
{
settings.salt = get_rand_string();
SortMode::Random
} else {
SortMode::Default
};
settings.dictionary_order = matches.is_present(OPT_DICTIONARY_ORDER);
settings.ignore_non_printing = matches.is_present(OPT_IGNORE_NONPRINTING);
if matches.is_present(OPT_PARALLEL) {
settings.dictionary_order = matches.is_present(options::DICTIONARY_ORDER);
settings.ignore_non_printing = matches.is_present(options::IGNORE_NONPRINTING);
if matches.is_present(options::PARALLEL) {
// "0" is default - threads = num of cores
settings.threads = matches
.value_of(OPT_PARALLEL)
.value_of(options::PARALLEL)
.map(String::from)
.unwrap_or_else(|| "0".to_string());
env::set_var("RAYON_NUM_THREADS", &settings.threads);
}
settings.buffer_size = matches
.value_of(OPT_BUF_SIZE)
.value_of(options::BUF_SIZE)
.map_or(DEFAULT_BUF_SIZE, |s| {
GlobalSettings::parse_byte_count(s)
.unwrap_or_else(|e| crash!(2, "{}", format_error_message(e, s, OPT_BUF_SIZE)))
.unwrap_or_else(|e| crash!(2, "{}", format_error_message(e, s, options::BUF_SIZE)))
});
settings.tmp_dir = matches
.value_of(OPT_TMP_DIR)
.value_of(options::TMP_DIR)
.map(PathBuf::from)
.unwrap_or_else(env::temp_dir);
settings.compress_prog = matches.value_of(OPT_COMPRESS_PROG).map(String::from);
settings.compress_prog = matches.value_of(options::COMPRESS_PROG).map(String::from);
if let Some(n_merge) = matches.value_of(OPT_BATCH_SIZE) {
if let Some(n_merge) = matches.value_of(options::BATCH_SIZE) {
settings.merge_batch_size = n_merge
.parse()
.unwrap_or_else(|_| crash!(2, "invalid --batch-size argument '{}'", n_merge));
}
settings.zero_terminated = matches.is_present(OPT_ZERO_TERMINATED);
settings.merge = matches.is_present(OPT_MERGE);
settings.zero_terminated = matches.is_present(options::ZERO_TERMINATED);
settings.merge = matches.is_present(options::MERGE);
settings.check = matches.is_present(OPT_CHECK);
if matches.is_present(OPT_CHECK_SILENT) {
settings.check_silent = matches.is_present(OPT_CHECK_SILENT);
settings.check = matches.is_present(options::check::CHECK);
if matches.is_present(options::check::CHECK_SILENT)
|| matches!(
matches.value_of(options::check::CHECK),
Some(options::check::SILENT) | Some(options::check::QUIET)
)
{
settings.check_silent = true;
settings.check = true;
};
settings.ignore_case = matches.is_present(OPT_IGNORE_CASE);
settings.ignore_case = matches.is_present(options::IGNORE_CASE);
settings.ignore_blanks = matches.is_present(OPT_IGNORE_BLANKS);
settings.ignore_leading_blanks = matches.is_present(options::IGNORE_LEADING_BLANKS);
settings.output_file = matches.value_of(OPT_OUTPUT).map(String::from);
settings.reverse = matches.is_present(OPT_REVERSE);
settings.stable = matches.is_present(OPT_STABLE);
settings.unique = matches.is_present(OPT_UNIQUE);
settings.output_file = matches.value_of(options::OUTPUT).map(String::from);
settings.reverse = matches.is_present(options::REVERSE);
settings.stable = matches.is_present(options::STABLE);
settings.unique = matches.is_present(options::UNIQUE);
if files.is_empty() {
/* if no file, default to stdin */
@ -1217,7 +1263,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
crash!(1, "extra operand `{}' not allowed with -c", files[1])
}
if let Some(arg) = matches.args.get(OPT_SEPARATOR) {
if let Some(arg) = matches.args.get(options::SEPARATOR) {
let separator = arg.vals[0].to_string_lossy();
let separator = separator;
if separator.len() != 1 {
@ -1226,15 +1272,15 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
settings.separator = Some(separator.chars().next().unwrap())
}
if matches.is_present(OPT_KEY) {
for key in &matches.args[OPT_KEY].vals {
if let Some(values) = matches.values_of(options::KEY) {
for value in values {
settings
.selectors
.push(FieldSelector::parse(&key.to_string_lossy(), &settings));
.push(FieldSelector::parse(value, &settings));
}
}
if !matches.is_present(OPT_KEY) {
if !matches.is_present(options::KEY) {
// add a default selector matching the whole line
let key_settings = KeySettings::from(&settings);
settings.selectors.push(

View file

@ -6,7 +6,7 @@
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
// spell-checker:ignore (vars) FiletestOp StrlenOp
// spell-checker:ignore (vars) egid euid FiletestOp StrlenOp
mod parser;
@ -96,8 +96,10 @@ fn eval(stack: &mut Vec<Symbol>) -> Result<bool, String> {
"-e" => path(&f, PathCondition::Exists),
"-f" => path(&f, PathCondition::Regular),
"-g" => path(&f, PathCondition::GroupIdFlag),
"-G" => path(&f, PathCondition::GroupOwns),
"-h" => path(&f, PathCondition::SymLink),
"-L" => path(&f, PathCondition::SymLink),
"-O" => path(&f, PathCondition::UserOwns),
"-p" => path(&f, PathCondition::Fifo),
"-r" => path(&f, PathCondition::Readable),
"-S" => path(&f, PathCondition::Socket),
@ -166,7 +168,9 @@ enum PathCondition {
Exists,
Regular,
GroupIdFlag,
GroupOwns,
SymLink,
UserOwns,
Fifo,
Readable,
Socket,
@ -190,18 +194,28 @@ fn path(path: &OsStr, condition: PathCondition) -> bool {
Execute = 0o1,
}
let perm = |metadata: Metadata, p: Permission| {
let geteuid = || {
#[cfg(not(target_os = "redox"))]
let (uid, gid) = unsafe { (libc::getuid(), libc::getgid()) };
let euid = unsafe { libc::geteuid() };
#[cfg(target_os = "redox")]
let (uid, gid) = (
syscall::getuid().unwrap() as u32,
syscall::getgid().unwrap() as u32,
);
let euid = syscall::geteuid().unwrap() as u32;
if uid == metadata.uid() {
euid
};
let getegid = || {
#[cfg(not(target_os = "redox"))]
let egid = unsafe { libc::getegid() };
#[cfg(target_os = "redox")]
let egid = syscall::getegid().unwrap() as u32;
egid
};
let perm = |metadata: Metadata, p: Permission| {
if geteuid() == metadata.uid() {
metadata.mode() & ((p as u32) << 6) != 0
} else if gid == metadata.gid() {
} else if getegid() == metadata.gid() {
metadata.mode() & ((p as u32) << 3) != 0
} else {
metadata.mode() & (p as u32) != 0
@ -230,7 +244,9 @@ fn path(path: &OsStr, condition: PathCondition) -> bool {
PathCondition::Exists => true,
PathCondition::Regular => file_type.is_file(),
PathCondition::GroupIdFlag => metadata.mode() & S_ISGID != 0,
PathCondition::GroupOwns => metadata.gid() == getegid(),
PathCondition::SymLink => metadata.file_type().is_symlink(),
PathCondition::UserOwns => metadata.uid() == geteuid(),
PathCondition::Fifo => file_type.is_fifo(),
PathCondition::Readable => perm(metadata, Permission::Read),
PathCondition::Socket => file_type.is_socket(),
@ -257,7 +273,9 @@ fn path(path: &OsStr, condition: PathCondition) -> bool {
PathCondition::Exists => true,
PathCondition::Regular => stat.is_file(),
PathCondition::GroupIdFlag => false,
PathCondition::GroupOwns => unimplemented!(),
PathCondition::SymLink => false,
PathCondition::UserOwns => unimplemented!(),
PathCondition::Fifo => false,
PathCondition::Readable => false, // TODO
PathCondition::Socket => false,

View file

@ -17,6 +17,7 @@ path = "src/timeout.rs"
[dependencies]
clap = "2.33"
libc = "0.2.42"
nix = "0.20.0"
uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["process", "signals"] }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }

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) tstr sigstr cmdname setpgid
// spell-checker:ignore (ToDO) tstr sigstr cmdname setpgid sigchld
#[macro_use]
extern crate uucore;
@ -17,13 +17,13 @@ use std::io::ErrorKind;
use std::process::{Command, Stdio};
use std::time::Duration;
use uucore::process::ChildExt;
use uucore::signals::signal_by_name_or_value;
use uucore::signals::{signal_by_name_or_value, signal_name_by_value};
use uucore::InvalidEncodingHandling;
static ABOUT: &str = "Start COMMAND, and kill it if still running after DURATION.";
fn get_usage() -> String {
format!("{0} [OPTION]... [FILE]...", executable!())
format!("{0} [OPTION] DURATION COMMAND...", executable!())
}
const ERR_EXIT_STATUS: i32 = 125;
@ -33,22 +33,22 @@ pub mod options {
pub static KILL_AFTER: &str = "kill-after";
pub static SIGNAL: &str = "signal";
pub static PRESERVE_STATUS: &str = "preserve-status";
pub static VERBOSE: &str = "verbose";
// Positional args.
pub static DURATION: &str = "duration";
pub static COMMAND: &str = "command";
pub static ARGS: &str = "args";
}
struct Config {
foreground: bool,
kill_after: Duration,
kill_after: Option<Duration>,
signal: usize,
duration: Duration,
preserve_status: bool,
verbose: bool,
command: String,
command_args: Vec<String>,
command: Vec<String>,
}
impl Config {
@ -66,23 +66,22 @@ impl Config {
_ => uucore::signals::signal_by_name_or_value("TERM").unwrap(),
};
let kill_after: Duration = match options.value_of(options::KILL_AFTER) {
Some(time) => uucore::parse_time::from_str(time).unwrap(),
None => Duration::new(0, 0),
};
let kill_after = options
.value_of(options::KILL_AFTER)
.map(|time| uucore::parse_time::from_str(time).unwrap());
let duration: Duration =
uucore::parse_time::from_str(options.value_of(options::DURATION).unwrap()).unwrap();
let preserve_status: bool = options.is_present(options::PRESERVE_STATUS);
let foreground = options.is_present(options::FOREGROUND);
let verbose = options.is_present(options::VERBOSE);
let command: String = options.value_of(options::COMMAND).unwrap().to_string();
let command_args: Vec<String> = match options.values_of(options::ARGS) {
Some(values) => values.map(|x| x.to_owned()).collect(),
None => vec![],
};
let command = options
.values_of(options::COMMAND)
.unwrap()
.map(String::from)
.collect::<Vec<_>>();
Config {
foreground,
@ -91,7 +90,7 @@ impl Config {
duration,
preserve_status,
command,
command_args,
verbose,
}
}
}
@ -128,6 +127,12 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.help("specify the signal to be sent on timeout; SIGNAL may be a name like 'HUP' or a number; see 'kill -l' for a list of signals")
.takes_value(true)
)
.arg(
Arg::with_name(options::VERBOSE)
.short("v")
.long(options::VERBOSE)
.help("diagnose to stderr any signal sent upon timeout")
)
.arg(
Arg::with_name(options::DURATION)
.index(1)
@ -137,9 +142,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
Arg::with_name(options::COMMAND)
.index(2)
.required(true)
)
.arg(
Arg::with_name(options::ARGS).multiple(true)
.multiple(true)
)
.setting(AppSettings::TrailingVarArg);
@ -148,31 +151,42 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
let config = Config::from(matches);
timeout(
&config.command,
&config.command_args,
config.duration,
config.signal,
config.kill_after,
config.foreground,
config.preserve_status,
config.verbose,
)
}
/// Remove pre-existing SIGCHLD handlers that would make waiting for the child's exit code fail.
fn unblock_sigchld() {
unsafe {
nix::sys::signal::signal(
nix::sys::signal::Signal::SIGCHLD,
nix::sys::signal::SigHandler::SigDfl,
)
.unwrap();
}
}
/// TODO: Improve exit codes, and make them consistent with the GNU Coreutils exit codes.
fn timeout(
cmdname: &str,
args: &[String],
cmd: &[String],
duration: Duration,
signal: usize,
kill_after: Duration,
kill_after: Option<Duration>,
foreground: bool,
preserve_status: bool,
verbose: bool,
) -> i32 {
if !foreground {
unsafe { libc::setpgid(0, 0) };
}
let mut process = match Command::new(cmdname)
.args(args)
let mut process = match Command::new(&cmd[0])
.args(&cmd[1..])
.stdin(Stdio::inherit())
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
@ -190,32 +204,44 @@ fn timeout(
}
}
};
unblock_sigchld();
match process.wait_or_timeout(duration) {
Ok(Some(status)) => status.code().unwrap_or_else(|| status.signal().unwrap()),
Ok(None) => {
if verbose {
show_error!(
"sending signal {} to command '{}'",
signal_name_by_value(signal).unwrap(),
cmd[0]
);
}
return_if_err!(ERR_EXIT_STATUS, process.send_signal(signal));
match process.wait_or_timeout(kill_after) {
Ok(Some(status)) => {
if preserve_status {
status.code().unwrap_or_else(|| status.signal().unwrap())
} else {
124
if let Some(kill_after) = kill_after {
match process.wait_or_timeout(kill_after) {
Ok(Some(status)) => {
if preserve_status {
status.code().unwrap_or_else(|| status.signal().unwrap())
} else {
124
}
}
}
Ok(None) => {
if kill_after == Duration::new(0, 0) {
// XXX: this may not be right
return 124;
Ok(None) => {
if verbose {
show_error!("sending signal KILL to command '{}'", cmd[0]);
}
return_if_err!(
ERR_EXIT_STATUS,
process.send_signal(
uucore::signals::signal_by_name_or_value("KILL").unwrap()
)
);
return_if_err!(ERR_EXIT_STATUS, process.wait());
137
}
return_if_err!(
ERR_EXIT_STATUS,
process
.send_signal(uucore::signals::signal_by_name_or_value("KILL").unwrap())
);
return_if_err!(ERR_EXIT_STATUS, process.wait());
137
Err(_) => 124,
}
Err(_) => 124,
} else {
124
}
}
Err(_) => {

View file

@ -166,7 +166,15 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
}
if let Err(e) = File::create(path) {
show_warning!("cannot touch '{}': {}", path, e);
match e.kind() {
std::io::ErrorKind::NotFound => {
show_error!("cannot touch '{}': {}", path, "No such file or directory")
}
std::io::ErrorKind::PermissionDenied => {
show_error!("cannot touch '{}': {}", path, "Permission denied")
}
_ => show_error!("cannot touch '{}': {}", path, e),
}
error_code = 1;
continue;
};

View file

@ -47,6 +47,9 @@ use std::io::Result as IOResult;
use std::ptr;
extern "C" {
/// From: https://man7.org/linux/man-pages/man3/getgrouplist.3.html
/// > The getgrouplist() function scans the group database to obtain
/// > the list of groups that user belongs to.
fn getgrouplist(
name: *const c_char,
gid: gid_t,
@ -55,6 +58,13 @@ extern "C" {
) -> c_int;
}
/// From: https://man7.org/linux/man-pages/man2/getgroups.2.html
/// > getgroups() returns the supplementary group IDs of the calling
/// > process in list.
/// > If size is zero, list is not modified, but the total number of
/// > 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().
pub fn get_groups() -> IOResult<Vec<gid_t>> {
let ngroups = unsafe { getgroups(0, ptr::null_mut()) };
if ngroups == -1 {
@ -83,17 +93,17 @@ pub fn get_groups() -> IOResult<Vec<gid_t>> {
/// for `id --groups --real` if `gid` and `egid` are not equal.
///
/// From: https://www.man7.org/linux/man-pages/man3/getgroups.3p.html
/// As implied by the definition of supplementary groups, the
/// effective group ID may appear in the array returned by
/// getgroups() or it may be returned only by getegid(). Duplication
/// may exist, but the application needs to call getegid() to be sure
/// of getting all of the information. Various implementation
/// variations and administrative sequences cause the set of groups
/// appearing in the result of getgroups() to vary in order and as to
/// whether the effective group ID is included, even when the set of
/// 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.)
/// > As implied by the definition of supplementary groups, the
/// > effective group ID may appear in the array returned by
/// > getgroups() or it may be returned only by getegid(). Duplication
/// > may exist, but the application needs to call getegid() to be sure
/// > of getting all of the information. Various implementation
/// > variations and administrative sequences cause the set of groups
/// > appearing in the result of getgroups() to vary in order and as to
/// > whether the effective group ID is included, even when the set of
/// > 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"))]
pub fn get_groups_gnu(arg_id: Option<u32>) -> IOResult<Vec<gid_t>> {
let groups = get_groups()?;
@ -187,20 +197,35 @@ impl Passwd {
/// This is a wrapper function for `libc::getgrouplist`.
///
/// From: https://man7.org/linux/man-pages/man3/getgrouplist.3.html
/// If the user is a member of more than *ngroups groups, then
/// getgrouplist() returns -1. In this case, the value returned in
/// *ngroups can be used to resize the buffer passed to a further
/// call getgrouplist().
/// > If the number of groups of which user is a member is less than or
/// > equal to *ngroups, then the value *ngroups is returned.
/// > If the user is a member of more than *ngroups groups, then
/// > getgrouplist() returns -1. In this case, the value returned in
/// > *ngroups can be used to resize the buffer passed to a further
/// > call getgrouplist().
///
/// However, on macOS/darwin (and maybe others?) `getgrouplist` does
/// not update `ngroups` if `ngroups` is too small. Therefore, if not
/// updated by `getgrouplist`, `ngroups` needs to be increased in a
/// loop until `getgrouplist` stops returning -1.
pub fn belongs_to(&self) -> Vec<gid_t> {
let mut ngroups: c_int = 8;
let mut ngroups_old: c_int;
let mut groups = Vec::with_capacity(ngroups as usize);
let gid = self.inner.pw_gid;
let name = self.inner.pw_name;
unsafe {
if getgrouplist(name, gid, groups.as_mut_ptr(), &mut ngroups) == -1 {
loop {
ngroups_old = ngroups;
if unsafe { getgrouplist(name, gid, groups.as_mut_ptr(), &mut ngroups) } == -1 {
if ngroups == ngroups_old {
ngroups *= 2;
}
groups.resize(ngroups as usize, 0);
getgrouplist(name, gid, groups.as_mut_ptr(), &mut ngroups);
} else {
break;
}
}
unsafe {
groups.set_len(ngroups as usize);
}
groups.truncate(ngroups as usize);

View file

@ -17,18 +17,22 @@ use std::process::ExitStatus as StdExitStatus;
use std::thread;
use std::time::{Duration, Instant};
/// `geteuid()` returns the effective user ID of the calling process.
pub fn geteuid() -> uid_t {
unsafe { libc::geteuid() }
}
/// `getegid()` returns the effective group ID of the calling process.
pub fn getegid() -> gid_t {
unsafe { libc::getegid() }
}
/// `getgid()` returns the real group ID of the calling process.
pub fn getgid() -> gid_t {
unsafe { libc::getgid() }
}
/// `getuid()` returns the real user ID of the calling process.
pub fn getuid() -> uid_t {
unsafe { libc::getuid() }
}
@ -93,6 +97,7 @@ pub trait ChildExt {
fn send_signal(&mut self, signal: usize) -> io::Result<()>;
/// Wait for a process to finish or return after the specified duration.
/// A `timeout` of zero disables the timeout.
fn wait_or_timeout(&mut self, timeout: Duration) -> io::Result<Option<ExitStatus>>;
}
@ -106,6 +111,11 @@ impl ChildExt for Child {
}
fn wait_or_timeout(&mut self, timeout: Duration) -> io::Result<Option<ExitStatus>> {
if timeout == Duration::from_micros(0) {
return self
.wait()
.map(|status| Some(ExitStatus::from_std_status(status)));
}
// .try_wait() doesn't drop stdin, so we do it manually
drop(self.stdin.take());

View file

@ -10,11 +10,6 @@
pub static DEFAULT_SIGNAL: usize = 15;
pub struct Signal<'a> {
pub name: &'a str,
pub value: usize,
}
/*
Linux Programmer's Manual
@ -29,131 +24,10 @@ Linux Programmer's Manual
*/
#[cfg(target_os = "linux")]
pub static ALL_SIGNALS: [Signal<'static>; 31] = [
Signal {
name: "HUP",
value: 1,
},
Signal {
name: "INT",
value: 2,
},
Signal {
name: "QUIT",
value: 3,
},
Signal {
name: "ILL",
value: 4,
},
Signal {
name: "TRAP",
value: 5,
},
Signal {
name: "ABRT",
value: 6,
},
Signal {
name: "BUS",
value: 7,
},
Signal {
name: "FPE",
value: 8,
},
Signal {
name: "KILL",
value: 9,
},
Signal {
name: "USR1",
value: 10,
},
Signal {
name: "SEGV",
value: 11,
},
Signal {
name: "USR2",
value: 12,
},
Signal {
name: "PIPE",
value: 13,
},
Signal {
name: "ALRM",
value: 14,
},
Signal {
name: "TERM",
value: 15,
},
Signal {
name: "STKFLT",
value: 16,
},
Signal {
name: "CHLD",
value: 17,
},
Signal {
name: "CONT",
value: 18,
},
Signal {
name: "STOP",
value: 19,
},
Signal {
name: "TSTP",
value: 20,
},
Signal {
name: "TTIN",
value: 21,
},
Signal {
name: "TTOU",
value: 22,
},
Signal {
name: "URG",
value: 23,
},
Signal {
name: "XCPU",
value: 24,
},
Signal {
name: "XFSZ",
value: 25,
},
Signal {
name: "VTALRM",
value: 26,
},
Signal {
name: "PROF",
value: 27,
},
Signal {
name: "WINCH",
value: 28,
},
Signal {
name: "POLL",
value: 29,
},
Signal {
name: "PWR",
value: 30,
},
Signal {
name: "SYS",
value: 31,
},
pub static ALL_SIGNALS: [&str; 32] = [
"EXIT", "HUP", "INT", "QUIT", "ILL", "TRAP", "ABRT", "BUS", "FPE", "KILL", "USR1", "SEGV",
"USR2", "PIPE", "ALRM", "TERM", "STKFLT", "CHLD", "CONT", "STOP", "TSTP", "TTIN", "TTOU",
"URG", "XCPU", "XFSZ", "VTALRM", "PROF", "WINCH", "POLL", "PWR", "SYS",
];
/*
@ -198,131 +72,10 @@ No Name Default Action Description
*/
#[cfg(any(target_vendor = "apple", target_os = "freebsd"))]
pub static ALL_SIGNALS: [Signal<'static>; 31] = [
Signal {
name: "HUP",
value: 1,
},
Signal {
name: "INT",
value: 2,
},
Signal {
name: "QUIT",
value: 3,
},
Signal {
name: "ILL",
value: 4,
},
Signal {
name: "TRAP",
value: 5,
},
Signal {
name: "ABRT",
value: 6,
},
Signal {
name: "EMT",
value: 7,
},
Signal {
name: "FPE",
value: 8,
},
Signal {
name: "KILL",
value: 9,
},
Signal {
name: "BUS",
value: 10,
},
Signal {
name: "SEGV",
value: 11,
},
Signal {
name: "SYS",
value: 12,
},
Signal {
name: "PIPE",
value: 13,
},
Signal {
name: "ALRM",
value: 14,
},
Signal {
name: "TERM",
value: 15,
},
Signal {
name: "URG",
value: 16,
},
Signal {
name: "STOP",
value: 17,
},
Signal {
name: "TSTP",
value: 18,
},
Signal {
name: "CONT",
value: 19,
},
Signal {
name: "CHLD",
value: 20,
},
Signal {
name: "TTIN",
value: 21,
},
Signal {
name: "TTOU",
value: 22,
},
Signal {
name: "IO",
value: 23,
},
Signal {
name: "XCPU",
value: 24,
},
Signal {
name: "XFSZ",
value: 25,
},
Signal {
name: "VTALRM",
value: 26,
},
Signal {
name: "PROF",
value: 27,
},
Signal {
name: "WINCH",
value: 28,
},
Signal {
name: "INFO",
value: 29,
},
Signal {
name: "USR1",
value: 30,
},
Signal {
name: "USR2",
value: 31,
},
pub static ALL_SIGNALS: [&str; 32] = [
"EXIT", "HUP", "INT", "QUIT", "ILL", "TRAP", "ABRT", "EMT", "FPE", "KILL", "BUS", "SEGV",
"SYS", "PIPE", "ALRM", "TERM", "URG", "STOP", "TSTP", "CONT", "CHLD", "TTIN", "TTOU", "IO",
"XCPU", "XFSZ", "VTALRM", "PROF", "WINCH", "INFO", "USR1", "USR2",
];
pub fn signal_by_name_or_value(signal_name_or_value: &str) -> Option<usize> {
@ -335,56 +88,45 @@ pub fn signal_by_name_or_value(signal_name_or_value: &str) -> Option<usize> {
}
let signal_name = signal_name_or_value.trim_start_matches("SIG");
ALL_SIGNALS
.iter()
.find(|s| s.name == signal_name)
.map(|s| s.value)
ALL_SIGNALS.iter().position(|&s| s == signal_name)
}
#[inline(always)]
pub fn is_signal(num: usize) -> bool {
// Named signals start at 1
num <= ALL_SIGNALS.len()
num < ALL_SIGNALS.len()
}
#[test]
fn signals_all_contiguous() {
for (i, signal) in ALL_SIGNALS.iter().enumerate() {
assert_eq!(signal.value, i + 1);
}
}
#[test]
fn signals_all_are_signal() {
for signal in &ALL_SIGNALS {
assert!(is_signal(signal.value));
}
pub fn signal_name_by_value(signal_value: usize) -> Option<&'static str> {
ALL_SIGNALS.get(signal_value).copied()
}
#[test]
fn signal_by_value() {
assert_eq!(signal_by_name_or_value("0"), Some(0));
for signal in &ALL_SIGNALS {
assert_eq!(
signal_by_name_or_value(&signal.value.to_string()),
Some(signal.value)
);
for (value, _signal) in ALL_SIGNALS.iter().enumerate() {
assert_eq!(signal_by_name_or_value(&value.to_string()), Some(value));
}
}
#[test]
fn signal_by_short_name() {
for signal in &ALL_SIGNALS {
assert_eq!(signal_by_name_or_value(signal.name), Some(signal.value));
for (value, signal) in ALL_SIGNALS.iter().enumerate() {
assert_eq!(signal_by_name_or_value(signal), Some(value));
}
}
#[test]
fn signal_by_long_name() {
for signal in &ALL_SIGNALS {
for (value, signal) in ALL_SIGNALS.iter().enumerate() {
assert_eq!(
signal_by_name_or_value(&format!("SIG{}", signal.name)),
Some(signal.value)
signal_by_name_or_value(&format!("SIG{}", signal)),
Some(value)
);
}
}
#[test]
fn name() {
for (value, signal) in ALL_SIGNALS.iter().enumerate() {
assert_eq!(signal_name_by_value(value), Some(*signal));
}
}

View file

@ -80,19 +80,22 @@ fn _du_basics_subdir(s: &str) {
#[test]
fn test_du_invalid_size() {
new_ucmd!()
.arg("--block-size=1fb4t")
.arg("/tmp")
.fails()
.code_is(1)
.stderr_only("du: invalid --block-size argument '1fb4t'");
#[cfg(not(target_pointer_width = "128"))]
new_ucmd!()
.arg("--block-size=1Y")
.arg("/tmp")
.fails()
.code_is(1)
.stderr_only("du: --block-size argument '1Y' too large");
let args = &["block-size", "threshold"];
for s in args {
new_ucmd!()
.arg(format!("--{}=1fb4t", s))
.arg("/tmp")
.fails()
.code_is(1)
.stderr_only(format!("du: invalid --{} argument '1fb4t'", s));
#[cfg(not(target_pointer_width = "128"))]
new_ucmd!()
.arg(format!("--{}=1Y", s))
.arg("/tmp")
.fails()
.code_is(1)
.stderr_only(format!("du: --{} argument '1Y' too large", s));
}
}
#[test]
@ -351,3 +354,24 @@ fn test_du_one_file_system() {
}
_du_basics_subdir(result.stdout_str());
}
#[test]
fn test_du_threshold() {
let scene = TestScenario::new(util_name!());
let threshold = if cfg!(windows) { "7K" } else { "10K" };
scene
.ucmd()
.arg(format!("--threshold={}", threshold))
.succeeds()
.stdout_contains("links")
.stdout_does_not_contain("deeper");
scene
.ucmd()
.arg(format!("--threshold=-{}", threshold))
.succeeds()
.stdout_does_not_contain("links")
.stdout_contains("deeper");
}

View file

@ -3,6 +3,7 @@
use crate::common::util::*;
use chrono::offset::Local;
use chrono::DateTime;
use chrono::Duration;
use std::fs::metadata;
fn file_last_modified_time(ucmd: &UCommand, path: &str) -> String {
@ -20,8 +21,22 @@ fn file_last_modified_time(ucmd: &UCommand, path: &str) -> String {
.unwrap_or_default()
}
fn now_time() -> String {
Local::now().format("%b %d %H:%M %Y").to_string()
fn all_minutes(from: DateTime<Local>, to: DateTime<Local>) -> Vec<String> {
const FORMAT: &str = "%b %d %H:%M %Y";
let mut vec = vec![];
let mut current = from;
while current < to {
vec.push(current.format(FORMAT).to_string());
current = current + Duration::minutes(1);
}
vec
}
fn valid_last_modified_template_vars(from: DateTime<Local>) -> Vec<Vec<(String, String)>> {
all_minutes(from, Local::now())
.into_iter()
.map(|time| vec![("{last_modified_time}".to_string(), time)])
.collect()
}
#[test]
@ -33,10 +48,7 @@ fn test_without_any_options() {
scenario
.args(&[test_file_path])
.succeeds()
.stdout_is_templated_fixture(
expected_test_file_path,
vec![(&"{last_modified_time}".to_string(), &value)],
);
.stdout_is_templated_fixture(expected_test_file_path, &[("{last_modified_time}", &value)]);
}
#[test]
@ -48,10 +60,7 @@ fn test_with_numbering_option_with_number_width() {
scenario
.args(&["-n", "2", test_file_path])
.succeeds()
.stdout_is_templated_fixture(
expected_test_file_path,
vec![(&"{last_modified_time}".to_string(), &value)],
);
.stdout_is_templated_fixture(expected_test_file_path, &[("{last_modified_time}", &value)]);
}
#[test]
@ -66,10 +75,7 @@ fn test_with_long_header_option() {
.succeeds()
.stdout_is_templated_fixture(
expected_test_file_path,
vec![
(&"{last_modified_time}".to_string(), &value),
(&"{header}".to_string(), &header.to_string()),
],
&[("{last_modified_time}", &value), ("{header}", header)],
);
new_ucmd!()
@ -77,10 +83,7 @@ fn test_with_long_header_option() {
.succeeds()
.stdout_is_templated_fixture(
expected_test_file_path,
vec![
(&"{last_modified_time}".to_string(), &value),
(&"{header}".to_string(), &header.to_string()),
],
&[("{last_modified_time}", &value), ("{header}", header)],
);
}
@ -93,18 +96,12 @@ fn test_with_double_space_option() {
scenario
.args(&["-d", test_file_path])
.succeeds()
.stdout_is_templated_fixture(
expected_test_file_path,
vec![(&"{last_modified_time}".to_string(), &value)],
);
.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,
vec![(&"{last_modified_time}".to_string(), &value)],
);
.stdout_is_templated_fixture(expected_test_file_path, &[("{last_modified_time}", &value)]);
}
#[test]
@ -116,10 +113,7 @@ fn test_with_first_line_number_option() {
scenario
.args(&["-N", "5", "-n", test_file_path])
.succeeds()
.stdout_is_templated_fixture(
expected_test_file_path,
vec![(&"{last_modified_time}".to_string(), &value)],
);
.stdout_is_templated_fixture(expected_test_file_path, &[("{last_modified_time}", &value)]);
}
#[test]
@ -131,10 +125,7 @@ fn test_with_first_line_number_long_option() {
scenario
.args(&["--first-line-number=5", "-n", test_file_path])
.succeeds()
.stdout_is_templated_fixture(
expected_test_file_path,
vec![(&"{last_modified_time}".to_string(), &value)],
);
.stdout_is_templated_fixture(expected_test_file_path, &[("{last_modified_time}", &value)]);
}
#[test]
@ -146,10 +137,7 @@ fn test_with_number_option_with_custom_separator_char() {
scenario
.args(&["-nc", test_file_path])
.succeeds()
.stdout_is_templated_fixture(
expected_test_file_path,
vec![(&"{last_modified_time}".to_string(), &value)],
);
.stdout_is_templated_fixture(expected_test_file_path, &[("{last_modified_time}", &value)]);
}
#[test]
@ -161,10 +149,7 @@ fn test_with_number_option_with_custom_separator_char_and_width() {
scenario
.args(&["-nc1", test_file_path])
.succeeds()
.stdout_is_templated_fixture(
expected_test_file_path,
vec![(&"{last_modified_time}".to_string(), &value)],
);
.stdout_is_templated_fixture(expected_test_file_path, &[("{last_modified_time}", &value)]);
}
#[test]
@ -207,25 +192,19 @@ fn test_with_page_range() {
scenario
.args(&["--pages=15", test_file_path])
.succeeds()
.stdout_is_templated_fixture(
expected_test_file_path,
vec![(&"{last_modified_time}".to_string(), &value)],
);
.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,
vec![(&"{last_modified_time}".to_string(), &value)],
);
.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,
vec![(&"{last_modified_time}".to_string(), &value)],
&[("{last_modified_time}", &value)],
);
new_ucmd!()
@ -233,7 +212,7 @@ fn test_with_page_range() {
.succeeds()
.stdout_is_templated_fixture(
expected_test_file_path1,
vec![(&"{last_modified_time}".to_string(), &value)],
&[("{last_modified_time}", &value)],
);
}
@ -246,10 +225,7 @@ fn test_with_no_header_trailer_option() {
scenario
.args(&["-t", test_file_path])
.succeeds()
.stdout_is_templated_fixture(
expected_test_file_path,
vec![(&"{last_modified_time}".to_string(), &value)],
);
.stdout_is_templated_fixture(expected_test_file_path, &[("{last_modified_time}", &value)]);
}
#[test]
@ -262,10 +238,7 @@ fn test_with_page_length_option() {
scenario
.args(&["--pages=2:3", "-l", "100", "-n", test_file_path])
.succeeds()
.stdout_is_templated_fixture(
expected_test_file_path,
vec![(&"{last_modified_time}".to_string(), &value)],
);
.stdout_is_templated_fixture(expected_test_file_path, &[("{last_modified_time}", &value)]);
new_ucmd!()
.args(&["--pages=2:3", "-l", "5", "-n", test_file_path])
@ -288,14 +261,14 @@ fn test_with_suppress_error_option() {
fn test_with_stdin() {
let expected_file_path = "stdin.log.expected";
let mut scenario = new_ucmd!();
let now = now_time();
let start = Local::now();
scenario
.pipe_in_fixture("stdin.log")
.args(&["--pages=1:2", "-n", "-"])
.run()
.stdout_is_templated_fixture(
.stdout_is_templated_fixture_any(
expected_file_path,
vec![(&"{last_modified_time}".to_string(), &now)],
&valid_last_modified_template_vars(start),
);
}
@ -308,18 +281,12 @@ fn test_with_column() {
scenario
.args(&["--pages=3:5", "--column=3", "-n", test_file_path])
.succeeds()
.stdout_is_templated_fixture(
expected_test_file_path,
vec![(&"{last_modified_time}".to_string(), &value)],
);
.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,
vec![(&"{last_modified_time}".to_string(), &value)],
);
.stdout_is_templated_fixture(expected_test_file_path, &[("{last_modified_time}", &value)]);
}
#[test]
@ -331,10 +298,7 @@ fn test_with_column_across_option() {
scenario
.args(&["--pages=3:5", "--column=3", "-a", "-n", test_file_path])
.succeeds()
.stdout_is_templated_fixture(
expected_test_file_path,
vec![(&"{last_modified_time}".to_string(), &value)],
);
.stdout_is_templated_fixture(expected_test_file_path, &[("{last_modified_time}", &value)]);
}
#[test]
@ -354,10 +318,7 @@ fn test_with_column_across_option_and_column_separator() {
test_file_path,
])
.succeeds()
.stdout_is_templated_fixture(
expected_test_file_path,
vec![(&"{last_modified_time}".to_string(), &value)],
);
.stdout_is_templated_fixture(expected_test_file_path, &[("{last_modified_time}", &value)]);
new_ucmd!()
.args(&[
@ -371,7 +332,7 @@ fn test_with_column_across_option_and_column_separator() {
.succeeds()
.stdout_is_templated_fixture(
expected_test_file_path1,
vec![(&"{last_modified_time}".to_string(), &value)],
&[("{last_modified_time}", &value)],
);
}
@ -382,25 +343,25 @@ fn test_with_mpr() {
let expected_test_file_path = "mpr.log.expected";
let expected_test_file_path1 = "mpr1.log.expected";
let expected_test_file_path2 = "mpr2.log.expected";
let now = now_time();
let start = Local::now();
new_ucmd!()
.args(&["--pages=1:2", "-m", "-n", test_file_path, test_file_path1])
.succeeds()
.stdout_is_templated_fixture(
.stdout_is_templated_fixture_any(
expected_test_file_path,
vec![(&"{last_modified_time}".to_string(), &now)],
&valid_last_modified_template_vars(start),
);
let now = now_time();
let start = Local::now();
new_ucmd!()
.args(&["--pages=2:4", "-m", "-n", test_file_path, test_file_path1])
.succeeds()
.stdout_is_templated_fixture(
.stdout_is_templated_fixture_any(
expected_test_file_path1,
vec![(&"{last_modified_time}".to_string(), &now)],
&valid_last_modified_template_vars(start),
);
let now = now_time();
let start = Local::now();
new_ucmd!()
.args(&[
"--pages=1:2",
@ -413,9 +374,9 @@ fn test_with_mpr() {
test_file_path,
])
.succeeds()
.stdout_is_templated_fixture(
.stdout_is_templated_fixture_any(
expected_test_file_path2,
vec![(&"{last_modified_time}".to_string(), &now)],
&valid_last_modified_template_vars(start),
);
}
@ -452,10 +413,7 @@ fn test_with_offset_space_option() {
test_file_path,
])
.succeeds()
.stdout_is_templated_fixture(
expected_test_file_path,
vec![(&"{last_modified_time}".to_string(), &value)],
);
.stdout_is_templated_fixture(expected_test_file_path, &[("{last_modified_time}", &value)]);
}
#[test]
@ -497,9 +455,9 @@ fn test_with_pr_core_utils_tests() {
scenario_with_expected_status.stdout_is_templated_fixture(
test_file_path,
vec![
(&"{last_modified_time}".to_string(), &value),
(&"{file_name}".to_string(), &input_file_path.to_string()),
&[
("{last_modified_time}", &value),
("{file_name}", input_file_path),
],
);
}
@ -511,12 +469,12 @@ fn test_with_join_lines_option() {
let test_file_2 = "test.log";
let expected_file_path = "joined.log.expected";
let mut scenario = new_ucmd!();
let now = now_time();
let start = Local::now();
scenario
.args(&["+1:2", "-J", "-m", test_file_1, test_file_2])
.run()
.stdout_is_templated_fixture(
.stdout_is_templated_fixture_any(
expected_file_path,
vec![(&"{last_modified_time}".to_string(), &now)],
&valid_last_modified_template_vars(start),
);
}

View file

@ -762,26 +762,30 @@ fn test_pipe() {
#[test]
fn test_check() {
new_ucmd!()
.arg("-c")
.arg("check_fail.txt")
.fails()
.stdout_is("sort: check_fail.txt:6: disorder: 5\n");
for diagnose_arg in &["-c", "--check", "--check=diagnose-first"] {
new_ucmd!()
.arg(diagnose_arg)
.arg("check_fail.txt")
.fails()
.stdout_is("sort: check_fail.txt:6: disorder: 5\n");
new_ucmd!()
.arg("-c")
.arg("multiple_files.expected")
.succeeds()
.stdout_is("");
new_ucmd!()
.arg(diagnose_arg)
.arg("multiple_files.expected")
.succeeds()
.stdout_is("");
}
}
#[test]
fn test_check_silent() {
new_ucmd!()
.arg("-C")
.arg("check_fail.txt")
.fails()
.stdout_is("");
for silent_arg in &["-C", "--check=silent", "--check=quiet"] {
new_ucmd!()
.arg(silent_arg)
.arg("check_fail.txt")
.fails()
.stdout_is("");
}
}
#[test]
@ -835,7 +839,7 @@ fn test_nonexistent_file() {
#[test]
fn test_blanks() {
test_helper("blanks", &["-b", "--ignore-blanks"]);
test_helper("blanks", &["-b", "--ignore-leading-blanks"]);
}
#[test]

View file

@ -8,7 +8,7 @@
// file that was distributed with this source code.
//
// spell-checker:ignore (words) pseudofloat
// spell-checker:ignore (words) egid euid pseudofloat
use crate::common::util::*;
@ -476,6 +476,52 @@ fn test_nonexistent_file_is_not_symlink() {
.succeeds();
}
#[test]
#[cfg(not(windows))]
fn test_file_owned_by_euid() {
new_ucmd!().args(&["-O", "regular_file"]).succeeds();
}
#[test]
#[cfg(not(windows))]
fn test_nonexistent_file_not_owned_by_euid() {
new_ucmd!()
.args(&["-O", "nonexistent_file"])
.run()
.status_code(1);
}
#[test]
#[cfg(all(not(windows), not(target_os = "freebsd")))]
fn test_file_not_owned_by_euid() {
new_ucmd!()
.args(&["-f", "/bin/sh", "-a", "!", "-O", "/bin/sh"])
.succeeds();
}
#[test]
#[cfg(not(windows))]
fn test_file_owned_by_egid() {
new_ucmd!().args(&["-G", "regular_file"]).succeeds();
}
#[test]
#[cfg(not(windows))]
fn test_nonexistent_file_not_owned_by_egid() {
new_ucmd!()
.args(&["-G", "nonexistent_file"])
.run()
.status_code(1);
}
#[test]
#[cfg(all(not(windows), not(target_os = "freebsd")))]
fn test_file_not_owned_by_egid() {
new_ucmd!()
.args(&["-f", "/bin/sh", "-a", "!", "-G", "/bin/sh"])
.succeeds();
}
#[test]
fn test_op_precedence_and_or_1() {
new_ucmd!().args(&[" ", "-o", "", "-a", ""]).succeeds();

View file

@ -9,3 +9,39 @@ fn test_subcommand_return_code() {
new_ucmd!().arg("1").arg("false").run().status_code(1);
}
#[test]
fn test_command_with_args() {
new_ucmd!()
.args(&["1700", "echo", "-n", "abcd"])
.succeeds()
.stdout_only("abcd");
}
#[test]
fn test_verbose() {
for &verbose_flag in &["-v", "--verbose"] {
new_ucmd!()
.args(&[verbose_flag, ".1", "sleep", "10"])
.fails()
.stderr_only("timeout: sending signal TERM to command 'sleep'");
new_ucmd!()
.args(&[verbose_flag, "-s0", "-k.1", ".1", "sleep", "10"])
.fails()
.stderr_only("timeout: sending signal EXIT to command 'sleep'\ntimeout: sending signal KILL to command 'sleep'");
}
}
#[test]
fn test_zero_timeout() {
new_ucmd!()
.args(&["-v", "0", "sleep", ".1"])
.succeeds()
.no_stderr()
.no_stdout();
new_ucmd!()
.args(&["-v", "0", "-s0", "-k0", "sleep", ".1"])
.succeeds()
.no_stderr()
.no_stdout();
}

View file

@ -6,6 +6,7 @@ use self::touch::filetime::{self, FileTime};
extern crate time;
use crate::common::util::*;
use std::path::PathBuf;
fn get_file_times(at: &AtPath, path: &str) -> (FileTime, FileTime) {
let m = at.metadata(path);
@ -466,3 +467,37 @@ fn test_touch_trailing_slash() {
let file = "no-file/";
ucmd.args(&[file]).fails();
}
#[test]
fn test_touch_no_such_file_error_msg() {
let dirname = "nonexistent";
let filename = "file";
let path = PathBuf::from(dirname).join(filename);
let path_str = path.to_str().unwrap();
new_ucmd!().arg(&path).fails().stderr_only(format!(
"touch: cannot touch '{}': No such file or directory",
path_str
));
}
#[test]
#[cfg(unix)]
fn test_touch_permission_denied_error_msg() {
let (at, mut ucmd) = at_and_ucmd!();
let dirname = "dir_with_read_only_access";
let filename = "file";
let path = PathBuf::from(dirname).join(filename);
let path_str = path.to_str().unwrap();
// create dest without write permissions
at.mkdir(dirname);
at.set_readonly(dirname);
let full_path = at.plus_as_string(path_str);
ucmd.arg(&full_path).fails().stderr_only(format!(
"touch: cannot touch '{}': Permission denied",
&full_path
));
}

View file

@ -223,6 +223,18 @@ impl CmdResult {
self
}
/// like `stdout_is`, but succeeds if any elements of `expected` matches stdout.
pub fn stdout_is_any<T: AsRef<str> + std::fmt::Debug>(&self, expected: Vec<T>) -> &CmdResult {
if !expected.iter().any(|msg| self.stdout_str() == msg.as_ref()) {
panic!(
"stdout was {}\nExpected any of {:#?}",
self.stdout_str(),
expected
)
}
self
}
/// Like `stdout_is` but newlines are normalized to `\n`.
pub fn normalized_newlines_stdout_is<T: AsRef<str>>(&self, msg: T) -> &CmdResult {
let msg = msg.as_ref().replace("\r\n", "\n");
@ -247,7 +259,7 @@ impl CmdResult {
pub fn stdout_is_templated_fixture<T: AsRef<OsStr>>(
&self,
file_rel_path: T,
template_vars: Vec<(&String, &String)>,
template_vars: &[(&str, &str)],
) -> &CmdResult {
let mut contents =
String::from_utf8(read_scenario_fixture(&self.tmpd, file_rel_path)).unwrap();
@ -257,6 +269,23 @@ impl CmdResult {
self.stdout_is(contents)
}
/// like `stdout_is_templated_fixture`, but succeeds if any replacement by `template_vars` results in the actual stdout.
pub fn stdout_is_templated_fixture_any<T: AsRef<OsStr>>(
&self,
file_rel_path: T,
template_vars: &[Vec<(String, String)>],
) {
let contents = String::from_utf8(read_scenario_fixture(&self.tmpd, file_rel_path)).unwrap();
let possible_values = template_vars.iter().map(|vars| {
let mut contents = contents.clone();
for kv in vars.iter() {
contents = contents.replace(&kv.0, &kv.1);
}
contents
});
self.stdout_is_any(possible_values.collect());
}
/// asserts that the command resulted in stderr stream output that equals the
/// passed in value, when both are trimmed of trailing whitespace
/// stderr_only is a better choice unless stdout may or will be non-empty