1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2025-07-29 12:07:46 +00:00

Merge branch 'master' of github.com:uutils/coreutils into hbina-tr-reimplement-expansion

This commit is contained in:
Hanif Ariffin 2021-11-20 17:06:36 +08:00
commit 38659de66d
16 changed files with 764 additions and 194 deletions

View file

@ -615,11 +615,6 @@ jobs:
# staging directory
STAGING='_staging'
outputs STAGING
## # check for CODECOV_TOKEN availability (work-around for inaccessible 'secrets' object for 'if'; see <https://github.community/t5/GitHub-Actions/jobs-lt-job-id-gt-if-does-not-work-with-env-secrets/m-p/38549>)
## # note: CODECOV_TOKEN / HAS_CODECOV_TOKEN is not needed for public repositories when using AppVeyor, Azure Pipelines, CircleCI, GitHub Actions, Travis (see <https://docs.codecov.io/docs/about-the-codecov-bash-uploader#section-upload-token>)
## unset HAS_CODECOV_TOKEN
## if [ -n $CODECOV_TOKEN ]; then HAS_CODECOV_TOKEN='true' ; fi
## outputs HAS_CODECOV_TOKEN
# target-specific options
# * CARGO_FEATURES_OPTION
CARGO_FEATURES_OPTION='--all-features' ; ## default to '--all-features' for code coverage

View file

