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:
commit
88367c6fb4
18 changed files with 701 additions and 647 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -2597,6 +2597,7 @@ version = "0.0.6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap",
|
"clap",
|
||||||
"libc",
|
"libc",
|
||||||
|
"nix 0.20.0",
|
||||||
"uucore",
|
"uucore",
|
||||||
"uucore_procs",
|
"uucore_procs",
|
||||||
]
|
]
|
||||||
|
|
|
@ -28,6 +28,7 @@ use std::os::windows::io::AsRawHandle;
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
use std::str::FromStr;
|
||||||
use std::time::{Duration, UNIX_EPOCH};
|
use std::time::{Duration, UNIX_EPOCH};
|
||||||
use uucore::parse_size::{parse_size, ParseSizeError};
|
use uucore::parse_size::{parse_size, ParseSizeError};
|
||||||
use uucore::InvalidEncodingHandling;
|
use uucore::InvalidEncodingHandling;
|
||||||
|
@ -56,6 +57,7 @@ mod options {
|
||||||
pub const BLOCK_SIZE_1M: &str = "m";
|
pub const BLOCK_SIZE_1M: &str = "m";
|
||||||
pub const SEPARATE_DIRS: &str = "S";
|
pub const SEPARATE_DIRS: &str = "S";
|
||||||
pub const SUMMARIZE: &str = "s";
|
pub const SUMMARIZE: &str = "s";
|
||||||
|
pub const THRESHOLD: &str = "threshold";
|
||||||
pub const SI: &str = "si";
|
pub const SI: &str = "si";
|
||||||
pub const TIME: &str = "time";
|
pub const TIME: &str = "time";
|
||||||
pub const TIME_STYLE: &str = "time-style";
|
pub const TIME_STYLE: &str = "time-style";
|
||||||
|
@ -510,6 +512,17 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
||||||
.long(options::ONE_FILE_SYSTEM)
|
.long(options::ONE_FILE_SYSTEM)
|
||||||
.help("skip directories on different file systems")
|
.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(
|
||||||
// Arg::with_name("")
|
// Arg::with_name("")
|
||||||
// .short("x")
|
// .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 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) {
|
let multiplier: u64 = if matches.is_present(options::SI) {
|
||||||
1000
|
1000
|
||||||
} else {
|
} else {
|
||||||
|
@ -654,6 +672,11 @@ Try '{} --help' for more information.",
|
||||||
// See: http://linux.die.net/man/2/stat
|
// See: http://linux.die.net/man/2/stat
|
||||||
stat.blocks * 512
|
stat.blocks * 512
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if threshold.map_or(false, |threshold| threshold.should_exclude(size)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if matches.is_present(options::TIME) {
|
if matches.is_present(options::TIME) {
|
||||||
let tm = {
|
let tm = {
|
||||||
let secs = {
|
let secs = {
|
||||||
|
@ -720,6 +743,37 @@ Try '{} --help' for more information.",
|
||||||
0
|
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 {
|
fn format_error_message(error: ParseSizeError, s: &str, option: &str) -> String {
|
||||||
// NOTE:
|
// NOTE:
|
||||||
// GNU's du echos affected flag, -B or --block-size (-t or --threshold), depending user's selection
|
// GNU's du echos affected flag, -B or --block-size (-t or --threshold), depending user's selection
|
||||||
|
|
|
@ -129,33 +129,24 @@ fn handle_obsolete(mut args: Vec<String>) -> (Vec<String>, Option<String>) {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn table() {
|
fn table() {
|
||||||
let mut name_width = 0;
|
let name_width = ALL_SIGNALS.iter().map(|n| n.len()).max().unwrap();
|
||||||
/* Compute the maximum width of a signal name. */
|
|
||||||
for s in &ALL_SIGNALS {
|
|
||||||
if s.name.len() > name_width {
|
|
||||||
name_width = s.name.len()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (idx, signal) in ALL_SIGNALS.iter().enumerate() {
|
for (idx, signal) in ALL_SIGNALS.iter().enumerate() {
|
||||||
print!("{0: >#2} {1: <#8}", idx + 1, signal.name);
|
print!("{0: >#2} {1: <#2$}", idx, signal, name_width + 2);
|
||||||
//TODO: obtain max signal width here
|
|
||||||
|
|
||||||
if (idx + 1) % 7 == 0 {
|
if (idx + 1) % 7 == 0 {
|
||||||
println!();
|
println!();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
println!()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn print_signal(signal_name_or_value: &str) {
|
fn print_signal(signal_name_or_value: &str) {
|
||||||
for signal in &ALL_SIGNALS {
|
for (value, &signal) in ALL_SIGNALS.iter().enumerate() {
|
||||||
if signal.name == signal_name_or_value
|
if signal == signal_name_or_value || (format!("SIG{}", signal)) == signal_name_or_value {
|
||||||
|| (format!("SIG{}", signal.name)) == signal_name_or_value
|
println!("{}", value);
|
||||||
{
|
|
||||||
println!("{}", signal.value);
|
|
||||||
exit!(EXIT_OK as i32)
|
exit!(EXIT_OK as i32)
|
||||||
} else if signal_name_or_value == signal.value.to_string() {
|
} else if signal_name_or_value == value.to_string() {
|
||||||
println!("{}", signal.name);
|
println!("{}", signal);
|
||||||
exit!(EXIT_OK as i32)
|
exit!(EXIT_OK as i32)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -165,8 +156,8 @@ fn print_signal(signal_name_or_value: &str) {
|
||||||
fn print_signals() {
|
fn print_signals() {
|
||||||
let mut pos = 0;
|
let mut pos = 0;
|
||||||
for (idx, signal) in ALL_SIGNALS.iter().enumerate() {
|
for (idx, signal) in ALL_SIGNALS.iter().enumerate() {
|
||||||
pos += signal.name.len();
|
pos += signal.len();
|
||||||
print!("{}", signal.name);
|
print!("{}", signal);
|
||||||
if idx > 0 && pos > 73 {
|
if idx > 0 && pos > 73 {
|
||||||
println!();
|
println!();
|
||||||
pos = 0;
|
pos = 0;
|
||||||
|
|
|
@ -46,8 +46,8 @@ use unicode_width::UnicodeWidthStr;
|
||||||
use uucore::parse_size::{parse_size, ParseSizeError};
|
use uucore::parse_size::{parse_size, ParseSizeError};
|
||||||
use uucore::InvalidEncodingHandling;
|
use uucore::InvalidEncodingHandling;
|
||||||
|
|
||||||
static NAME: &str = "sort";
|
const NAME: &str = "sort";
|
||||||
static ABOUT: &str = "Display sorted concatenation of all FILE(s).";
|
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].
|
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.";
|
Valid options are: MbdfhnRrV. They override the global options for this key.";
|
||||||
|
|
||||||
static OPT_HUMAN_NUMERIC_SORT: &str = "human-numeric-sort";
|
mod options {
|
||||||
static OPT_MONTH_SORT: &str = "month-sort";
|
pub mod modes {
|
||||||
static OPT_NUMERIC_SORT: &str = "numeric-sort";
|
pub const SORT: &str = "sort";
|
||||||
static OPT_GENERAL_NUMERIC_SORT: &str = "general-numeric-sort";
|
|
||||||
static OPT_VERSION_SORT: &str = "version-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] = &[
|
pub const ALL_SORT_MODES: [&str; 6] = [
|
||||||
OPT_GENERAL_NUMERIC_SORT,
|
GENERAL_NUMERIC,
|
||||||
OPT_HUMAN_NUMERIC_SORT,
|
HUMAN_NUMERIC,
|
||||||
OPT_MONTH_SORT,
|
MONTH,
|
||||||
OPT_NUMERIC_SORT,
|
NUMERIC,
|
||||||
OPT_VERSION_SORT,
|
VERSION,
|
||||||
OPT_RANDOM,
|
RANDOM,
|
||||||
];
|
];
|
||||||
|
}
|
||||||
|
|
||||||
static OPT_DICTIONARY_ORDER: &str = "dictionary-order";
|
pub mod check {
|
||||||
static OPT_MERGE: &str = "merge";
|
pub const CHECK: &str = "check";
|
||||||
static OPT_CHECK: &str = "check";
|
pub const CHECK_SILENT: &str = "check-silent";
|
||||||
static OPT_CHECK_SILENT: &str = "check-silent";
|
pub const SILENT: &str = "silent";
|
||||||
static OPT_DEBUG: &str = "debug";
|
pub const QUIET: &str = "quiet";
|
||||||
static OPT_IGNORE_CASE: &str = "ignore-case";
|
pub const DIAGNOSE_FIRST: &str = "diagnose-first";
|
||||||
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";
|
|
||||||
|
|
||||||
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 NEGATIVE: char = '-';
|
||||||
const POSITIVE: char = '+';
|
const POSITIVE: char = '+';
|
||||||
|
@ -109,7 +119,7 @@ const POSITIVE: char = '+';
|
||||||
// Choosing a higher buffer size does not result in performance improvements
|
// 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
|
// (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.
|
// 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)]
|
#[derive(Eq, Ord, PartialEq, PartialOrd, Clone, Copy, Debug)]
|
||||||
enum SortMode {
|
enum SortMode {
|
||||||
|
@ -140,7 +150,7 @@ impl SortMode {
|
||||||
pub struct GlobalSettings {
|
pub struct GlobalSettings {
|
||||||
mode: SortMode,
|
mode: SortMode,
|
||||||
debug: bool,
|
debug: bool,
|
||||||
ignore_blanks: bool,
|
ignore_leading_blanks: bool,
|
||||||
ignore_case: bool,
|
ignore_case: bool,
|
||||||
dictionary_order: bool,
|
dictionary_order: bool,
|
||||||
ignore_non_printing: bool,
|
ignore_non_printing: bool,
|
||||||
|
@ -207,7 +217,7 @@ impl Default for GlobalSettings {
|
||||||
GlobalSettings {
|
GlobalSettings {
|
||||||
mode: SortMode::Default,
|
mode: SortMode::Default,
|
||||||
debug: false,
|
debug: false,
|
||||||
ignore_blanks: false,
|
ignore_leading_blanks: false,
|
||||||
ignore_case: false,
|
ignore_case: false,
|
||||||
dictionary_order: false,
|
dictionary_order: false,
|
||||||
ignore_non_printing: false,
|
ignore_non_printing: false,
|
||||||
|
@ -298,7 +308,7 @@ impl From<&GlobalSettings> for KeySettings {
|
||||||
fn from(settings: &GlobalSettings) -> Self {
|
fn from(settings: &GlobalSettings) -> Self {
|
||||||
Self {
|
Self {
|
||||||
mode: settings.mode,
|
mode: settings.mode,
|
||||||
ignore_blanks: settings.ignore_blanks,
|
ignore_blanks: settings.ignore_leading_blanks,
|
||||||
ignore_case: settings.ignore_case,
|
ignore_case: settings.ignore_case,
|
||||||
ignore_non_printing: settings.ignore_non_printing,
|
ignore_non_printing: settings.ignore_non_printing,
|
||||||
reverse: settings.reverse,
|
reverse: settings.reverse,
|
||||||
|
@ -505,7 +515,7 @@ impl<'a> Line<'a> {
|
||||||
&& !settings.stable
|
&& !settings.stable
|
||||||
&& !settings.unique
|
&& !settings.unique
|
||||||
&& (settings.dictionary_order
|
&& (settings.dictionary_order
|
||||||
|| settings.ignore_blanks
|
|| settings.ignore_leading_blanks
|
||||||
|| settings.ignore_case
|
|| settings.ignore_case
|
||||||
|| settings.ignore_non_printing
|
|| settings.ignore_non_printing
|
||||||
|| settings.mode != SortMode::Default
|
|| 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
|
// This would be ideal for a try block, I think. In the meantime this closure allows
|
||||||
// to use the `?` operator here.
|
// to use the `?` operator here.
|
||||||
Self::new(
|
Self::new(
|
||||||
KeyPosition::new(from, 1, global_settings.ignore_blanks)?,
|
KeyPosition::new(from, 1, global_settings.ignore_leading_blanks)?,
|
||||||
to.map(|(to, _)| KeyPosition::new(to, 0, global_settings.ignore_blanks))
|
to.map(|(to, _)| {
|
||||||
.transpose()?,
|
KeyPosition::new(to, 0, global_settings.ignore_leading_blanks)
|
||||||
|
})
|
||||||
|
.transpose()?,
|
||||||
KeySettings::from(global_settings),
|
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> {
|
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);
|
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 {
|
if *possible_mode != mode {
|
||||||
arg = arg.conflicts_with(possible_mode);
|
arg = arg.conflicts_with(possible_mode);
|
||||||
}
|
}
|
||||||
|
@ -909,8 +922,8 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
||||||
.about(ABOUT)
|
.about(ABOUT)
|
||||||
.usage(&usage[..])
|
.usage(&usage[..])
|
||||||
.arg(
|
.arg(
|
||||||
Arg::with_name(OPT_SORT)
|
Arg::with_name(options::modes::SORT)
|
||||||
.long(OPT_SORT)
|
.long(options::modes::SORT)
|
||||||
.takes_value(true)
|
.takes_value(true)
|
||||||
.possible_values(
|
.possible_values(
|
||||||
&[
|
&[
|
||||||
|
@ -922,199 +935,221 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
||||||
"random",
|
"random",
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
.conflicts_with_all(ALL_SORT_MODES)
|
.conflicts_with_all(&options::modes::ALL_SORT_MODES)
|
||||||
)
|
)
|
||||||
.arg(
|
.arg(
|
||||||
make_sort_mode_arg(
|
make_sort_mode_arg(
|
||||||
OPT_HUMAN_NUMERIC_SORT,
|
options::modes::HUMAN_NUMERIC,
|
||||||
"h",
|
"h",
|
||||||
"compare according to human readable sizes, eg 1M > 100k"
|
"compare according to human readable sizes, eg 1M > 100k"
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.arg(
|
.arg(
|
||||||
make_sort_mode_arg(
|
make_sort_mode_arg(
|
||||||
OPT_MONTH_SORT,
|
options::modes::MONTH,
|
||||||
"M",
|
"M",
|
||||||
"compare according to month name abbreviation"
|
"compare according to month name abbreviation"
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.arg(
|
.arg(
|
||||||
make_sort_mode_arg(
|
make_sort_mode_arg(
|
||||||
OPT_NUMERIC_SORT,
|
options::modes::NUMERIC,
|
||||||
"n",
|
"n",
|
||||||
"compare according to string numerical value"
|
"compare according to string numerical value"
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.arg(
|
.arg(
|
||||||
make_sort_mode_arg(
|
make_sort_mode_arg(
|
||||||
OPT_GENERAL_NUMERIC_SORT,
|
options::modes::GENERAL_NUMERIC,
|
||||||
"g",
|
"g",
|
||||||
"compare according to string general numerical value"
|
"compare according to string general numerical value"
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.arg(
|
.arg(
|
||||||
make_sort_mode_arg(
|
make_sort_mode_arg(
|
||||||
OPT_VERSION_SORT,
|
options::modes::VERSION,
|
||||||
"V",
|
"V",
|
||||||
"Sort by SemVer version number, eg 1.12.2 > 1.1.2",
|
"Sort by SemVer version number, eg 1.12.2 > 1.1.2",
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.arg(
|
.arg(
|
||||||
make_sort_mode_arg(
|
make_sort_mode_arg(
|
||||||
OPT_RANDOM,
|
options::modes::RANDOM,
|
||||||
"R",
|
"R",
|
||||||
"shuffle in random order",
|
"shuffle in random order",
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::with_name(OPT_DICTIONARY_ORDER)
|
Arg::with_name(options::DICTIONARY_ORDER)
|
||||||
.short("d")
|
.short("d")
|
||||||
.long(OPT_DICTIONARY_ORDER)
|
.long(options::DICTIONARY_ORDER)
|
||||||
.help("consider only blanks and alphanumeric characters")
|
.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(
|
||||||
Arg::with_name(OPT_MERGE)
|
Arg::with_name(options::MERGE)
|
||||||
.short("m")
|
.short("m")
|
||||||
.long(OPT_MERGE)
|
.long(options::MERGE)
|
||||||
.help("merge already sorted files; do not sort"),
|
.help("merge already sorted files; do not sort"),
|
||||||
)
|
)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::with_name(OPT_CHECK)
|
Arg::with_name(options::check::CHECK)
|
||||||
.short("c")
|
.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"),
|
.help("check for sorted input; do not sort"),
|
||||||
)
|
)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::with_name(OPT_CHECK_SILENT)
|
Arg::with_name(options::check::CHECK_SILENT)
|
||||||
.short("C")
|
.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."),
|
.help("exit successfully if the given file is already sorted, and exit with status 1 otherwise."),
|
||||||
)
|
)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::with_name(OPT_IGNORE_CASE)
|
Arg::with_name(options::IGNORE_CASE)
|
||||||
.short("f")
|
.short("f")
|
||||||
.long(OPT_IGNORE_CASE)
|
.long(options::IGNORE_CASE)
|
||||||
.help("fold lower case to upper case characters"),
|
.help("fold lower case to upper case characters"),
|
||||||
)
|
)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::with_name(OPT_IGNORE_NONPRINTING)
|
Arg::with_name(options::IGNORE_NONPRINTING)
|
||||||
.short("i")
|
.short("i")
|
||||||
.long(OPT_IGNORE_NONPRINTING)
|
.long(options::IGNORE_NONPRINTING)
|
||||||
.help("ignore nonprinting characters")
|
.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(
|
||||||
Arg::with_name(OPT_IGNORE_BLANKS)
|
Arg::with_name(options::IGNORE_LEADING_BLANKS)
|
||||||
.short("b")
|
.short("b")
|
||||||
.long(OPT_IGNORE_BLANKS)
|
.long(options::IGNORE_LEADING_BLANKS)
|
||||||
.help("ignore leading blanks when finding sort keys in each line"),
|
.help("ignore leading blanks when finding sort keys in each line"),
|
||||||
)
|
)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::with_name(OPT_OUTPUT)
|
Arg::with_name(options::OUTPUT)
|
||||||
.short("o")
|
.short("o")
|
||||||
.long(OPT_OUTPUT)
|
.long(options::OUTPUT)
|
||||||
.help("write output to FILENAME instead of stdout")
|
.help("write output to FILENAME instead of stdout")
|
||||||
.takes_value(true)
|
.takes_value(true)
|
||||||
.value_name("FILENAME"),
|
.value_name("FILENAME"),
|
||||||
)
|
)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::with_name(OPT_REVERSE)
|
Arg::with_name(options::REVERSE)
|
||||||
.short("r")
|
.short("r")
|
||||||
.long(OPT_REVERSE)
|
.long(options::REVERSE)
|
||||||
.help("reverse the output"),
|
.help("reverse the output"),
|
||||||
)
|
)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::with_name(OPT_STABLE)
|
Arg::with_name(options::STABLE)
|
||||||
.short("s")
|
.short("s")
|
||||||
.long(OPT_STABLE)
|
.long(options::STABLE)
|
||||||
.help("stabilize sort by disabling last-resort comparison"),
|
.help("stabilize sort by disabling last-resort comparison"),
|
||||||
)
|
)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::with_name(OPT_UNIQUE)
|
Arg::with_name(options::UNIQUE)
|
||||||
.short("u")
|
.short("u")
|
||||||
.long(OPT_UNIQUE)
|
.long(options::UNIQUE)
|
||||||
.help("output only the first of an equal run"),
|
.help("output only the first of an equal run"),
|
||||||
)
|
)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::with_name(OPT_KEY)
|
Arg::with_name(options::KEY)
|
||||||
.short("k")
|
.short("k")
|
||||||
.long(OPT_KEY)
|
.long(options::KEY)
|
||||||
.help("sort by a key")
|
.help("sort by a key")
|
||||||
.long_help(LONG_HELP_KEYS)
|
.long_help(LONG_HELP_KEYS)
|
||||||
.multiple(true)
|
.multiple(true)
|
||||||
.takes_value(true),
|
.takes_value(true),
|
||||||
)
|
)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::with_name(OPT_SEPARATOR)
|
Arg::with_name(options::SEPARATOR)
|
||||||
.short("t")
|
.short("t")
|
||||||
.long(OPT_SEPARATOR)
|
.long(options::SEPARATOR)
|
||||||
.help("custom separator for -k")
|
.help("custom separator for -k")
|
||||||
.takes_value(true))
|
.takes_value(true))
|
||||||
.arg(
|
.arg(
|
||||||
Arg::with_name(OPT_ZERO_TERMINATED)
|
Arg::with_name(options::ZERO_TERMINATED)
|
||||||
.short("z")
|
.short("z")
|
||||||
.long(OPT_ZERO_TERMINATED)
|
.long(options::ZERO_TERMINATED)
|
||||||
.help("line delimiter is NUL, not newline"),
|
.help("line delimiter is NUL, not newline"),
|
||||||
)
|
)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::with_name(OPT_PARALLEL)
|
Arg::with_name(options::PARALLEL)
|
||||||
.long(OPT_PARALLEL)
|
.long(options::PARALLEL)
|
||||||
.help("change the number of threads running concurrently to NUM_THREADS")
|
.help("change the number of threads running concurrently to NUM_THREADS")
|
||||||
.takes_value(true)
|
.takes_value(true)
|
||||||
.value_name("NUM_THREADS"),
|
.value_name("NUM_THREADS"),
|
||||||
)
|
)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::with_name(OPT_BUF_SIZE)
|
Arg::with_name(options::BUF_SIZE)
|
||||||
.short("S")
|
.short("S")
|
||||||
.long(OPT_BUF_SIZE)
|
.long(options::BUF_SIZE)
|
||||||
.help("sets the maximum SIZE of each segment in number of sorted items")
|
.help("sets the maximum SIZE of each segment in number of sorted items")
|
||||||
.takes_value(true)
|
.takes_value(true)
|
||||||
.value_name("SIZE"),
|
.value_name("SIZE"),
|
||||||
)
|
)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::with_name(OPT_TMP_DIR)
|
Arg::with_name(options::TMP_DIR)
|
||||||
.short("T")
|
.short("T")
|
||||||
.long(OPT_TMP_DIR)
|
.long(options::TMP_DIR)
|
||||||
.help("use DIR for temporaries, not $TMPDIR or /tmp")
|
.help("use DIR for temporaries, not $TMPDIR or /tmp")
|
||||||
.takes_value(true)
|
.takes_value(true)
|
||||||
.value_name("DIR"),
|
.value_name("DIR"),
|
||||||
)
|
)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::with_name(OPT_COMPRESS_PROG)
|
Arg::with_name(options::COMPRESS_PROG)
|
||||||
.long(OPT_COMPRESS_PROG)
|
.long(options::COMPRESS_PROG)
|
||||||
.help("compress temporary files with PROG, decompress with PROG -d")
|
.help("compress temporary files with PROG, decompress with PROG -d")
|
||||||
.long_help("PROG has to take input from stdin and output to stdout")
|
.long_help("PROG has to take input from stdin and output to stdout")
|
||||||
.value_name("PROG")
|
.value_name("PROG")
|
||||||
)
|
)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::with_name(OPT_BATCH_SIZE)
|
Arg::with_name(options::BATCH_SIZE)
|
||||||
.long(OPT_BATCH_SIZE)
|
.long(options::BATCH_SIZE)
|
||||||
.help("Merge at most N_MERGE inputs at once.")
|
.help("Merge at most N_MERGE inputs at once.")
|
||||||
.value_name("N_MERGE")
|
.value_name("N_MERGE")
|
||||||
)
|
)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::with_name(OPT_FILES0_FROM)
|
Arg::with_name(options::FILES0_FROM)
|
||||||
.long(OPT_FILES0_FROM)
|
.long(options::FILES0_FROM)
|
||||||
.help("read input from the files specified by NUL-terminated NUL_FILES")
|
.help("read input from the files specified by NUL-terminated NUL_FILES")
|
||||||
.takes_value(true)
|
.takes_value(true)
|
||||||
.value_name("NUL_FILES")
|
.value_name("NUL_FILES")
|
||||||
.multiple(true),
|
.multiple(true),
|
||||||
)
|
)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::with_name(OPT_DEBUG)
|
Arg::with_name(options::DEBUG)
|
||||||
.long(OPT_DEBUG)
|
.long(options::DEBUG)
|
||||||
.help("underline the parts of the line that are actually used for sorting"),
|
.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);
|
.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
|
// 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
|
let files0_from: Vec<String> = matches
|
||||||
.values_of(OPT_FILES0_FROM)
|
.values_of(options::FILES0_FROM)
|
||||||
.map(|v| v.map(ToString::to_string).collect())
|
.map(|v| v.map(ToString::to_string).collect())
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
@ -1133,82 +1168,93 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
||||||
files
|
files
|
||||||
} else {
|
} else {
|
||||||
matches
|
matches
|
||||||
.values_of(ARG_FILES)
|
.values_of(options::FILES)
|
||||||
.map(|v| v.map(ToString::to_string).collect())
|
.map(|v| v.map(ToString::to_string).collect())
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
};
|
};
|
||||||
|
|
||||||
settings.mode = if matches.is_present(OPT_HUMAN_NUMERIC_SORT)
|
settings.mode = if matches.is_present(options::modes::HUMAN_NUMERIC)
|
||||||
|| matches.value_of(OPT_SORT) == Some("human-numeric")
|
|| matches.value_of(options::modes::SORT) == Some("human-numeric")
|
||||||
{
|
{
|
||||||
SortMode::HumanNumeric
|
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
|
SortMode::Month
|
||||||
} else if matches.is_present(OPT_GENERAL_NUMERIC_SORT)
|
} else if matches.is_present(options::modes::GENERAL_NUMERIC)
|
||||||
|| matches.value_of(OPT_SORT) == Some("general-numeric")
|
|| matches.value_of(options::modes::SORT) == Some("general-numeric")
|
||||||
{
|
{
|
||||||
SortMode::GeneralNumeric
|
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
|
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
|
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();
|
settings.salt = get_rand_string();
|
||||||
SortMode::Random
|
SortMode::Random
|
||||||
} else {
|
} else {
|
||||||
SortMode::Default
|
SortMode::Default
|
||||||
};
|
};
|
||||||
|
|
||||||
settings.dictionary_order = matches.is_present(OPT_DICTIONARY_ORDER);
|
settings.dictionary_order = matches.is_present(options::DICTIONARY_ORDER);
|
||||||
settings.ignore_non_printing = matches.is_present(OPT_IGNORE_NONPRINTING);
|
settings.ignore_non_printing = matches.is_present(options::IGNORE_NONPRINTING);
|
||||||
if matches.is_present(OPT_PARALLEL) {
|
if matches.is_present(options::PARALLEL) {
|
||||||
// "0" is default - threads = num of cores
|
// "0" is default - threads = num of cores
|
||||||
settings.threads = matches
|
settings.threads = matches
|
||||||
.value_of(OPT_PARALLEL)
|
.value_of(options::PARALLEL)
|
||||||
.map(String::from)
|
.map(String::from)
|
||||||
.unwrap_or_else(|| "0".to_string());
|
.unwrap_or_else(|| "0".to_string());
|
||||||
env::set_var("RAYON_NUM_THREADS", &settings.threads);
|
env::set_var("RAYON_NUM_THREADS", &settings.threads);
|
||||||
}
|
}
|
||||||
|
|
||||||
settings.buffer_size = matches
|
settings.buffer_size = matches
|
||||||
.value_of(OPT_BUF_SIZE)
|
.value_of(options::BUF_SIZE)
|
||||||
.map_or(DEFAULT_BUF_SIZE, |s| {
|
.map_or(DEFAULT_BUF_SIZE, |s| {
|
||||||
GlobalSettings::parse_byte_count(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
|
settings.tmp_dir = matches
|
||||||
.value_of(OPT_TMP_DIR)
|
.value_of(options::TMP_DIR)
|
||||||
.map(PathBuf::from)
|
.map(PathBuf::from)
|
||||||
.unwrap_or_else(env::temp_dir);
|
.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
|
settings.merge_batch_size = n_merge
|
||||||
.parse()
|
.parse()
|
||||||
.unwrap_or_else(|_| crash!(2, "invalid --batch-size argument '{}'", n_merge));
|
.unwrap_or_else(|_| crash!(2, "invalid --batch-size argument '{}'", n_merge));
|
||||||
}
|
}
|
||||||
|
|
||||||
settings.zero_terminated = matches.is_present(OPT_ZERO_TERMINATED);
|
settings.zero_terminated = matches.is_present(options::ZERO_TERMINATED);
|
||||||
settings.merge = matches.is_present(OPT_MERGE);
|
settings.merge = matches.is_present(options::MERGE);
|
||||||
|
|
||||||
settings.check = matches.is_present(OPT_CHECK);
|
settings.check = matches.is_present(options::check::CHECK);
|
||||||
if matches.is_present(OPT_CHECK_SILENT) {
|
if matches.is_present(options::check::CHECK_SILENT)
|
||||||
settings.check_silent = matches.is_present(OPT_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.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.output_file = matches.value_of(options::OUTPUT).map(String::from);
|
||||||
settings.reverse = matches.is_present(OPT_REVERSE);
|
settings.reverse = matches.is_present(options::REVERSE);
|
||||||
settings.stable = matches.is_present(OPT_STABLE);
|
settings.stable = matches.is_present(options::STABLE);
|
||||||
settings.unique = matches.is_present(OPT_UNIQUE);
|
settings.unique = matches.is_present(options::UNIQUE);
|
||||||
|
|
||||||
if files.is_empty() {
|
if files.is_empty() {
|
||||||
/* if no file, default to stdin */
|
/* 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])
|
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 = arg.vals[0].to_string_lossy();
|
||||||
let separator = separator;
|
let separator = separator;
|
||||||
if separator.len() != 1 {
|
if separator.len() != 1 {
|
||||||
|
@ -1226,15 +1272,15 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
||||||
settings.separator = Some(separator.chars().next().unwrap())
|
settings.separator = Some(separator.chars().next().unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
if matches.is_present(OPT_KEY) {
|
if let Some(values) = matches.values_of(options::KEY) {
|
||||||
for key in &matches.args[OPT_KEY].vals {
|
for value in values {
|
||||||
settings
|
settings
|
||||||
.selectors
|
.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
|
// add a default selector matching the whole line
|
||||||
let key_settings = KeySettings::from(&settings);
|
let key_settings = KeySettings::from(&settings);
|
||||||
settings.selectors.push(
|
settings.selectors.push(
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
// For the full copyright and license information, please view the LICENSE
|
// For the full copyright and license information, please view the LICENSE
|
||||||
// file that was distributed with this source code.
|
// file that was distributed with this source code.
|
||||||
|
|
||||||
// spell-checker:ignore (vars) FiletestOp StrlenOp
|
// spell-checker:ignore (vars) egid euid FiletestOp StrlenOp
|
||||||
|
|
||||||
mod parser;
|
mod parser;
|
||||||
|
|
||||||
|
@ -96,8 +96,10 @@ fn eval(stack: &mut Vec<Symbol>) -> Result<bool, String> {
|
||||||
"-e" => path(&f, PathCondition::Exists),
|
"-e" => path(&f, PathCondition::Exists),
|
||||||
"-f" => path(&f, PathCondition::Regular),
|
"-f" => path(&f, PathCondition::Regular),
|
||||||
"-g" => path(&f, PathCondition::GroupIdFlag),
|
"-g" => path(&f, PathCondition::GroupIdFlag),
|
||||||
|
"-G" => path(&f, PathCondition::GroupOwns),
|
||||||
"-h" => path(&f, PathCondition::SymLink),
|
"-h" => path(&f, PathCondition::SymLink),
|
||||||
"-L" => path(&f, PathCondition::SymLink),
|
"-L" => path(&f, PathCondition::SymLink),
|
||||||
|
"-O" => path(&f, PathCondition::UserOwns),
|
||||||
"-p" => path(&f, PathCondition::Fifo),
|
"-p" => path(&f, PathCondition::Fifo),
|
||||||
"-r" => path(&f, PathCondition::Readable),
|
"-r" => path(&f, PathCondition::Readable),
|
||||||
"-S" => path(&f, PathCondition::Socket),
|
"-S" => path(&f, PathCondition::Socket),
|
||||||
|
@ -166,7 +168,9 @@ enum PathCondition {
|
||||||
Exists,
|
Exists,
|
||||||
Regular,
|
Regular,
|
||||||
GroupIdFlag,
|
GroupIdFlag,
|
||||||
|
GroupOwns,
|
||||||
SymLink,
|
SymLink,
|
||||||
|
UserOwns,
|
||||||
Fifo,
|
Fifo,
|
||||||
Readable,
|
Readable,
|
||||||
Socket,
|
Socket,
|
||||||
|
@ -190,18 +194,28 @@ fn path(path: &OsStr, condition: PathCondition) -> bool {
|
||||||
Execute = 0o1,
|
Execute = 0o1,
|
||||||
}
|
}
|
||||||
|
|
||||||
let perm = |metadata: Metadata, p: Permission| {
|
let geteuid = || {
|
||||||
#[cfg(not(target_os = "redox"))]
|
#[cfg(not(target_os = "redox"))]
|
||||||
let (uid, gid) = unsafe { (libc::getuid(), libc::getgid()) };
|
let euid = unsafe { libc::geteuid() };
|
||||||
#[cfg(target_os = "redox")]
|
#[cfg(target_os = "redox")]
|
||||||
let (uid, gid) = (
|
let euid = syscall::geteuid().unwrap() as u32;
|
||||||
syscall::getuid().unwrap() as u32,
|
|
||||||
syscall::getgid().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
|
metadata.mode() & ((p as u32) << 6) != 0
|
||||||
} else if gid == metadata.gid() {
|
} else if getegid() == metadata.gid() {
|
||||||
metadata.mode() & ((p as u32) << 3) != 0
|
metadata.mode() & ((p as u32) << 3) != 0
|
||||||
} else {
|
} else {
|
||||||
metadata.mode() & (p as u32) != 0
|
metadata.mode() & (p as u32) != 0
|
||||||
|
@ -230,7 +244,9 @@ fn path(path: &OsStr, condition: PathCondition) -> bool {
|
||||||
PathCondition::Exists => true,
|
PathCondition::Exists => true,
|
||||||
PathCondition::Regular => file_type.is_file(),
|
PathCondition::Regular => file_type.is_file(),
|
||||||
PathCondition::GroupIdFlag => metadata.mode() & S_ISGID != 0,
|
PathCondition::GroupIdFlag => metadata.mode() & S_ISGID != 0,
|
||||||
|
PathCondition::GroupOwns => metadata.gid() == getegid(),
|
||||||
PathCondition::SymLink => metadata.file_type().is_symlink(),
|
PathCondition::SymLink => metadata.file_type().is_symlink(),
|
||||||
|
PathCondition::UserOwns => metadata.uid() == geteuid(),
|
||||||
PathCondition::Fifo => file_type.is_fifo(),
|
PathCondition::Fifo => file_type.is_fifo(),
|
||||||
PathCondition::Readable => perm(metadata, Permission::Read),
|
PathCondition::Readable => perm(metadata, Permission::Read),
|
||||||
PathCondition::Socket => file_type.is_socket(),
|
PathCondition::Socket => file_type.is_socket(),
|
||||||
|
@ -257,7 +273,9 @@ fn path(path: &OsStr, condition: PathCondition) -> bool {
|
||||||
PathCondition::Exists => true,
|
PathCondition::Exists => true,
|
||||||
PathCondition::Regular => stat.is_file(),
|
PathCondition::Regular => stat.is_file(),
|
||||||
PathCondition::GroupIdFlag => false,
|
PathCondition::GroupIdFlag => false,
|
||||||
|
PathCondition::GroupOwns => unimplemented!(),
|
||||||
PathCondition::SymLink => false,
|
PathCondition::SymLink => false,
|
||||||
|
PathCondition::UserOwns => unimplemented!(),
|
||||||
PathCondition::Fifo => false,
|
PathCondition::Fifo => false,
|
||||||
PathCondition::Readable => false, // TODO
|
PathCondition::Readable => false, // TODO
|
||||||
PathCondition::Socket => false,
|
PathCondition::Socket => false,
|
||||||
|
|
|
@ -17,6 +17,7 @@ path = "src/timeout.rs"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
clap = "2.33"
|
clap = "2.33"
|
||||||
libc = "0.2.42"
|
libc = "0.2.42"
|
||||||
|
nix = "0.20.0"
|
||||||
uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["process", "signals"] }
|
uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["process", "signals"] }
|
||||||
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
|
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
// * For the full copyright and license information, please view the LICENSE
|
// * For the full copyright and license information, please view the LICENSE
|
||||||
// * file that was distributed with this source code.
|
// * file that was distributed with this source code.
|
||||||
|
|
||||||
// spell-checker:ignore (ToDO) tstr sigstr cmdname setpgid
|
// spell-checker:ignore (ToDO) tstr sigstr cmdname setpgid sigchld
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate uucore;
|
extern crate uucore;
|
||||||
|
@ -17,13 +17,13 @@ use std::io::ErrorKind;
|
||||||
use std::process::{Command, Stdio};
|
use std::process::{Command, Stdio};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use uucore::process::ChildExt;
|
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;
|
use uucore::InvalidEncodingHandling;
|
||||||
|
|
||||||
static ABOUT: &str = "Start COMMAND, and kill it if still running after DURATION.";
|
static ABOUT: &str = "Start COMMAND, and kill it if still running after DURATION.";
|
||||||
|
|
||||||
fn get_usage() -> String {
|
fn get_usage() -> String {
|
||||||
format!("{0} [OPTION]... [FILE]...", executable!())
|
format!("{0} [OPTION] DURATION COMMAND...", executable!())
|
||||||
}
|
}
|
||||||
|
|
||||||
const ERR_EXIT_STATUS: i32 = 125;
|
const ERR_EXIT_STATUS: i32 = 125;
|
||||||
|
@ -33,22 +33,22 @@ pub mod options {
|
||||||
pub static KILL_AFTER: &str = "kill-after";
|
pub static KILL_AFTER: &str = "kill-after";
|
||||||
pub static SIGNAL: &str = "signal";
|
pub static SIGNAL: &str = "signal";
|
||||||
pub static PRESERVE_STATUS: &str = "preserve-status";
|
pub static PRESERVE_STATUS: &str = "preserve-status";
|
||||||
|
pub static VERBOSE: &str = "verbose";
|
||||||
|
|
||||||
// Positional args.
|
// Positional args.
|
||||||
pub static DURATION: &str = "duration";
|
pub static DURATION: &str = "duration";
|
||||||
pub static COMMAND: &str = "command";
|
pub static COMMAND: &str = "command";
|
||||||
pub static ARGS: &str = "args";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Config {
|
struct Config {
|
||||||
foreground: bool,
|
foreground: bool,
|
||||||
kill_after: Duration,
|
kill_after: Option<Duration>,
|
||||||
signal: usize,
|
signal: usize,
|
||||||
duration: Duration,
|
duration: Duration,
|
||||||
preserve_status: bool,
|
preserve_status: bool,
|
||||||
|
verbose: bool,
|
||||||
|
|
||||||
command: String,
|
command: Vec<String>,
|
||||||
command_args: Vec<String>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Config {
|
impl Config {
|
||||||
|
@ -66,23 +66,22 @@ impl Config {
|
||||||
_ => uucore::signals::signal_by_name_or_value("TERM").unwrap(),
|
_ => uucore::signals::signal_by_name_or_value("TERM").unwrap(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let kill_after: Duration = match options.value_of(options::KILL_AFTER) {
|
let kill_after = options
|
||||||
Some(time) => uucore::parse_time::from_str(time).unwrap(),
|
.value_of(options::KILL_AFTER)
|
||||||
None => Duration::new(0, 0),
|
.map(|time| uucore::parse_time::from_str(time).unwrap());
|
||||||
};
|
|
||||||
|
|
||||||
let duration: Duration =
|
let duration: Duration =
|
||||||
uucore::parse_time::from_str(options.value_of(options::DURATION).unwrap()).unwrap();
|
uucore::parse_time::from_str(options.value_of(options::DURATION).unwrap()).unwrap();
|
||||||
|
|
||||||
let preserve_status: bool = options.is_present(options::PRESERVE_STATUS);
|
let preserve_status: bool = options.is_present(options::PRESERVE_STATUS);
|
||||||
let foreground = options.is_present(options::FOREGROUND);
|
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 = options
|
||||||
|
.values_of(options::COMMAND)
|
||||||
let command_args: Vec<String> = match options.values_of(options::ARGS) {
|
.unwrap()
|
||||||
Some(values) => values.map(|x| x.to_owned()).collect(),
|
.map(String::from)
|
||||||
None => vec![],
|
.collect::<Vec<_>>();
|
||||||
};
|
|
||||||
|
|
||||||
Config {
|
Config {
|
||||||
foreground,
|
foreground,
|
||||||
|
@ -91,7 +90,7 @@ impl Config {
|
||||||
duration,
|
duration,
|
||||||
preserve_status,
|
preserve_status,
|
||||||
command,
|
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")
|
.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)
|
.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(
|
||||||
Arg::with_name(options::DURATION)
|
Arg::with_name(options::DURATION)
|
||||||
.index(1)
|
.index(1)
|
||||||
|
@ -137,9 +142,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
||||||
Arg::with_name(options::COMMAND)
|
Arg::with_name(options::COMMAND)
|
||||||
.index(2)
|
.index(2)
|
||||||
.required(true)
|
.required(true)
|
||||||
)
|
.multiple(true)
|
||||||
.arg(
|
|
||||||
Arg::with_name(options::ARGS).multiple(true)
|
|
||||||
)
|
)
|
||||||
.setting(AppSettings::TrailingVarArg);
|
.setting(AppSettings::TrailingVarArg);
|
||||||
|
|
||||||
|
@ -148,31 +151,42 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
||||||
let config = Config::from(matches);
|
let config = Config::from(matches);
|
||||||
timeout(
|
timeout(
|
||||||
&config.command,
|
&config.command,
|
||||||
&config.command_args,
|
|
||||||
config.duration,
|
config.duration,
|
||||||
config.signal,
|
config.signal,
|
||||||
config.kill_after,
|
config.kill_after,
|
||||||
config.foreground,
|
config.foreground,
|
||||||
config.preserve_status,
|
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.
|
/// TODO: Improve exit codes, and make them consistent with the GNU Coreutils exit codes.
|
||||||
|
|
||||||
fn timeout(
|
fn timeout(
|
||||||
cmdname: &str,
|
cmd: &[String],
|
||||||
args: &[String],
|
|
||||||
duration: Duration,
|
duration: Duration,
|
||||||
signal: usize,
|
signal: usize,
|
||||||
kill_after: Duration,
|
kill_after: Option<Duration>,
|
||||||
foreground: bool,
|
foreground: bool,
|
||||||
preserve_status: bool,
|
preserve_status: bool,
|
||||||
|
verbose: bool,
|
||||||
) -> i32 {
|
) -> i32 {
|
||||||
if !foreground {
|
if !foreground {
|
||||||
unsafe { libc::setpgid(0, 0) };
|
unsafe { libc::setpgid(0, 0) };
|
||||||
}
|
}
|
||||||
let mut process = match Command::new(cmdname)
|
let mut process = match Command::new(&cmd[0])
|
||||||
.args(args)
|
.args(&cmd[1..])
|
||||||
.stdin(Stdio::inherit())
|
.stdin(Stdio::inherit())
|
||||||
.stdout(Stdio::inherit())
|
.stdout(Stdio::inherit())
|
||||||
.stderr(Stdio::inherit())
|
.stderr(Stdio::inherit())
|
||||||
|
@ -190,32 +204,44 @@ fn timeout(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
unblock_sigchld();
|
||||||
match process.wait_or_timeout(duration) {
|
match process.wait_or_timeout(duration) {
|
||||||
Ok(Some(status)) => status.code().unwrap_or_else(|| status.signal().unwrap()),
|
Ok(Some(status)) => status.code().unwrap_or_else(|| status.signal().unwrap()),
|
||||||
Ok(None) => {
|
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));
|
return_if_err!(ERR_EXIT_STATUS, process.send_signal(signal));
|
||||||
match process.wait_or_timeout(kill_after) {
|
if let Some(kill_after) = kill_after {
|
||||||
Ok(Some(status)) => {
|
match process.wait_or_timeout(kill_after) {
|
||||||
if preserve_status {
|
Ok(Some(status)) => {
|
||||||
status.code().unwrap_or_else(|| status.signal().unwrap())
|
if preserve_status {
|
||||||
} else {
|
status.code().unwrap_or_else(|| status.signal().unwrap())
|
||||||
124
|
} else {
|
||||||
|
124
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
Ok(None) => {
|
||||||
Ok(None) => {
|
if verbose {
|
||||||
if kill_after == Duration::new(0, 0) {
|
show_error!("sending signal KILL to command '{}'", cmd[0]);
|
||||||
// XXX: this may not be right
|
}
|
||||||
return 124;
|
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(_) => 124,
|
||||||
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,
|
} else {
|
||||||
|
124
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
|
|
|
@ -166,7 +166,15 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Err(e) = File::create(path) {
|
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;
|
error_code = 1;
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
|
@ -47,6 +47,9 @@ use std::io::Result as IOResult;
|
||||||
use std::ptr;
|
use std::ptr;
|
||||||
|
|
||||||
extern "C" {
|
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(
|
fn getgrouplist(
|
||||||
name: *const c_char,
|
name: *const c_char,
|
||||||
gid: gid_t,
|
gid: gid_t,
|
||||||
|
@ -55,6 +58,13 @@ extern "C" {
|
||||||
) -> c_int;
|
) -> 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>> {
|
pub fn get_groups() -> IOResult<Vec<gid_t>> {
|
||||||
let ngroups = unsafe { getgroups(0, ptr::null_mut()) };
|
let ngroups = unsafe { getgroups(0, ptr::null_mut()) };
|
||||||
if ngroups == -1 {
|
if ngroups == -1 {
|
||||||
|
@ -83,17 +93,17 @@ pub fn get_groups() -> IOResult<Vec<gid_t>> {
|
||||||
/// for `id --groups --real` if `gid` and `egid` are not equal.
|
/// for `id --groups --real` if `gid` and `egid` are not equal.
|
||||||
///
|
///
|
||||||
/// From: https://www.man7.org/linux/man-pages/man3/getgroups.3p.html
|
/// From: https://www.man7.org/linux/man-pages/man3/getgroups.3p.html
|
||||||
/// As implied by the definition of supplementary groups, the
|
/// > As implied by the definition of supplementary groups, the
|
||||||
/// effective group ID may appear in the array returned by
|
/// > effective group ID may appear in the array returned by
|
||||||
/// getgroups() or it may be returned only by getegid(). Duplication
|
/// > getgroups() or it may be returned only by getegid(). Duplication
|
||||||
/// may exist, but the application needs to call getegid() to be sure
|
/// > may exist, but the application needs to call getegid() to be sure
|
||||||
/// of getting all of the information. Various implementation
|
/// > of getting all of the information. Various implementation
|
||||||
/// variations and administrative sequences cause the set of groups
|
/// > variations and administrative sequences cause the set of groups
|
||||||
/// appearing in the result of getgroups() to vary in order and as to
|
/// > 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
|
/// > whether the effective group ID is included, even when the set of
|
||||||
/// groups is the same (in the mathematical sense of ``set''). (The
|
/// > groups is the same (in the mathematical sense of ``set''). (The
|
||||||
/// history of a process and its parents could affect the details of
|
/// > history of a process and its parents could affect the details of
|
||||||
/// the result.)
|
/// > the result.)
|
||||||
#[cfg(all(unix, feature = "process"))]
|
#[cfg(all(unix, feature = "process"))]
|
||||||
pub fn get_groups_gnu(arg_id: Option<u32>) -> IOResult<Vec<gid_t>> {
|
pub fn get_groups_gnu(arg_id: Option<u32>) -> IOResult<Vec<gid_t>> {
|
||||||
let groups = get_groups()?;
|
let groups = get_groups()?;
|
||||||
|
@ -187,20 +197,35 @@ impl Passwd {
|
||||||
/// This is a wrapper function for `libc::getgrouplist`.
|
/// This is a wrapper function for `libc::getgrouplist`.
|
||||||
///
|
///
|
||||||
/// From: https://man7.org/linux/man-pages/man3/getgrouplist.3.html
|
/// From: https://man7.org/linux/man-pages/man3/getgrouplist.3.html
|
||||||
/// If the user is a member of more than *ngroups groups, then
|
/// > If the number of groups of which user is a member is less than or
|
||||||
/// getgrouplist() returns -1. In this case, the value returned in
|
/// > equal to *ngroups, then the value *ngroups is returned.
|
||||||
/// *ngroups can be used to resize the buffer passed to a further
|
/// > If the user is a member of more than *ngroups groups, then
|
||||||
/// call getgrouplist().
|
/// > 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> {
|
pub fn belongs_to(&self) -> Vec<gid_t> {
|
||||||
let mut ngroups: c_int = 8;
|
let mut ngroups: c_int = 8;
|
||||||
|
let mut ngroups_old: c_int;
|
||||||
let mut groups = Vec::with_capacity(ngroups as usize);
|
let mut groups = Vec::with_capacity(ngroups as usize);
|
||||||
let gid = self.inner.pw_gid;
|
let gid = self.inner.pw_gid;
|
||||||
let name = self.inner.pw_name;
|
let name = self.inner.pw_name;
|
||||||
unsafe {
|
loop {
|
||||||
if getgrouplist(name, gid, groups.as_mut_ptr(), &mut ngroups) == -1 {
|
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);
|
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.set_len(ngroups as usize);
|
||||||
}
|
}
|
||||||
groups.truncate(ngroups as usize);
|
groups.truncate(ngroups as usize);
|
||||||
|
|
|
@ -17,18 +17,22 @@ use std::process::ExitStatus as StdExitStatus;
|
||||||
use std::thread;
|
use std::thread;
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
|
/// `geteuid()` returns the effective user ID of the calling process.
|
||||||
pub fn geteuid() -> uid_t {
|
pub fn geteuid() -> uid_t {
|
||||||
unsafe { libc::geteuid() }
|
unsafe { libc::geteuid() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// `getegid()` returns the effective group ID of the calling process.
|
||||||
pub fn getegid() -> gid_t {
|
pub fn getegid() -> gid_t {
|
||||||
unsafe { libc::getegid() }
|
unsafe { libc::getegid() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// `getgid()` returns the real group ID of the calling process.
|
||||||
pub fn getgid() -> gid_t {
|
pub fn getgid() -> gid_t {
|
||||||
unsafe { libc::getgid() }
|
unsafe { libc::getgid() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// `getuid()` returns the real user ID of the calling process.
|
||||||
pub fn getuid() -> uid_t {
|
pub fn getuid() -> uid_t {
|
||||||
unsafe { libc::getuid() }
|
unsafe { libc::getuid() }
|
||||||
}
|
}
|
||||||
|
@ -93,6 +97,7 @@ pub trait ChildExt {
|
||||||
fn send_signal(&mut self, signal: usize) -> io::Result<()>;
|
fn send_signal(&mut self, signal: usize) -> io::Result<()>;
|
||||||
|
|
||||||
/// Wait for a process to finish or return after the specified duration.
|
/// 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>>;
|
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>> {
|
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
|
// .try_wait() doesn't drop stdin, so we do it manually
|
||||||
drop(self.stdin.take());
|
drop(self.stdin.take());
|
||||||
|
|
||||||
|
|
|
@ -10,11 +10,6 @@
|
||||||
|
|
||||||
pub static DEFAULT_SIGNAL: usize = 15;
|
pub static DEFAULT_SIGNAL: usize = 15;
|
||||||
|
|
||||||
pub struct Signal<'a> {
|
|
||||||
pub name: &'a str,
|
|
||||||
pub value: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
||||||
Linux Programmer's Manual
|
Linux Programmer's Manual
|
||||||
|
@ -29,131 +24,10 @@ Linux Programmer's Manual
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
pub static ALL_SIGNALS: [Signal<'static>; 31] = [
|
pub static ALL_SIGNALS: [&str; 32] = [
|
||||||
Signal {
|
"EXIT", "HUP", "INT", "QUIT", "ILL", "TRAP", "ABRT", "BUS", "FPE", "KILL", "USR1", "SEGV",
|
||||||
name: "HUP",
|
"USR2", "PIPE", "ALRM", "TERM", "STKFLT", "CHLD", "CONT", "STOP", "TSTP", "TTIN", "TTOU",
|
||||||
value: 1,
|
"URG", "XCPU", "XFSZ", "VTALRM", "PROF", "WINCH", "POLL", "PWR", "SYS",
|
||||||
},
|
|
||||||
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,
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -198,131 +72,10 @@ No Name Default Action Description
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#[cfg(any(target_vendor = "apple", target_os = "freebsd"))]
|
#[cfg(any(target_vendor = "apple", target_os = "freebsd"))]
|
||||||
pub static ALL_SIGNALS: [Signal<'static>; 31] = [
|
pub static ALL_SIGNALS: [&str; 32] = [
|
||||||
Signal {
|
"EXIT", "HUP", "INT", "QUIT", "ILL", "TRAP", "ABRT", "EMT", "FPE", "KILL", "BUS", "SEGV",
|
||||||
name: "HUP",
|
"SYS", "PIPE", "ALRM", "TERM", "URG", "STOP", "TSTP", "CONT", "CHLD", "TTIN", "TTOU", "IO",
|
||||||
value: 1,
|
"XCPU", "XFSZ", "VTALRM", "PROF", "WINCH", "INFO", "USR1", "USR2",
|
||||||
},
|
|
||||||
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 fn signal_by_name_or_value(signal_name_or_value: &str) -> Option<usize> {
|
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");
|
let signal_name = signal_name_or_value.trim_start_matches("SIG");
|
||||||
|
|
||||||
ALL_SIGNALS
|
ALL_SIGNALS.iter().position(|&s| s == signal_name)
|
||||||
.iter()
|
|
||||||
.find(|s| s.name == signal_name)
|
|
||||||
.map(|s| s.value)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
pub fn is_signal(num: usize) -> bool {
|
pub fn is_signal(num: usize) -> bool {
|
||||||
// Named signals start at 1
|
num < ALL_SIGNALS.len()
|
||||||
num <= ALL_SIGNALS.len()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
pub fn signal_name_by_value(signal_value: usize) -> Option<&'static str> {
|
||||||
fn signals_all_contiguous() {
|
ALL_SIGNALS.get(signal_value).copied()
|
||||||
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));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn signal_by_value() {
|
fn signal_by_value() {
|
||||||
assert_eq!(signal_by_name_or_value("0"), Some(0));
|
assert_eq!(signal_by_name_or_value("0"), Some(0));
|
||||||
for signal in &ALL_SIGNALS {
|
for (value, _signal) in ALL_SIGNALS.iter().enumerate() {
|
||||||
assert_eq!(
|
assert_eq!(signal_by_name_or_value(&value.to_string()), Some(value));
|
||||||
signal_by_name_or_value(&signal.value.to_string()),
|
|
||||||
Some(signal.value)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn signal_by_short_name() {
|
fn signal_by_short_name() {
|
||||||
for signal in &ALL_SIGNALS {
|
for (value, signal) in ALL_SIGNALS.iter().enumerate() {
|
||||||
assert_eq!(signal_by_name_or_value(signal.name), Some(signal.value));
|
assert_eq!(signal_by_name_or_value(signal), Some(value));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn signal_by_long_name() {
|
fn signal_by_long_name() {
|
||||||
for signal in &ALL_SIGNALS {
|
for (value, signal) in ALL_SIGNALS.iter().enumerate() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
signal_by_name_or_value(&format!("SIG{}", signal.name)),
|
signal_by_name_or_value(&format!("SIG{}", signal)),
|
||||||
Some(signal.value)
|
Some(value)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn name() {
|
||||||
|
for (value, signal) in ALL_SIGNALS.iter().enumerate() {
|
||||||
|
assert_eq!(signal_name_by_value(value), Some(*signal));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -80,19 +80,22 @@ fn _du_basics_subdir(s: &str) {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_du_invalid_size() {
|
fn test_du_invalid_size() {
|
||||||
new_ucmd!()
|
let args = &["block-size", "threshold"];
|
||||||
.arg("--block-size=1fb4t")
|
for s in args {
|
||||||
.arg("/tmp")
|
new_ucmd!()
|
||||||
.fails()
|
.arg(format!("--{}=1fb4t", s))
|
||||||
.code_is(1)
|
.arg("/tmp")
|
||||||
.stderr_only("du: invalid --block-size argument '1fb4t'");
|
.fails()
|
||||||
#[cfg(not(target_pointer_width = "128"))]
|
.code_is(1)
|
||||||
new_ucmd!()
|
.stderr_only(format!("du: invalid --{} argument '1fb4t'", s));
|
||||||
.arg("--block-size=1Y")
|
#[cfg(not(target_pointer_width = "128"))]
|
||||||
.arg("/tmp")
|
new_ucmd!()
|
||||||
.fails()
|
.arg(format!("--{}=1Y", s))
|
||||||
.code_is(1)
|
.arg("/tmp")
|
||||||
.stderr_only("du: --block-size argument '1Y' too large");
|
.fails()
|
||||||
|
.code_is(1)
|
||||||
|
.stderr_only(format!("du: --{} argument '1Y' too large", s));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -351,3 +354,24 @@ fn test_du_one_file_system() {
|
||||||
}
|
}
|
||||||
_du_basics_subdir(result.stdout_str());
|
_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");
|
||||||
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
use crate::common::util::*;
|
use crate::common::util::*;
|
||||||
use chrono::offset::Local;
|
use chrono::offset::Local;
|
||||||
use chrono::DateTime;
|
use chrono::DateTime;
|
||||||
|
use chrono::Duration;
|
||||||
use std::fs::metadata;
|
use std::fs::metadata;
|
||||||
|
|
||||||
fn file_last_modified_time(ucmd: &UCommand, path: &str) -> String {
|
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()
|
.unwrap_or_default()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn now_time() -> String {
|
fn all_minutes(from: DateTime<Local>, to: DateTime<Local>) -> Vec<String> {
|
||||||
Local::now().format("%b %d %H:%M %Y").to_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]
|
#[test]
|
||||||
|
@ -33,10 +48,7 @@ fn test_without_any_options() {
|
||||||
scenario
|
scenario
|
||||||
.args(&[test_file_path])
|
.args(&[test_file_path])
|
||||||
.succeeds()
|
.succeeds()
|
||||||
.stdout_is_templated_fixture(
|
.stdout_is_templated_fixture(expected_test_file_path, &[("{last_modified_time}", &value)]);
|
||||||
expected_test_file_path,
|
|
||||||
vec![(&"{last_modified_time}".to_string(), &value)],
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -48,10 +60,7 @@ fn test_with_numbering_option_with_number_width() {
|
||||||
scenario
|
scenario
|
||||||
.args(&["-n", "2", test_file_path])
|
.args(&["-n", "2", test_file_path])
|
||||||
.succeeds()
|
.succeeds()
|
||||||
.stdout_is_templated_fixture(
|
.stdout_is_templated_fixture(expected_test_file_path, &[("{last_modified_time}", &value)]);
|
||||||
expected_test_file_path,
|
|
||||||
vec![(&"{last_modified_time}".to_string(), &value)],
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -66,10 +75,7 @@ fn test_with_long_header_option() {
|
||||||
.succeeds()
|
.succeeds()
|
||||||
.stdout_is_templated_fixture(
|
.stdout_is_templated_fixture(
|
||||||
expected_test_file_path,
|
expected_test_file_path,
|
||||||
vec![
|
&[("{last_modified_time}", &value), ("{header}", header)],
|
||||||
(&"{last_modified_time}".to_string(), &value),
|
|
||||||
(&"{header}".to_string(), &header.to_string()),
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
|
|
||||||
new_ucmd!()
|
new_ucmd!()
|
||||||
|
@ -77,10 +83,7 @@ fn test_with_long_header_option() {
|
||||||
.succeeds()
|
.succeeds()
|
||||||
.stdout_is_templated_fixture(
|
.stdout_is_templated_fixture(
|
||||||
expected_test_file_path,
|
expected_test_file_path,
|
||||||
vec![
|
&[("{last_modified_time}", &value), ("{header}", header)],
|
||||||
(&"{last_modified_time}".to_string(), &value),
|
|
||||||
(&"{header}".to_string(), &header.to_string()),
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -93,18 +96,12 @@ fn test_with_double_space_option() {
|
||||||
scenario
|
scenario
|
||||||
.args(&["-d", test_file_path])
|
.args(&["-d", test_file_path])
|
||||||
.succeeds()
|
.succeeds()
|
||||||
.stdout_is_templated_fixture(
|
.stdout_is_templated_fixture(expected_test_file_path, &[("{last_modified_time}", &value)]);
|
||||||
expected_test_file_path,
|
|
||||||
vec![(&"{last_modified_time}".to_string(), &value)],
|
|
||||||
);
|
|
||||||
|
|
||||||
new_ucmd!()
|
new_ucmd!()
|
||||||
.args(&["--double-space", test_file_path])
|
.args(&["--double-space", test_file_path])
|
||||||
.succeeds()
|
.succeeds()
|
||||||
.stdout_is_templated_fixture(
|
.stdout_is_templated_fixture(expected_test_file_path, &[("{last_modified_time}", &value)]);
|
||||||
expected_test_file_path,
|
|
||||||
vec![(&"{last_modified_time}".to_string(), &value)],
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -116,10 +113,7 @@ fn test_with_first_line_number_option() {
|
||||||
scenario
|
scenario
|
||||||
.args(&["-N", "5", "-n", test_file_path])
|
.args(&["-N", "5", "-n", test_file_path])
|
||||||
.succeeds()
|
.succeeds()
|
||||||
.stdout_is_templated_fixture(
|
.stdout_is_templated_fixture(expected_test_file_path, &[("{last_modified_time}", &value)]);
|
||||||
expected_test_file_path,
|
|
||||||
vec![(&"{last_modified_time}".to_string(), &value)],
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -131,10 +125,7 @@ fn test_with_first_line_number_long_option() {
|
||||||
scenario
|
scenario
|
||||||
.args(&["--first-line-number=5", "-n", test_file_path])
|
.args(&["--first-line-number=5", "-n", test_file_path])
|
||||||
.succeeds()
|
.succeeds()
|
||||||
.stdout_is_templated_fixture(
|
.stdout_is_templated_fixture(expected_test_file_path, &[("{last_modified_time}", &value)]);
|
||||||
expected_test_file_path,
|
|
||||||
vec![(&"{last_modified_time}".to_string(), &value)],
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -146,10 +137,7 @@ fn test_with_number_option_with_custom_separator_char() {
|
||||||
scenario
|
scenario
|
||||||
.args(&["-nc", test_file_path])
|
.args(&["-nc", test_file_path])
|
||||||
.succeeds()
|
.succeeds()
|
||||||
.stdout_is_templated_fixture(
|
.stdout_is_templated_fixture(expected_test_file_path, &[("{last_modified_time}", &value)]);
|
||||||
expected_test_file_path,
|
|
||||||
vec![(&"{last_modified_time}".to_string(), &value)],
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -161,10 +149,7 @@ fn test_with_number_option_with_custom_separator_char_and_width() {
|
||||||
scenario
|
scenario
|
||||||
.args(&["-nc1", test_file_path])
|
.args(&["-nc1", test_file_path])
|
||||||
.succeeds()
|
.succeeds()
|
||||||
.stdout_is_templated_fixture(
|
.stdout_is_templated_fixture(expected_test_file_path, &[("{last_modified_time}", &value)]);
|
||||||
expected_test_file_path,
|
|
||||||
vec![(&"{last_modified_time}".to_string(), &value)],
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -207,25 +192,19 @@ fn test_with_page_range() {
|
||||||
scenario
|
scenario
|
||||||
.args(&["--pages=15", test_file_path])
|
.args(&["--pages=15", test_file_path])
|
||||||
.succeeds()
|
.succeeds()
|
||||||
.stdout_is_templated_fixture(
|
.stdout_is_templated_fixture(expected_test_file_path, &[("{last_modified_time}", &value)]);
|
||||||
expected_test_file_path,
|
|
||||||
vec![(&"{last_modified_time}".to_string(), &value)],
|
|
||||||
);
|
|
||||||
|
|
||||||
new_ucmd!()
|
new_ucmd!()
|
||||||
.args(&["+15", test_file_path])
|
.args(&["+15", test_file_path])
|
||||||
.succeeds()
|
.succeeds()
|
||||||
.stdout_is_templated_fixture(
|
.stdout_is_templated_fixture(expected_test_file_path, &[("{last_modified_time}", &value)]);
|
||||||
expected_test_file_path,
|
|
||||||
vec![(&"{last_modified_time}".to_string(), &value)],
|
|
||||||
);
|
|
||||||
|
|
||||||
new_ucmd!()
|
new_ucmd!()
|
||||||
.args(&["--pages=15:17", test_file_path])
|
.args(&["--pages=15:17", test_file_path])
|
||||||
.succeeds()
|
.succeeds()
|
||||||
.stdout_is_templated_fixture(
|
.stdout_is_templated_fixture(
|
||||||
expected_test_file_path1,
|
expected_test_file_path1,
|
||||||
vec![(&"{last_modified_time}".to_string(), &value)],
|
&[("{last_modified_time}", &value)],
|
||||||
);
|
);
|
||||||
|
|
||||||
new_ucmd!()
|
new_ucmd!()
|
||||||
|
@ -233,7 +212,7 @@ fn test_with_page_range() {
|
||||||
.succeeds()
|
.succeeds()
|
||||||
.stdout_is_templated_fixture(
|
.stdout_is_templated_fixture(
|
||||||
expected_test_file_path1,
|
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
|
scenario
|
||||||
.args(&["-t", test_file_path])
|
.args(&["-t", test_file_path])
|
||||||
.succeeds()
|
.succeeds()
|
||||||
.stdout_is_templated_fixture(
|
.stdout_is_templated_fixture(expected_test_file_path, &[("{last_modified_time}", &value)]);
|
||||||
expected_test_file_path,
|
|
||||||
vec![(&"{last_modified_time}".to_string(), &value)],
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -262,10 +238,7 @@ fn test_with_page_length_option() {
|
||||||
scenario
|
scenario
|
||||||
.args(&["--pages=2:3", "-l", "100", "-n", test_file_path])
|
.args(&["--pages=2:3", "-l", "100", "-n", test_file_path])
|
||||||
.succeeds()
|
.succeeds()
|
||||||
.stdout_is_templated_fixture(
|
.stdout_is_templated_fixture(expected_test_file_path, &[("{last_modified_time}", &value)]);
|
||||||
expected_test_file_path,
|
|
||||||
vec![(&"{last_modified_time}".to_string(), &value)],
|
|
||||||
);
|
|
||||||
|
|
||||||
new_ucmd!()
|
new_ucmd!()
|
||||||
.args(&["--pages=2:3", "-l", "5", "-n", test_file_path])
|
.args(&["--pages=2:3", "-l", "5", "-n", test_file_path])
|
||||||
|
@ -288,14 +261,14 @@ fn test_with_suppress_error_option() {
|
||||||
fn test_with_stdin() {
|
fn test_with_stdin() {
|
||||||
let expected_file_path = "stdin.log.expected";
|
let expected_file_path = "stdin.log.expected";
|
||||||
let mut scenario = new_ucmd!();
|
let mut scenario = new_ucmd!();
|
||||||
let now = now_time();
|
let start = Local::now();
|
||||||
scenario
|
scenario
|
||||||
.pipe_in_fixture("stdin.log")
|
.pipe_in_fixture("stdin.log")
|
||||||
.args(&["--pages=1:2", "-n", "-"])
|
.args(&["--pages=1:2", "-n", "-"])
|
||||||
.run()
|
.run()
|
||||||
.stdout_is_templated_fixture(
|
.stdout_is_templated_fixture_any(
|
||||||
expected_file_path,
|
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
|
scenario
|
||||||
.args(&["--pages=3:5", "--column=3", "-n", test_file_path])
|
.args(&["--pages=3:5", "--column=3", "-n", test_file_path])
|
||||||
.succeeds()
|
.succeeds()
|
||||||
.stdout_is_templated_fixture(
|
.stdout_is_templated_fixture(expected_test_file_path, &[("{last_modified_time}", &value)]);
|
||||||
expected_test_file_path,
|
|
||||||
vec![(&"{last_modified_time}".to_string(), &value)],
|
|
||||||
);
|
|
||||||
|
|
||||||
new_ucmd!()
|
new_ucmd!()
|
||||||
.args(&["--pages=3:5", "-3", "-n", test_file_path])
|
.args(&["--pages=3:5", "-3", "-n", test_file_path])
|
||||||
.succeeds()
|
.succeeds()
|
||||||
.stdout_is_templated_fixture(
|
.stdout_is_templated_fixture(expected_test_file_path, &[("{last_modified_time}", &value)]);
|
||||||
expected_test_file_path,
|
|
||||||
vec![(&"{last_modified_time}".to_string(), &value)],
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -331,10 +298,7 @@ fn test_with_column_across_option() {
|
||||||
scenario
|
scenario
|
||||||
.args(&["--pages=3:5", "--column=3", "-a", "-n", test_file_path])
|
.args(&["--pages=3:5", "--column=3", "-a", "-n", test_file_path])
|
||||||
.succeeds()
|
.succeeds()
|
||||||
.stdout_is_templated_fixture(
|
.stdout_is_templated_fixture(expected_test_file_path, &[("{last_modified_time}", &value)]);
|
||||||
expected_test_file_path,
|
|
||||||
vec![(&"{last_modified_time}".to_string(), &value)],
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -354,10 +318,7 @@ fn test_with_column_across_option_and_column_separator() {
|
||||||
test_file_path,
|
test_file_path,
|
||||||
])
|
])
|
||||||
.succeeds()
|
.succeeds()
|
||||||
.stdout_is_templated_fixture(
|
.stdout_is_templated_fixture(expected_test_file_path, &[("{last_modified_time}", &value)]);
|
||||||
expected_test_file_path,
|
|
||||||
vec![(&"{last_modified_time}".to_string(), &value)],
|
|
||||||
);
|
|
||||||
|
|
||||||
new_ucmd!()
|
new_ucmd!()
|
||||||
.args(&[
|
.args(&[
|
||||||
|
@ -371,7 +332,7 @@ fn test_with_column_across_option_and_column_separator() {
|
||||||
.succeeds()
|
.succeeds()
|
||||||
.stdout_is_templated_fixture(
|
.stdout_is_templated_fixture(
|
||||||
expected_test_file_path1,
|
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_path = "mpr.log.expected";
|
||||||
let expected_test_file_path1 = "mpr1.log.expected";
|
let expected_test_file_path1 = "mpr1.log.expected";
|
||||||
let expected_test_file_path2 = "mpr2.log.expected";
|
let expected_test_file_path2 = "mpr2.log.expected";
|
||||||
let now = now_time();
|
let start = Local::now();
|
||||||
new_ucmd!()
|
new_ucmd!()
|
||||||
.args(&["--pages=1:2", "-m", "-n", test_file_path, test_file_path1])
|
.args(&["--pages=1:2", "-m", "-n", test_file_path, test_file_path1])
|
||||||
.succeeds()
|
.succeeds()
|
||||||
.stdout_is_templated_fixture(
|
.stdout_is_templated_fixture_any(
|
||||||
expected_test_file_path,
|
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!()
|
new_ucmd!()
|
||||||
.args(&["--pages=2:4", "-m", "-n", test_file_path, test_file_path1])
|
.args(&["--pages=2:4", "-m", "-n", test_file_path, test_file_path1])
|
||||||
.succeeds()
|
.succeeds()
|
||||||
.stdout_is_templated_fixture(
|
.stdout_is_templated_fixture_any(
|
||||||
expected_test_file_path1,
|
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!()
|
new_ucmd!()
|
||||||
.args(&[
|
.args(&[
|
||||||
"--pages=1:2",
|
"--pages=1:2",
|
||||||
|
@ -413,9 +374,9 @@ fn test_with_mpr() {
|
||||||
test_file_path,
|
test_file_path,
|
||||||
])
|
])
|
||||||
.succeeds()
|
.succeeds()
|
||||||
.stdout_is_templated_fixture(
|
.stdout_is_templated_fixture_any(
|
||||||
expected_test_file_path2,
|
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,
|
test_file_path,
|
||||||
])
|
])
|
||||||
.succeeds()
|
.succeeds()
|
||||||
.stdout_is_templated_fixture(
|
.stdout_is_templated_fixture(expected_test_file_path, &[("{last_modified_time}", &value)]);
|
||||||
expected_test_file_path,
|
|
||||||
vec![(&"{last_modified_time}".to_string(), &value)],
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -497,9 +455,9 @@ fn test_with_pr_core_utils_tests() {
|
||||||
|
|
||||||
scenario_with_expected_status.stdout_is_templated_fixture(
|
scenario_with_expected_status.stdout_is_templated_fixture(
|
||||||
test_file_path,
|
test_file_path,
|
||||||
vec![
|
&[
|
||||||
(&"{last_modified_time}".to_string(), &value),
|
("{last_modified_time}", &value),
|
||||||
(&"{file_name}".to_string(), &input_file_path.to_string()),
|
("{file_name}", input_file_path),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -511,12 +469,12 @@ fn test_with_join_lines_option() {
|
||||||
let test_file_2 = "test.log";
|
let test_file_2 = "test.log";
|
||||||
let expected_file_path = "joined.log.expected";
|
let expected_file_path = "joined.log.expected";
|
||||||
let mut scenario = new_ucmd!();
|
let mut scenario = new_ucmd!();
|
||||||
let now = now_time();
|
let start = Local::now();
|
||||||
scenario
|
scenario
|
||||||
.args(&["+1:2", "-J", "-m", test_file_1, test_file_2])
|
.args(&["+1:2", "-J", "-m", test_file_1, test_file_2])
|
||||||
.run()
|
.run()
|
||||||
.stdout_is_templated_fixture(
|
.stdout_is_templated_fixture_any(
|
||||||
expected_file_path,
|
expected_file_path,
|
||||||
vec![(&"{last_modified_time}".to_string(), &now)],
|
&valid_last_modified_template_vars(start),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -762,26 +762,30 @@ fn test_pipe() {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_check() {
|
fn test_check() {
|
||||||
new_ucmd!()
|
for diagnose_arg in &["-c", "--check", "--check=diagnose-first"] {
|
||||||
.arg("-c")
|
new_ucmd!()
|
||||||
.arg("check_fail.txt")
|
.arg(diagnose_arg)
|
||||||
.fails()
|
.arg("check_fail.txt")
|
||||||
.stdout_is("sort: check_fail.txt:6: disorder: 5\n");
|
.fails()
|
||||||
|
.stdout_is("sort: check_fail.txt:6: disorder: 5\n");
|
||||||
|
|
||||||
new_ucmd!()
|
new_ucmd!()
|
||||||
.arg("-c")
|
.arg(diagnose_arg)
|
||||||
.arg("multiple_files.expected")
|
.arg("multiple_files.expected")
|
||||||
.succeeds()
|
.succeeds()
|
||||||
.stdout_is("");
|
.stdout_is("");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_check_silent() {
|
fn test_check_silent() {
|
||||||
new_ucmd!()
|
for silent_arg in &["-C", "--check=silent", "--check=quiet"] {
|
||||||
.arg("-C")
|
new_ucmd!()
|
||||||
.arg("check_fail.txt")
|
.arg(silent_arg)
|
||||||
.fails()
|
.arg("check_fail.txt")
|
||||||
.stdout_is("");
|
.fails()
|
||||||
|
.stdout_is("");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -835,7 +839,7 @@ fn test_nonexistent_file() {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_blanks() {
|
fn test_blanks() {
|
||||||
test_helper("blanks", &["-b", "--ignore-blanks"]);
|
test_helper("blanks", &["-b", "--ignore-leading-blanks"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
// file that was distributed with this source code.
|
// file that was distributed with this source code.
|
||||||
//
|
//
|
||||||
|
|
||||||
// spell-checker:ignore (words) pseudofloat
|
// spell-checker:ignore (words) egid euid pseudofloat
|
||||||
|
|
||||||
use crate::common::util::*;
|
use crate::common::util::*;
|
||||||
|
|
||||||
|
@ -476,6 +476,52 @@ fn test_nonexistent_file_is_not_symlink() {
|
||||||
.succeeds();
|
.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]
|
#[test]
|
||||||
fn test_op_precedence_and_or_1() {
|
fn test_op_precedence_and_or_1() {
|
||||||
new_ucmd!().args(&[" ", "-o", "", "-a", ""]).succeeds();
|
new_ucmd!().args(&[" ", "-o", "", "-a", ""]).succeeds();
|
||||||
|
|
|
@ -9,3 +9,39 @@ fn test_subcommand_return_code() {
|
||||||
|
|
||||||
new_ucmd!().arg("1").arg("false").run().status_code(1);
|
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();
|
||||||
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ use self::touch::filetime::{self, FileTime};
|
||||||
extern crate time;
|
extern crate time;
|
||||||
|
|
||||||
use crate::common::util::*;
|
use crate::common::util::*;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
fn get_file_times(at: &AtPath, path: &str) -> (FileTime, FileTime) {
|
fn get_file_times(at: &AtPath, path: &str) -> (FileTime, FileTime) {
|
||||||
let m = at.metadata(path);
|
let m = at.metadata(path);
|
||||||
|
@ -466,3 +467,37 @@ fn test_touch_trailing_slash() {
|
||||||
let file = "no-file/";
|
let file = "no-file/";
|
||||||
ucmd.args(&[file]).fails();
|
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
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
|
@ -223,6 +223,18 @@ impl CmdResult {
|
||||||
self
|
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`.
|
/// Like `stdout_is` but newlines are normalized to `\n`.
|
||||||
pub fn normalized_newlines_stdout_is<T: AsRef<str>>(&self, msg: T) -> &CmdResult {
|
pub fn normalized_newlines_stdout_is<T: AsRef<str>>(&self, msg: T) -> &CmdResult {
|
||||||
let msg = msg.as_ref().replace("\r\n", "\n");
|
let msg = msg.as_ref().replace("\r\n", "\n");
|
||||||
|
@ -247,7 +259,7 @@ impl CmdResult {
|
||||||
pub fn stdout_is_templated_fixture<T: AsRef<OsStr>>(
|
pub fn stdout_is_templated_fixture<T: AsRef<OsStr>>(
|
||||||
&self,
|
&self,
|
||||||
file_rel_path: T,
|
file_rel_path: T,
|
||||||
template_vars: Vec<(&String, &String)>,
|
template_vars: &[(&str, &str)],
|
||||||
) -> &CmdResult {
|
) -> &CmdResult {
|
||||||
let mut contents =
|
let mut contents =
|
||||||
String::from_utf8(read_scenario_fixture(&self.tmpd, file_rel_path)).unwrap();
|
String::from_utf8(read_scenario_fixture(&self.tmpd, file_rel_path)).unwrap();
|
||||||
|
@ -257,6 +269,23 @@ impl CmdResult {
|
||||||
self.stdout_is(contents)
|
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
|
/// asserts that the command resulted in stderr stream output that equals the
|
||||||
/// passed in value, when both are trimmed of trailing whitespace
|
/// passed in value, when both are trimmed of trailing whitespace
|
||||||
/// stderr_only is a better choice unless stdout may or will be non-empty
|
/// stderr_only is a better choice unless stdout may or will be non-empty
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue