Merge branch 'master' into clippy_warnings

This commit is contained in:
Jan Scheer 2021-10-19 00:48:38 +02:00
commit 4cf26f0fc2
No known key found for this signature in database
GPG key ID: C62AD4C29E2B9828
53 changed files with 809 additions and 427 deletions

11
Cargo.lock generated
View file

@ -1064,6 +1064,15 @@ version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b16bd47d9e329435e309c58469fe0791c2d0d1ba96ec0954152a5ae2b04387dc"
[[package]]
name = "memmap2"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4647a11b578fead29cdbb34d4adef8dd3dc35b876c9c6d5240d83f205abfe96e"
dependencies = [
"libc",
]
[[package]]
name = "memoffset"
version = "0.6.4"
@ -2608,6 +2617,7 @@ dependencies = [
"lscolors",
"number_prefix",
"once_cell",
"selinux",
"term_grid",
"termsize",
"unicode-width",
@ -3024,6 +3034,7 @@ version = "0.0.7"
dependencies = [
"clap",
"memchr 2.4.0",
"memmap2",
"regex",
"uucore",
"uucore_procs",

View file

@ -148,7 +148,7 @@ feat_os_unix_musl = [
# NOTE:
# The selinux(-sys) crate requires `libselinux` headers and shared library to be accessible in the C toolchain at compile time.
# Running a uutils compiled with `feat_selinux` requires an SELinux enabled Kernel at run time.
feat_selinux = ["cp/selinux", "id/selinux", "selinux", "feat_require_selinux"]
feat_selinux = ["cp/selinux", "id/selinux", "ls/selinux", "selinux", "feat_require_selinux"]
# "feat_acl" == set of utilities providing support for acl (access control lists) if enabled with `--features feat_acl`.
# NOTE:
# On linux, the posix-acl/acl-sys crate requires `libacl` headers and shared library to be accessible in the C toolchain at compile time.

View file

@ -6,9 +6,6 @@
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
#[macro_use]
extern crate uucore;
use platform_info::*;
use clap::{crate_version, App};

View file

@ -5,13 +5,10 @@
// For the full copyright and license information, please view the LICENSE file
// that was distributed with this source code.
#[macro_use]
extern crate uucore;
use std::io::{stdin, Read};
use clap::App;
use uucore::encoding::Format;
use uucore::{encoding::Format, error::UResult};
pub mod base_common;
@ -24,27 +21,22 @@ static ABOUT: &str = "
to attempt to recover from any other non-alphabet bytes in the
encoded stream.
";
static VERSION: &str = env!("CARGO_PKG_VERSION");
static BASE_CMD_PARSE_ERROR: i32 = 1;
fn usage() -> String {
format!("{0} [OPTION]... [FILE]", uucore::execution_phrase())
}
pub fn uumain(args: impl uucore::Args) -> i32 {
#[uucore_procs::gen_uumain]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let format = Format::Base32;
let usage = usage();
let name = uucore::util_name();
let config_result: Result<base_common::Config, String> =
base_common::parse_base_cmd_args(args, name, VERSION, ABOUT, &usage);
let config = config_result.unwrap_or_else(|s| crash!(BASE_CMD_PARSE_ERROR, "{}", s));
let config: base_common::Config = base_common::parse_base_cmd_args(args, ABOUT, &usage)?;
// Create a reference to stdin so we can return a locked stdin from
// parse_base_cmd_args
let stdin_raw = stdin();
let mut input: Box<dyn Read> = base_common::get_input(&config, &stdin_raw);
let mut input: Box<dyn Read> = base_common::get_input(&config, &stdin_raw)?;
base_common::handle_input(
&mut input,
@ -52,12 +44,9 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
config.wrap_cols,
config.ignore_garbage,
config.decode,
name,
);
0
)
}
pub fn uu_app() -> App<'static, 'static> {
base_common::base_app(uucore::util_name(), VERSION, ABOUT)
base_common::base_app(ABOUT)
}

View file

@ -11,13 +11,16 @@ use std::io::{stdout, Read, Write};
use uucore::display::Quotable;
use uucore::encoding::{wrap_print, Data, Format};
use uucore::error::{FromIo, UResult, USimpleError, UUsageError};
use uucore::InvalidEncodingHandling;
use std::fs::File;
use std::io::{BufReader, Stdin};
use std::path::Path;
use clap::{App, Arg};
use clap::{crate_version, App, Arg};
pub static BASE_CMD_PARSE_ERROR: i32 = 1;
// Config.
pub struct Config {
@ -35,15 +38,14 @@ pub mod options {
}
impl Config {
pub fn from(app_name: &str, options: &clap::ArgMatches) -> Result<Config, String> {
pub fn from(options: &clap::ArgMatches) -> UResult<Config> {
let file: Option<String> = match options.values_of(options::FILE) {
Some(mut values) => {
let name = values.next().unwrap();
if let Some(extra_op) = values.next() {
return Err(format!(
"extra operand {}\nTry '{} --help' for more information.",
extra_op.quote(),
app_name
return Err(UUsageError::new(
BASE_CMD_PARSE_ERROR,
format!("extra operand {}", extra_op.quote(),),
));
}
@ -51,7 +53,10 @@ impl Config {
None
} else {
if !Path::exists(Path::new(name)) {
return Err(format!("{}: No such file or directory", name.maybe_quote()));
return Err(USimpleError::new(
BASE_CMD_PARSE_ERROR,
format!("{}: No such file or directory", name.maybe_quote()),
));
}
Some(name.to_owned())
}
@ -62,8 +67,12 @@ impl Config {
let cols = options
.value_of(options::WRAP)
.map(|num| {
num.parse::<usize>()
.map_err(|_| format!("invalid wrap size: {}", num.quote()))
num.parse::<usize>().map_err(|_| {
USimpleError::new(
BASE_CMD_PARSE_ERROR,
format!("invalid wrap size: {}", num.quote()),
)
})
})
.transpose()?;
@ -76,23 +85,17 @@ impl Config {
}
}
pub fn parse_base_cmd_args(
args: impl uucore::Args,
name: &str,
version: &str,
about: &str,
usage: &str,
) -> Result<Config, String> {
let app = base_app(name, version, about).usage(usage);
pub fn parse_base_cmd_args(args: impl uucore::Args, about: &str, usage: &str) -> UResult<Config> {
let app = base_app(about).usage(usage);
let arg_list = args
.collect_str(InvalidEncodingHandling::ConvertLossy)
.accept_any();
Config::from(name, &app.get_matches_from(arg_list))
Config::from(&app.get_matches_from(arg_list))
}
pub fn base_app<'a>(name: &str, version: &'a str, about: &'a str) -> App<'static, 'a> {
App::new(name)
.version(version)
pub fn base_app<'a>(about: &'a str) -> App<'static, 'a> {
App::new(uucore::util_name())
.version(crate_version!())
.about(about)
// Format arguments.
.arg(
@ -121,14 +124,15 @@ pub fn base_app<'a>(name: &str, version: &'a str, about: &'a str) -> App<'static
.arg(Arg::with_name(options::FILE).index(1).multiple(true))
}
pub fn get_input<'a>(config: &Config, stdin_ref: &'a Stdin) -> Box<dyn Read + 'a> {
pub fn get_input<'a>(config: &Config, stdin_ref: &'a Stdin) -> UResult<Box<dyn Read + 'a>> {
match &config.to_read {
Some(name) => {
let file_buf = crash_if_err!(1, File::open(Path::new(name)));
Box::new(BufReader::new(file_buf)) // as Box<dyn Read>
let file_buf =
File::open(Path::new(name)).map_err_context(|| name.maybe_quote().to_string())?;
Ok(Box::new(BufReader::new(file_buf))) // as Box<dyn Read>
}
None => {
Box::new(stdin_ref.lock()) // as Box<dyn Read>
Ok(Box::new(stdin_ref.lock())) // as Box<dyn Read>
}
}
}
@ -139,8 +143,7 @@ pub fn handle_input<R: Read>(
line_wrap: Option<usize>,
ignore_garbage: bool,
decode: bool,
name: &str,
) {
) -> UResult<()> {
let mut data = Data::new(input, format).ignore_garbage(ignore_garbage);
if let Some(wrap) = line_wrap {
data = data.line_wrap(wrap);
@ -150,28 +153,23 @@ pub fn handle_input<R: Read>(
match data.encode() {
Ok(s) => {
wrap_print(&data, s);
Ok(())
}
Err(_) => {
eprintln!(
"{}: error: invalid input (length must be multiple of 4 characters)",
name
);
exit!(1)
}
Err(_) => Err(USimpleError::new(
1,
"error: invalid input (length must be multiple of 4 characters)",
)),
}
} else {
match data.decode() {
Ok(s) => {
if stdout().write_all(&s).is_err() {
// on windows console, writing invalid utf8 returns an error
eprintln!("{}: error: Cannot write non-utf8 data", name);
exit!(1)
return Err(USimpleError::new(1, "error: cannot write non-utf8 data"));
}
Ok(())
}
Err(_) => {
eprintln!("{}: error: invalid input", name);
exit!(1)
}
Err(_) => Err(USimpleError::new(1, "error: invalid input")),
}
}
}

View file

@ -6,13 +6,10 @@
// For the full copyright and license information, please view the LICENSE file
// that was distributed with this source code.
#[macro_use]
extern crate uucore;
use uu_base32::base_common;
pub use uu_base32::uu_app;
use uucore::encoding::Format;
use uucore::{encoding::Format, error::UResult};
use std::io::{stdin, Read};
@ -25,26 +22,22 @@ static ABOUT: &str = "
to attempt to recover from any other non-alphabet bytes in the
encoded stream.
";
static VERSION: &str = env!("CARGO_PKG_VERSION");
static BASE_CMD_PARSE_ERROR: i32 = 1;
fn usage() -> String {
format!("{0} [OPTION]... [FILE]", uucore::execution_phrase())
}
pub fn uumain(args: impl uucore::Args) -> i32 {
#[uucore_procs::gen_uumain]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let format = Format::Base64;
let usage = usage();
let name = uucore::util_name();
let config_result: Result<base_common::Config, String> =
base_common::parse_base_cmd_args(args, name, VERSION, ABOUT, &usage);
let config = config_result.unwrap_or_else(|s| crash!(BASE_CMD_PARSE_ERROR, "{}", s));
let config: base_common::Config = base_common::parse_base_cmd_args(args, ABOUT, &usage)?;
// Create a reference to stdin so we can return a locked stdin from
// parse_base_cmd_args
let stdin_raw = stdin();
let mut input: Box<dyn Read> = base_common::get_input(&config, &stdin_raw);
let mut input: Box<dyn Read> = base_common::get_input(&config, &stdin_raw)?;
base_common::handle_input(
&mut input,
@ -52,8 +45,5 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
config.wrap_cols,
config.ignore_garbage,
config.decode,
name,
);
0
)
}

View file

@ -8,13 +8,14 @@
//spell-checker:ignore (args) lsbf msbf
#[macro_use]
extern crate uucore;
use clap::{App, Arg};
use uu_base32::base_common::{self, Config, BASE_CMD_PARSE_ERROR};
use clap::{crate_version, App, Arg};
use uu_base32::base_common::{self, Config};
use uucore::{encoding::Format, InvalidEncodingHandling};
use uucore::{
encoding::Format,
error::{UResult, UUsageError},
InvalidEncodingHandling,
};
use std::io::{stdin, Read};
@ -26,8 +27,6 @@ static ABOUT: &str = "
from any other non-alphabet bytes in the encoded stream.
";
static BASE_CMD_PARSE_ERROR: i32 = 1;
const ENCODINGS: &[(&str, Format)] = &[
("base64", Format::Base64),
("base64url", Format::Base64Url),
@ -47,14 +46,14 @@ fn usage() -> String {
}
pub fn uu_app() -> App<'static, 'static> {
let mut app = base_common::base_app(uucore::util_name(), crate_version!(), ABOUT);
let mut app = base_common::base_app(ABOUT);
for encoding in ENCODINGS {
app = app.arg(Arg::with_name(encoding.0).long(encoding.0));
}
app
}
fn parse_cmd_args(args: impl uucore::Args) -> (Config, Format) {
fn parse_cmd_args(args: impl uucore::Args) -> UResult<(Config, Format)> {
let usage = usage();
let matches = uu_app().usage(&usage[..]).get_matches_from(
args.collect_str(InvalidEncodingHandling::ConvertLossy)
@ -63,24 +62,19 @@ fn parse_cmd_args(args: impl uucore::Args) -> (Config, Format) {
let format = ENCODINGS
.iter()
.find(|encoding| matches.is_present(encoding.0))
.unwrap_or_else(|| {
show_usage_error!("missing encoding type");
std::process::exit(1)
})
.ok_or_else(|| UUsageError::new(BASE_CMD_PARSE_ERROR, "missing encoding type"))?
.1;
(
Config::from("basenc", &matches).unwrap_or_else(|s| crash!(BASE_CMD_PARSE_ERROR, "{}", s)),
format,
)
let config = Config::from(&matches)?;
Ok((config, format))
}
pub fn uumain(args: impl uucore::Args) -> i32 {
let name = uucore::util_name();
let (config, format) = parse_cmd_args(args);
#[uucore_procs::gen_uumain]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let (config, format) = parse_cmd_args(args)?;
// Create a reference to stdin so we can return a locked stdin from
// parse_base_cmd_args
let stdin_raw = stdin();
let mut input: Box<dyn Read> = base_common::get_input(&config, &stdin_raw);
let mut input: Box<dyn Read> = base_common::get_input(&config, &stdin_raw)?;
base_common::handle_input(
&mut input,
@ -88,8 +82,5 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
config.wrap_cols,
config.ignore_garbage,
config.decode,
name,
);
0
)
}

View file

@ -12,8 +12,6 @@
#[cfg(unix)]
extern crate unix_socket;
#[macro_use]
extern crate uucore;
// last synced with: cat (GNU coreutils) 8.13
use clap::{crate_version, App, Arg};

View file

@ -7,8 +7,6 @@
// spell-checker:ignore (ToDO) COMFOLLOW Chowner RFILE RFILE's derefer dgid nonblank nonprint nonprinting
#[macro_use]
extern crate uucore;
use uucore::display::Quotable;
pub use uucore::entries;
use uucore::error::{FromIo, UResult, USimpleError};

View file

@ -7,8 +7,6 @@
// spell-checker:ignore (ToDO) COMFOLLOW Passwd RFILE RFILE's derefer dgid duid groupname
#[macro_use]
extern crate uucore;
use uucore::display::Quotable;
pub use uucore::entries::{self, Group, Locate, Passwd};
use uucore::perms::{chown_base, options, IfFrom};

View file

@ -67,7 +67,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
// TODO: refactor the args and command matching
// See: https://github.com/uutils/coreutils/pull/2365#discussion_r647849967
let command: Vec<&str> = match commands.len() {
1 => {
0 => {
let shell: &str = match user_shell {
Err(_) => default_shell,
Ok(ref s) => s.as_ref(),
@ -77,12 +77,28 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
_ => commands,
};
assert!(!command.is_empty());
let chroot_command = command[0];
let chroot_args = &command[1..];
// NOTE: Tests can only trigger code beyond this point if they're invoked with root permissions
set_context(newroot, &matches);
let pstatus = Command::new(command[0])
.args(&command[1..])
let pstatus = Command::new(chroot_command)
.args(chroot_args)
.status()
.unwrap_or_else(|e| crash!(1, "Cannot exec: {}", e));
.unwrap_or_else(|e| {
// TODO: Exit status:
// 125 if chroot itself fails
// 126 if command is found but cannot be invoked
// 127 if command cannot be found
crash!(
1,
"failed to run command {}: {}",
command[0].to_string().quote(),
e
)
});
if pstatus.success() {
0

View file

@ -49,6 +49,7 @@ use std::path::{Path, PathBuf, StripPrefixError};
use std::str::FromStr;
use std::string::ToString;
use uucore::backup_control::{self, BackupMode};
use uucore::error::{set_exit_code, ExitCode, UError, UResult};
use uucore::fs::{canonicalize, MissingHandling, ResolveMode};
use walkdir::WalkDir;
@ -105,6 +106,12 @@ quick_error! {
}
}
impl UError for Error {
fn code(&self) -> i32 {
EXIT_ERR
}
}
/// Continue next iteration of loop if result of expression is error
macro_rules! or_continue(
($expr:expr) => (match $expr {
@ -220,7 +227,6 @@ pub struct Options {
static ABOUT: &str = "Copy SOURCE to DEST, or multiple SOURCE(s) to DIRECTORY.";
static LONG_HELP: &str = "";
static EXIT_OK: i32 = 0;
static EXIT_ERR: i32 = 1;
fn usage() -> String {
@ -446,7 +452,8 @@ pub fn uu_app() -> App<'static, 'static> {
.multiple(true))
}
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()
.after_help(&*format!(
@ -457,11 +464,11 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.usage(&usage[..])
.get_matches_from(args);
let options = crash_if_err!(EXIT_ERR, Options::from_matches(&matches));
let options = Options::from_matches(&matches)?;
if options.overwrite == OverwriteMode::NoClobber && options.backup != BackupMode::NoBackup {
show_usage_error!("options --backup and --no-clobber are mutually exclusive");
return 1;
return Err(ExitCode(EXIT_ERR).into());
}
let paths: Vec<String> = matches
@ -469,7 +476,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.map(|v| v.map(ToString::to_string).collect())
.unwrap_or_default();
let (sources, target) = crash_if_err!(EXIT_ERR, parse_path_args(&paths, &options));
let (sources, target) = parse_path_args(&paths, &options)?;
if let Err(error) = copy(&sources, &target, &options) {
match error {
@ -479,10 +486,10 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
// Else we caught a fatal bubbled-up error, log it to stderr
_ => show_error!("{}", error),
};
return EXIT_ERR;
set_exit_code(EXIT_ERR);
}
EXIT_OK
Ok(())
}
impl ClobberMode {
@ -1124,7 +1131,7 @@ fn copy_attribute(source: &Path, dest: &Path, attribute: &Attribute) -> CopyResu
let xattrs = xattr::list(source)?;
for attr in xattrs {
if let Some(attr_value) = xattr::get(source, attr.clone())? {
crash_if_err!(EXIT_ERR, xattr::set(dest, attr, &attr_value[..]));
xattr::set(dest, attr, &attr_value[..])?;
}
}
}

View file

@ -320,18 +320,19 @@ impl<'a> SplitWriter<'a> {
let l = line?;
match n.cmp(&(&ln + 1)) {
Ordering::Less => {
if input_iter.add_line_to_buffer(ln, l).is_some() {
panic!("the buffer is big enough to contain 1 line");
}
assert!(
input_iter.add_line_to_buffer(ln, l).is_none(),
"the buffer is big enough to contain 1 line"
);
ret = Ok(());
break;
}
Ordering::Equal => {
if !self.options.suppress_matched
&& input_iter.add_line_to_buffer(ln, l).is_some()
{
panic!("the buffer is big enough to contain 1 line");
}
assert!(
self.options.suppress_matched
|| input_iter.add_line_to_buffer(ln, l).is_none(),
"the buffer is big enough to contain 1 line"
);
ret = Ok(());
break;
}
@ -378,9 +379,10 @@ impl<'a> SplitWriter<'a> {
match (self.options.suppress_matched, offset) {
// no offset, add the line to the next split
(false, 0) => {
if input_iter.add_line_to_buffer(ln, l).is_some() {
panic!("the buffer is big enough to contain 1 line");
}
assert!(
input_iter.add_line_to_buffer(ln, l).is_none(),
"the buffer is big enough to contain 1 line"
);
}
// a positive offset, some more lines need to be added to the current split
(false, _) => self.writeln(l)?,
@ -425,9 +427,10 @@ impl<'a> SplitWriter<'a> {
if !self.options.suppress_matched {
// add 1 to the buffer size to make place for the matched line
input_iter.set_size_of_buffer(offset_usize + 1);
if input_iter.add_line_to_buffer(ln, l).is_some() {
panic!("should be big enough to hold every lines");
}
assert!(
input_iter.add_line_to_buffer(ln, l).is_none(),
"should be big enough to hold every lines"
);
}
self.finish_split();
if input_iter.buffer_len() < offset_usize {

View file

@ -35,12 +35,11 @@ fn unimplemented_flags_should_error_non_linux() {
}
}
if !succeeded.is_empty() {
panic!(
"The following flags did not panic as expected: {:?}",
succeeded
);
}
assert!(
succeeded.is_empty(),
"The following flags did not panic as expected: {:?}",
succeeded
);
}
#[test]
@ -64,12 +63,11 @@ fn unimplemented_flags_should_error() {
}
}
if !succeeded.is_empty() {
panic!(
"The following flags did not panic as expected: {:?}",
succeeded
);
}
assert!(
succeeded.is_empty(),
"The following flags did not panic as expected: {:?}",
succeeded
);
}
#[test]

View file

@ -6,8 +6,6 @@
// For the full copyright and license information, please view the LICENSE file
// that was distributed with this source code.
#[macro_use]
extern crate uucore;
use uucore::error::UError;
use uucore::error::UResult;
#[cfg(unix)]

View file

@ -5,9 +5,6 @@
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
#[macro_use]
extern crate uucore;
use clap::{crate_version, App, Arg};
use std::path::Path;
use uucore::display::print_verbatim;

View file

@ -6,9 +6,6 @@
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
#[macro_use]
extern crate uucore;
use clap::{crate_version, App, Arg};
use std::io::{self, Write};
use std::iter::Peekable;

View file

@ -5,10 +5,8 @@
//* For the full copyright and license information, please view the LICENSE
//* file that was distributed with this source code.
#[macro_use]
extern crate uucore;
use clap::{crate_version, App, Arg};
use uucore::error::{UResult, USimpleError};
use uucore::InvalidEncodingHandling;
mod syntax_tree;
@ -23,7 +21,8 @@ pub fn uu_app() -> App<'static, 'static> {
.arg(Arg::with_name(HELP).long(HELP))
}
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::ConvertLossy)
.accept_any();
@ -32,13 +31,13 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
// The following usage should work without escaping hyphens: `expr -15 = 1 + 2 \* \( 3 - -4 \)`
if maybe_handle_help_or_version(&args) {
0
Ok(())
} else {
let token_strings = args[1..].to_vec();
match process_expr(&token_strings) {
Ok(expr_result) => print_expr_ok(&expr_result),
Err(expr_error) => print_expr_error(&expr_error),
Err(expr_error) => Err(USimpleError::new(2, &expr_error)),
}
}
}
@ -49,19 +48,15 @@ fn process_expr(token_strings: &[String]) -> Result<String, String> {
evaluate_ast(maybe_ast)
}
fn print_expr_ok(expr_result: &str) -> i32 {
fn print_expr_ok(expr_result: &str) -> UResult<()> {
println!("{}", expr_result);
if expr_result == "0" || expr_result.is_empty() {
1
Err(1.into())
} else {
0
Ok(())
}
}
fn print_expr_error(expr_error: &str) -> ! {
crash!(2, "{}", expr_error)
}
fn evaluate_ast(maybe_ast: Result<Box<syntax_tree::AstNode>, String>) -> Result<String, String> {
maybe_ast.and_then(|ast| ast.evaluate())
}

View file

@ -5,9 +5,6 @@
// * For the full copyright and license information, please view the LICENSE
// * file that was distributed with this source code.
#[macro_use]
extern crate uucore;
use clap::App;
use uucore::error::UResult;

View file

@ -108,6 +108,12 @@ enum Modes {
Bytes(usize),
}
impl Default for Modes {
fn default() -> Self {
Modes::Lines(10)
}
}
fn parse_mode<F>(src: &str, closure: F) -> Result<(Modes, bool), String>
where
F: FnOnce(usize) -> Modes,
@ -144,7 +150,7 @@ fn arg_iterate<'a>(
}
}
#[derive(Debug, PartialEq)]
#[derive(Debug, PartialEq, Default)]
struct HeadOptions {
pub quiet: bool,
pub verbose: bool,
@ -155,22 +161,11 @@ struct HeadOptions {
}
impl HeadOptions {
pub fn new() -> HeadOptions {
HeadOptions {
quiet: false,
verbose: false,
zeroed: false,
all_but_last: false,
mode: Modes::Lines(10),
files: Vec::new(),
}
}
///Construct options from matches
pub fn get_from(args: impl uucore::Args) -> Result<Self, String> {
let matches = uu_app().get_matches_from(arg_iterate(args)?);
let mut options = HeadOptions::new();
let mut options: HeadOptions = Default::default();
options.quiet = matches.is_present(options::QUIET_NAME);
options.verbose = matches.is_present(options::VERBOSE_NAME);
@ -197,12 +192,6 @@ impl HeadOptions {
Ok(options)
}
}
// to make clippy shut up
impl Default for HeadOptions {
fn default() -> Self {
Self::new()
}
}
fn read_n_bytes<R>(input: R, n: usize) -> std::io::Result<()>
where
@ -523,17 +512,13 @@ mod tests {
assert!(options("-c IsThisJustFantasy").is_err());
}
#[test]
#[allow(clippy::bool_comparison)]
fn test_options_correct_defaults() {
let opts = HeadOptions::new();
let opts2: HeadOptions = Default::default();
let opts: HeadOptions = Default::default();
assert_eq!(opts, opts2);
assert!(opts.verbose == false);
assert!(opts.quiet == false);
assert!(opts.zeroed == false);
assert!(opts.all_but_last == false);
assert!(!opts.verbose);
assert!(!opts.quiet);
assert!(!opts.zeroed);
assert!(!opts.all_but_last);
assert_eq!(opts.mode, Modes::Lines(10));
assert!(opts.files.is_empty());
}

View file

@ -7,9 +7,6 @@
// spell-checker:ignore (ToDO) gethostid
#[macro_use]
extern crate uucore;
use clap::{crate_version, App};
use libc::c_long;
use uucore::error::UResult;

View file

@ -7,9 +7,6 @@
// spell-checker:ignore (ToDO) MAKEWORD addrs hashset
#[macro_use]
extern crate uucore;
use std::collections::hash_set::HashSet;
use std::net::ToSocketAddrs;
use std::str;

View file

@ -158,18 +158,13 @@ fn print_signal(signal_name_or_value: &str) -> UResult<()> {
}
fn print_signals() {
let mut pos = 0;
for (idx, signal) in ALL_SIGNALS.iter().enumerate() {
pos += signal.len();
print!("{}", signal);
if idx > 0 && pos > 73 {
println!();
pos = 0;
} else {
pos += 1;
if idx > 0 {
print!(" ");
}
print!("{}", signal);
}
println!();
}
fn list(arg: Option<String>) -> UResult<()> {

View file

@ -27,6 +27,7 @@ uucore = { version = ">=0.0.8", package = "uucore", path = "../../uucore", featu
uucore_procs = { version=">=0.0.6", package = "uucore_procs", path = "../../uucore_procs" }
once_cell = "1.7.2"
atty = "0.2"
selinux = { version="0.2.1", optional = true }
[target.'cfg(unix)'.dependencies]
lazy_static = "1.4.0"
@ -35,6 +36,9 @@ lazy_static = "1.4.0"
name = "ls"
path = "src/main.rs"
[features]
feat_selinux = ["selinux"]
[package.metadata.cargo-udeps.ignore]
# Necessary for "make all"
normal = ["uucore_procs"]

View file

@ -50,6 +50,11 @@ use unicode_width::UnicodeWidthStr;
use uucore::libc::{S_IXGRP, S_IXOTH, S_IXUSR};
use uucore::{fs::display_permissions, version_cmp::version_cmp};
#[cfg(not(feature = "selinux"))]
static CONTEXT_HELP_TEXT: &str = "print any security context of each file (not enabled)";
#[cfg(feature = "selinux")]
static CONTEXT_HELP_TEXT: &str = "print any security context of each file";
fn usage() -> String {
format!("{0} [OPTION]... [FILE]...", uucore::execution_phrase())
}
@ -129,6 +134,7 @@ pub mod options {
pub static FULL_TIME: &str = "full-time";
pub static HIDE: &str = "hide";
pub static IGNORE: &str = "ignore";
pub static CONTEXT: &str = "context";
}
const DEFAULT_TERM_WIDTH: u16 = 80;
@ -239,6 +245,8 @@ struct Config {
quoting_style: QuotingStyle,
indicator_style: IndicatorStyle,
time_style: TimeStyle,
context: bool,
selinux_supported: bool,
}
// Fields that can be removed or added to the long format
@ -250,9 +258,18 @@ struct LongFormat {
numeric_uid_gid: bool,
}
struct PaddingCollection {
longest_link_count_len: usize,
longest_uname_len: usize,
longest_group_len: usize,
longest_context_len: usize,
longest_size_len: usize,
}
impl Config {
#[allow(clippy::cognitive_complexity)]
fn from(options: &clap::ArgMatches) -> UResult<Config> {
let context = options.is_present(options::CONTEXT);
let (mut format, opt) = if let Some(format_) = options.value_of(options::FORMAT) {
(
match format_ {
@ -596,6 +613,17 @@ impl Config {
quoting_style,
indicator_style,
time_style,
context,
selinux_supported: {
#[cfg(feature = "selinux")]
{
selinux::kernel_support() != selinux::KernelSupport::Unsupported
}
#[cfg(not(feature = "selinux"))]
{
false
}
},
})
}
}
@ -1157,6 +1185,12 @@ only ignore '.' and '..'.",
.overrides_with(options::FULL_TIME)
.help("like -l --time-style=full-iso"),
)
.arg(
Arg::with_name(options::CONTEXT)
.short("Z")
.long(options::CONTEXT)
.help(CONTEXT_HELP_TEXT),
)
// Positional arguments
.arg(
Arg::with_name(options::PATHS)
@ -1181,6 +1215,7 @@ struct PathData {
// PathBuf that all above data corresponds to
p_buf: PathBuf,
must_dereference: bool,
security_context: String,
}
impl PathData {
@ -1224,12 +1259,19 @@ impl PathData {
None => OnceCell::new(),
};
let security_context = if config.context {
get_security_context(config, &p_buf, must_dereference)
} else {
String::new()
};
Self {
md: OnceCell::new(),
ft,
display_name,
p_buf,
must_dereference,
security_context,
}
}
@ -1398,7 +1440,7 @@ fn get_metadata(entry: &Path, dereference: bool) -> std::io::Result<Metadata> {
}
fn display_dir_entry_size(entry: &PathData, config: &Config) -> (usize, usize, usize, usize) {
// TODO: Cache/memoize the display_* results so we don't have to recalculate them.
// TODO: Cache/memorize the display_* results so we don't have to recalculate them.
if let Some(md) = entry.md() {
(
display_symlink_count(md).len(),
@ -1411,31 +1453,40 @@ fn display_dir_entry_size(entry: &PathData, config: &Config) -> (usize, usize, u
}
}
fn pad_left(string: String, count: usize) -> String {
fn pad_left(string: &str, count: usize) -> String {
format!("{:>width$}", string, width = count)
}
fn pad_right(string: String, count: usize) -> String {
fn pad_right(string: &str, count: usize) -> String {
format!("{:<width$}", string, width = count)
}
fn display_items(items: &[PathData], config: &Config, out: &mut BufWriter<Stdout>) {
// `-Z`, `--context`:
// Display the SELinux security context or '?' if none is found. When used with the `-l`
// option, print the security context to the left of the size column.
if config.format == Format::Long {
let (
mut longest_link_count_len,
mut longest_uname_len,
mut longest_group_len,
mut longest_context_len,
mut longest_size_len,
) = (1, 1, 1, 1);
) = (1, 1, 1, 1, 1);
let mut total_size = 0;
for item in items {
let context_len = item.security_context.len();
let (link_count_len, uname_len, group_len, size_len) =
display_dir_entry_size(item, config);
longest_link_count_len = link_count_len.max(longest_link_count_len);
longest_size_len = size_len.max(longest_size_len);
longest_uname_len = uname_len.max(longest_uname_len);
longest_group_len = group_len.max(longest_group_len);
if config.context {
longest_context_len = context_len.max(longest_context_len);
}
longest_size_len = size_len.max(longest_size_len);
total_size += item.md().map_or(0, |md| get_block_size(md, config));
}
@ -1447,16 +1498,31 @@ fn display_items(items: &[PathData], config: &Config, out: &mut BufWriter<Stdout
for item in items {
display_item_long(
item,
longest_link_count_len,
longest_uname_len,
longest_group_len,
longest_size_len,
PaddingCollection {
longest_link_count_len,
longest_uname_len,
longest_group_len,
longest_context_len,
longest_size_len,
},
config,
out,
);
}
} else {
let names = items.iter().filter_map(|i| display_file_name(i, config));
let mut longest_context_len = 1;
let prefix_context = if config.context {
for item in items {
let context_len = item.security_context.len();
longest_context_len = context_len.max(longest_context_len);
}
Some(longest_context_len)
} else {
None
};
let names = items
.iter()
.filter_map(|i| display_file_name(i, config, prefix_context));
match config.format {
Format::Columns => display_grid(names, config.width, Direction::TopToBottom, out),
@ -1581,15 +1647,13 @@ fn display_grid(
/// longest_link_count_len: usize,
/// longest_uname_len: usize,
/// longest_group_len: usize,
/// longest_context_len: usize,
/// longest_size_len: usize,
/// ```
/// that decide the maximum possible character count of each field.
fn display_item_long(
item: &PathData,
longest_link_count_len: usize,
longest_uname_len: usize,
longest_group_len: usize,
longest_size_len: usize,
padding: PaddingCollection,
config: &Config,
out: &mut BufWriter<Stdout>,
) {
@ -1610,16 +1674,23 @@ fn display_item_long(
let _ = write!(
out,
"{} {}",
"{}{} {}",
display_permissions(md, true),
pad_left(display_symlink_count(md), longest_link_count_len),
if item.security_context.len() > 1 {
// GNU `ls` uses a "." character to indicate a file with a security context,
// but not other alternate access method.
"."
} else {
""
},
pad_left(&display_symlink_count(md), padding.longest_link_count_len),
);
if config.long.owner {
let _ = write!(
out,
" {}",
pad_right(display_uname(md, config), longest_uname_len)
pad_right(&display_uname(md, config), padding.longest_uname_len)
);
}
@ -1627,7 +1698,15 @@ fn display_item_long(
let _ = write!(
out,
" {}",
pad_right(display_group(md, config), longest_group_len)
pad_right(&display_group(md, config), padding.longest_group_len)
);
}
if config.context {
let _ = write!(
out,
" {}",
pad_right(&item.security_context, padding.longest_context_len)
);
}
@ -1637,19 +1716,19 @@ fn display_item_long(
let _ = write!(
out,
" {}",
pad_right(display_uname(md, config), longest_uname_len)
pad_right(&display_uname(md, config), padding.longest_uname_len)
);
}
let _ = writeln!(
out,
" {} {} {}",
pad_left(display_size_or_rdev(md, config), longest_size_len),
pad_left(&display_size_or_rdev(md, config), padding.longest_size_len),
display_date(md, config),
// unwrap is fine because it fails when metadata is not available
// but we already know that it is because it's checked at the
// start of the function.
display_file_name(item, config).unwrap().contents,
display_file_name(item, config, None).unwrap().contents,
);
}
@ -1873,21 +1952,22 @@ fn classify_file(path: &PathData) -> Option<char> {
/// * `config.indicator_style` to append specific characters to `name` using [`classify_file`].
/// * `config.format` to display symlink targets if `Format::Long`. This function is also
/// responsible for coloring symlink target names if `config.color` is specified.
/// * `config.context` to prepend security context to `name` if compiled with `feat_selinux`.
///
/// Note that non-unicode sequences in symlink targets are dealt with using
/// [`std::path::Path::to_string_lossy`].
fn display_file_name(path: &PathData, config: &Config) -> Option<Cell> {
fn display_file_name(
path: &PathData,
config: &Config,
prefix_context: Option<usize>,
) -> Option<Cell> {
// This is our return value. We start by `&path.display_name` and modify it along the way.
let mut name = escape_name(&path.display_name, &config.quoting_style);
#[cfg(unix)]
{
if config.format != Format::Long && config.inode {
name = path
.md()
.map_or_else(|| "?".to_string(), |md| get_inode(md))
+ " "
+ &name;
name = path.md().map_or_else(|| "?".to_string(), get_inode) + " " + &name;
}
}
@ -1968,6 +2048,20 @@ fn display_file_name(path: &PathData, config: &Config) -> Option<Cell> {
}
}
// Prepend the security context to the `name` and adjust `width` in order
// to get correct alignment from later calls to`display_grid()`.
if config.context {
if let Some(pad_count) = prefix_context {
let security_context = if !matches!(config.format, Format::Commas) {
pad_left(&path.security_context, pad_count)
} else {
path.security_context.to_owned()
};
name = format!("{} {}", security_context, name);
width += security_context.len() + 1;
}
}
Some(Cell {
contents: name,
width,
@ -1992,3 +2086,44 @@ fn display_symlink_count(_metadata: &Metadata) -> String {
fn display_symlink_count(metadata: &Metadata) -> String {
metadata.nlink().to_string()
}
// This returns the SELinux security context as UTF8 `String`.
// In the long term this should be changed to `OsStr`, see discussions at #2621/#2656
#[allow(unused_variables)]
fn get_security_context(config: &Config, p_buf: &Path, must_dereference: bool) -> String {
let substitute_string = "?".to_string();
if config.selinux_supported {
#[cfg(feature = "selinux")]
{
match selinux::SecurityContext::of_path(p_buf, must_dereference, false) {
Err(_r) => {
// TODO: show the actual reason why it failed
show_warning!("failed to get security context of: {}", p_buf.quote());
substitute_string
}
Ok(None) => substitute_string,
Ok(Some(context)) => {
let mut context = context.as_bytes();
if context.ends_with(&[0]) {
// TODO: replace with `strip_prefix()` when MSRV >= 1.51
context = &context[..context.len() - 1]
};
String::from_utf8(context.to_vec()).unwrap_or_else(|e| {
show_warning!(
"getting security context of: {}: {}",
p_buf.quote(),
e.to_string()
);
String::from_utf8_lossy(context).into_owned()
})
}
}
}
#[cfg(not(feature = "selinux"))]
{
substitute_string
}
} else {
substitute_string
}
}

View file

@ -8,9 +8,6 @@
// spell-checker:ignore (paths) GPGHome
#[macro_use]
extern crate uucore;
use clap::{crate_version, App, Arg};
use uucore::display::{println_verbatim, Quotable};
use uucore::error::{FromIo, UError, UResult};

View file

@ -210,7 +210,7 @@ fn reset_term(stdout: &mut std::io::Stdout) {
#[inline(always)]
fn reset_term(_: &mut usize) {}
fn more(buff: &str, mut stdout: &mut Stdout, next_file: Option<&str>, silent: bool) {
fn more(buff: &str, stdout: &mut Stdout, next_file: Option<&str>, silent: bool) {
let (cols, rows) = terminal::size().unwrap();
let lines = break_buff(buff, usize::from(cols));
@ -232,7 +232,7 @@ fn more(buff: &str, mut stdout: &mut Stdout, next_file: Option<&str>, silent: bo
code: KeyCode::Char('c'),
modifiers: KeyModifiers::CONTROL,
}) => {
reset_term(&mut stdout);
reset_term(stdout);
std::process::exit(0);
}
Event::Key(KeyEvent {

View file

@ -145,13 +145,12 @@ impl OutputInfo {
byte_size_block: usize,
print_width_block: usize,
) -> [usize; MAX_BYTES_PER_UNIT] {
if byte_size_block > MAX_BYTES_PER_UNIT {
panic!(
"{}-bits types are unsupported. Current max={}-bits.",
8 * byte_size_block,
8 * MAX_BYTES_PER_UNIT
);
}
assert!(
byte_size_block <= MAX_BYTES_PER_UNIT,
"{}-bits types are unsupported. Current max={}-bits.",
8 * byte_size_block,
8 * MAX_BYTES_PER_UNIT
);
let mut spacing = [0; MAX_BYTES_PER_UNIT];
let mut byte_size = sf.byte_size();

View file

@ -20,7 +20,7 @@ use quick_error::ResultExt;
use regex::Regex;
use std::convert::From;
use std::fs::{metadata, File};
use std::io::{stdin, stdout, BufRead, BufReader, Lines, Read, Stdout, Write};
use std::io::{stdin, stdout, BufRead, BufReader, Lines, Read, Write};
#[cfg(unix)]
use std::os::unix::fs::FileTypeExt;
@ -1036,15 +1036,16 @@ fn print_page(lines: &[FileLine], options: &OutputOptions, page: usize) -> Resul
let header = header_content(options, page);
let trailer_content = trailer_content(options);
let out = &mut stdout();
out.lock();
let out = stdout();
let mut out = out.lock();
for x in header {
out.write_all(x.as_bytes())?;
out.write_all(line_separator)?;
}
let lines_written = write_columns(lines, options, out)?;
let lines_written = write_columns(lines, options, &mut out)?;
for (index, x) in trailer_content.iter().enumerate() {
out.write_all(x.as_bytes())?;
@ -1060,7 +1061,7 @@ fn print_page(lines: &[FileLine], options: &OutputOptions, page: usize) -> Resul
fn write_columns(
lines: &[FileLine],
options: &OutputOptions,
out: &mut Stdout,
out: &mut impl Write,
) -> Result<usize, IOError> {
let line_separator = options.content_line_separator.as_bytes();

View file

@ -5,9 +5,6 @@
// * For the full copyright and license information, please view the LICENSE
// * file that was distributed with this source code.
#[macro_use]
extern crate uucore;
use clap::{crate_version, App, Arg};
use std::env;
use std::io;

View file

@ -88,30 +88,65 @@ impl FromStr for Number {
if s.starts_with('+') {
s = &s[1..];
}
let is_neg = s.starts_with('-');
match s.parse::<BigInt>() {
Ok(n) => {
// If `s` is '-0', then `parse()` returns
// `BigInt::zero()`, but we need to return
// `Number::MinusZero` instead.
if n == BigInt::zero() && s.starts_with('-') {
Ok(Number::MinusZero)
} else {
Ok(Number::BigInt(n))
match s.to_lowercase().find("0x") {
Some(i) if i <= 1 => match &s.as_bytes()[i + 2] {
b'-' | b'+' => Err(format!(
"invalid hexadecimal argument: {}\nTry '{} --help' for more information.",
s.quote(),
uucore::execution_phrase(),
)),
// TODO: hexadecimal floating point parsing (see #2660)
b'.' => Err(format!(
"NotImplemented: hexadecimal floating point numbers: {}\nTry '{} --help' for more information.",
s.quote(),
uucore::execution_phrase(),
)),
_ => {
let num = BigInt::from_str_radix(&s[i + 2..], 16)
.map_err(|_| format!(
"invalid hexadecimal argument: {}\nTry '{} --help' for more information.",
s.quote(),
uucore::execution_phrase(),
))?;
match (is_neg, num == BigInt::zero()) {
(true, true) => Ok(Number::MinusZero),
(true, false) => Ok(Number::BigInt(-num)),
(false, _) => Ok(Number::BigInt(num)),
}
}
},
Some(_) => Err(format!(
"invalid hexadecimal argument: {}\nTry '{} --help' for more information.",
s.quote(),
uucore::execution_phrase(),
)),
None => match s.parse::<BigInt>() {
Ok(n) => {
// If `s` is '-0', then `parse()` returns
// `BigInt::zero()`, but we need to return
// `Number::MinusZero` instead.
if n == BigInt::zero() && is_neg {
Ok(Number::MinusZero)
} else {
Ok(Number::BigInt(n))
}
}
}
Err(_) => match s.parse::<f64>() {
Ok(value) if value.is_nan() => Err(format!(
Err(_) => match s.parse::<f64>() {
Ok(value) if value.is_nan() => Err(format!(
"invalid 'not-a-number' argument: {}\nTry '{} --help' for more information.",
s.quote(),
uucore::execution_phrase(),
)),
Ok(value) => Ok(Number::F64(value)),
Err(_) => Err(format!(
Ok(value) => Ok(Number::F64(value)),
Err(_) => Err(format!(
"invalid floating point argument: {}\nTry '{} --help' for more information.",
s.quote(),
uucore::execution_phrase(),
)),
},
},
}
}

View file

@ -5,9 +5,6 @@
// * For the full copyright and license information, please view the LICENSE
// * file that was distributed with this source code.
#[macro_use]
extern crate uucore;
use std::thread;
use std::time::Duration;

View file

@ -825,7 +825,7 @@ impl FieldSelector {
fn parse(key: &str, global_settings: &GlobalSettings) -> UResult<Self> {
let mut from_to = key.split(',');
let (from, from_options) = Self::split_key_options(from_to.next().unwrap());
let to = from_to.next().map(|to| Self::split_key_options(to));
let to = from_to.next().map(Self::split_key_options);
let options_are_empty = from_options.is_empty() && matches!(to, None | Some((_, "")));
if options_are_empty {

View file

@ -0,0 +1,25 @@
## Benchmarking `tac`
<!-- spell-checker:ignore wikidatawiki -->
`tac` is often used to process log files in reverse chronological order, i.e. from newer towards older entries. In this case, the performance target to yield results as fast as possible, i.e. without reading in the whole file that is to be reversed line-by-line. Therefore, a sensible benchmark is to read a large log file containing N lines and measure how long it takes to produce the last K lines from that file.
Large text files can for example be found in the [Wikipedia database dumps](https://dumps.wikimedia.org/wikidatawiki/latest/), usually sized at multiple gigabytes and comprising more than 100M lines.
After you have obtained and uncompressed such a file, you need to build `tac` in release mode
```shell
$ cargo build --release --package uu_tac
```
and then you can time how it long it takes to extract the last 10M lines by running
```shell
$ /usr/bin/time ./target/release/tac wikidatawiki-20211001-pages-logging.xml | head -n10000000 >/dev/null
```
For more systematic measurements that include warm-ups, repetitions and comparisons, [Hyperfine](https://github.com/sharkdp/hyperfine) can be helpful. For example, to compare this implementation to the one provided by your distribution run
```shell
$ hyperfine "./target/release/tac wikidatawiki-20211001-pages-logging.xml | head -n10000000 >/dev/null" "/usr/bin/tac wikidatawiki-20211001-pages-logging.xml | head -n10000000 >/dev/null"
```

View file

@ -1,3 +1,5 @@
# spell-checker:ignore memmap
[package]
name = "uu_tac"
version = "0.0.7"
@ -16,6 +18,7 @@ path = "src/tac.rs"
[dependencies]
memchr = "2"
memmap2 = "0.5"
regex = "1"
clap = { version = "2.33", features = ["wrap_help"] }
uucore = { version=">=0.0.9", package="uucore", path="../../uucore" }

View file

@ -5,15 +5,19 @@
// * For the full copyright and license information, please view the LICENSE
// * file that was distributed with this source code.
// spell-checker:ignore (ToDO) sbytes slen dlen memmem
// spell-checker:ignore (ToDO) sbytes slen dlen memmem memmap Mmap mmap SIGBUS
#[macro_use]
extern crate uucore;
use clap::{crate_version, App, Arg};
use memchr::memmem;
use std::io::{stdin, stdout, BufReader, Read, Write};
use std::{fs::File, path::Path};
use memmap2::Mmap;
use std::io::{stdin, stdout, BufWriter, Read, Write};
use std::{
fs::{read, File},
path::Path,
};
use uucore::display::Quotable;
use uucore::InvalidEncodingHandling;
@ -44,9 +48,9 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
raw_separator
};
let files: Vec<String> = match matches.values_of(options::FILE) {
Some(v) => v.map(|v| v.to_owned()).collect(),
None => vec!["-".to_owned()],
let files: Vec<&str> = match matches.values_of(options::FILE) {
Some(v) => v.collect(),
None => vec!["-"],
};
tac(files, before, regex, separator)
@ -102,10 +106,11 @@ pub fn uu_app() -> App<'static, 'static> {
/// returns [`std::io::Error`].
fn buffer_tac_regex(
data: &[u8],
pattern: regex::bytes::Regex,
pattern: &regex::bytes::Regex,
before: bool,
) -> std::io::Result<()> {
let mut out = stdout();
let out = stdout();
let mut out = BufWriter::new(out.lock());
// The index of the line separator for the current line.
//
@ -171,7 +176,8 @@ fn buffer_tac_regex(
/// `separator` appears at the beginning of each line, as in
/// `"/abc/def"`.
fn buffer_tac(data: &[u8], before: bool, separator: &str) -> std::io::Result<()> {
let mut out = stdout();
let out = stdout();
let mut out = BufWriter::new(out.lock());
// The number of bytes in the line separator.
let slen = separator.as_bytes().len();
@ -208,12 +214,33 @@ fn buffer_tac(data: &[u8], before: bool, separator: &str) -> std::io::Result<()>
Ok(())
}
fn tac(filenames: Vec<String>, before: bool, regex: bool, separator: &str) -> i32 {
fn tac(filenames: Vec<&str>, before: bool, regex: bool, separator: &str) -> i32 {
let mut exit_code = 0;
for filename in &filenames {
let mut file = BufReader::new(if filename == "-" {
Box::new(stdin()) as Box<dyn Read>
let pattern = if regex {
Some(crash_if_err!(1, regex::bytes::Regex::new(separator)))
} else {
None
};
for &filename in &filenames {
let mmap;
let buf;
let data: &[u8] = if filename == "-" {
if let Some(mmap1) = try_mmap_stdin() {
mmap = mmap1;
&mmap
} else {
let mut buf1 = Vec::new();
if let Err(e) = stdin().read_to_end(&mut buf1) {
show_error!("failed to read from stdin: {}", e);
exit_code = 1;
continue;
}
buf = buf1;
&buf
}
} else {
let path = Path::new(filename);
if path.is_dir() || path.metadata().is_err() {
@ -228,29 +255,47 @@ fn tac(filenames: Vec<String>, before: bool, regex: bool, separator: &str) -> i3
exit_code = 1;
continue;
}
match File::open(path) {
Ok(f) => Box::new(f) as Box<dyn Read>,
Err(e) => {
show_error!("failed to open {} for reading: {}", filename.quote(), e);
exit_code = 1;
continue;
if let Some(mmap1) = try_mmap_path(path) {
mmap = mmap1;
&mmap
} else {
match read(path) {
Ok(buf1) => {
buf = buf1;
&buf
}
Err(e) => {
show_error!("failed to read {}: {}", filename.quote(), e);
exit_code = 1;
continue;
}
}
}
});
let mut data = Vec::new();
if let Err(e) = file.read_to_end(&mut data) {
show_error!("failed to read {}: {}", filename.quote(), e);
exit_code = 1;
continue;
};
if regex {
let pattern = crash_if_err!(1, regex::bytes::Regex::new(separator));
buffer_tac_regex(&data, pattern, before)
if let Some(pattern) = &pattern {
buffer_tac_regex(data, pattern, before)
} else {
buffer_tac(&data, before, separator)
buffer_tac(data, before, separator)
}
.unwrap_or_else(|e| crash!(1, "failed to write to stdout: {}", e));
}
exit_code
}
fn try_mmap_stdin() -> Option<Mmap> {
// SAFETY: If the file is truncated while we map it, SIGBUS will be raised
// and our process will be terminated, thus preventing access of invalid memory.
unsafe { Mmap::map(&stdin()).ok() }
}
fn try_mmap_path(path: &Path) -> Option<Mmap> {
let file = File::open(path).ok()?;
// SAFETY: If the file is truncated while we map it, SIGBUS will be raised
// and our process will be terminated, thus preventing access of invalid memory.
let mmap = unsafe { Mmap::map(&file).ok()? };
Some(mmap)
}

View file

@ -5,9 +5,6 @@
// * For the full copyright and license information, please view the LICENSE
// * file that was distributed with this source code.
#[macro_use]
extern crate uucore;
use clap::App;
use uucore::error::UResult;

View file

@ -7,9 +7,6 @@
/* last synced with: unlink (GNU coreutils) 8.21 */
#[macro_use]
extern crate uucore;
use std::fs::remove_file;
use std::path::Path;

View file

@ -11,8 +11,6 @@
use chrono::{Local, TimeZone, Utc};
use clap::{crate_version, App, Arg};
#[macro_use]
extern crate uucore;
// import crate time from utmpx
pub use uucore::libc;
use uucore::libc::time_t;

View file

@ -11,6 +11,7 @@
use std::path::Path;
use clap::{crate_version, App, Arg};
use uucore::error::UResult;
use uucore::utmpx::{self, Utmpx};
static ABOUT: &str = "Print the user names of users currently logged in to the current host";
@ -29,7 +30,8 @@ If FILE is not specified, use {}. /var/log/wtmp as FILE is common.",
)
}
pub fn uumain(args: impl uucore::Args) -> i32 {
#[uucore_procs::gen_uumain]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let usage = usage();
let after_help = get_long_usage();
@ -59,7 +61,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
println!("{}", users.join(" "));
}
0
Ok(())
}
pub fn uu_app() -> App<'static, 'static> {

View file

@ -9,8 +9,6 @@
#[macro_use]
extern crate clap;
#[macro_use]
extern crate uucore;
use clap::App;

View file

@ -12,8 +12,6 @@ use std::io::{self, Write};
#[macro_use]
extern crate clap;
#[macro_use]
extern crate uucore;
use clap::{App, Arg};
use uucore::error::{UResult, USimpleError};

View file

@ -101,7 +101,7 @@ pub fn gen_uumain(_args: TokenStream, stream: TokenStream) -> TokenStream {
Err(e) => {
let s = format!("{}", e);
if s != "" {
show_error!("{}", s);
uucore::show_error!("{}", s);
}
if e.usage() {
eprintln!("Try '{} --help' for more information.", uucore::execution_phrase());

View file

@ -113,12 +113,18 @@ fn test_wrap_bad_arg() {
#[test]
fn test_base32_extra_operand() {
let ts = TestScenario::new(util_name!());
// Expect a failure when multiple files are specified.
new_ucmd!()
ts.ucmd()
.arg("a.txt")
.arg("b.txt")
.fails()
.stderr_only("base32: extra operand 'b.txt'\nTry 'base32 --help' for more information.");
.stderr_only(format!(
"{0}: extra operand 'b.txt'\nTry '{1} {0} --help' for more information.",
ts.util_name,
ts.bin_path.to_string_lossy()
));
}
#[test]

View file

@ -95,12 +95,18 @@ fn test_wrap_bad_arg() {
#[test]
fn test_base64_extra_operand() {
let ts = TestScenario::new(util_name!());
// Expect a failure when multiple files are specified.
new_ucmd!()
ts.ucmd()
.arg("a.txt")
.arg("b.txt")
.fails()
.stderr_only("base64: extra operand 'b.txt'\nTry 'base64 --help' for more information.");
.stderr_only(format!(
"{0}: extra operand 'b.txt'\nTry '{1} {0} --help' for more information.",
ts.util_name,
ts.bin_path.to_string_lossy()
));
}
#[test]

View file

@ -15,6 +15,7 @@ fn test_missing_operand() {
#[test]
fn test_enter_chroot_fails() {
// NOTE: since #2689 this test also ensures that we don't regress #2687
let (at, mut ucmd) = at_and_ucmd!();
at.mkdir("jail");
@ -89,3 +90,26 @@ fn test_preference_of_userspec() {
println!("result.stdout = {}", result.stdout_str());
println!("result.stderr = {}", result.stderr_str());
}
#[test]
fn test_default_shell() {
// NOTE: This test intends to trigger code which can only be reached with root permissions.
let ts = TestScenario::new(util_name!());
let at = &ts.fixtures;
let dir = "CHROOT_DIR";
at.mkdir(dir);
let shell = std::env::var("SHELL").unwrap_or_else(|_| "/bin/sh".to_string());
let _expected = format!(
"chroot: failed to run command '{}': No such file or directory",
shell
);
// TODO: [2021-09; jhscheer] uncomment if/when #2692 gets merged
// if let Ok(result) = run_ucmd_as_root(&ts, &[dir]) {
// result.stderr_contains(expected);
// } else {
// print!("TEST SKIPPED");
// }
}

View file

@ -28,9 +28,7 @@ macro_rules! fixture_path {
macro_rules! assert_fixture_exists {
($fname:expr) => {{
let fpath = fixture_path!($fname);
if !fpath.exists() {
panic!("Fixture missing: {:?}", fpath);
}
assert!(fpath.exists(), "Fixture missing: {:?}", fpath);
}};
}
@ -38,9 +36,7 @@ macro_rules! assert_fixture_exists {
macro_rules! assert_fixture_not_exists {
($fname:expr) => {{
let fpath = PathBuf::from(format!("./fixtures/dd/{}", $fname));
if fpath.exists() {
panic!("Fixture present: {:?}", fpath);
}
assert!(!fpath.exists(), "Fixture present: {:?}", fpath);
}};
}

View file

@ -56,6 +56,12 @@ fn test_kill_list_all_signals() {
.stdout_contains("HUP");
}
#[test]
fn test_kill_list_final_new_line() {
let re = Regex::new("\\n$").unwrap();
assert!(re.is_match(new_ucmd!().arg("-l").succeeds().stdout_str()));
}
#[test]
fn test_kill_list_all_signals_as_table() {
// Check for a few signals. Do not try to be comprehensive.

View file

@ -347,6 +347,7 @@ fn test_ls_long_format() {
// A line of the output should be:
// One of the characters -bcCdDlMnpPsStTx?
// rwx, with - for missing permissions, thrice.
// Zero or one "." for indicating a file with security context
// A number, preceded by column whitespace, and followed by a single space.
// A username, currently [^ ], followed by column whitespace, twice (or thrice for Hurd).
// A number, followed by a single space.
@ -356,13 +357,13 @@ fn test_ls_long_format() {
// and followed by a single space.
// Whatever comes after is irrelevant to this specific test.
scene.ucmd().arg(arg).arg("test-long-dir").succeeds().stdout_matches(&Regex::new(
r"\n[-bcCdDlMnpPsStTx?]([r-][w-][xt-]){3} +\d+ [^ ]+ +[^ ]+( +[^ ]+)? +\d+ [A-Z][a-z]{2} {0,2}\d{0,2} {0,2}[0-9:]+ "
r"\n[-bcCdDlMnpPsStTx?]([r-][w-][xt-]){3}\.? +\d+ [^ ]+ +[^ ]+( +[^ ]+)? +\d+ [A-Z][a-z]{2} {0,2}\d{0,2} {0,2}[0-9:]+ "
).unwrap());
}
// This checks for the line with the .. entry. The uname and group should be digits.
scene.ucmd().arg("-lan").arg("test-long-dir").succeeds().stdout_matches(&Regex::new(
r"\nd([r-][w-][xt-]){3} +\d+ \d+ +\d+( +\d+)? +\d+ [A-Z][a-z]{2} {0,2}\d{0,2} {0,2}[0-9:]+ \.\."
r"\nd([r-][w-][xt-]){3}\.? +\d+ \d+ +\d+( +\d+)? +\d+ [A-Z][a-z]{2} {0,2}\d{0,2} {0,2}[0-9:]+ \.\."
).unwrap());
}
@ -370,6 +371,7 @@ fn test_ls_long_format() {
/// This test is mainly about coloring, but, the recursion, symlink `->` processing,
/// and `.` and `..` being present in `-a` all need to work for the test to pass.
/// This test does not really test anything provided by `-l` but the file names and symlinks.
#[cfg(all(feature = "ln", feature = "mkdir", feature = "touch"))]
#[test]
#[cfg(all(feature = "ln", feature = "mkdir", feature = "touch"))]
fn test_ls_long_symlink_color() {
@ -636,55 +638,57 @@ fn test_ls_long_formats() {
let at = &scene.fixtures;
at.touch(&at.plus_as_string("test-long-formats"));
// Zero or one "." for indicating a file with security context
// Regex for three names, so all of author, group and owner
let re_three = Regex::new(r"[xrw-]{9} \d ([-0-9_a-z]+ ){3}0").unwrap();
let re_three = Regex::new(r"[xrw-]{9}\.? \d ([-0-9_a-z]+ ){3}0").unwrap();
#[cfg(unix)]
let re_three_num = Regex::new(r"[xrw-]{9} \d (\d+ ){3}0").unwrap();
let re_three_num = Regex::new(r"[xrw-]{9}\.? \d (\d+ ){3}0").unwrap();
// Regex for two names, either:
// - group and owner
// - author and owner
// - author and group
let re_two = Regex::new(r"[xrw-]{9} \d ([-0-9_a-z]+ ){2}0").unwrap();
let re_two = Regex::new(r"[xrw-]{9}\.? \d ([-0-9_a-z]+ ){2}0").unwrap();
#[cfg(unix)]
let re_two_num = Regex::new(r"[xrw-]{9} \d (\d+ ){2}0").unwrap();
let re_two_num = Regex::new(r"[xrw-]{9}\.? \d (\d+ ){2}0").unwrap();
// Regex for one name: author, group or owner
let re_one = Regex::new(r"[xrw-]{9} \d [-0-9_a-z]+ 0").unwrap();
let re_one = Regex::new(r"[xrw-]{9}\.? \d [-0-9_a-z]+ 0").unwrap();
#[cfg(unix)]
let re_one_num = Regex::new(r"[xrw-]{9} \d \d+ 0").unwrap();
let re_one_num = Regex::new(r"[xrw-]{9}\.? \d \d+ 0").unwrap();
// Regex for no names
let re_zero = Regex::new(r"[xrw-]{9} \d 0").unwrap();
let re_zero = Regex::new(r"[xrw-]{9}\.? \d 0").unwrap();
let result = scene
scene
.ucmd()
.arg("-l")
.arg("--author")
.arg("test-long-formats")
.succeeds();
assert!(re_three.is_match(result.stdout_str()));
.succeeds()
.stdout_matches(&re_three);
let result = scene
scene
.ucmd()
.arg("-l1")
.arg("--author")
.arg("test-long-formats")
.succeeds();
assert!(re_three.is_match(result.stdout_str()));
.succeeds()
.stdout_matches(&re_three);
#[cfg(unix)]
{
let result = scene
scene
.ucmd()
.arg("-n")
.arg("--author")
.arg("test-long-formats")
.succeeds();
assert!(re_three_num.is_match(result.stdout_str()));
.succeeds()
.stdout_matches(&re_three_num);
}
for arg in &[
@ -694,22 +698,22 @@ fn test_ls_long_formats() {
"-lG --author", // only author and owner
"-l --no-group --author", // only author and owner
] {
let result = scene
scene
.ucmd()
.args(&arg.split(' ').collect::<Vec<_>>())
.arg("test-long-formats")
.succeeds();
assert!(re_two.is_match(result.stdout_str()));
.succeeds()
.stdout_matches(&re_two);
#[cfg(unix)]
{
let result = scene
scene
.ucmd()
.arg("-n")
.args(&arg.split(' ').collect::<Vec<_>>())
.arg("test-long-formats")
.succeeds();
assert!(re_two_num.is_match(result.stdout_str()));
.succeeds()
.stdout_matches(&re_two_num);
}
}
@ -723,22 +727,22 @@ fn test_ls_long_formats() {
"-l --no-group", // only owner
"-gG --author", // only author
] {
let result = scene
scene
.ucmd()
.args(&arg.split(' ').collect::<Vec<_>>())
.arg("test-long-formats")
.succeeds();
assert!(re_one.is_match(result.stdout_str()));
.succeeds()
.stdout_matches(&re_one);
#[cfg(unix)]
{
let result = scene
scene
.ucmd()
.arg("-n")
.args(&arg.split(' ').collect::<Vec<_>>())
.arg("test-long-formats")
.succeeds();
assert!(re_one_num.is_match(result.stdout_str()));
.succeeds()
.stdout_matches(&re_one_num);
}
}
@ -755,22 +759,22 @@ fn test_ls_long_formats() {
"-og1",
"-og1l",
] {
let result = scene
scene
.ucmd()
.args(&arg.split(' ').collect::<Vec<_>>())
.arg("test-long-formats")
.succeeds();
assert!(re_zero.is_match(result.stdout_str()));
.succeeds()
.stdout_matches(&re_zero);
#[cfg(unix)]
{
let result = scene
scene
.ucmd()
.arg("-n")
.args(&arg.split(' ').collect::<Vec<_>>())
.arg("test-long-formats")
.succeeds();
assert!(re_zero.is_match(result.stdout_str()));
.succeeds()
.stdout_matches(&re_zero);
}
}
}
@ -1248,7 +1252,7 @@ fn test_ls_inode() {
at.touch(file);
let re_short = Regex::new(r" *(\d+) test_inode").unwrap();
let re_long = Regex::new(r" *(\d+) [xrw-]{10} \d .+ test_inode").unwrap();
let re_long = Regex::new(r" *(\d+) [xrw-]{10}\.? \d .+ test_inode").unwrap();
let result = scene.ucmd().arg("test_inode").arg("-i").succeeds();
assert!(re_short.is_match(result.stdout_str()));
@ -2272,3 +2276,68 @@ fn test_ls_dangling_symlinks() {
.succeeds() // this should fail, though at the moment, ls lacks a way to propagate errors encountered during display
.stdout_contains(if cfg!(windows) { "dangle" } else { "? dangle" });
}
#[test]
#[cfg(feature = "feat_selinux")]
fn test_ls_context1() {
use selinux::{self, KernelSupport};
if selinux::kernel_support() == KernelSupport::Unsupported {
println!("test skipped: Kernel has no support for SElinux context",);
return;
}
let file = "test_ls_context_file";
let expected = format!("unconfined_u:object_r:user_tmp_t:s0 {}\n", file);
let (at, mut ucmd) = at_and_ucmd!();
at.touch(file);
ucmd.args(&["-Z", file]).succeeds().stdout_is(expected);
}
#[test]
#[cfg(feature = "feat_selinux")]
fn test_ls_context2() {
use selinux::{self, KernelSupport};
if selinux::kernel_support() == KernelSupport::Unsupported {
println!("test skipped: Kernel has no support for SElinux context",);
return;
}
let ts = TestScenario::new(util_name!());
for c_flag in &["-Z", "--context"] {
ts.ucmd()
.args(&[c_flag, &"/"])
.succeeds()
.stdout_only(unwrap_or_return!(expected_result(&ts, &[c_flag, &"/"])).stdout_str());
}
}
#[test]
#[cfg(feature = "feat_selinux")]
fn test_ls_context_format() {
use selinux::{self, KernelSupport};
if selinux::kernel_support() == KernelSupport::Unsupported {
println!("test skipped: Kernel has no support for SElinux context",);
return;
}
let ts = TestScenario::new(util_name!());
// NOTE:
// --format=long/verbose matches the output of GNU's ls for --context
// except for the size count which may differ to the size count reported by GNU's ls.
for word in &[
"across",
"commas",
"horizontal",
// "long",
"single-column",
// "verbose",
"vertical",
] {
let format = format!("--format={}", word);
ts.ucmd()
.args(&[&"-Z", &format.as_str(), &"/"])
.succeeds()
.stdout_only(
unwrap_or_return!(expected_result(&ts, &[&"-Z", &format.as_str(), &"/"]))
.stdout_str(),
);
}
}

View file

@ -1,3 +1,4 @@
// spell-checker:ignore abcdefghijklmnopqrstuvwxyz
// * This file is part of the uutils coreutils package.
// *
// * For the full copyright and license information, please view the LICENSE
@ -35,9 +36,10 @@ fn test_file() {
{
let mut f = File::create(&file).unwrap();
// spell-checker:disable-next-line
if f.write_all(b"abcdefghijklmnopqrstuvwxyz\n").is_err() {
panic!("Test setup failed - could not write file");
}
assert!(
!f.write_all(b"abcdefghijklmnopqrstuvwxyz\n").is_err(),
"Test setup failed - could not write file"
);
}
new_ucmd!()
@ -75,9 +77,10 @@ fn test_2files() {
// spell-checker:disable-next-line
for &(path, data) in &[(&file1, "abcdefghijklmnop"), (&file2, "qrstuvwxyz\n")] {
let mut f = File::create(&path).unwrap();
if f.write_all(data.as_bytes()).is_err() {
panic!("Test setup failed - could not write file");
}
assert!(
!f.write_all(data.as_bytes()).is_err(),
"Test setup failed - could not write file"
);
}
new_ucmd!()
@ -126,9 +129,10 @@ fn test_from_mixed() {
let (data1, data2, data3) = ("abcdefg", "hijklmnop", "qrstuvwxyz\n");
for &(path, data) in &[(&file1, data1), (&file3, data3)] {
let mut f = File::create(&path).unwrap();
if f.write_all(data.as_bytes()).is_err() {
panic!("Test setup failed - could not write file");
}
assert!(
!f.write_all(data.as_bytes()).is_err(),
"Test setup failed - could not write file"
);
}
new_ucmd!()

View file

@ -1,6 +1,69 @@
use crate::common::util::*;
use std::io::Read;
#[test]
fn test_hex_rejects_sign_after_identifier() {
new_ucmd!()
.args(&["0x-123ABC"])
.fails()
.no_stdout()
.stderr_contains("invalid hexadecimal argument: '0x-123ABC'")
.stderr_contains("for more information.");
new_ucmd!()
.args(&["0x+123ABC"])
.fails()
.no_stdout()
.stderr_contains("invalid hexadecimal argument: '0x+123ABC'")
.stderr_contains("for more information.");
new_ucmd!()
.args(&["-0x-123ABC"])
.fails()
.no_stdout()
.stderr_contains("invalid hexadecimal argument: '-0x-123ABC'")
.stderr_contains("for more information.");
new_ucmd!()
.args(&["-0x+123ABC"])
.fails()
.no_stdout()
.stderr_contains("invalid hexadecimal argument: '-0x+123ABC'")
.stderr_contains("for more information.");
}
#[test]
fn test_hex_lowercase_uppercase() {
new_ucmd!()
.args(&["0xa", "0xA"])
.succeeds()
.stdout_is("10\n");
new_ucmd!()
.args(&["0Xa", "0XA"])
.succeeds()
.stdout_is("10\n");
}
#[test]
fn test_hex_big_number() {
new_ucmd!()
.args(&[
"0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF",
"0x100000000000000000000000000000000",
])
.succeeds()
.stdout_is(
"340282366920938463463374607431768211455\n340282366920938463463374607431768211456\n",
);
}
#[test]
fn test_hex_identifier_in_wrong_place() {
new_ucmd!()
.args(&["1234ABCD0x"])
.fails()
.no_stdout()
.stderr_contains("invalid hexadecimal argument: '1234ABCD0x'")
.stderr_contains("for more information.");
}
#[test]
fn test_rejects_nan() {
let ts = TestScenario::new(util_name!());

View file

@ -163,25 +163,23 @@ impl CmdResult {
/// asserts that the command resulted in a success (zero) status code
pub fn success(&self) -> &CmdResult {
if !self.success {
panic!(
"Command was expected to succeed.\nstdout = {}\n stderr = {}",
self.stdout_str(),
self.stderr_str()
);
}
assert!(
self.success,
"Command was expected to succeed.\nstdout = {}\n stderr = {}",
self.stdout_str(),
self.stderr_str()
);
self
}
/// asserts that the command resulted in a failure (non-zero) status code
pub fn failure(&self) -> &CmdResult {
if self.success {
panic!(
"Command was expected to fail.\nstdout = {}\n stderr = {}",
self.stdout_str(),
self.stderr_str()
);
}
assert!(
!self.success,
"Command was expected to fail.\nstdout = {}\n stderr = {}",
self.stdout_str(),
self.stderr_str()
);
self
}
@ -197,12 +195,11 @@ impl CmdResult {
/// 1. you can not know exactly what stdout will be or
/// 2. you know that stdout will also be empty
pub fn no_stderr(&self) -> &CmdResult {
if !self.stderr.is_empty() {
panic!(
"Expected stderr to be empty, but it's:\n{}",
self.stderr_str()
);
}
assert!(
self.stderr.is_empty(),
"Expected stderr to be empty, but it's:\n{}",
self.stderr_str()
);
self
}
@ -213,12 +210,11 @@ impl CmdResult {
/// 1. you can not know exactly what stderr will be or
/// 2. you know that stderr will also be empty
pub fn no_stdout(&self) -> &CmdResult {
if !self.stdout.is_empty() {
panic!(
"Expected stdout to be empty, but it's:\n{}",
self.stderr_str()
);
}
assert!(
self.stdout.is_empty(),
"Expected stdout to be empty, but it's:\n{}",
self.stderr_str()
);
self
}
@ -868,9 +864,7 @@ impl UCommand {
/// Add a parameter to the invocation. Path arguments are treated relative
/// to the test environment directory.
pub fn arg<S: AsRef<OsStr>>(&mut self, arg: S) -> &mut UCommand {
if self.has_run {
panic!("{}", ALREADY_RUN);
}
assert!(!self.has_run, ALREADY_RUN);
self.comm_string.push(' ');
self.comm_string
.push_str(arg.as_ref().to_str().unwrap_or_default());
@ -881,9 +875,7 @@ impl UCommand {
/// Add multiple parameters to the invocation. Path arguments are treated relative
/// to the test environment directory.
pub fn args<S: AsRef<OsStr>>(&mut self, args: &[S]) -> &mut UCommand {
if self.has_run {
panic!("{}", MULTIPLE_STDIN_MEANINGLESS);
}
assert!(!self.has_run, MULTIPLE_STDIN_MEANINGLESS);
let strings = args
.iter()
.map(|s| s.as_ref().to_os_string())
@ -901,9 +893,7 @@ impl UCommand {
/// provides standard input to feed in to the command when spawned
pub fn pipe_in<T: Into<Vec<u8>>>(&mut self, input: T) -> &mut UCommand {
if self.bytes_into_stdin.is_some() {
panic!("{}", MULTIPLE_STDIN_MEANINGLESS);
}
assert!(!self.bytes_into_stdin.is_some(), MULTIPLE_STDIN_MEANINGLESS);
self.bytes_into_stdin = Some(input.into());
self
}
@ -918,9 +908,7 @@ impl UCommand {
/// This is typically useful to test non-standard workflows
/// like feeding something to a command that does not read it
pub fn ignore_stdin_write_error(&mut self) -> &mut UCommand {
if self.bytes_into_stdin.is_none() {
panic!("{}", NO_STDIN_MEANINGLESS);
}
assert!(!self.bytes_into_stdin.is_none(), NO_STDIN_MEANINGLESS);
self.ignore_stdin_write_error = true;
self
}
@ -930,9 +918,7 @@ impl UCommand {
K: AsRef<OsStr>,
V: AsRef<OsStr>,
{
if self.has_run {
panic!("{}", ALREADY_RUN);
}
assert!(!self.has_run, ALREADY_RUN);
self.raw.env(key, val);
self
}
@ -951,9 +937,7 @@ impl UCommand {
/// Spawns the command, feeds the stdin if any, and returns the
/// child process immediately.
pub fn run_no_wait(&mut self) -> Child {
if self.has_run {
panic!("{}", ALREADY_RUN);
}
assert!(!self.has_run, ALREADY_RUN);
self.has_run = true;
log_info("run", &self.comm_string);
let mut child = self
@ -1169,7 +1153,7 @@ pub fn check_coreutil_version(
if s.contains(&format!("(GNU coreutils) {}", version_expected)) {
Ok(format!("{}: {}", UUTILS_INFO, s.to_string()))
} else if s.contains("(GNU coreutils)") {
let version_found = s.split_whitespace().last().unwrap()[..4].parse::<f32>().unwrap_or_default();
let version_found = parse_coreutil_version(s);
let version_expected = version_expected.parse::<f32>().unwrap_or_default();
if version_found > version_expected {
Ok(format!("{}: version for the reference coreutil '{}' is higher than expected; expected: {}, found: {}", UUTILS_INFO, util_name, version_expected, version_found))
@ -1182,6 +1166,20 @@ pub fn check_coreutil_version(
)
}
// simple heuristic to parse the coreutils SemVer string, e.g. "id (GNU coreutils) 8.32.263-0475"
fn parse_coreutil_version(version_string: &str) -> f32 {
version_string
.split_whitespace()
.last()
.unwrap()
.split('.')
.take(2)
.collect::<Vec<_>>()
.join(".")
.parse::<f32>()
.unwrap_or_default()
}
/// This runs the GNU coreutils `util_name` binary in `$PATH` in order to
/// dynamically gather reference values on the system.
/// If the `util_name` in `$PATH` doesn't include a coreutils version string,
@ -1474,6 +1472,36 @@ mod tests {
res.normalized_newlines_stdout_is("A\r\nB\nC\n");
}
#[test]
#[cfg(unix)]
fn test_parse_coreutil_version() {
use std::assert_eq;
assert_eq!(
parse_coreutil_version("id (GNU coreutils) 9.0.123-0123").to_string(),
"9"
);
assert_eq!(
parse_coreutil_version("id (GNU coreutils) 8.32.263-0475").to_string(),
"8.32"
);
assert_eq!(
parse_coreutil_version("id (GNU coreutils) 8.25.123-0123").to_string(),
"8.25"
);
assert_eq!(
parse_coreutil_version("id (GNU coreutils) 9.0").to_string(),
"9"
);
assert_eq!(
parse_coreutil_version("id (GNU coreutils) 8.32").to_string(),
"8.32"
);
assert_eq!(
parse_coreutil_version("id (GNU coreutils) 8.25").to_string(),
"8.25"
);
}
#[test]
#[cfg(unix)]
fn test_check_coreutil_version() {

View file

@ -5,12 +5,12 @@
set -e
if test ! -d ../gnu; then
echo "Could not find ../gnu"
echo "git clone git@github.com:coreutils/coreutils.git gnu"
echo "git clone https://github.com:coreutils/coreutils.git gnu"
exit 1
fi
if test ! -d ../gnulib; then
echo "Could not find ../gnulib"
echo "git clone git@github.com:coreutils/gnulib.git gnulib"
echo "git clone https://github.com/coreutils/gnulib.git gnulib"
exit 1
fi