@ -7,19 +7,17 @@
// spell-checker:ignore (ToDO) Chmoder cmode fmode fperm fref ugoa RFILE RFILE's
#[macro_use]
extern crate uucore;
use clap::{crate_version, App, Arg};
use std::fs;
use std::os::unix::fs::{MetadataExt, PermissionsExt};
use std::path::Path;
use uucore::display::Quotable;
use uucore::error::{ExitCode, UResult, USimpleError, UUsageError};
use uucore::fs::display_permissions_unix;
use uucore::libc::mode_t;
#[cfg(not(windows))]
use uucore::mode;
use uucore::InvalidEncodingHandling;
use uucore::{show_error, InvalidEncodingHandling};
use walkdir::WalkDir;
static ABOUT: &str = "Change the mode of each FILE to MODE.
@ -50,7 +48,8 @@ fn get_long_usage() -> String {
String::from("Each MODE is of the form '[ugoa]*([-+=]([rwxXst]*|[ugo]))+|[-+=]?[0-7]+'.")
}
pub fn uumain(args: impl uucore::Args) -> i32 {
#[uucore_procs::gen_uumain]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let mut args = args
.collect_str(InvalidEncodingHandling::ConvertLossy)
.accept_any();
@ -72,12 +71,18 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
let verbose = matches.is_present(options::VERBOSE);
let preserve_root = matches.is_present(options::PRESERVE_ROOT);
let recursive = matches.is_present(options::RECURSIVE);
let fmode = matches
.value_of(options::REFERENCE)
.and_then(|fref| match fs::metadata(fref) {
let fmode = match matches.value_of(options::REFERENCE) {
Some(fref) => match fs::metadata(fref) {
Ok(meta) => Some(meta.mode()),
Err(err) => crash!(1, "cannot stat attributes of {}: {}", fref.quote(), err),
});
Err(err) => {
return Err(USimpleError::new(
1,
format!("cannot stat attributes of {}: {}", fref.quote(), err),
))
}
},
None => None,
};
let modes = matches.value_of(options::MODE).unwrap(); // should always be Some because required
let cmode = if mode_had_minus_prefix {
// clap parsing is finished, now put prefix back
@ -100,7 +105,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
};
if files.is_empty() {
crash!(1, "missing operand");
return Err(UUsageError::new(1, "missing operand".to_string()));
}
let chmoder = Chmoder {
@ -112,12 +117,8 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
fmode,
cmode,
};
match chmoder.chmod(files) {
Ok(()) => {}
Err(e) => return e,
}
0
chmoder.chmod(files)
}
pub fn uu_app() -> App<'static, 'static> {
@ -191,7 +192,7 @@ struct Chmoder {
}
impl Chmoder {
fn chmod(&self, files: Vec<String>) -> Result<(), i32> {
fn chmod(&self, files: Vec<String>) -> UResult<()> {
let mut r = Ok(());
for filename in &files {
@ -204,22 +205,30 @@ impl Chmoder {
filename.quote()
);
if !self.quiet {
show_error!("cannot operate on dangling symlink {}", filename.quote());
return Err(USimpleError::new(
1,
format!("cannot operate on dangling symlink {}", filename.quote()),
));
}
} else if !self.quiet {
show_error!(
"cannot access {}: No such file or directory",
filename.quote()
);
return Err(USimpleError::new(
1,
format!(
"cannot access {}: No such file or directory",
filename.quote()
),
));
}
return Err(1);
return Err(ExitCode::new(1));
}
if self.recursive && self.preserve_root && filename == "/" {
show_error!(
"it is dangerous to operate recursively on {}\nuse --no-preserve-root to override this failsafe",
filename.quote()
);
return Err(1);
return Err(USimpleError::new(
1,
format!(
"it is dangerous to operate recursively on {}\nuse --no-preserve-root to override this failsafe",
filename.quote()
)
));
}
if !self.recursive {
r = self.chmod_file(file).and(r);
@ -234,14 +243,14 @@ impl Chmoder {
}
#[cfg(windows)]
fn chmod_file(&self, file: &Path) -> Result<(), i32> {
fn chmod_file(&self, file: &Path) -> UResult<()> {
// chmod is useless on Windows
// it doesn't set any permissions at all
// instead it just sets the readonly attribute on the file
Err(0)
Ok(())
}
#[cfg(unix)]
fn chmod_file(&self, file: &Path) -> Result<(), i32> {
fn chmod_file(&self, file: &Path) -> UResult<()> {
use uucore::mode::get_umask;
let fperm = match fs::metadata(file) {
@ -258,11 +267,13 @@ impl Chmoder {
} else if err.kind() == std::io::ErrorKind::PermissionDenied {
// These two filenames would normally be conditionally
// quoted, but GNU's tests expect them to always be quoted
show_error!("{}: Permission denied", file.quote());
return Err(USimpleError::new(
1,
format!("{}: Permission denied", file.quote()),
));
} else {
show_error!("{}: {}", file.quote(), err);
return Err(USimpleError::new(1, format!("{}: {}", file.quote(), err)));
}
return Err(1);
}
};
match self.fmode {
@ -296,22 +307,25 @@ impl Chmoder {
}
Err(f) => {
if !self.quiet {
show_error!("{}", f);
return Err(USimpleError::new(1, f));
} else {
return Err(ExitCode::new(1));
}
return Err(1);
}
}
}
self.change_file(fperm, new_mode, file)?;
// if a permission would have been removed if umask was 0, but it wasn't because umask was not 0, print an error and fail
if (new_mode & !naively_expected_new_mode) != 0 {
show_error!(
"{}: new permissions are {}, not {}",
file.maybe_quote(),
display_permissions_unix(new_mode as mode_t, false),
display_permissions_unix(naively_expected_new_mode as mode_t, false)
);
return Err(1);
return Err(USimpleError::new(
1,
format!(
"{}: new permissions are {}, not {}",
file.maybe_quote(),
display_permissions_unix(new_mode as mode_t, false),
display_permissions_unix(naively_expected_new_mode as mode_t, false)
),
));
}
}
}

43
src/uu/env/src/env.rs vendored
View file

@ -1,6 +1,7 @@
// This file is part of the uutils coreutils package.
//
// (c) Jordi Boggiano <j.boggiano@seld.be>
// (c) Thomas Queiroz <thomasqueirozb@gmail.com>
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
@ -23,7 +24,7 @@ use std::io::{self, Write};
use std::iter::Iterator;
use std::process::Command;
use uucore::display::Quotable;
use uucore::error::{UResult, USimpleError};
use uucore::error::{UResult, USimpleError, UUsageError};
const USAGE: &str = "env [OPTION]... [-] [NAME=VALUE]... [COMMAND [ARG]...]";
const AFTER_HELP: &str = "\
@ -50,7 +51,7 @@ fn print_env(null: bool) {
}
}
fn parse_name_value_opt<'a>(opts: &mut Options<'a>, opt: &'a str) -> Result<bool, i32> {
fn parse_name_value_opt<'a>(opts: &mut Options<'a>, opt: &'a str) -> UResult<bool> {
// is it a NAME=VALUE like opt ?
if let Some(idx) = opt.find('=') {
// yes, so push name, value pair
@ -64,17 +65,12 @@ fn parse_name_value_opt<'a>(opts: &mut Options<'a>, opt: &'a str) -> Result<bool
}
}
fn parse_program_opt<'a>(opts: &mut Options<'a>, opt: &'a str) -> Result<(), i32> {
fn parse_program_opt<'a>(opts: &mut Options<'a>, opt: &'a str) -> UResult<()> {
if opts.null {
eprintln!(
"{}: cannot specify --null (-0) with command",
uucore::util_name()
);
eprintln!(
"Type \"{} --help\" for detailed information",
uucore::execution_phrase()
);
Err(1)
Err(UUsageError::new(
125,
"cannot specify --null (-0) with command".to_string(),
))
} else {
opts.program.push(opt);
Ok(())
@ -93,10 +89,8 @@ fn load_config_file(opts: &mut Options) -> UResult<()> {
Ini::load_from_file(file)
};
let conf = conf.map_err(|error| {
show_error!("{}: {}", file.maybe_quote(), error);
1
})?;
let conf =
conf.map_err(|e| USimpleError::new(1, format!("{}: {}", file.maybe_quote(), e)))?;
for (_, prop) in &conf {
// ignore all INI section lines (treat them as comments)
@ -138,7 +132,7 @@ pub fn uu_app() -> App<'static, 'static> {
.long("ignore-environment")
.help("start with an empty environment"))
.arg(Arg::with_name("chdir")
.short("c")
.short("C") // GNU env compatibility
.long("chdir")
.takes_value(true)
.number_of_values(1)
@ -236,6 +230,14 @@ fn run_env(args: impl uucore::Args) -> UResult<()> {
}
}
// GNU env tests this behavior
if opts.program.is_empty() && running_directory.is_some() {
return Err(UUsageError::new(
125,
"must specify command with --chdir (-C)".to_string(),
));
}
// NOTE: we manually set and unset the env vars below rather than using Command::env() to more
// easily handle the case where no command is given
@ -251,6 +253,13 @@ fn run_env(args: impl uucore::Args) -> UResult<()> {
// unset specified env vars
for name in &opts.unsets {
if name.is_empty() || name.contains(0 as char) || name.contains('=') {
return Err(USimpleError::new(
125,
format!("cannot unset {}: Invalid argument", name.quote()),
));
}
env::remove_var(name);
}

View file

@ -44,7 +44,8 @@ on Daniel Lemire's [*Microbenchmarking calls for idealized conditions*][lemire],
which I recommend reading if you want to add benchmarks to `factor`.
1. Select a small, self-contained, deterministic component
`gcd` and `table::factor` are good example of such:
(`gcd` and `table::factor` are good examples):
- no I/O or access to external data structures ;
- no call into other components ;
- behavior is deterministic: no RNG, no concurrency, ... ;
@ -53,16 +54,19 @@ which I recommend reading if you want to add benchmarks to `factor`.
maximizing the numbers of samples we can take in a given time.
2. Benchmarks are immutable (once merged in `uutils`)
Modifying a benchmark means previously-collected values cannot meaningfully
be compared, silently giving nonsensical results. If you must modify an
existing benchmark, rename it.
3. Test common cases
We are interested in overall performance, rather than specific edge-cases;
use **reproducibly-randomized inputs**, sampling from either all possible
input values or some subset of interest.
4. Use [`criterion`], `criterion::black_box`, ...
`criterion` isn't perfect, but it is also much better than ad-hoc
solutions in each benchmark.

View file

@ -52,7 +52,7 @@ pub fn gcd(mut u: u64, mut v: u64) -> u64 {
#[cfg(test)]
mod tests {
use super::*;
use quickcheck::quickcheck;
use quickcheck::{quickcheck, TestResult};
quickcheck! {
fn euclidean(a: u64, b: u64) -> bool {
@ -76,13 +76,12 @@ mod tests {
gcd(0, a) == a
}
fn divisor(a: u64, b: u64) -> () {
fn divisor(a: u64, b: u64) -> TestResult {
// Test that gcd(a, b) divides a and b, unless a == b == 0
if a == 0 && b == 0 { return; }
if a == 0 && b == 0 { return TestResult::discard(); } // restrict test domain to !(a == b == 0)
let g = gcd(a, b);
assert_eq!(a % g, 0);
assert_eq!(b % g, 0);
TestResult::from_bool( g != 0 && a % g == 0 && b % g == 0 )
}
fn commutative(a: u64, b: u64) -> bool {

View file

@ -9,6 +9,7 @@
use clap::{crate_version, App, Arg};
use std::env;
use uucore::error::UResult;
static ABOUT: &str = "Display the values of the specified environment VARIABLE(s), or (with no VARIABLE) display name and value pairs for them all.";
@ -20,7 +21,8 @@ fn usage() -> String {
format!("{0} [VARIABLE]... [OPTION]...", uucore::execution_phrase())
}
pub fn uumain(args: impl uucore::Args) -> i32 {
#[uucore_procs::gen_uumain]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let usage = usage();
let matches = uu_app().usage(&usage[..]).get_matches_from(args);
@ -40,15 +42,23 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
for (env_var, value) in env::vars() {
print!("{}={}{}", env_var, value, separator);
}
return 0;
return Ok(());
}
let mut not_found = false;
for env_var in variables {
if let Ok(var) = env::var(env_var) {
print!("{}{}", var, separator);
} else {
not_found = true;
}
}
0
if not_found {
Err(1.into())
} else {
Ok(())
}
}
pub fn uu_app() -> App<'static, 'static> {

View file

@ -50,6 +50,7 @@ static OPT_PROMPT_MORE: &str = "prompt-more";
static OPT_RECURSIVE: &str = "recursive";
static OPT_RECURSIVE_R: &str = "recursive_R";
static OPT_VERBOSE: &str = "verbose";
static PRESUME_INPUT_TTY: &str = "presume-input-tty";
static ARG_FILES: &str = "files";
@ -208,6 +209,17 @@ pub fn uu_app() -> App<'static, 'static> {
.long(OPT_VERBOSE)
.help("explain what is being done")
)
// From the GNU source code:
// This is solely for testing.
// Do not document.
// It is relatively difficult to ensure that there is a tty on stdin.
// Since rm acts differently depending on that, without this option,
// it'd be harder to test the parts of rm that depend on that setting.
.arg(
Arg::with_name(PRESUME_INPUT_TTY)
.long(PRESUME_INPUT_TTY)
.hidden(true)
)
.arg(
Arg::with_name(ARG_FILES)
.multiple(true)

161
src/uu/tail/src/parse.rs Normal file
View file

@ -0,0 +1,161 @@
// * This file is part of the uutils coreutils package.
// *
// * For the full copyright and license information, please view the LICENSE
// * file that was distributed with this source code.
use std::ffi::OsString;
#[derive(PartialEq, Debug)]
pub enum ParseError {
Syntax,
Overflow,
}
/// Parses obsolete syntax
/// tail -NUM[kmzv] // spell-checker:disable-line
pub fn parse_obsolete(src: &str) -> Option<Result<impl Iterator<Item = OsString>, ParseError>> {
let mut chars = src.char_indices();
if let Some((_, '-')) = chars.next() {
let mut num_end = 0usize;
let mut has_num = false;
let mut last_char = 0 as char;
for (n, c) in &mut chars {
if c.is_numeric() {
has_num = true;
num_end = n;
} else {
last_char = c;
break;
}
}
if has_num {
match src[1..=num_end].parse::<usize>() {
Ok(num) => {
let mut quiet = false;
let mut verbose = false;
let mut zero_terminated = false;
let mut multiplier = None;
let mut c = last_char;
loop {
// not that here, we only match lower case 'k', 'c', and 'm'
match c {
// we want to preserve order
// this also saves us 1 heap allocation
'q' => {
quiet = true;
verbose = false
}
'v' => {
verbose = true;
quiet = false
}
'z' => zero_terminated = true,
'c' => multiplier = Some(1),
'b' => multiplier = Some(512),
'k' => multiplier = Some(1024),
'm' => multiplier = Some(1024 * 1024),
'\0' => {}
_ => return Some(Err(ParseError::Syntax)),
}
if let Some((_, next)) = chars.next() {
c = next
} else {
break;
}
}
let mut options = Vec::new();
if quiet {
options.push(OsString::from("-q"))
}
if verbose {
options.push(OsString::from("-v"))
}
if zero_terminated {
options.push(OsString::from("-z"))
}
if let Some(n) = multiplier {
options.push(OsString::from("-c"));
let num = match num.checked_mul(n) {
Some(n) => n,
None => return Some(Err(ParseError::Overflow)),
};
options.push(OsString::from(format!("{}", num)));
} else {
options.push(OsString::from("-n"));
options.push(OsString::from(format!("{}", num)));
}
Some(Ok(options.into_iter()))
}
Err(_) => Some(Err(ParseError::Overflow)),
}
} else {
None
}
} else {
None
}
}
#[cfg(test)]
mod tests {
use super::*;
fn obsolete(src: &str) -> Option<Result<Vec<String>, ParseError>> {
let r = parse_obsolete(src);
match r {
Some(s) => match s {
Ok(v) => Some(Ok(v.map(|s| s.to_str().unwrap().to_owned()).collect())),
Err(e) => Some(Err(e)),
},
None => None,
}
}
fn obsolete_result(src: &[&str]) -> Option<Result<Vec<String>, ParseError>> {
Some(Ok(src.iter().map(|s| s.to_string()).collect()))
}
#[test]
fn test_parse_numbers_obsolete() {
assert_eq!(obsolete("-5"), obsolete_result(&["-n", "5"]));
assert_eq!(obsolete("-100"), obsolete_result(&["-n", "100"]));
assert_eq!(obsolete("-5m"), obsolete_result(&["-c", "5242880"]));
assert_eq!(obsolete("-1k"), obsolete_result(&["-c", "1024"]));
assert_eq!(obsolete("-2b"), obsolete_result(&["-c", "1024"]));
assert_eq!(obsolete("-1mmk"), obsolete_result(&["-c", "1024"]));
assert_eq!(obsolete("-1vz"), obsolete_result(&["-v", "-z", "-n", "1"]));
assert_eq!(
obsolete("-1vzqvq"), // spell-checker:disable-line
obsolete_result(&["-q", "-z", "-n", "1"])
);
assert_eq!(obsolete("-1vzc"), obsolete_result(&["-v", "-z", "-c", "1"]));
assert_eq!(
obsolete("-105kzm"),
obsolete_result(&["-z", "-c", "110100480"])
);
}
#[test]
fn test_parse_errors_obsolete() {
assert_eq!(obsolete("-5n"), Some(Err(ParseError::Syntax)));
assert_eq!(obsolete("-5c5"), Some(Err(ParseError::Syntax)));
}
#[test]
fn test_parse_obsolete_no_match() {
assert_eq!(obsolete("-k"), None);
assert_eq!(obsolete("asd"), None);
}
#[test]
#[cfg(target_pointer_width = "64")]
fn test_parse_obsolete_overflow_x64() {
assert_eq!(
obsolete("-1000000000000000m"),
Some(Err(ParseError::Overflow))
);
assert_eq!(
obsolete("-10000000000000000000000"),
Some(Err(ParseError::Overflow))
);
}
#[test]
#[cfg(target_pointer_width = "32")]
fn test_parse_obsolete_overflow_x32() {
assert_eq!(obsolete("-42949672960"), Some(Err(ParseError::Overflow)));
assert_eq!(obsolete("-42949672k"), Some(Err(ParseError::Overflow)));
}
}

View file

@ -16,17 +16,21 @@ extern crate clap;
extern crate uucore;
mod chunks;
mod parse;
mod platform;
use chunks::ReverseChunks;
use clap::{App, Arg};
use std::collections::VecDeque;
use std::ffi::OsString;
use std::fmt;
use std::fs::{File, Metadata};
use std::io::{stdin, stdout, BufRead, BufReader, Read, Seek, SeekFrom, Write};
use std::path::Path;
use std::thread::sleep;
use std::time::Duration;
use uucore::display::Quotable;
use uucore::error::{UResult, USimpleError};
use uucore::parse_size::{parse_size, ParseSizeError};
use uucore::ringbuffer::RingBuffer;
@ -58,105 +62,122 @@ pub mod options {
pub static ARG_FILES: &str = "files";
}
#[derive(Debug)]
enum FilterMode {
Bytes(usize),
Lines(usize, u8), // (number of lines, delimiter)
}
impl Default for FilterMode {
fn default() -> Self {
FilterMode::Lines(10, b'\n')
}
}
#[derive(Debug, Default)]
struct Settings {
quiet: bool,
verbose: bool,
mode: FilterMode,
sleep_msec: u32,
beginning: bool,
follow: bool,
pid: platform::Pid,
files: Vec<String>,
}
impl Default for Settings {
fn default() -> Settings {
Settings {
mode: FilterMode::Lines(10, b'\n'),
impl Settings {
pub fn get_from(args: impl uucore::Args) -> Result<Self, String> {
let matches = uu_app().get_matches_from(arg_iterate(args)?);
let mut settings: Settings = Settings {
sleep_msec: 1000,
beginning: false,
follow: false,
pid: 0,
follow: matches.is_present(options::FOLLOW),
..Default::default()
};
if settings.follow {
if let Some(n) = matches.value_of(options::SLEEP_INT) {
let parsed: Option<u32> = n.parse().ok();
if let Some(m) = parsed {
settings.sleep_msec = m * 1000
}
}
}
if let Some(pid_str) = matches.value_of(options::PID) {
if let Ok(pid) = pid_str.parse() {
settings.pid = pid;
if pid != 0 {
if !settings.follow {
show_warning!("PID ignored; --pid=PID is useful only when following");
}
if !platform::supports_pid_checks(pid) {
show_warning!("--pid=PID is not supported on this system");
settings.pid = 0;
}
}
}
}
let mode_and_beginning = if let Some(arg) = matches.value_of(options::BYTES) {
match parse_num(arg) {
Ok((n, beginning)) => (FilterMode::Bytes(n), beginning),
Err(e) => return Err(format!("invalid number of bytes: {}", e)),
}
} else if let Some(arg) = matches.value_of(options::LINES) {
match parse_num(arg) {
Ok((n, beginning)) => (FilterMode::Lines(n, b'\n'), beginning),
Err(e) => return Err(format!("invalid number of lines: {}", e)),
}
} else {
(FilterMode::Lines(10, b'\n'), false)
};
settings.mode = mode_and_beginning.0;
settings.beginning = mode_and_beginning.1;
if matches.is_present(options::ZERO_TERM) {
if let FilterMode::Lines(count, _) = settings.mode {
settings.mode = FilterMode::Lines(count, 0);
}
}
settings.verbose = matches.is_present(options::verbosity::VERBOSE);
settings.quiet = matches.is_present(options::verbosity::QUIET);
settings.files = match matches.values_of(options::ARG_FILES) {
Some(v) => v.map(|s| s.to_owned()).collect(),
None => vec!["-".to_owned()],
};
Ok(settings)
}
}
#[allow(clippy::cognitive_complexity)]
pub fn uumain(args: impl uucore::Args) -> i32 {
let mut settings: Settings = Default::default();
let app = uu_app();
let matches = app.get_matches_from(args);
settings.follow = matches.is_present(options::FOLLOW);
if settings.follow {
if let Some(n) = matches.value_of(options::SLEEP_INT) {
let parsed: Option<u32> = n.parse().ok();
if let Some(m) = parsed {
settings.sleep_msec = m * 1000
}
#[uucore_procs::gen_uumain]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let args = match Settings::get_from(args) {
Ok(o) => o,
Err(s) => {
return Err(USimpleError::new(1, s));
}
}
if let Some(pid_str) = matches.value_of(options::PID) {
if let Ok(pid) = pid_str.parse() {
settings.pid = pid;
if pid != 0 {
if !settings.follow {
show_warning!("PID ignored; --pid=PID is useful only when following");
}
if !platform::supports_pid_checks(pid) {
show_warning!("--pid=PID is not supported on this system");
settings.pid = 0;
}
}
}
}
let mode_and_beginning = if let Some(arg) = matches.value_of(options::BYTES) {
match parse_num(arg) {
Ok((n, beginning)) => (FilterMode::Bytes(n), beginning),
Err(e) => crash!(1, "invalid number of bytes: {}", e.to_string()),
}
} else if let Some(arg) = matches.value_of(options::LINES) {
match parse_num(arg) {
Ok((n, beginning)) => (FilterMode::Lines(n, b'\n'), beginning),
Err(e) => crash!(1, "invalid number of lines: {}", e.to_string()),
}
} else {
(FilterMode::Lines(10, b'\n'), false)
};
settings.mode = mode_and_beginning.0;
settings.beginning = mode_and_beginning.1;
uu_tail(&args)
}
if matches.is_present(options::ZERO_TERM) {
if let FilterMode::Lines(count, _) = settings.mode {
settings.mode = FilterMode::Lines(count, 0);
}
}
let verbose = matches.is_present(options::verbosity::VERBOSE);
let quiet = matches.is_present(options::verbosity::QUIET);
let files: Vec<String> = matches
.values_of(options::ARG_FILES)
.map(|v| v.map(ToString::to_string).collect())
.unwrap_or_else(|| vec![String::from("-")]);
let multiple = files.len() > 1;
fn uu_tail(settings: &Settings) -> UResult<()> {
let multiple = settings.files.len() > 1;
let mut first_header = true;
let mut readers: Vec<(Box<dyn BufRead>, &String)> = Vec::new();
#[cfg(unix)]
let stdin_string = String::from("standard input");
for filename in &files {
for filename in &settings.files {
let use_stdin = filename.as_str() == "-";
if (multiple || verbose) && !quiet {
if (multiple || settings.verbose) && !settings.quiet {
if !first_header {
println!();
}
@ -170,7 +191,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
if use_stdin {
let mut reader = BufReader::new(stdin());
unbounded_tail(&mut reader, &settings);
unbounded_tail(&mut reader, settings);
// Don't follow stdin since there are no checks for pipes/FIFOs
//
@ -202,14 +223,14 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
let mut file = File::open(&path).unwrap();
let md = file.metadata().unwrap();
if is_seekable(&mut file) && get_block_size(&md) > 0 {
bounded_tail(&mut file, &settings);
bounded_tail(&mut file, settings);
if settings.follow {
let reader = BufReader::new(file);
readers.push((Box::new(reader), filename));
}
} else {
let mut reader = BufReader::new(file);
unbounded_tail(&mut reader, &settings);
unbounded_tail(&mut reader, settings);
if settings.follow {
readers.push((Box::new(reader), filename));
}
@ -218,10 +239,36 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
}
if settings.follow {
follow(&mut readers[..], &settings);
follow(&mut readers[..], settings);
}
0
Ok(())
}
fn arg_iterate<'a>(
mut args: impl uucore::Args + 'a,
) -> Result<Box<dyn Iterator<Item = OsString> + 'a>, String> {
// argv[0] is always present
let first = args.next().unwrap();
if let Some(second) = args.next() {
if let Some(s) = second.to_str() {
match parse::parse_obsolete(s) {
Some(Ok(iter)) => Ok(Box::new(vec![first].into_iter().chain(iter).chain(args))),
Some(Err(e)) => match e {
parse::ParseError::Syntax => Err(format!("bad argument format: {}", s.quote())),
parse::ParseError::Overflow => Err(format!(
"invalid argument: {} Value too large for defined datatype",
s.quote()
)),
},
None => Ok(Box::new(vec![first, second].into_iter().chain(args))),
}
} else {
Err("bad argument encoding".to_owned())
}
} else {
Ok(Box::new(vec![first].into_iter()))
}
}
pub fn uu_app() -> App<'static, 'static> {

View file

@ -7,8 +7,8 @@
// spell-checker:ignore (ToDO) ttyname hostnames runlevel mesg wtmp statted boottime deadprocs initspawn clockchange curr runlvline pidstr exitstr hoststr
#[macro_use]
extern crate uucore;
use uucore::display::Quotable;
use uucore::error::{FromIo, UResult};
use uucore::libc::{ttyname, STDIN_FILENO, S_IWGRP};
use uucore::utmpx::{self, time, Utmpx};
@ -59,7 +59,8 @@ fn get_long_usage() -> String {
)
}
pub fn uumain(args: impl uucore::Args) -> i32 {
#[uucore_procs::gen_uumain]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let args = args
.collect_str(InvalidEncodingHandling::Ignore)
.accept_any();
@ -157,9 +158,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
args: files,
};
who.exec();
0
who.exec()
}
pub fn uu_app() -> App<'static, 'static> {
@ -326,7 +325,7 @@ fn current_tty() -> String {
}
impl Who {
fn exec(&mut self) {
fn exec(&mut self) -> UResult<()> {
let run_level_chk = |_record: i16| {
#[cfg(not(target_os = "linux"))]
return false;
@ -362,7 +361,7 @@ impl Who {
for ut in records {
if !self.my_line_only || cur_tty == ut.tty_device() {
if self.need_users && ut.is_user_process() {
self.print_user(&ut);
self.print_user(&ut)?;
} else if self.need_runlevel && run_level_chk(ut.record_type()) {
if cfg!(target_os = "linux") {
self.print_runlevel(&ut);
@ -383,6 +382,7 @@ impl Who {
if ut.record_type() == utmpx::BOOT_TIME {}
}
}
Ok(())
}
#[inline]
@ -464,7 +464,7 @@ impl Who {
self.print_line("", ' ', "system boot", &time_string(ut), "", "", "", "");
}
fn print_user(&self, ut: &Utmpx) {
fn print_user(&self, ut: &Utmpx) -> UResult<()> {
let mut p = PathBuf::from("/dev");
p.push(ut.tty_device().as_str());
let mesg;
@ -491,7 +491,13 @@ impl Who {
};
let s = if self.do_lookup {
crash_if_err!(1, ut.canon_host())
ut.canon_host().map_err_context(|| {
let host = ut.host();
format!(
"failed to canonicalize {}",
host.split(':').next().unwrap_or(&host).quote()
)
})?
} else {
ut.host()
};
@ -507,6 +513,8 @@ impl Who {
hoststr.as_str(),
"",
);
Ok(())
}
#[allow(clippy::too_many_arguments)]

View file

@ -236,17 +236,20 @@ impl Utmpx {
flags: AI_CANONNAME,
..AddrInfoHints::default()
};
let sockets = getaddrinfo(Some(hostname), None, Some(hints))
.unwrap()
.collect::<IOResult<Vec<_>>>()?;
for socket in sockets {
if let Some(ai_canonname) = socket.canonname {
return Ok(if display.is_empty() {
ai_canonname
} else {
format!("{}:{}", ai_canonname, display)
});
if let Ok(sockets) = getaddrinfo(Some(hostname), None, Some(hints)) {
let sockets = sockets.collect::<IOResult<Vec<_>>>()?;
for socket in sockets {
if let Some(ai_canonname) = socket.canonname {
return Ok(if display.is_empty() {
ai_canonname
} else {
format!("{}:{}", ai_canonname, display)
});
}
}
} else {
// GNU coreutils has this behavior
return Ok(hostname.to_string());
}
}

View file

@ -1,3 +1,37 @@
//! Macros for the uucore utilities.
//!
//! This module bundles all macros used across the uucore utilities. These
//! include macros for reporting errors in various formats, aborting program
//! execution and more.
//!
//! To make use of all macros in this module, they must be imported like so:
//!
//! ```ignore
//! #[macro_use]
//! extern crate uucore;
//! ```
//!
//! Alternatively, you can import single macros by importing them through their
//! fully qualified name like this:
//!
//! ```no_run
//! use uucore::{show, crash};
//! ```
//!
//! Here's an overview of the macros sorted by purpose
//!
//! - Print errors
//! - From types implementing [`crate::error::UError`]: [`show!`],
//! [`show_if_err!`]
//! - From custom messages: [`show_error!`], [`show_usage_error!`]
//! - Print warnings: [`show_warning!`]
//! - Terminate util execution
//! - Terminate regularly: [`exit!`], [`return_if_err!`]
//! - Crash program: [`crash!`], [`crash_if_err!`], [`safe_unwrap!`]
//! - Unwrapping result types: [`safe_unwrap!`]
// spell-checker:ignore sourcepath targetpath
use std::sync::atomic::AtomicBool;
// This file is part of the uutils coreutils package.
@ -12,6 +46,45 @@ pub static UTILITY_IS_SECOND_ARG: AtomicBool = AtomicBool::new(false);
//====
/// Display a [`crate::error::UError`] and set global exit code.
///
/// Prints the error message contained in an [`crate::error::UError`] to stderr
/// and sets the exit code through [`crate::error::set_exit_code`]. The printed
/// error message is prepended with the calling utility's name. A call to this
/// macro will not finish program execution.
///
/// # Examples
///
/// The following example would print a message "Some error occurred" and set
/// the utility's exit code to 2.
///
/// ```
/// # #[macro_use]
/// # extern crate uucore;
///
/// use uucore::error::{self, USimpleError};
///
/// fn main() {
/// let err = USimpleError::new(2, "Some error occurred.");
/// show!(err);
/// assert_eq!(error::get_exit_code(), 2);
/// }
/// ```
///
/// If not using [`crate::error::UError`], one may achieve the same behavior
/// like this:
///
/// ```
/// # #[macro_use]
/// # extern crate uucore;
///
/// use uucore::error::set_exit_code;
///
/// fn main() {
/// set_exit_code(2);
/// show_error!("Some error occurred.");
/// }
/// ```
#[macro_export]
macro_rules! show(
($err:expr) => ({
@ -21,6 +94,36 @@ macro_rules! show(
})
);
/// Display an error and set global exit code in error case.
///
/// Wraps around [`show!`] and takes a [`crate::error::UResult`] instead of a
/// [`crate::error::UError`] type. This macro invokes [`show!`] if the
/// [`crate::error::UResult`] is an `Err`-variant. This can be invoked directly
/// on the result of a function call, like in the `install` utility:
///
/// ```ignore
/// show_if_err!(copy(sourcepath, &targetpath, b));
/// ```
///
/// # Examples
///
/// ```ignore
/// # #[macro_use]
/// # extern crate uucore;
/// # use uucore::error::{UError, UIoError, UResult, USimpleError};
///
/// # fn main() {
/// let is_ok = Ok(1);
/// // This does nothing at all
/// show_if_err!(is_ok);
///
/// let is_err = Err(USimpleError::new(1, "I'm an error").into());
/// // Calls `show!` on the contained USimpleError
/// show_if_err!(is_err);
/// # }
/// ```
///
///
#[macro_export]
macro_rules! show_if_err(
($res:expr) => ({
@ -31,6 +134,19 @@ macro_rules! show_if_err(
);
/// Show an error to stderr in a similar style to GNU coreutils.
///
/// Takes a [`format!`]-like input and prints it to stderr. The output is
/// prepended with the current utility's name.
///
/// # Examples
///
/// ```
/// # #[macro_use]
/// # extern crate uucore;
/// # fn main() {
/// show_error!("Couldn't apply {} to {}", "foo", "bar");
/// # }
/// ```
#[macro_export]
macro_rules! show_error(
($($args:tt)+) => ({
@ -40,6 +156,17 @@ macro_rules! show_error(
);
/// Show a warning to stderr in a similar style to GNU coreutils.
///
/// Is this really required? Used in the following locations:
///
/// ./src/uu/head/src/head.rs:12
/// ./src/uu/head/src/head.rs:424
/// ./src/uu/head/src/head.rs:427
/// ./src/uu/head/src/head.rs:430
/// ./src/uu/head/src/head.rs:453
/// ./src/uu/du/src/du.rs:339
/// ./src/uu/wc/src/wc.rs:270
/// ./src/uu/wc/src/wc.rs:273
#[macro_export]
macro_rules! show_error_custom_description (
($err:expr,$($args:tt)+) => ({
@ -48,6 +175,21 @@ macro_rules! show_error_custom_description (
})
);
/// Print a warning message to stderr.
///
/// Takes [`format!`]-compatible input and prepends it with the current
/// utility's name and "warning: " before printing to stderr.
///
/// # Examples
///
/// ```
/// # #[macro_use]
/// # extern crate uucore;
/// # fn main() {
/// // outputs <name>: warning: Couldn't apply foo to bar
/// show_warning!("Couldn't apply {} to {}", "foo", "bar");
/// # }
/// ```
#[macro_export]
macro_rules! show_warning(
($($args:tt)+) => ({
@ -57,6 +199,21 @@ macro_rules! show_warning(
);
/// Show a bad invocation help message in a similar style to GNU coreutils.
///
/// Takes a [`format!`]-compatible input and prepends it with the current
/// utility's name before printing to stderr.
///
/// # Examples
///
/// ```
/// # #[macro_use]
/// # extern crate uucore;
/// # fn main() {
/// // outputs <name>: Couldn't apply foo to bar
/// // Try '<name> --help' for more information.
/// show_usage_error!("Couldn't apply {} to {}", "foo", "bar");
/// # }
/// ```
#[macro_export]
macro_rules! show_usage_error(
($($args:tt)+) => ({
@ -68,7 +225,9 @@ macro_rules! show_usage_error(
//====
/// Calls `exit()` with the provided exit code.
/// Calls [`std::process::exit`] with the provided exit code.
///
/// Why not call exit directly?
#[macro_export]
macro_rules! exit(
($exit_code:expr) => ({
@ -76,7 +235,22 @@ macro_rules! exit(
})
);
/// Display the provided error message, then `exit()` with the provided exit code
/// Display an error and [`exit!`]
///
/// Displays the provided error message using [`show_error!`], then invokes
/// [`exit!`] with the provided exit code.
///
/// # Examples
///
/// ```should_panic
/// # #[macro_use]
/// # extern crate uucore;
/// # fn main() {
/// // outputs <name>: Couldn't apply foo to bar
/// // and terminates execution
/// crash!(1, "Couldn't apply {} to {}", "foo", "bar");
/// # }
/// ```
#[macro_export]
macro_rules! crash(
($exit_code:expr, $($args:tt)+) => ({
@ -85,8 +259,26 @@ macro_rules! crash(
})
);
/// Unwraps the Result. Instead of panicking, it exists the program with the
/// provided exit code.
/// Unwrap a [`std::result::Result`], crashing instead of panicking.
///
/// If the result is an `Ok`-variant, returns the value contained inside. If it
/// is an `Err`-variant, invokes [`crash!`] with the formatted error instead.
///
/// # Examples
///
/// ```should_panic
/// # #[macro_use]
/// # extern crate uucore;
/// # fn main() {
/// let is_ok: Result<u32, &str> = Ok(1);
/// // Does nothing
/// crash_if_err!(1, is_ok);
///
/// let is_err: Result<u32, &str> = Err("This didn't work...");
/// // Calls `crash!`
/// crash_if_err!(1, is_err);
/// # }
/// ```
#[macro_export]
macro_rules! crash_if_err(
($exit_code:expr, $exp:expr) => (
@ -97,18 +289,42 @@ macro_rules! crash_if_err(
)
);
//====
/// Unwrap some Result, crashing instead of panicking.
///
/// Drop this in favor of `crash_if_err!`
#[macro_export]
macro_rules! safe_write(
($fd:expr, $($args:tt)+) => (
match write!($fd, $($args)+) {
Ok(_) => {}
Err(f) => panic!("{}", f)
macro_rules! safe_unwrap(
($exp:expr) => (
match $exp {
Ok(m) => m,
Err(f) => $crate::crash!(1, "{}", f.to_string())
}
)
);
//====
/// Unwraps the Result. Instead of panicking, it shows the error and then
/// returns from the function with the provided exit code.
/// Assumes the current function returns an i32 value.
///
/// Replace with `crash_if_err`?
#[macro_export]
macro_rules! return_if_err(
($exit_code:expr, $exp:expr) => (
match $exp {
Ok(m) => m,
Err(f) => {
$crate::show_error!("{}", f);
return $exit_code;
}
}
)
);
//====
/// This is used exclusively by du...
#[macro_export]
macro_rules! safe_writeln(
($fd:expr, $($args:tt)+) => (
@ -123,6 +339,7 @@ macro_rules! safe_writeln(
//-- message templates : (join utility sub-macros)
// used only by "cut"
#[macro_export]
macro_rules! snippet_list_join_oxford_comma {
($conjunction:expr, $valOne:expr, $valTwo:expr) => (
@ -133,6 +350,7 @@ macro_rules! snippet_list_join_oxford_comma {
);
}
// used only by "cut"
#[macro_export]
macro_rules! snippet_list_join {
($conjunction:expr, $valOne:expr, $valTwo:expr) => (
@ -167,6 +385,7 @@ macro_rules! msg_invalid_opt_use {
};
}
// Only used by "cut"
#[macro_export]
macro_rules! msg_opt_only_usable_if {
($clause:expr, $flag:expr) => {
@ -181,6 +400,7 @@ macro_rules! msg_opt_only_usable_if {
};
}
// Used only by "cut"
#[macro_export]
macro_rules! msg_opt_invalid_should_be {
($expects:expr, $received:expr, $flag:expr) => {
@ -200,6 +420,7 @@ macro_rules! msg_opt_invalid_should_be {
// -- message templates : invalid input : input combinations
// UNUSED!
#[macro_export]
macro_rules! msg_expects_one_of {
($valOne:expr $(, $remaining_values:expr)*) => (
@ -207,6 +428,7 @@ macro_rules! msg_expects_one_of {
);
}
// Used only by "cut"
#[macro_export]
macro_rules! msg_expects_no_more_than_one_of {
($valOne:expr $(, $remaining_values:expr)*) => (

View file

@ -541,7 +541,7 @@ fn test_no_operands() {
.arg("777")
.fails()
.code_is(1)
.stderr_is("chmod: missing operand");
.usage_error("missing operand");
}
#[test]

View file

@ -1,7 +1,4 @@
// spell-checker:ignore (words) bamf chdir
#[cfg(not(windows))]
use std::fs;
// spell-checker:ignore (words) bamf chdir rlimit prlimit COMSPEC
use crate::common::util::*;
use std::env;
@ -80,6 +77,20 @@ fn test_combined_file_set_unset() {
);
}
#[test]
fn test_unset_invalid_variables() {
use uucore::display::Quotable;
// Cannot test input with \0 in it, since output will also contain \0. rlimit::prlimit fails
// with this error: Error { kind: InvalidInput, message: "nul byte found in provided data" }
for var in &["", "a=b"] {
new_ucmd!().arg("-u").arg(var).run().stderr_only(format!(
"env: cannot unset {}: Invalid argument",
var.quote()
));
}
}
#[test]
fn test_single_name_value_pair() {
let out = new_ucmd!().arg("FOO=bar").run();
@ -163,7 +174,7 @@ fn test_fail_null_with_program() {
fn test_change_directory() {
let scene = TestScenario::new(util_name!());
let temporary_directory = tempdir().unwrap();
let temporary_path = fs::canonicalize(temporary_directory.path()).unwrap();
let temporary_path = std::fs::canonicalize(temporary_directory.path()).unwrap();
assert_ne!(env::current_dir().unwrap(), temporary_path);
// command to print out current working directory
@ -179,27 +190,36 @@ fn test_change_directory() {
assert_eq!(out.trim(), temporary_path.as_os_str())
}
// no way to consistently get "current working directory", `cd` doesn't work @ CI
// instead, we test that the unique temporary directory appears somewhere in the printed variables
#[cfg(windows)]
#[test]
fn test_change_directory() {
let scene = TestScenario::new(util_name!());
let temporary_directory = tempdir().unwrap();
let temporary_path = temporary_directory.path();
assert_ne!(env::current_dir().unwrap(), temporary_path);
let temporary_path = temporary_directory.path();
let temporary_path = temporary_path
.strip_prefix(r"\\?\")
.unwrap_or(temporary_path);
let env_cd = env::current_dir().unwrap();
let env_cd = env_cd.strip_prefix(r"\\?\").unwrap_or(&env_cd);
assert_ne!(env_cd, temporary_path);
// COMSPEC is a variable that contains the full path to cmd.exe
let cmd_path = env::var("COMSPEC").unwrap();
// command to print out current working directory
let pwd = [&*cmd_path, "/C", "cd"];
let out = scene
.ucmd()
.arg("--chdir")
.arg(&temporary_path)
.args(&pwd)
.succeeds()
.stdout_move_str();
assert!(!out
.lines()
.any(|line| line.ends_with(temporary_path.file_name().unwrap().to_str().unwrap())));
assert_eq!(out.trim(), temporary_path.as_os_str())
}
#[test]

View file

@ -314,3 +314,39 @@ fn test_rm_verbose_slash() {
assert!(!at.dir_exists(dir));
assert!(!at.file_exists(file_a));
}
#[test]
fn test_rm_silently_accepts_presume_input_tty1() {
let (at, mut ucmd) = at_and_ucmd!();
let file_1 = "test_rm_silently_accepts_presume_input_tty1";
at.touch(file_1);
ucmd.arg("--presume-input-tty").arg(file_1).succeeds();
assert!(!at.file_exists(file_1));
}
#[test]
fn test_rm_silently_accepts_presume_input_tty2() {
let (at, mut ucmd) = at_and_ucmd!();
let file_2 = "test_rm_silently_accepts_presume_input_tty2";
at.touch(file_2);
ucmd.arg("---presume-input-tty").arg(file_2).succeeds();
assert!(!at.file_exists(file_2));
}
#[test]
fn test_rm_silently_accepts_presume_input_tty3() {
let (at, mut ucmd) = at_and_ucmd!();
let file_3 = "test_rm_silently_accepts_presume_input_tty3";
at.touch(file_3);
ucmd.arg("----presume-input-tty").arg(file_3).succeeds();
assert!(!at.file_exists(file_3));
}

View file

@ -358,6 +358,36 @@ fn test_positive_lines() {
.stdout_is("c\nd\ne\n");
}
/// Test for reading all but the first NUM lines: `tail -3`.
#[test]
fn test_obsolete_syntax_positive_lines() {
new_ucmd!()
.args(&["-3"])
.pipe_in("a\nb\nc\nd\ne\n")
.succeeds()
.stdout_is("c\nd\ne\n");
}
/// Test for reading all but the first NUM lines: `tail -n -10`.
#[test]
fn test_small_file() {
new_ucmd!()
.args(&["-n -10"])
.pipe_in("a\nb\nc\nd\ne\n")
.succeeds()
.stdout_is("a\nb\nc\nd\ne\n");
}
/// Test for reading all but the first NUM lines: `tail -10`.
#[test]
fn test_obsolete_syntax_small_file() {
new_ucmd!()
.args(&["-10"])
.pipe_in("a\nb\nc\nd\ne\n")
.succeeds()
.stdout_is("a\nb\nc\nd\ne\n");
}
/// Test for reading all lines, specified by `tail -n +0`.
#[test]
fn test_positive_zero_lines() {