From d1d57a41fee4cfeb50650649ddafd24e15a6f4ff Mon Sep 17 00:00:00 2001 From: Hanif Bin Ariffin Date: Sun, 13 Jun 2021 08:13:54 +0800 Subject: [PATCH 001/206] Add touch as a common core utility Found out that touch wasn't compiled in what I assume in ordinary Ubuntu Linux setup. ``` ./+o+- hbina@komputa yyyyy- -yyyyyy+ OS: Ubuntu 20.04 focal ://+//////-yyyyyyo Kernel: x86_64 Linux 5.12.9-051209-generic .++ .:/++++++/-.+sss/` Uptime: 1h 5m .:++o: /++++++++/:--:/- Packages: 2241 o:+o+:++.`..```.-/oo+++++/ Shell: bash 5.0.17 .:+o:+o/. `+sssoo+/ Resolution: 1920x1080 .++/+:+oo+o:` /sssooo. DE: GNOME 3.36.5 /+++//+:`oo+o /::--:. WM: Mutter \+/+o+++`o++o ++////. WM Theme: Adwaita .++.o+++oo+:` /dddhhh. GTK Theme: Yaru [GTK2/3] .+.o+oo:. `oddhhhh+ Icon Theme: ubuntu-mono-dark \+.++o+o``-````.:ohdhhhhh+ Font: Cantarell 11 `:o+++ `ohhhhhhhhyo++os: Disk: 142G / 475G (32%) .o:`.syhhhhhhh/.oo++o` CPU: AMD Ryzen 7 4800U with Radeon Graphics @ 16x 1.8GHz /osyyyyyyo++ooo+++/ GPU: AMD/ATI ````` +oo+++o\: RAM: 9370MiB / 31584MiB `oo++. ``` Signed-off-by: Hanif Bin Ariffin --- Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.toml b/Cargo.toml index 804c5f978..2fc8f3ba9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -90,6 +90,7 @@ feat_common_core = [ "true", "truncate", "tsort", + "touch", "unexpand", "uniq", "wc", From d8d71cc4779310ce649a45da6e39a18459131f60 Mon Sep 17 00:00:00 2001 From: Daniel Rocco Date: Sat, 24 Jul 2021 20:49:24 -0400 Subject: [PATCH 002/206] test: handle additional parentheses edge cases MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Handle additional edge cases arising from test(1)’s lack of reserved words. For example, left parenthesis followed by an operator could indicate either * string comparison of a literal left parenthesis, e.g. `( = foo` * parenthesized expression using an operator as a literal, e.g. `( = != foo )` --- src/uu/test/src/parser.rs | 142 ++++++++++++++++++++++++++-------- src/uu/test/src/test.rs | 12 +-- tests/by-util/test_test.rs | 154 +++++++++++++++++++++++++++++++++---- 3 files changed, 256 insertions(+), 52 deletions(-) diff --git a/src/uu/test/src/parser.rs b/src/uu/test/src/parser.rs index 5eec781ba..7d06035d5 100644 --- a/src/uu/test/src/parser.rs +++ b/src/uu/test/src/parser.rs @@ -10,6 +10,21 @@ use std::ffi::OsString; use std::iter::Peekable; +/// Represents one of the binary comparison operators for strings, integers, or files +#[derive(Debug, PartialEq)] +pub enum Op { + StringOp(OsString), + IntOp(OsString), + FileOp(OsString), +} + +/// Represents one of the unary test operators for strings or files +#[derive(Debug, PartialEq)] +pub enum UnaryOp { + StrlenOp(OsString), + FiletestOp(OsString), +} + /// Represents a parsed token from a test expression #[derive(Debug, PartialEq)] pub enum Symbol { @@ -17,11 +32,8 @@ pub enum Symbol { Bang, BoolOp(OsString), Literal(OsString), - StringOp(OsString), - IntOp(OsString), - FileOp(OsString), - StrlenOp(OsString), - FiletestOp(OsString), + Op(Op), + UnaryOp(UnaryOp), None, } @@ -35,12 +47,14 @@ impl Symbol { "(" => Symbol::LParen, "!" => Symbol::Bang, "-a" | "-o" => Symbol::BoolOp(s), - "=" | "==" | "!=" => Symbol::StringOp(s), - "-eq" | "-ge" | "-gt" | "-le" | "-lt" | "-ne" => Symbol::IntOp(s), - "-ef" | "-nt" | "-ot" => Symbol::FileOp(s), - "-n" | "-z" => Symbol::StrlenOp(s), + "=" | "==" | "!=" => Symbol::Op(Op::StringOp(s)), + "-eq" | "-ge" | "-gt" | "-le" | "-lt" | "-ne" => Symbol::Op(Op::IntOp(s)), + "-ef" | "-nt" | "-ot" => Symbol::Op(Op::FileOp(s)), + "-n" | "-z" => Symbol::UnaryOp(UnaryOp::StrlenOp(s)), "-b" | "-c" | "-d" | "-e" | "-f" | "-g" | "-G" | "-h" | "-k" | "-L" | "-O" - | "-p" | "-r" | "-s" | "-S" | "-t" | "-u" | "-w" | "-x" => Symbol::FiletestOp(s), + | "-p" | "-r" | "-s" | "-S" | "-t" | "-u" | "-w" | "-x" => { + Symbol::UnaryOp(UnaryOp::FiletestOp(s)) + } _ => Symbol::Literal(s), }, None => Symbol::None, @@ -60,11 +74,11 @@ impl Symbol { Symbol::Bang => OsString::from("!"), Symbol::BoolOp(s) | Symbol::Literal(s) - | Symbol::StringOp(s) - | Symbol::IntOp(s) - | Symbol::FileOp(s) - | Symbol::StrlenOp(s) - | Symbol::FiletestOp(s) => s, + | Symbol::Op(Op::StringOp(s)) + | Symbol::Op(Op::IntOp(s)) + | Symbol::Op(Op::FileOp(s)) + | Symbol::UnaryOp(UnaryOp::StrlenOp(s)) + | Symbol::UnaryOp(UnaryOp::FiletestOp(s)) => s, Symbol::None => panic!(), }) } @@ -78,7 +92,6 @@ impl Symbol { /// /// EXPR → TERM | EXPR BOOLOP EXPR /// TERM → ( EXPR ) -/// TERM → ( ) /// TERM → ! EXPR /// TERM → UOP str /// UOP → STRLEN | FILETEST @@ -113,6 +126,20 @@ impl Parser { Symbol::new(self.tokens.next()) } + /// Consume the next token & verify that it matches the provided value. + /// + /// # Panics + /// + /// Panics if the next token does not match the provided value. + /// + /// TODO: remove panics and convert Parser to return error messages. + fn expect(&mut self, value: &str) { + match self.next_token() { + Symbol::Literal(s) if s == value => (), + _ => panic!("expected ‘{}’", value), + } + } + /// Peek at the next token from the input stream, returning it as a Symbol. /// The stream is unchanged and will return the same Symbol on subsequent /// calls to `next()` or `peek()`. @@ -144,8 +171,7 @@ impl Parser { match symbol { Symbol::LParen => self.lparen(), Symbol::Bang => self.bang(), - Symbol::StrlenOp(_) => self.uop(symbol), - Symbol::FiletestOp(_) => self.uop(symbol), + Symbol::UnaryOp(_) => self.uop(symbol), Symbol::None => self.stack.push(symbol), literal => self.literal(literal), } @@ -154,21 +180,75 @@ impl Parser { /// Parse a (possibly) parenthesized expression. /// /// test has no reserved keywords, so "(" will be interpreted as a literal - /// if it is followed by nothing or a comparison operator OP. + /// in certain cases: + /// + /// * when found at the end of the token stream + /// * when followed by a binary operator that is not _itself_ interpreted + /// as a literal + /// fn lparen(&mut self) { - match self.peek() { - // lparen is a literal when followed by nothing or comparison - Symbol::None | Symbol::StringOp(_) | Symbol::IntOp(_) | Symbol::FileOp(_) => { + // Look ahead up to 3 tokens to determine if the lparen is being used + // as a grouping operator or should be treated as a literal string + let peek3: Vec = self + .tokens + .clone() + .take(3) + .map(|token| Symbol::new(Some(token))) + .collect(); + + match peek3.as_slice() { + // case 1: lparen is a literal when followed by nothing + [] => self.literal(Symbol::LParen.into_literal()), + + // case 2: error if end of stream is `( ` + [symbol] => { + eprintln!("test: missing argument after ‘{:?}’", symbol); + std::process::exit(2); + } + + // case 3: `( uop )` → parenthesized unary operation; + // this case ensures we don’t get confused by `( -f ) )` + // or `( -f ( )`, for example + [Symbol::UnaryOp(_), _, Symbol::Literal(s)] if s == ")" => { + let symbol = self.next_token(); + self.uop(symbol); + self.expect(")"); + } + + // case 4: binary comparison of literal lparen, e.g. `( != )` + [Symbol::Op(_), Symbol::Literal(s)] | [Symbol::Op(_), Symbol::Literal(s), _] + if s == ")" => + { self.literal(Symbol::LParen.into_literal()); } - // empty parenthetical - Symbol::Literal(s) if s == ")" => {} + + // case 5: after handling the prior cases, any single token inside + // parentheses is a literal, e.g. `( -f )` + [_, Symbol::Literal(s)] | [_, Symbol::Literal(s), _] if s == ")" => { + let symbol = self.next_token(); + self.literal(symbol); + self.expect(")"); + } + + // case 6: two binary ops in a row, treat the first op as a literal + [Symbol::Op(_), Symbol::Op(_), _] => { + let symbol = self.next_token(); + self.literal(symbol); + self.expect(")"); + } + + // case 7: if earlier cases didn’t match, `( op …` + // indicates binary comparison of literal lparen with + // anything _except_ ")" (case 4) + [Symbol::Op(_), _] | [Symbol::Op(_), _, _] => { + self.literal(Symbol::LParen.into_literal()); + } + + // Otherwise, lparen indicates the start of a parenthesized + // expression _ => { self.expr(); - match self.next_token() { - Symbol::Literal(s) if s == ")" => (), - _ => panic!("expected ')'"), - } + self.expect(")"); } } } @@ -192,7 +272,7 @@ impl Parser { /// fn bang(&mut self) { match self.peek() { - Symbol::StringOp(_) | Symbol::IntOp(_) | Symbol::FileOp(_) | Symbol::BoolOp(_) => { + Symbol::Op(_) | Symbol::BoolOp(_) => { // we need to peek ahead one more token to disambiguate the first // three cases listed above let peek2 = Symbol::new(self.tokens.clone().nth(1)); @@ -200,7 +280,7 @@ impl Parser { match peek2 { // case 1: `! ` // case 3: `! = OP str` - Symbol::StringOp(_) | Symbol::None => { + Symbol::Op(_) | Symbol::None => { // op is literal let op = self.next_token().into_literal(); self.literal(op); @@ -294,7 +374,7 @@ impl Parser { // EXPR → str OP str match self.peek() { - Symbol::StringOp(_) | Symbol::IntOp(_) | Symbol::FileOp(_) => { + Symbol::Op(_) => { let op = self.next_token(); match self.next_token() { diff --git a/src/uu/test/src/test.rs b/src/uu/test/src/test.rs index bed1472e2..b1367b6ed 100644 --- a/src/uu/test/src/test.rs +++ b/src/uu/test/src/test.rs @@ -11,7 +11,7 @@ mod parser; use clap::{crate_version, App, AppSettings}; -use parser::{parse, Symbol}; +use parser::{parse, Op, Symbol, UnaryOp}; use std::ffi::{OsStr, OsString}; use std::path::Path; use uucore::executable; @@ -160,19 +160,19 @@ fn eval(stack: &mut Vec) -> Result { Ok(!result) } - Some(Symbol::StringOp(op)) => { + Some(Symbol::Op(Op::StringOp(op))) => { let b = stack.pop(); let a = stack.pop(); Ok(if op == "!=" { a != b } else { a == b }) } - Some(Symbol::IntOp(op)) => { + Some(Symbol::Op(Op::IntOp(op))) => { let b = pop_literal!(); let a = pop_literal!(); Ok(integers(&a, &b, &op)?) } - Some(Symbol::FileOp(_op)) => unimplemented!(), - Some(Symbol::StrlenOp(op)) => { + Some(Symbol::Op(Op::FileOp(_op))) => unimplemented!(), + Some(Symbol::UnaryOp(UnaryOp::StrlenOp(op))) => { let s = match stack.pop() { Some(Symbol::Literal(s)) => s, Some(Symbol::None) => OsString::from(""), @@ -190,7 +190,7 @@ fn eval(stack: &mut Vec) -> Result { !s.is_empty() }) } - Some(Symbol::FiletestOp(op)) => { + Some(Symbol::UnaryOp(UnaryOp::FiletestOp(op))) => { let op = op.to_string_lossy(); let f = pop_literal!(); diff --git a/tests/by-util/test_test.rs b/tests/by-util/test_test.rs index 79c24651a..f13795e3f 100644 --- a/tests/by-util/test_test.rs +++ b/tests/by-util/test_test.rs @@ -35,6 +35,35 @@ fn test_solo_and_or_or_is_a_literal() { new_ucmd!().arg("-o").succeeds(); } +#[test] +fn test_some_literals() { + let scenario = TestScenario::new(util_name!()); + let tests = [ + "a string", + "(", + ")", + "-", + "--", + "-0", + "-f", + "--help", + "--version", + "-eq", + "-lt", + "-ef", + "[", + ]; + + for test in &tests { + scenario.ucmd().arg(test).succeeds(); + } + + // run the inverse of all these tests + for test in &tests { + scenario.ucmd().arg("!").arg(test).run().status_code(1); + } +} + #[test] fn test_double_not_is_false() { new_ucmd!().args(&["!", "!"]).run().status_code(1); @@ -99,21 +128,6 @@ fn test_zero_len_of_empty() { new_ucmd!().args(&["-z", ""]).succeeds(); } -#[test] -fn test_solo_parenthesis_is_literal() { - let scenario = TestScenario::new(util_name!()); - let tests = [["("], [")"]]; - - for test in &tests { - scenario.ucmd().args(&test[..]).succeeds(); - } -} - -#[test] -fn test_solo_empty_parenthetical_is_error() { - new_ucmd!().args(&["(", ")"]).run().status_code(2); -} - #[test] fn test_zero_len_equals_zero_len() { new_ucmd!().args(&["", "=", ""]).succeeds(); @@ -139,6 +153,7 @@ fn test_string_comparison() { ["contained\nnewline", "=", "contained\nnewline"], ["(", "=", "("], ["(", "!=", ")"], + ["(", "!=", "="], ["!", "=", "!"], ["=", "=", "="], ]; @@ -199,11 +214,13 @@ fn test_a_bunch_of_not() { #[test] fn test_pseudofloat_equal() { + // string comparison; test(1) doesn't support comparison of actual floats new_ucmd!().args(&["123.45", "=", "123.45"]).succeeds(); } #[test] fn test_pseudofloat_not_equal() { + // string comparison; test(1) doesn't support comparison of actual floats new_ucmd!().args(&["123.45", "!=", "123.450"]).succeeds(); } @@ -230,6 +247,16 @@ fn test_some_int_compares() { for test in &tests { scenario.ucmd().args(&test[..]).succeeds(); } + + // run the inverse of all these tests + for test in &tests { + scenario + .ucmd() + .arg("!") + .args(&test[..]) + .run() + .status_code(1); + } } #[test] @@ -257,6 +284,16 @@ fn test_negative_int_compare() { for test in &tests { scenario.ucmd().args(&test[..]).succeeds(); } + + // run the inverse of all these tests + for test in &tests { + scenario + .ucmd() + .arg("!") + .args(&test[..]) + .run() + .status_code(1); + } } #[test] @@ -497,6 +534,93 @@ fn test_file_is_not_sticky() { .status_code(1); } +#[test] +fn test_solo_empty_parenthetical_is_error() { + new_ucmd!().args(&["(", ")"]).run().status_code(2); +} + +#[test] +fn test_parenthesized_literal() { + let scenario = TestScenario::new(util_name!()); + let tests = [ + "a string", + "(", + ")", + "-", + "--", + "-0", + "-f", + "--help", + "--version", + "-e", + "-t", + "!", + "-n", + "-z", + "[", + "-a", + "-o", + ]; + + for test in &tests { + scenario.ucmd().arg("(").arg(test).arg(")").succeeds(); + } + + // run the inverse of all these tests + for test in &tests { + scenario + .ucmd() + .arg("!") + .arg("(") + .arg(test) + .arg(")") + .run() + .status_code(1); + } +} + +#[test] +fn test_parenthesized_op_compares_literal_parenthesis() { + // ensure we aren’t treating this case as “string length of literal equal + // sign” + new_ucmd!().args(&["(", "=", ")"]).run().status_code(1); +} + +#[test] +fn test_parenthesized_string_comparison() { + let scenario = TestScenario::new(util_name!()); + let tests = [ + ["(", "foo", "!=", "bar", ")"], + ["(", "contained\nnewline", "=", "contained\nnewline", ")"], + ["(", "(", "=", "(", ")"], + ["(", "(", "!=", ")", ")"], + ["(", "!", "=", "!", ")"], + ["(", "=", "=", "=", ")"], + ]; + + for test in &tests { + scenario.ucmd().args(&test[..]).succeeds(); + } + + // run the inverse of all these tests + for test in &tests { + scenario + .ucmd() + .arg("!") + .args(&test[..]) + .run() + .status_code(1); + } +} + +#[test] +fn test_parenthesized_right_parenthesis_as_literal() { + new_ucmd!() + .args(&["(", "-f", ")", ")"]) + .run() + .status_code(1); +} + #[test] #[cfg(not(windows))] fn test_file_owned_by_euid() { From 0d348f05f25b614b4c35af8a0e68156dadaed71a Mon Sep 17 00:00:00 2001 From: Jeffrey Finkelstein Date: Fri, 30 Jul 2021 11:06:55 -0400 Subject: [PATCH 003/206] basename: handle paths comprising only slashes Fix an issue where `basename` would print the empty path when provided an input comprising only slashes (for example, "/" or "//" or "///"). Now it correctly prints the path "/". --- src/uu/basename/src/basename.rs | 8 +++++++- tests/by-util/test_basename.rs | 27 +++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/src/uu/basename/src/basename.rs b/src/uu/basename/src/basename.rs index 5450ee3f2..d9447d066 100644 --- a/src/uu/basename/src/basename.rs +++ b/src/uu/basename/src/basename.rs @@ -119,9 +119,15 @@ pub fn uu_app() -> App<'static, 'static> { } fn basename(fullname: &str, suffix: &str) -> String { - // Remove all platform-specific path separators from the end + // Remove all platform-specific path separators from the end. let path = fullname.trim_end_matches(is_separator); + // If the path contained *only* suffix characters (for example, if + // `fullname` were "///" and `suffix` were "/"), then `path` would + // be left with the empty string. In that case, we set `path` to be + // the original `fullname` to avoid returning the empty path. + let path = if path.is_empty() { fullname } else { path }; + // Convert to path buffer and get last path component let pb = PathBuf::from(path); match pb.components().last() { diff --git a/tests/by-util/test_basename.rs b/tests/by-util/test_basename.rs index d9632106e..4a3e6914a 100644 --- a/tests/by-util/test_basename.rs +++ b/tests/by-util/test_basename.rs @@ -147,3 +147,30 @@ fn invalid_utf8_args_unix() { let os_str = OsStr::from_bytes(&source[..]); test_invalid_utf8_args(os_str); } + +#[test] +fn test_root() { + new_ucmd!().arg("/").succeeds().stdout_is("/\n"); +} + +#[test] +fn test_double_slash() { + // TODO The GNU tests seem to suggest that some systems treat "//" + // as the same directory as "/" directory but not all systems. We + // should extend this test to account for that possibility. + let expected = if cfg!(windows) { "\\" } else { "/\n" }; + new_ucmd!().arg("//").succeeds().stdout_is(expected); + new_ucmd!() + .args(&["//", "/"]) + .succeeds() + .stdout_is(expected); + new_ucmd!() + .args(&["//", "//"]) + .succeeds() + .stdout_is(expected); +} + +#[test] +fn test_triple_slash() { + new_ucmd!().arg("///").succeeds().stdout_is("/\n"); +} From 8f5b785180c0a97524f7a5c0a698467c7a1e8bd1 Mon Sep 17 00:00:00 2001 From: Jeffrey Finkelstein Date: Sun, 1 Aug 2021 10:05:01 -0400 Subject: [PATCH 004/206] fixup! basename: handle paths comprising only slashes --- tests/by-util/test_basename.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/tests/by-util/test_basename.rs b/tests/by-util/test_basename.rs index 4a3e6914a..f7332af5f 100644 --- a/tests/by-util/test_basename.rs +++ b/tests/by-util/test_basename.rs @@ -150,7 +150,8 @@ fn invalid_utf8_args_unix() { #[test] fn test_root() { - new_ucmd!().arg("/").succeeds().stdout_is("/\n"); + let expected = if cfg!(windows) { "\\\n" } else { "/\n" }; + new_ucmd!().arg("/").succeeds().stdout_is(expected); } #[test] @@ -158,7 +159,7 @@ fn test_double_slash() { // TODO The GNU tests seem to suggest that some systems treat "//" // as the same directory as "/" directory but not all systems. We // should extend this test to account for that possibility. - let expected = if cfg!(windows) { "\\" } else { "/\n" }; + let expected = if cfg!(windows) { "\\\n" } else { "/\n" }; new_ucmd!().arg("//").succeeds().stdout_is(expected); new_ucmd!() .args(&["//", "/"]) @@ -172,5 +173,6 @@ fn test_double_slash() { #[test] fn test_triple_slash() { - new_ucmd!().arg("///").succeeds().stdout_is("/\n"); + let expected = if cfg!(windows) { "\\\n" } else { "/\n" }; + new_ucmd!().arg("///").succeeds().stdout_is(expected); } From 0e04f959c2adbcced6b2711b433f823b7fbeddd3 Mon Sep 17 00:00:00 2001 From: James Robson Date: Sun, 27 Jun 2021 11:47:23 +0100 Subject: [PATCH 005/206] Add Physical mode to realpath This adds the 'Physical Mode' and 'Logical Mode' switches to realpath, which control when symlinks are resolved. --- src/uu/cp/src/cp.rs | 6 +-- src/uu/ln/src/ln.rs | 10 +++-- src/uu/readlink/src/readlink.rs | 27 +++++++----- src/uu/realpath/src/realpath.rs | 33 +++++++++++---- src/uu/relpath/src/relpath.rs | 8 ++-- src/uucore/src/lib/features/fs.rs | 70 +++++++++++++++++++++++-------- tests/by-util/test_readlink.rs | 11 +++++ tests/by-util/test_realpath.rs | 33 +++++++++++++++ 8 files changed, 153 insertions(+), 45 deletions(-) diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index 91ea7ef37..dfed4e0fc 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -48,7 +48,7 @@ use std::path::{Path, PathBuf, StripPrefixError}; use std::str::FromStr; use std::string::ToString; use uucore::backup_control::{self, BackupMode}; -use uucore::fs::{canonicalize, CanonicalizeMode}; +use uucore::fs::{canonicalize, MissingHandling, ResolveMode}; use walkdir::WalkDir; #[cfg(unix)] @@ -1431,8 +1431,8 @@ pub fn localize_to_target(root: &Path, source: &Path, target: &Path) -> CopyResu pub fn paths_refer_to_same_file(p1: &Path, p2: &Path) -> io::Result { // We have to take symlinks and relative paths into account. - let pathbuf1 = canonicalize(p1, CanonicalizeMode::Normal)?; - let pathbuf2 = canonicalize(p2, CanonicalizeMode::Normal)?; + let pathbuf1 = canonicalize(p1, MissingHandling::Normal, ResolveMode::Logical)?; + let pathbuf2 = canonicalize(p2, MissingHandling::Normal, ResolveMode::Logical)?; Ok(pathbuf1 == pathbuf2) } diff --git a/src/uu/ln/src/ln.rs b/src/uu/ln/src/ln.rs index d354acce9..3df859d8f 100644 --- a/src/uu/ln/src/ln.rs +++ b/src/uu/ln/src/ln.rs @@ -23,7 +23,7 @@ use std::os::unix::fs::symlink; use std::os::windows::fs::{symlink_dir, symlink_file}; use std::path::{Path, PathBuf}; use uucore::backup_control::{self, BackupMode}; -use uucore::fs::{canonicalize, CanonicalizeMode}; +use uucore::fs::{canonicalize, MissingHandling, ResolveMode}; pub struct Settings { overwrite: OverwriteMode, @@ -361,8 +361,12 @@ fn link_files_in_dir(files: &[PathBuf], target_dir: &Path, settings: &Settings) } fn relative_path<'a>(src: &Path, dst: &Path) -> Result> { - let src_abs = canonicalize(src, CanonicalizeMode::Normal)?; - let mut dst_abs = canonicalize(dst.parent().unwrap(), CanonicalizeMode::Normal)?; + let src_abs = canonicalize(src, MissingHandling::Normal, ResolveMode::Logical)?; + let mut dst_abs = canonicalize( + dst.parent().unwrap(), + MissingHandling::Normal, + ResolveMode::Logical, + )?; dst_abs.push(dst.components().last().unwrap()); let suffix_pos = src_abs .components() diff --git a/src/uu/readlink/src/readlink.rs b/src/uu/readlink/src/readlink.rs index 826fa0254..f9885939a 100644 --- a/src/uu/readlink/src/readlink.rs +++ b/src/uu/readlink/src/readlink.rs @@ -14,7 +14,7 @@ use clap::{crate_version, App, Arg}; use std::fs; use std::io::{stdout, Write}; use std::path::{Path, PathBuf}; -use uucore::fs::{canonicalize, CanonicalizeMode}; +use uucore::fs::{canonicalize, MissingHandling, ResolveMode}; const NAME: &str = "readlink"; const ABOUT: &str = "Print value of a symbolic link or canonical file name."; @@ -42,14 +42,21 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let silent = matches.is_present(OPT_SILENT) || matches.is_present(OPT_QUIET); let verbose = matches.is_present(OPT_VERBOSE); - let can_mode = if matches.is_present(OPT_CANONICALIZE) { - CanonicalizeMode::Normal - } else if matches.is_present(OPT_CANONICALIZE_EXISTING) { - CanonicalizeMode::Existing - } else if matches.is_present(OPT_CANONICALIZE_MISSING) { - CanonicalizeMode::Missing + let res_mode = if matches.is_present(OPT_CANONICALIZE) + || matches.is_present(OPT_CANONICALIZE_EXISTING) + || matches.is_present(OPT_CANONICALIZE_MISSING) + { + ResolveMode::Logical } else { - CanonicalizeMode::None + ResolveMode::None + }; + + let can_mode = if matches.is_present(OPT_CANONICALIZE_EXISTING) { + MissingHandling::Existing + } else if matches.is_present(OPT_CANONICALIZE_MISSING) { + MissingHandling::Missing + } else { + MissingHandling::Normal }; let files: Vec = matches @@ -71,7 +78,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { for f in &files { let p = PathBuf::from(f); - if can_mode == CanonicalizeMode::None { + if res_mode == ResolveMode::None { match fs::read_link(&p) { Ok(path) => show(&path, no_newline, use_zero), Err(err) => { @@ -82,7 +89,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } } } else { - match canonicalize(&p, can_mode) { + match canonicalize(&p, can_mode, res_mode) { Ok(path) => show(&path, no_newline, use_zero), Err(err) => { if verbose { diff --git a/src/uu/realpath/src/realpath.rs b/src/uu/realpath/src/realpath.rs index fe2ad4ccc..40cd94383 100644 --- a/src/uu/realpath/src/realpath.rs +++ b/src/uu/realpath/src/realpath.rs @@ -12,13 +12,15 @@ extern crate uucore; use clap::{crate_version, App, Arg}; use std::path::{Path, PathBuf}; -use uucore::fs::{canonicalize, CanonicalizeMode}; +use uucore::fs::{canonicalize, MissingHandling, ResolveMode}; static ABOUT: &str = "print the resolved path"; static OPT_QUIET: &str = "quiet"; static OPT_STRIP: &str = "strip"; static OPT_ZERO: &str = "zero"; +static OPT_PHYSICAL: &str = "physical"; +static OPT_LOGICAL: &str = "logical"; static ARG_FILES: &str = "files"; @@ -42,9 +44,10 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let strip = matches.is_present(OPT_STRIP); let zero = matches.is_present(OPT_ZERO); let quiet = matches.is_present(OPT_QUIET); + let logical = matches.is_present(OPT_LOGICAL); let mut retcode = 0; for path in &paths { - if let Err(e) = resolve_path(path, strip, zero) { + if let Err(e) = resolve_path(path, strip, zero, logical) { if !quiet { show_error!("{}: {}", e, path.display()); } @@ -76,6 +79,19 @@ pub fn uu_app() -> App<'static, 'static> { .long(OPT_ZERO) .help("Separate output filenames with \\0 rather than newline"), ) + .arg( + Arg::with_name(OPT_LOGICAL) + .short("L") + .long(OPT_LOGICAL) + .help("resolve '..' components before symlinks"), + ) + .arg( + Arg::with_name(OPT_PHYSICAL) + .short("P") + .long(OPT_PHYSICAL) + .overrides_with_all(&[OPT_STRIP, OPT_LOGICAL]) + .help("resolve symlinks as encountered (default)"), + ) .arg( Arg::with_name(ARG_FILES) .multiple(true) @@ -96,14 +112,17 @@ pub fn uu_app() -> App<'static, 'static> { /// /// This function returns an error if there is a problem resolving /// symbolic links. -fn resolve_path(p: &Path, strip: bool, zero: bool) -> std::io::Result<()> { - let mode = if strip { - CanonicalizeMode::None +fn resolve_path(p: &Path, strip: bool, zero: bool, logical: bool) -> std::io::Result<()> { + let resolve = if strip { + ResolveMode::None + } else if logical { + ResolveMode::Logical } else { - CanonicalizeMode::Normal + ResolveMode::Physical }; - let abs = canonicalize(p, mode)?; + let abs = canonicalize(p, MissingHandling::Normal, resolve)?; let line_ending = if zero { '\0' } else { '\n' }; + print!("{}{}", abs.display(), line_ending); Ok(()) } diff --git a/src/uu/relpath/src/relpath.rs b/src/uu/relpath/src/relpath.rs index cb0fba7cc..48d02414d 100644 --- a/src/uu/relpath/src/relpath.rs +++ b/src/uu/relpath/src/relpath.rs @@ -13,7 +13,7 @@ extern crate uucore; use clap::{crate_version, App, Arg}; use std::env; use std::path::{Path, PathBuf}; -use uucore::fs::{canonicalize, CanonicalizeMode}; +use uucore::fs::{canonicalize, MissingHandling, ResolveMode}; use uucore::InvalidEncodingHandling; static ABOUT: &str = "Convert TO destination to the relative path from the FROM dir. @@ -42,12 +42,12 @@ pub fn uumain(args: impl uucore::Args) -> i32 { Some(p) => Path::new(p).to_path_buf(), None => env::current_dir().unwrap(), }; - let absto = canonicalize(to, CanonicalizeMode::Normal).unwrap(); - let absfrom = canonicalize(from, CanonicalizeMode::Normal).unwrap(); + let absto = canonicalize(to, MissingHandling::Normal, ResolveMode::Logical).unwrap(); + let absfrom = canonicalize(from, MissingHandling::Normal, ResolveMode::Logical).unwrap(); if matches.is_present(options::DIR) { let base = Path::new(&matches.value_of(options::DIR).unwrap()).to_path_buf(); - let absbase = canonicalize(base, CanonicalizeMode::Normal).unwrap(); + let absbase = canonicalize(base, MissingHandling::Normal, ResolveMode::Logical).unwrap(); if !absto.as_path().starts_with(absbase.as_path()) || !absfrom.as_path().starts_with(absbase.as_path()) { diff --git a/src/uucore/src/lib/features/fs.rs b/src/uucore/src/lib/features/fs.rs index 36bdbfed0..ea1743880 100644 --- a/src/uucore/src/lib/features/fs.rs +++ b/src/uucore/src/lib/features/fs.rs @@ -56,11 +56,8 @@ pub fn resolve_relative_path(path: &Path) -> Cow { /// Controls how symbolic links should be handled when canonicalizing a path. #[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub enum CanonicalizeMode { - /// Do not resolve any symbolic links. - None, - - /// Resolve all symbolic links. +pub enum MissingHandling { + /// Return an error if any part of the path is missing. Normal, /// Resolve symbolic links, ignoring errors on the final component. @@ -70,6 +67,19 @@ pub enum CanonicalizeMode { Missing, } +/// Controls when symbolic links are resolved +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum ResolveMode { + /// Do not resolve any symbolic links. + None, + + /// Resolve symlinks as encountered when processing the path + Physical, + + /// Resolve '..' elements before symlinks + Logical, +} + // copied from https://github.com/rust-lang/cargo/blob/2e4cfc2b7d43328b207879228a2ca7d427d188bb/src/cargo/util/paths.rs#L65-L90 // both projects are MIT https://github.com/rust-lang/cargo/blob/master/LICENSE-MIT // for std impl progress see rfc https://github.com/rust-lang/rfcs/issues/2208 @@ -130,20 +140,32 @@ fn resolve>(original: P) -> IOResult { /// This function is a generalization of [`std::fs::canonicalize`] that /// allows controlling how symbolic links are resolved and how to deal /// with missing components. It returns the canonical, absolute form of -/// a path. The `can_mode` parameter controls how symbolic links are -/// resolved: +/// a path. +/// The `miss_mode` parameter controls how missing path elements are handled /// -/// * [`CanonicalizeMode::Normal`] makes this function behave like +/// * [`MissingHandling::Normal`] makes this function behave like /// [`std::fs::canonicalize`], resolving symbolic links and returning /// an error if the path does not exist. -/// * [`CanonicalizeMode::Missing`] makes this function ignore non-final +/// * [`MissingHandling::Missing`] makes this function ignore non-final /// components of the path that could not be resolved. -/// * [`CanonicalizeMode::Existing`] makes this function return an error +/// * [`MissingHandling::Existing`] makes this function return an error /// if the final component of the path does not exist. -/// * [`CanonicalizeMode::None`] makes this function not try to resolve -/// any symbolic links. /// -pub fn canonicalize>(original: P, can_mode: CanonicalizeMode) -> IOResult { +/// The `res_mode` parameter controls how symbolic links are +/// resolved: +/// +/// * [`ResolveMode::None`] makes this function not try to resolve +/// any symbolic links. +/// * [`ResolveMode::Physical`] makes this function resolve symlinks as they +/// are encountered +/// * [`ResolveMode::Logical`] makes this function resolve '..' components +/// before symlinks +/// +pub fn canonicalize>( + original: P, + miss_mode: MissingHandling, + res_mode: ResolveMode, +) -> IOResult { // Create an absolute path let original = original.as_ref(); let original = if original.is_absolute() { @@ -167,7 +189,11 @@ pub fn canonicalize>(original: P, can_mode: CanonicalizeMode) -> } Component::CurDir => (), Component::ParentDir => { - parts.pop(); + if res_mode == ResolveMode::Logical { + parts.pop(); + } else { + parts.push(part.as_os_str()); + } } Component::Normal(_) => { parts.push(part.as_os_str()); @@ -180,12 +206,17 @@ pub fn canonicalize>(original: P, can_mode: CanonicalizeMode) -> for part in parts[..parts.len() - 1].iter() { result.push(part); - if can_mode == CanonicalizeMode::None { + //resolve as we go to handle long relative paths on windows + if res_mode == ResolveMode::Physical { + result = normalize_path(&result); + } + + if res_mode == ResolveMode::None { continue; } match resolve(&result) { - Err(_) if can_mode == CanonicalizeMode::Missing => continue, + Err(_) if miss_mode == MissingHandling::Missing => continue, Err(e) => return Err(e), Ok(path) => { result.pop(); @@ -196,12 +227,12 @@ pub fn canonicalize>(original: P, can_mode: CanonicalizeMode) -> result.push(parts.last().unwrap()); - if can_mode == CanonicalizeMode::None { + if res_mode == ResolveMode::None { return Ok(result); } match resolve(&result) { - Err(e) if can_mode == CanonicalizeMode::Existing => { + Err(e) if miss_mode == MissingHandling::Existing => { return Err(e); } Ok(path) => { @@ -210,6 +241,9 @@ pub fn canonicalize>(original: P, can_mode: CanonicalizeMode) -> } Err(_) => (), } + if res_mode == ResolveMode::Physical { + result = normalize_path(&result); + } } Ok(result) } diff --git a/tests/by-util/test_readlink.rs b/tests/by-util/test_readlink.rs index 51aebbed2..25f29004f 100644 --- a/tests/by-util/test_readlink.rs +++ b/tests/by-util/test_readlink.rs @@ -2,6 +2,17 @@ use crate::common::util::*; static GIBBERISH: &str = "supercalifragilisticexpialidocious"; +#[test] +fn test_resolve() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + at.touch("foo"); + at.symlink_file("foo", "bar"); + + scene.ucmd().arg("bar").succeeds().stdout_contains("foo\n"); +} + #[test] fn test_canonicalize() { let (at, mut ucmd) = at_and_ucmd!(); diff --git a/tests/by-util/test_realpath.rs b/tests/by-util/test_realpath.rs index e1384ac74..8cb1551f0 100644 --- a/tests/by-util/test_realpath.rs +++ b/tests/by-util/test_realpath.rs @@ -106,3 +106,36 @@ fn test_realpath_file_and_links_strip_zero() { .succeeds() .stdout_contains("bar\u{0}"); } + +#[test] +fn test_realpath_physical_mode() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + at.mkdir("dir1"); + at.mkdir_all("dir2/bar"); + at.symlink_dir("dir2/bar", "dir1/foo"); + + scene + .ucmd() + .arg("dir1/foo/..") + .succeeds() + .stdout_contains("dir2\n"); +} + +#[test] +fn test_realpath_logical_mode() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + at.mkdir("dir1"); + at.mkdir("dir2"); + at.symlink_dir("dir2", "dir1/foo"); + + scene + .ucmd() + .arg("-L") + .arg("dir1/foo/..") + .succeeds() + .stdout_contains("dir1\n"); +} From 21043d36054461ddfe6b8276c34044f5c51416cb Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Sat, 7 Aug 2021 23:00:42 +0200 Subject: [PATCH 006/206] sort: prevent race when deleting files Move the creation of temporary files into next_file so that it happens while the lock is taken. Previously, only the computation of the new file path happened while the lock was taken, while the creation happened later. --- src/uu/sort/src/ext_sort.rs | 5 +++-- src/uu/sort/src/merge.rs | 18 +++++++----------- src/uu/sort/src/tmp_dir.rs | 9 +++++++-- 3 files changed, 17 insertions(+), 15 deletions(-) diff --git a/src/uu/sort/src/ext_sort.rs b/src/uu/sort/src/ext_sort.rs index b4827f962..bf332e4e8 100644 --- a/src/uu/sort/src/ext_sort.rs +++ b/src/uu/sort/src/ext_sort.rs @@ -12,6 +12,7 @@ //! The buffers for the individual chunks are recycled. There are two buffers. use std::cmp::Ordering; +use std::fs::File; use std::io::Write; use std::path::PathBuf; use std::{ @@ -238,7 +239,7 @@ fn read_write_loop( let tmp_file = write::( &mut chunk, - tmp_dir.next_file_path()?, + tmp_dir.next_file()?, settings.compress_prog.as_deref(), separator, )?; @@ -268,7 +269,7 @@ fn read_write_loop( /// `compress_prog` is used to optionally compress file contents. fn write( chunk: &mut Chunk, - file: PathBuf, + file: (File, PathBuf), compress_prog: Option<&str>, separator: u8, ) -> UResult { diff --git a/src/uu/sort/src/merge.rs b/src/uu/sort/src/merge.rs index 64a7632bf..934d1c208 100644 --- a/src/uu/sort/src/merge.rs +++ b/src/uu/sort/src/merge.rs @@ -46,7 +46,7 @@ fn replace_output_file_in_input_files( if let Some(copy) = © { *file = copy.clone().into_os_string(); } else { - let copy_path = tmp_dir.next_file_path()?; + let (_file, copy_path) = tmp_dir.next_file()?; std::fs::copy(file_path, ©_path) .map_err(|error| SortError::OpenTmpFileFailed { error })?; *file = copy_path.clone().into_os_string(); @@ -110,7 +110,7 @@ pub fn merge_with_file_limit< remaining_files = remaining_files.saturating_sub(settings.merge_batch_size); let merger = merge_without_limit(batches.next().unwrap(), settings)?; let mut tmp_file = - Tmp::create(tmp_dir.next_file_path()?, settings.compress_prog.as_deref())?; + Tmp::create(tmp_dir.next_file()?, settings.compress_prog.as_deref())?; merger.write_all_to(settings, tmp_file.as_write())?; temporary_files.push(tmp_file.finished_writing()?); } @@ -379,7 +379,7 @@ fn check_child_success(mut child: Child, program: &str) -> UResult<()> { pub trait WriteableTmpFile: Sized { type Closed: ClosedTmpFile; type InnerWrite: Write; - fn create(path: PathBuf, compress_prog: Option<&str>) -> UResult; + fn create(file: (File, PathBuf), compress_prog: Option<&str>) -> UResult; /// Closes the temporary file. fn finished_writing(self) -> UResult; fn as_write(&mut self) -> &mut Self::InnerWrite; @@ -414,11 +414,9 @@ impl WriteableTmpFile for WriteablePlainTmpFile { type Closed = ClosedPlainTmpFile; type InnerWrite = BufWriter; - fn create(path: PathBuf, _: Option<&str>) -> UResult { + fn create((file, path): (File, PathBuf), _: Option<&str>) -> UResult { Ok(WriteablePlainTmpFile { - file: BufWriter::new( - File::create(&path).map_err(|error| SortError::OpenTmpFileFailed { error })?, - ), + file: BufWriter::new(file), path, }) } @@ -476,12 +474,10 @@ impl WriteableTmpFile for WriteableCompressedTmpFile { type Closed = ClosedCompressedTmpFile; type InnerWrite = BufWriter; - fn create(path: PathBuf, compress_prog: Option<&str>) -> UResult { + fn create((file, path): (File, PathBuf), compress_prog: Option<&str>) -> UResult { let compress_prog = compress_prog.unwrap(); let mut command = Command::new(compress_prog); - let tmp_file = - File::create(&path).map_err(|error| SortError::OpenTmpFileFailed { error })?; - command.stdin(Stdio::piped()).stdout(tmp_file); + command.stdin(Stdio::piped()).stdout(file); let mut child = command .spawn() .map_err(|err| SortError::CompressProgExecutionFailed { diff --git a/src/uu/sort/src/tmp_dir.rs b/src/uu/sort/src/tmp_dir.rs index 884f2cd00..32ffbbf0d 100644 --- a/src/uu/sort/src/tmp_dir.rs +++ b/src/uu/sort/src/tmp_dir.rs @@ -1,4 +1,5 @@ use std::{ + fs::File, path::{Path, PathBuf}, sync::{Arc, Mutex}, }; @@ -54,7 +55,7 @@ impl TmpDirWrapper { .map_err(|e| USimpleError::new(2, format!("failed to set up signal handler: {}", e))) } - pub fn next_file_path(&mut self) -> UResult { + pub fn next_file(&mut self) -> UResult<(File, PathBuf)> { if self.temp_dir.is_none() { self.init_tmp_dir()?; } @@ -62,7 +63,11 @@ impl TmpDirWrapper { let _lock = self.lock.lock().unwrap(); let file_name = self.size.to_string(); self.size += 1; - Ok(self.temp_dir.as_ref().unwrap().path().join(file_name)) + let path = self.temp_dir.as_ref().unwrap().path().join(file_name); + Ok(( + File::create(&path).map_err(|error| SortError::OpenTmpFileFailed { error })?, + path, + )) } } From 6f58da00dd43da607a13502206f5bbd0961634f5 Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Wed, 30 Jun 2021 23:27:10 +0200 Subject: [PATCH 007/206] refactor/uucore ~ add `util_name!()`; correct implementation of `executable!()` --- src/uucore/src/lib/macros.rs | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/src/uucore/src/lib/macros.rs b/src/uucore/src/lib/macros.rs index 6e3a2166f..3eeb4d625 100644 --- a/src/uucore/src/lib/macros.rs +++ b/src/uucore/src/lib/macros.rs @@ -5,19 +5,24 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -/// Deduce the name of the binary from the current source code filename. -/// -/// e.g.: `src/uu/cp/src/cp.rs` -> `cp` +/// Get the utility name. +#[macro_export] +macro_rules! util_name( + () => ({ + let crate_name = env!("CARGO_PKG_NAME"); + if crate_name.starts_with("uu_") { + &crate_name[3..] + } else { + &crate_name + } + }) +); + +/// Get the executable name. #[macro_export] macro_rules! executable( () => ({ - let module = module_path!(); - let module = module.split("::").next().unwrap_or(module); - if &module[0..3] == "uu_" { - &module[3..] - } else { - module - } + &std::env::args().next().unwrap() }) ); From be8f0732175fb81af362c4d1b24b1f8a7f55015b Mon Sep 17 00:00:00 2001 From: Roy Ivy III Date: Wed, 30 Jun 2021 21:59:04 -0500 Subject: [PATCH 008/206] refactor/uucore ~ add OsString support for `executable!()` --- src/uucore/src/lib/macros.rs | 41 ++++++++++++++++++++++++++---------- 1 file changed, 30 insertions(+), 11 deletions(-) diff --git a/src/uucore/src/lib/macros.rs b/src/uucore/src/lib/macros.rs index 3eeb4d625..e659c4757 100644 --- a/src/uucore/src/lib/macros.rs +++ b/src/uucore/src/lib/macros.rs @@ -18,11 +18,33 @@ macro_rules! util_name( }) ); -/// Get the executable name. +/// Get the executable path (as `OsString`). +#[macro_export] +macro_rules! executable_os( + () => ({ + &std::env::args_os().next().unwrap() + }) +); + +/// Get the executable path (as `String`; lossless). #[macro_export] macro_rules! executable( () => ({ - &std::env::args().next().unwrap() + let exe = match executable_os!().to_str() { + // * UTF-8 + Some(s) => s.to_string(), + // * "lossless" debug format if `executable_os!()` is not well-formed UTF-8 + None => format!("{:?}", executable_os!()) + }; + &exe.to_owned() + }) +); + +/// Get the executable name. +#[macro_export] +macro_rules! executable_name( + () => ({ + &std::path::Path::new(executable_os!()).file_stem().unwrap().to_string_lossy() }) ); @@ -31,10 +53,7 @@ macro_rules! show( ($err:expr) => ({ let e = $err; uucore::error::set_exit_code(e.code()); - eprintln!("{}: {}", executable!(), e); - if e.usage() { - eprintln!("Try '{} --help' for more information.", executable!()); - } + eprintln!("{}: {}", executable_name!(), e); }) ); @@ -51,7 +70,7 @@ macro_rules! show_if_err( #[macro_export] macro_rules! show_error( ($($args:tt)+) => ({ - eprint!("{}: ", executable!()); + eprint!("{}: ", executable_name!()); eprintln!($($args)+); }) ); @@ -60,7 +79,7 @@ macro_rules! show_error( #[macro_export] macro_rules! show_error_custom_description ( ($err:expr,$($args:tt)+) => ({ - eprint!("{}: {}: ", executable!(), $err); + eprint!("{}: {}: ", executable_name!(), $err); eprintln!($($args)+); }) ); @@ -68,7 +87,7 @@ macro_rules! show_error_custom_description ( #[macro_export] macro_rules! show_warning( ($($args:tt)+) => ({ - eprint!("{}: warning: ", executable!()); + eprint!("{}: warning: ", executable_name!()); eprintln!($($args)+); }) ); @@ -77,9 +96,9 @@ macro_rules! show_warning( #[macro_export] macro_rules! show_usage_error( ($($args:tt)+) => ({ - eprint!("{}: ", executable!()); + eprint!("{}: ", executable_name!()); eprintln!($($args)+); - eprintln!("Try '{} --help' for more information.", executable!()); + eprintln!("Try `{:?} --help` for more information.", executable!()); }) ); From 27c4530f3c67d53718a884e32b9c33b8bbb1d8ef Mon Sep 17 00:00:00 2001 From: Roy Ivy III Date: Wed, 30 Jun 2021 22:14:37 -0500 Subject: [PATCH 009/206] refactor/uucore ~ reorganize macros into sections by 'type' --- src/uucore/src/lib/macros.rs | 50 +++++++++++++++++++++--------------- 1 file changed, 29 insertions(+), 21 deletions(-) diff --git a/src/uucore/src/lib/macros.rs b/src/uucore/src/lib/macros.rs index e659c4757..5a374b36e 100644 --- a/src/uucore/src/lib/macros.rs +++ b/src/uucore/src/lib/macros.rs @@ -5,19 +5,6 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -/// Get the utility name. -#[macro_export] -macro_rules! util_name( - () => ({ - let crate_name = env!("CARGO_PKG_NAME"); - if crate_name.starts_with("uu_") { - &crate_name[3..] - } else { - &crate_name - } - }) -); - /// Get the executable path (as `OsString`). #[macro_export] macro_rules! executable_os( @@ -48,6 +35,21 @@ macro_rules! executable_name( }) ); +/// Derive the utility name. +#[macro_export] +macro_rules! util_name( + () => ({ + let crate_name = env!("CARGO_PKG_NAME"); + if crate_name.starts_with("uu_") { + &crate_name[3..] + } else { + &crate_name + } + }) +); + +//==== + #[macro_export] macro_rules! show( ($err:expr) => ({ @@ -102,14 +104,7 @@ macro_rules! show_usage_error( }) ); -/// Display the provided error message, then `exit()` with the provided exit code -#[macro_export] -macro_rules! crash( - ($exit_code:expr, $($args:tt)+) => ({ - show_error!($($args)+); - ::std::process::exit($exit_code) - }) -); +//==== /// Calls `exit()` with the provided exit code. #[macro_export] @@ -119,6 +114,15 @@ macro_rules! exit( }) ); +/// Display the provided error message, then `exit()` with the provided exit code +#[macro_export] +macro_rules! crash( + ($exit_code:expr, $($args:tt)+) => ({ + show_error!($($args)+); + ::std::process::exit($exit_code) + }) +); + /// Unwraps the Result. Instead of panicking, it exists the program with the /// provided exit code. #[macro_export] @@ -131,6 +135,8 @@ macro_rules! crash_if_err( ) ); +//==== + /// 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. @@ -147,6 +153,8 @@ macro_rules! return_if_err( ) ); +//==== + #[macro_export] macro_rules! safe_write( ($fd:expr, $($args:tt)+) => ( From 065330c4ca066c593f83909f5e0c7d6528230f65 Mon Sep 17 00:00:00 2001 From: Roy Ivy III Date: Wed, 30 Jun 2021 22:18:13 -0500 Subject: [PATCH 010/206] refactor/uucore ~ improve `crash!()` (DRY use of `exit!()`) --- src/uucore/src/lib/macros.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uucore/src/lib/macros.rs b/src/uucore/src/lib/macros.rs index 5a374b36e..090fbd00c 100644 --- a/src/uucore/src/lib/macros.rs +++ b/src/uucore/src/lib/macros.rs @@ -119,7 +119,7 @@ macro_rules! exit( macro_rules! crash( ($exit_code:expr, $($args:tt)+) => ({ show_error!($($args)+); - ::std::process::exit($exit_code) + exit!($exit_code) }) ); From ed240a7e50d4209019944ef8b0aab1317e6337ad Mon Sep 17 00:00:00 2001 From: Roy Ivy III Date: Wed, 30 Jun 2021 22:19:18 -0500 Subject: [PATCH 011/206] fix/uucore ~ revise and fix msg macros and sub-macros --- src/uucore/src/lib/macros.rs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/uucore/src/lib/macros.rs b/src/uucore/src/lib/macros.rs index 090fbd00c..63d105b1b 100644 --- a/src/uucore/src/lib/macros.rs +++ b/src/uucore/src/lib/macros.rs @@ -189,25 +189,25 @@ macro_rules! safe_unwrap( //-- message templates -//-- message templates : general +//-- message templates : (join utility sub-macros) #[macro_export] -macro_rules! snippet_list_join_oxford { +macro_rules! snippet_list_join_oxford_comma { ($conjunction:expr, $valOne:expr, $valTwo:expr) => ( format!("{}, {} {}", $valOne, $conjunction, $valTwo) ); ($conjunction:expr, $valOne:expr, $valTwo:expr $(, $remaining_values:expr)*) => ( - format!("{}, {}", $valOne, snippet_list_join_inner!($conjunction, $valTwo $(, $remaining_values)*)) + format!("{}, {}", $valOne, snippet_list_join_oxford_comma!($conjunction, $valTwo $(, $remaining_values)*)) ); } #[macro_export] -macro_rules! snippet_list_join_or { - ($valOne:expr, $valTwo:expr) => ( - format!("{} or {}", $valOne, $valTwo) +macro_rules! snippet_list_join { + ($conjunction:expr, $valOne:expr, $valTwo:expr) => ( + format!("{} {} {}", $valOne, $conjunction, $valTwo) ); - ($valOne:expr, $valTwo:expr $(, $remaining_values:expr)*) => ( - format!("{}, {}", $valOne, snippet_list_join_oxford!("or", $valTwo $(, $remaining_values)*)) + ($conjunction:expr, $valOne:expr, $valTwo:expr $(, $remaining_values:expr)*) => ( + format!("{}, {}", $valOne, snippet_list_join_oxford_comma!($conjunction, $valTwo $(, $remaining_values)*)) ); } @@ -271,13 +271,13 @@ macro_rules! msg_opt_invalid_should_be { #[macro_export] macro_rules! msg_expects_one_of { ($valOne:expr $(, $remaining_values:expr)*) => ( - msg_invalid_input!(format!("expects one of {}", snippet_list_join_or!($valOne $(, $remaining_values)*))) + msg_invalid_input!(format!("expects one of {}", snippet_list_join!("or", $valOne $(, $remaining_values)*))) ); } #[macro_export] macro_rules! msg_expects_no_more_than_one_of { ($valOne:expr $(, $remaining_values:expr)*) => ( - msg_invalid_input!(format!("expects no more than one of {}", snippet_list_join_or!($valOne $(, $remaining_values)*))) ; + msg_invalid_input!(format!("expects no more than one of {}", snippet_list_join!("or", $valOne $(, $remaining_values)*))) ; ); } From fa5dc90789bba6c80ecf03546f9f733ccc7d021f Mon Sep 17 00:00:00 2001 From: Roy Ivy III Date: Wed, 30 Jun 2021 22:24:41 -0500 Subject: [PATCH 012/206] refactor/uucore ~ improve macro scope hygiene --- src/uucore/src/lib/macros.rs | 48 ++++++++++++++++++------------------ 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/src/uucore/src/lib/macros.rs b/src/uucore/src/lib/macros.rs index 63d105b1b..715752c84 100644 --- a/src/uucore/src/lib/macros.rs +++ b/src/uucore/src/lib/macros.rs @@ -17,11 +17,11 @@ macro_rules! executable_os( #[macro_export] macro_rules! executable( () => ({ - let exe = match executable_os!().to_str() { + let exe = match $crate::executable_os!().to_str() { // * UTF-8 Some(s) => s.to_string(), // * "lossless" debug format if `executable_os!()` is not well-formed UTF-8 - None => format!("{:?}", executable_os!()) + None => format!("{:?}", $crate::executable_os!()) }; &exe.to_owned() }) @@ -31,7 +31,7 @@ macro_rules! executable( #[macro_export] macro_rules! executable_name( () => ({ - &std::path::Path::new(executable_os!()).file_stem().unwrap().to_string_lossy() + &std::path::Path::new($crate::executable_os!()).file_stem().unwrap().to_string_lossy() }) ); @@ -55,7 +55,7 @@ macro_rules! show( ($err:expr) => ({ let e = $err; uucore::error::set_exit_code(e.code()); - eprintln!("{}: {}", executable_name!(), e); + eprintln!("{}: {}", $crate::executable_name!(), e); }) ); @@ -72,7 +72,7 @@ macro_rules! show_if_err( #[macro_export] macro_rules! show_error( ($($args:tt)+) => ({ - eprint!("{}: ", executable_name!()); + eprint!("{}: ", $crate::executable_name!()); eprintln!($($args)+); }) ); @@ -81,7 +81,7 @@ macro_rules! show_error( #[macro_export] macro_rules! show_error_custom_description ( ($err:expr,$($args:tt)+) => ({ - eprint!("{}: {}: ", executable_name!(), $err); + eprint!("{}: {}: ", $crate::executable_name!(), $err); eprintln!($($args)+); }) ); @@ -89,7 +89,7 @@ macro_rules! show_error_custom_description ( #[macro_export] macro_rules! show_warning( ($($args:tt)+) => ({ - eprint!("{}: warning: ", executable_name!()); + eprint!("{}: warning: ", $crate::executable_name!()); eprintln!($($args)+); }) ); @@ -98,9 +98,9 @@ macro_rules! show_warning( #[macro_export] macro_rules! show_usage_error( ($($args:tt)+) => ({ - eprint!("{}: ", executable_name!()); + eprint!("{}: ", $crate::executable_name!()); eprintln!($($args)+); - eprintln!("Try `{:?} --help` for more information.", executable!()); + eprintln!("Try `{:?} --help` for more information.", $crate::executable!()); }) ); @@ -118,8 +118,8 @@ macro_rules! exit( #[macro_export] macro_rules! crash( ($exit_code:expr, $($args:tt)+) => ({ - show_error!($($args)+); - exit!($exit_code) + $crate::show_error!($($args)+); + $crate::exit!($exit_code) }) ); @@ -130,7 +130,7 @@ macro_rules! crash_if_err( ($exit_code:expr, $exp:expr) => ( match $exp { Ok(m) => m, - Err(f) => crash!($exit_code, "{}", f), + Err(f) => $crate::crash!($exit_code, "{}", f), } ) ); @@ -146,7 +146,7 @@ macro_rules! return_if_err( match $exp { Ok(m) => m, Err(f) => { - show_error!("{}", f); + $crate::show_error!("{}", f); return $exit_code; } } @@ -182,7 +182,7 @@ macro_rules! safe_unwrap( ($exp:expr) => ( match $exp { Ok(m) => m, - Err(f) => crash!(1, "{}", f.to_string()) + Err(f) => $crate::crash!(1, "{}", f.to_string()) } ) ); @@ -197,7 +197,7 @@ macro_rules! snippet_list_join_oxford_comma { format!("{}, {} {}", $valOne, $conjunction, $valTwo) ); ($conjunction:expr, $valOne:expr, $valTwo:expr $(, $remaining_values:expr)*) => ( - format!("{}, {}", $valOne, snippet_list_join_oxford_comma!($conjunction, $valTwo $(, $remaining_values)*)) + format!("{}, {}", $valOne, $crate::snippet_list_join_oxford_comma!($conjunction, $valTwo $(, $remaining_values)*)) ); } @@ -207,7 +207,7 @@ macro_rules! snippet_list_join { format!("{} {} {}", $valOne, $conjunction, $valTwo) ); ($conjunction:expr, $valOne:expr, $valTwo:expr $(, $remaining_values:expr)*) => ( - format!("{}, {}", $valOne, snippet_list_join_oxford_comma!($conjunction, $valTwo $(, $remaining_values)*)) + format!("{}, {}", $valOne, $crate::snippet_list_join_oxford_comma!($conjunction, $valTwo $(, $remaining_values)*)) ); } @@ -225,10 +225,10 @@ macro_rules! msg_invalid_input { #[macro_export] macro_rules! msg_invalid_opt_use { ($about:expr, $flag:expr) => { - msg_invalid_input!(format!("The '{}' option {}", $flag, $about)) + $crate::msg_invalid_input!(format!("The '{}' option {}", $flag, $about)) }; ($about:expr, $long_flag:expr, $short_flag:expr) => { - msg_invalid_input!(format!( + $crate::msg_invalid_input!(format!( "The '{}' ('{}') option {}", $long_flag, $short_flag, $about )) @@ -238,10 +238,10 @@ macro_rules! msg_invalid_opt_use { #[macro_export] macro_rules! msg_opt_only_usable_if { ($clause:expr, $flag:expr) => { - msg_invalid_opt_use!(format!("only usable if {}", $clause), $flag) + $crate::msg_invalid_opt_use!(format!("only usable if {}", $clause), $flag) }; ($clause:expr, $long_flag:expr, $short_flag:expr) => { - msg_invalid_opt_use!( + $crate::msg_invalid_opt_use!( format!("only usable if {}", $clause), $long_flag, $short_flag @@ -252,13 +252,13 @@ macro_rules! msg_opt_only_usable_if { #[macro_export] macro_rules! msg_opt_invalid_should_be { ($expects:expr, $received:expr, $flag:expr) => { - msg_invalid_opt_use!( + $crate::msg_invalid_opt_use!( format!("expects {}, but was provided {}", $expects, $received), $flag ) }; ($expects:expr, $received:expr, $long_flag:expr, $short_flag:expr) => { - msg_invalid_opt_use!( + $crate::msg_invalid_opt_use!( format!("expects {}, but was provided {}", $expects, $received), $long_flag, $short_flag @@ -271,13 +271,13 @@ macro_rules! msg_opt_invalid_should_be { #[macro_export] macro_rules! msg_expects_one_of { ($valOne:expr $(, $remaining_values:expr)*) => ( - msg_invalid_input!(format!("expects one of {}", snippet_list_join!("or", $valOne $(, $remaining_values)*))) + $crate::msg_invalid_input!(format!("expects one of {}", $crate::snippet_list_join!("or", $valOne $(, $remaining_values)*))) ); } #[macro_export] macro_rules! msg_expects_no_more_than_one_of { ($valOne:expr $(, $remaining_values:expr)*) => ( - msg_invalid_input!(format!("expects no more than one of {}", snippet_list_join!("or", $valOne $(, $remaining_values)*))) ; + $crate::msg_invalid_input!(format!("expects no more than one of {}", $crate::snippet_list_join!("or", $valOne $(, $remaining_values)*))) ; ); } From c5792c2a0fbe64d020c5863f92b2b8bf98470b67 Mon Sep 17 00:00:00 2001 From: Roy Ivy III Date: Wed, 30 Jun 2021 22:40:32 -0500 Subject: [PATCH 013/206] refactor ~ use `util_name!()` as clap::app::App name argument for all utils --- src/uu/arch/src/arch.rs | 2 +- src/uu/basename/src/basename.rs | 2 +- src/uu/cat/src/cat.rs | 2 +- src/uu/chgrp/src/chgrp.rs | 2 +- src/uu/chmod/src/chmod.rs | 2 +- src/uu/chown/src/chown.rs | 2 +- src/uu/chroot/src/chroot.rs | 2 +- src/uu/cksum/src/cksum.rs | 2 +- src/uu/comm/src/comm.rs | 2 +- src/uu/cp/src/cp.rs | 2 +- src/uu/csplit/src/csplit.rs | 2 +- src/uu/cut/src/cut.rs | 2 +- src/uu/date/src/date.rs | 2 +- src/uu/df/src/df.rs | 2 +- src/uu/dircolors/src/dircolors.rs | 2 +- src/uu/dirname/src/dirname.rs | 2 +- src/uu/du/src/du.rs | 2 +- src/uu/echo/src/echo.rs | 2 +- src/uu/expand/src/expand.rs | 2 +- src/uu/expr/src/expr.rs | 2 +- src/uu/factor/src/cli.rs | 2 +- src/uu/false/src/false.rs | 5 +++-- src/uu/fmt/src/fmt.rs | 2 +- src/uu/fold/src/fold.rs | 2 +- src/uu/groups/src/groups.rs | 2 +- src/uu/hashsum/src/hashsum.rs | 2 +- src/uu/head/src/head.rs | 4 ++-- src/uu/hostid/src/hostid.rs | 2 +- src/uu/hostname/src/hostname.rs | 2 +- src/uu/id/src/id.rs | 2 +- src/uu/install/src/install.rs | 2 +- src/uu/kill/src/kill.rs | 2 +- src/uu/link/src/link.rs | 2 +- src/uu/ln/src/ln.rs | 2 +- src/uu/logname/src/logname.rs | 2 +- src/uu/ls/src/ls.rs | 2 +- src/uu/mkdir/src/mkdir.rs | 2 +- src/uu/mkfifo/src/mkfifo.rs | 2 +- src/uu/mknod/src/mknod.rs | 2 +- src/uu/mktemp/src/mktemp.rs | 2 +- src/uu/more/src/more.rs | 2 +- src/uu/mv/src/mv.rs | 2 +- src/uu/nice/src/nice.rs | 2 +- src/uu/nl/src/nl.rs | 2 +- src/uu/nohup/src/nohup.rs | 2 +- src/uu/nproc/src/nproc.rs | 2 +- src/uu/numfmt/src/numfmt.rs | 2 +- src/uu/od/src/od.rs | 2 +- src/uu/paste/src/paste.rs | 2 +- src/uu/pathchk/src/pathchk.rs | 2 +- src/uu/pinky/src/pinky.rs | 2 +- src/uu/pr/src/pr.rs | 4 ++-- src/uu/printenv/src/printenv.rs | 2 +- src/uu/printf/src/printf.rs | 2 +- src/uu/ptx/src/ptx.rs | 2 +- src/uu/pwd/src/pwd.rs | 2 +- src/uu/readlink/src/readlink.rs | 2 +- src/uu/realpath/src/realpath.rs | 2 +- src/uu/relpath/src/relpath.rs | 2 +- src/uu/rm/src/rm.rs | 2 +- src/uu/rmdir/src/rmdir.rs | 2 +- src/uu/seq/src/seq.rs | 2 +- src/uu/shred/src/shred.rs | 2 +- src/uu/shuf/src/shuf.rs | 2 +- src/uu/sleep/src/sleep.rs | 2 +- src/uu/sort/src/sort.rs | 2 +- src/uu/split/src/split.rs | 2 +- src/uu/stat/src/stat.rs | 2 +- src/uu/stdbuf/src/stdbuf.rs | 2 +- src/uu/sum/src/sum.rs | 2 +- src/uu/sync/src/sync.rs | 2 +- src/uu/tac/src/tac.rs | 2 +- src/uu/tail/src/tail.rs | 2 +- src/uu/tee/src/tee.rs | 2 +- src/uu/test/src/test.rs | 4 ++-- src/uu/touch/src/touch.rs | 2 +- src/uu/tr/src/tr.rs | 2 +- src/uu/true/src/true.rs | 4 ++-- src/uu/truncate/src/truncate.rs | 2 +- src/uu/tsort/src/tsort.rs | 2 +- src/uu/tty/src/tty.rs | 2 +- src/uu/uname/src/uname.rs | 2 +- src/uu/unexpand/src/unexpand.rs | 2 +- src/uu/uniq/src/uniq.rs | 2 +- src/uu/unlink/src/unlink.rs | 2 +- src/uu/uptime/src/uptime.rs | 2 +- src/uu/users/src/users.rs | 2 +- src/uu/wc/src/wc.rs | 2 +- src/uu/who/src/who.rs | 2 +- 89 files changed, 95 insertions(+), 94 deletions(-) diff --git a/src/uu/arch/src/arch.rs b/src/uu/arch/src/arch.rs index 94ec97e98..30adbaad9 100644 --- a/src/uu/arch/src/arch.rs +++ b/src/uu/arch/src/arch.rs @@ -27,7 +27,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } pub fn uu_app() -> App<'static, 'static> { - App::new(executable!()) + App::new(util_name!()) .version(crate_version!()) .about(ABOUT) .after_help(SUMMARY) diff --git a/src/uu/basename/src/basename.rs b/src/uu/basename/src/basename.rs index 5450ee3f2..26fb67c54 100644 --- a/src/uu/basename/src/basename.rs +++ b/src/uu/basename/src/basename.rs @@ -93,7 +93,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> App<'static, 'static> { - App::new(executable!()) + App::new(util_name!()) .version(crate_version!()) .about(SUMMARY) .arg( diff --git a/src/uu/cat/src/cat.rs b/src/uu/cat/src/cat.rs index b9d07bcda..cb12830a4 100644 --- a/src/uu/cat/src/cat.rs +++ b/src/uu/cat/src/cat.rs @@ -234,7 +234,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } pub fn uu_app() -> App<'static, 'static> { - App::new(executable!()) + App::new(util_name!()) .name(NAME) .version(crate_version!()) .usage(SYNTAX) diff --git a/src/uu/chgrp/src/chgrp.rs b/src/uu/chgrp/src/chgrp.rs index dd851c504..db1e20e57 100644 --- a/src/uu/chgrp/src/chgrp.rs +++ b/src/uu/chgrp/src/chgrp.rs @@ -197,7 +197,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> App<'static, 'static> { - App::new(executable!()) + App::new(util_name!()) .version(VERSION) .about(ABOUT) .arg( diff --git a/src/uu/chmod/src/chmod.rs b/src/uu/chmod/src/chmod.rs index d89827c97..d39ac4556 100644 --- a/src/uu/chmod/src/chmod.rs +++ b/src/uu/chmod/src/chmod.rs @@ -116,7 +116,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> App<'static, 'static> { - App::new(executable!()) + App::new(util_name!()) .version(crate_version!()) .about(ABOUT) .arg( diff --git a/src/uu/chown/src/chown.rs b/src/uu/chown/src/chown.rs index 7df263c5d..50c651e1f 100644 --- a/src/uu/chown/src/chown.rs +++ b/src/uu/chown/src/chown.rs @@ -165,7 +165,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } pub fn uu_app() -> App<'static, 'static> { - App::new(executable!()) + App::new(util_name!()) .version(crate_version!()) .about(ABOUT) .arg( diff --git a/src/uu/chroot/src/chroot.rs b/src/uu/chroot/src/chroot.rs index 2c0f8522c..b548b7734 100644 --- a/src/uu/chroot/src/chroot.rs +++ b/src/uu/chroot/src/chroot.rs @@ -92,7 +92,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> App<'static, 'static> { - App::new(executable!()) + App::new(util_name!()) .version(crate_version!()) .about(ABOUT) .usage(SYNTAX) diff --git a/src/uu/cksum/src/cksum.rs b/src/uu/cksum/src/cksum.rs index e88cc78b3..8173fa451 100644 --- a/src/uu/cksum/src/cksum.rs +++ b/src/uu/cksum/src/cksum.rs @@ -213,7 +213,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> App<'static, 'static> { - App::new(executable!()) + App::new(util_name!()) .name(NAME) .version(crate_version!()) .about(SUMMARY) diff --git a/src/uu/comm/src/comm.rs b/src/uu/comm/src/comm.rs index aa10432a2..69db9c556 100644 --- a/src/uu/comm/src/comm.rs +++ b/src/uu/comm/src/comm.rs @@ -148,7 +148,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> App<'static, 'static> { - App::new(executable!()) + App::new(util_name!()) .version(crate_version!()) .about(ABOUT) .after_help(LONG_HELP) diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index 7c67649c2..8920cd439 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -293,7 +293,7 @@ static DEFAULT_ATTRIBUTES: &[Attribute] = &[ ]; pub fn uu_app() -> App<'static, 'static> { - App::new(executable!()) + App::new(util_name!()) .version(crate_version!()) .about(ABOUT) .arg(Arg::with_name(options::TARGET_DIRECTORY) diff --git a/src/uu/csplit/src/csplit.rs b/src/uu/csplit/src/csplit.rs index 048ec80d8..039501401 100644 --- a/src/uu/csplit/src/csplit.rs +++ b/src/uu/csplit/src/csplit.rs @@ -739,7 +739,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> App<'static, 'static> { - App::new(executable!()) + App::new(util_name!()) .version(crate_version!()) .about(SUMMARY) .arg( diff --git a/src/uu/cut/src/cut.rs b/src/uu/cut/src/cut.rs index e33b8a2fe..6410b9efc 100644 --- a/src/uu/cut/src/cut.rs +++ b/src/uu/cut/src/cut.rs @@ -548,7 +548,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> App<'static, 'static> { - App::new(executable!()) + App::new(util_name!()) .name(NAME) .version(crate_version!()) .usage(SYNTAX) diff --git a/src/uu/date/src/date.rs b/src/uu/date/src/date.rs index 042daa616..3d2a8c6d0 100644 --- a/src/uu/date/src/date.rs +++ b/src/uu/date/src/date.rs @@ -253,7 +253,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> App<'static, 'static> { - App::new(executable!()) + App::new(util_name!()) .version(crate_version!()) .about(ABOUT) .arg( diff --git a/src/uu/df/src/df.rs b/src/uu/df/src/df.rs index cdfdf0b2d..9977370c4 100644 --- a/src/uu/df/src/df.rs +++ b/src/uu/df/src/df.rs @@ -427,7 +427,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } pub fn uu_app() -> App<'static, 'static> { - App::new(executable!()) + App::new(util_name!()) .version(crate_version!()) .about(ABOUT) .arg( diff --git a/src/uu/dircolors/src/dircolors.rs b/src/uu/dircolors/src/dircolors.rs index 70b609e31..27b43b9a0 100644 --- a/src/uu/dircolors/src/dircolors.rs +++ b/src/uu/dircolors/src/dircolors.rs @@ -153,7 +153,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> App<'static, 'static> { - App::new(executable!()) + App::new(util_name!()) .version(crate_version!()) .about(SUMMARY) .after_help(LONG_HELP) diff --git a/src/uu/dirname/src/dirname.rs b/src/uu/dirname/src/dirname.rs index e7dcc2195..d6e09acc1 100644 --- a/src/uu/dirname/src/dirname.rs +++ b/src/uu/dirname/src/dirname.rs @@ -86,7 +86,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } pub fn uu_app() -> App<'static, 'static> { - App::new(executable!()) + App::new(util_name!()) .about(ABOUT) .version(crate_version!()) .arg( diff --git a/src/uu/du/src/du.rs b/src/uu/du/src/du.rs index d85cc941c..02a05b911 100644 --- a/src/uu/du/src/du.rs +++ b/src/uu/du/src/du.rs @@ -625,7 +625,7 @@ fn parse_depth(max_depth_str: Option<&str>, summarize: bool) -> UResult App<'static, 'static> { - App::new(executable!()) + App::new(util_name!()) .version(crate_version!()) .about(SUMMARY) .after_help(LONG_HELP) diff --git a/src/uu/echo/src/echo.rs b/src/uu/echo/src/echo.rs index aae1ad10d..d34460df0 100644 --- a/src/uu/echo/src/echo.rs +++ b/src/uu/echo/src/echo.rs @@ -132,7 +132,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } pub fn uu_app() -> App<'static, 'static> { - App::new(executable!()) + App::new(util_name!()) .name(NAME) // TrailingVarArg specifies the final positional argument is a VarArg // and it doesn't attempts the parse any further args. diff --git a/src/uu/expand/src/expand.rs b/src/uu/expand/src/expand.rs index d5c37ce21..a821aed33 100644 --- a/src/uu/expand/src/expand.rs +++ b/src/uu/expand/src/expand.rs @@ -178,7 +178,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> App<'static, 'static> { - App::new(executable!()) + App::new(util_name!()) .version(crate_version!()) .about(ABOUT) .after_help(LONG_HELP) diff --git a/src/uu/expr/src/expr.rs b/src/uu/expr/src/expr.rs index 92c15565d..b37e1ced6 100644 --- a/src/uu/expr/src/expr.rs +++ b/src/uu/expr/src/expr.rs @@ -18,7 +18,7 @@ const VERSION: &str = "version"; const HELP: &str = "help"; pub fn uu_app() -> App<'static, 'static> { - App::new(executable!()) + App::new(util_name!()) .arg(Arg::with_name(VERSION).long(VERSION)) .arg(Arg::with_name(HELP).long(HELP)) } diff --git a/src/uu/factor/src/cli.rs b/src/uu/factor/src/cli.rs index 7963f162f..628ded969 100644 --- a/src/uu/factor/src/cli.rs +++ b/src/uu/factor/src/cli.rs @@ -75,7 +75,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> App<'static, 'static> { - App::new(executable!()) + App::new(util_name!()) .version(crate_version!()) .about(SUMMARY) .arg(Arg::with_name(options::NUMBER).multiple(true)) diff --git a/src/uu/false/src/false.rs b/src/uu/false/src/false.rs index 170788898..8f7710912 100644 --- a/src/uu/false/src/false.rs +++ b/src/uu/false/src/false.rs @@ -9,7 +9,8 @@ extern crate uucore; use clap::App; -use uucore::{error::UResult, executable}; +use uucore::error::UResult; +use uucore::util_name; #[uucore_procs::gen_uumain] pub fn uumain(args: impl uucore::Args) -> UResult<()> { @@ -18,5 +19,5 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } pub fn uu_app() -> App<'static, 'static> { - App::new(executable!()) + App::new(util_name!()) } diff --git a/src/uu/fmt/src/fmt.rs b/src/uu/fmt/src/fmt.rs index 8c2c8d9d9..87febb124 100644 --- a/src/uu/fmt/src/fmt.rs +++ b/src/uu/fmt/src/fmt.rs @@ -211,7 +211,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> App<'static, 'static> { - App::new(executable!()) + App::new(util_name!()) .version(crate_version!()) .about(ABOUT) .arg( diff --git a/src/uu/fold/src/fold.rs b/src/uu/fold/src/fold.rs index 1dbc8cdc7..a2321d5ee 100644 --- a/src/uu/fold/src/fold.rs +++ b/src/uu/fold/src/fold.rs @@ -64,7 +64,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> App<'static, 'static> { - App::new(executable!()) + App::new(util_name!()) .name(NAME) .version(crate_version!()) .usage(SYNTAX) diff --git a/src/uu/groups/src/groups.rs b/src/uu/groups/src/groups.rs index a40d1a490..a7c989ddf 100644 --- a/src/uu/groups/src/groups.rs +++ b/src/uu/groups/src/groups.rs @@ -85,7 +85,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> App<'static, 'static> { - App::new(executable!()) + App::new(util_name!()) .version(crate_version!()) .about(ABOUT) .arg( diff --git a/src/uu/hashsum/src/hashsum.rs b/src/uu/hashsum/src/hashsum.rs index d9feb6648..332e08009 100644 --- a/src/uu/hashsum/src/hashsum.rs +++ b/src/uu/hashsum/src/hashsum.rs @@ -342,7 +342,7 @@ pub fn uu_app_common() -> App<'static, 'static> { const TEXT_HELP: &str = "read in text mode"; #[cfg(not(windows))] const TEXT_HELP: &str = "read in text mode (default)"; - App::new(executable!()) + App::new(util_name!()) .version(crate_version!()) .about("Compute and check message digests.") .arg( diff --git a/src/uu/head/src/head.rs b/src/uu/head/src/head.rs index e17e17034..11ea56449 100644 --- a/src/uu/head/src/head.rs +++ b/src/uu/head/src/head.rs @@ -9,7 +9,7 @@ use clap::{crate_version, App, Arg}; use std::convert::TryFrom; use std::ffi::OsString; use std::io::{self, ErrorKind, Read, Seek, SeekFrom, Write}; -use uucore::{crash, executable, show_error, show_error_custom_description}; +use uucore::{crash, show_error_custom_description, util_name}; const EXIT_FAILURE: i32 = 1; const EXIT_SUCCESS: i32 = 0; @@ -41,7 +41,7 @@ use lines::zlines; use take::take_all_but; pub fn uu_app() -> App<'static, 'static> { - App::new(executable!()) + App::new(util_name!()) .version(crate_version!()) .about(ABOUT) .usage(USAGE) diff --git a/src/uu/hostid/src/hostid.rs b/src/uu/hostid/src/hostid.rs index b0f68968d..e593ff7ee 100644 --- a/src/uu/hostid/src/hostid.rs +++ b/src/uu/hostid/src/hostid.rs @@ -29,7 +29,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } pub fn uu_app() -> App<'static, 'static> { - App::new(executable!()) + App::new(util_name!()) .version(crate_version!()) .usage(SYNTAX) } diff --git a/src/uu/hostname/src/hostname.rs b/src/uu/hostname/src/hostname.rs index 045e43045..e42629eff 100644 --- a/src/uu/hostname/src/hostname.rs +++ b/src/uu/hostname/src/hostname.rs @@ -73,7 +73,7 @@ fn execute(args: impl uucore::Args) -> UResult<()> { } pub fn uu_app() -> App<'static, 'static> { - App::new(executable!()) + App::new(util_name!()) .version(crate_version!()) .about(ABOUT) .arg( diff --git a/src/uu/id/src/id.rs b/src/uu/id/src/id.rs index 16c665273..ce2d8eaf1 100644 --- a/src/uu/id/src/id.rs +++ b/src/uu/id/src/id.rs @@ -347,7 +347,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } pub fn uu_app() -> App<'static, 'static> { - App::new(executable!()) + App::new(util_name!()) .version(crate_version!()) .about(ABOUT) .arg( diff --git a/src/uu/install/src/install.rs b/src/uu/install/src/install.rs index cbbe9c18b..33a21be88 100644 --- a/src/uu/install/src/install.rs +++ b/src/uu/install/src/install.rs @@ -202,7 +202,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } pub fn uu_app() -> App<'static, 'static> { - App::new(executable!()) + App::new(util_name!()) .version(crate_version!()) .about(ABOUT) .arg( diff --git a/src/uu/kill/src/kill.rs b/src/uu/kill/src/kill.rs index b3f5010ca..e9ec70964 100644 --- a/src/uu/kill/src/kill.rs +++ b/src/uu/kill/src/kill.rs @@ -76,7 +76,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } pub fn uu_app() -> App<'static, 'static> { - App::new(executable!()) + App::new(util_name!()) .version(crate_version!()) .about(ABOUT) .arg( diff --git a/src/uu/link/src/link.rs b/src/uu/link/src/link.rs index ad7702044..5344eb9c8 100644 --- a/src/uu/link/src/link.rs +++ b/src/uu/link/src/link.rs @@ -51,7 +51,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> App<'static, 'static> { - App::new(executable!()) + App::new(util_name!()) .version(crate_version!()) .about(ABOUT) .arg( diff --git a/src/uu/ln/src/ln.rs b/src/uu/ln/src/ln.rs index 7010ff5e4..a9a8379a0 100644 --- a/src/uu/ln/src/ln.rs +++ b/src/uu/ln/src/ln.rs @@ -196,7 +196,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } pub fn uu_app() -> App<'static, 'static> { - App::new(executable!()) + App::new(util_name!()) .version(crate_version!()) .about(ABOUT) .arg( diff --git a/src/uu/logname/src/logname.rs b/src/uu/logname/src/logname.rs index 4a6f43418..45187be18 100644 --- a/src/uu/logname/src/logname.rs +++ b/src/uu/logname/src/logname.rs @@ -56,7 +56,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> App<'static, 'static> { - App::new(executable!()) + App::new(util_name!()) .version(crate_version!()) .about(SUMMARY) } diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index 450acf8cd..6e49e7ef9 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -618,7 +618,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } pub fn uu_app() -> App<'static, 'static> { - App::new(executable!()) + App::new(util_name!()) .version(crate_version!()) .about( "By default, ls will list the files and contents of any directories on \ diff --git a/src/uu/mkdir/src/mkdir.rs b/src/uu/mkdir/src/mkdir.rs index a99867570..8d492a93c 100644 --- a/src/uu/mkdir/src/mkdir.rs +++ b/src/uu/mkdir/src/mkdir.rs @@ -51,7 +51,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } pub fn uu_app() -> App<'static, 'static> { - App::new(executable!()) + App::new(util_name!()) .version(crate_version!()) .about(ABOUT) .arg( diff --git a/src/uu/mkfifo/src/mkfifo.rs b/src/uu/mkfifo/src/mkfifo.rs index ea0906567..10ea8337c 100644 --- a/src/uu/mkfifo/src/mkfifo.rs +++ b/src/uu/mkfifo/src/mkfifo.rs @@ -70,7 +70,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> App<'static, 'static> { - App::new(executable!()) + App::new(util_name!()) .name(NAME) .version(crate_version!()) .usage(USAGE) diff --git a/src/uu/mknod/src/mknod.rs b/src/uu/mknod/src/mknod.rs index 8cc7db908..72b35cd33 100644 --- a/src/uu/mknod/src/mknod.rs +++ b/src/uu/mknod/src/mknod.rs @@ -145,7 +145,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> App<'static, 'static> { - App::new(executable!()) + App::new(util_name!()) .version(crate_version!()) .usage(USAGE) .after_help(LONG_HELP) diff --git a/src/uu/mktemp/src/mktemp.rs b/src/uu/mktemp/src/mktemp.rs index e1dd604a0..645c5ac28 100644 --- a/src/uu/mktemp/src/mktemp.rs +++ b/src/uu/mktemp/src/mktemp.rs @@ -134,7 +134,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } pub fn uu_app() -> App<'static, 'static> { - App::new(executable!()) + App::new(util_name!()) .version(crate_version!()) .about(ABOUT) .arg( diff --git a/src/uu/more/src/more.rs b/src/uu/more/src/more.rs index ecc779ba6..ee03c0d8a 100644 --- a/src/uu/more/src/more.rs +++ b/src/uu/more/src/more.rs @@ -93,7 +93,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> App<'static, 'static> { - App::new(executable!()) + App::new(util_name!()) .about("A file perusal filter for CRT viewing.") .version(crate_version!()) .arg( diff --git a/src/uu/mv/src/mv.rs b/src/uu/mv/src/mv.rs index 166e8cb1a..5f7431203 100644 --- a/src/uu/mv/src/mv.rs +++ b/src/uu/mv/src/mv.rs @@ -133,7 +133,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> App<'static, 'static> { - App::new(executable!()) + App::new(util_name!()) .version(crate_version!()) .about(ABOUT) .arg( diff --git a/src/uu/nice/src/nice.rs b/src/uu/nice/src/nice.rs index 49efe32e0..62565fc5c 100644 --- a/src/uu/nice/src/nice.rs +++ b/src/uu/nice/src/nice.rs @@ -101,7 +101,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> App<'static, 'static> { - App::new(executable!()) + App::new(util_name!()) .setting(AppSettings::TrailingVarArg) .version(crate_version!()) .arg( diff --git a/src/uu/nl/src/nl.rs b/src/uu/nl/src/nl.rs index 81e76aa26..d5ea18f8a 100644 --- a/src/uu/nl/src/nl.rs +++ b/src/uu/nl/src/nl.rs @@ -143,7 +143,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> App<'static, 'static> { - App::new(executable!()) + App::new(util_name!()) .name(NAME) .version(crate_version!()) .usage(USAGE) diff --git a/src/uu/nohup/src/nohup.rs b/src/uu/nohup/src/nohup.rs index acc101e4e..60654672d 100644 --- a/src/uu/nohup/src/nohup.rs +++ b/src/uu/nohup/src/nohup.rs @@ -71,7 +71,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> App<'static, 'static> { - App::new(executable!()) + App::new(util_name!()) .version(crate_version!()) .about(ABOUT) .after_help(LONG_HELP) diff --git a/src/uu/nproc/src/nproc.rs b/src/uu/nproc/src/nproc.rs index 1f284685b..ae8e66492 100644 --- a/src/uu/nproc/src/nproc.rs +++ b/src/uu/nproc/src/nproc.rs @@ -70,7 +70,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> App<'static, 'static> { - App::new(executable!()) + App::new(util_name!()) .version(crate_version!()) .about(ABOUT) .arg( diff --git a/src/uu/numfmt/src/numfmt.rs b/src/uu/numfmt/src/numfmt.rs index 01f12c51b..4f9d90a6c 100644 --- a/src/uu/numfmt/src/numfmt.rs +++ b/src/uu/numfmt/src/numfmt.rs @@ -175,7 +175,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> App<'static, 'static> { - App::new(executable!()) + App::new(util_name!()) .version(crate_version!()) .about(ABOUT) .after_help(LONG_HELP) diff --git a/src/uu/od/src/od.rs b/src/uu/od/src/od.rs index 359531d4e..b37990fd5 100644 --- a/src/uu/od/src/od.rs +++ b/src/uu/od/src/od.rs @@ -252,7 +252,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> clap::App<'static, 'static> { - clap::App::new(executable!()) + clap::App::new(util_name!()) .version(crate_version!()) .about(ABOUT) .usage(USAGE) diff --git a/src/uu/paste/src/paste.rs b/src/uu/paste/src/paste.rs index 7f7969687..801de50dc 100644 --- a/src/uu/paste/src/paste.rs +++ b/src/uu/paste/src/paste.rs @@ -52,7 +52,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> App<'static, 'static> { - App::new(executable!()) + App::new(util_name!()) .version(crate_version!()) .about(ABOUT) .arg( diff --git a/src/uu/pathchk/src/pathchk.rs b/src/uu/pathchk/src/pathchk.rs index 7f728667f..44e4ed4ac 100644 --- a/src/uu/pathchk/src/pathchk.rs +++ b/src/uu/pathchk/src/pathchk.rs @@ -96,7 +96,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> App<'static, 'static> { - App::new(executable!()) + App::new(util_name!()) .version(crate_version!()) .about(ABOUT) .arg( diff --git a/src/uu/pinky/src/pinky.rs b/src/uu/pinky/src/pinky.rs index 16bcfd3c9..58c4f56ed 100644 --- a/src/uu/pinky/src/pinky.rs +++ b/src/uu/pinky/src/pinky.rs @@ -130,7 +130,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> App<'static, 'static> { - App::new(executable!()) + App::new(util_name!()) .version(crate_version!()) .about(ABOUT) .arg( diff --git a/src/uu/pr/src/pr.rs b/src/uu/pr/src/pr.rs index d6b9e8ca3..9ee5158ec 100644 --- a/src/uu/pr/src/pr.rs +++ b/src/uu/pr/src/pr.rs @@ -23,7 +23,7 @@ use std::fs::{metadata, File}; use std::io::{stdin, stdout, BufRead, BufReader, Lines, Read, Stdout, Write}; #[cfg(unix)] use std::os::unix::fs::FileTypeExt; -use uucore::executable; +use uucore::util_name; type IOError = std::io::Error; @@ -170,7 +170,7 @@ quick_error! { pub fn uu_app() -> clap::App<'static, 'static> { // TODO: migrate to clap to get more shell completions - clap::App::new(executable!()) + clap::App::new(util_name!()) } pub fn uumain(args: impl uucore::Args) -> i32 { diff --git a/src/uu/printenv/src/printenv.rs b/src/uu/printenv/src/printenv.rs index 6e0ca7157..7164f5092 100644 --- a/src/uu/printenv/src/printenv.rs +++ b/src/uu/printenv/src/printenv.rs @@ -55,7 +55,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> App<'static, 'static> { - App::new(executable!()) + App::new(util_name!()) .version(crate_version!()) .about(ABOUT) .arg( diff --git a/src/uu/printf/src/printf.rs b/src/uu/printf/src/printf.rs index efa9aea57..bc672156f 100644 --- a/src/uu/printf/src/printf.rs +++ b/src/uu/printf/src/printf.rs @@ -303,7 +303,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> App<'static, 'static> { - App::new(executable!()) + App::new(util_name!()) .arg(Arg::with_name(VERSION).long(VERSION)) .arg(Arg::with_name(HELP).long(HELP)) } diff --git a/src/uu/ptx/src/ptx.rs b/src/uu/ptx/src/ptx.rs index 01b14bc4d..4f16af470 100644 --- a/src/uu/ptx/src/ptx.rs +++ b/src/uu/ptx/src/ptx.rs @@ -659,7 +659,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> App<'static, 'static> { - App::new(executable!()) + App::new(util_name!()) .name(NAME) .version(crate_version!()) .usage(BRIEF) diff --git a/src/uu/pwd/src/pwd.rs b/src/uu/pwd/src/pwd.rs index 37effe618..f29a3abfa 100644 --- a/src/uu/pwd/src/pwd.rs +++ b/src/uu/pwd/src/pwd.rs @@ -66,7 +66,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } pub fn uu_app() -> App<'static, 'static> { - App::new(executable!()) + App::new(util_name!()) .version(crate_version!()) .about(ABOUT) .arg( diff --git a/src/uu/readlink/src/readlink.rs b/src/uu/readlink/src/readlink.rs index 826fa0254..7ae2f9423 100644 --- a/src/uu/readlink/src/readlink.rs +++ b/src/uu/readlink/src/readlink.rs @@ -98,7 +98,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> App<'static, 'static> { - App::new(executable!()) + App::new(util_name!()) .version(crate_version!()) .about(ABOUT) .arg( diff --git a/src/uu/realpath/src/realpath.rs b/src/uu/realpath/src/realpath.rs index fe2ad4ccc..34b2640ab 100644 --- a/src/uu/realpath/src/realpath.rs +++ b/src/uu/realpath/src/realpath.rs @@ -55,7 +55,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> App<'static, 'static> { - App::new(executable!()) + App::new(util_name!()) .version(crate_version!()) .about(ABOUT) .arg( diff --git a/src/uu/relpath/src/relpath.rs b/src/uu/relpath/src/relpath.rs index cb0fba7cc..e27ee0ce9 100644 --- a/src/uu/relpath/src/relpath.rs +++ b/src/uu/relpath/src/relpath.rs @@ -82,7 +82,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> App<'static, 'static> { - App::new(executable!()) + App::new(util_name!()) .version(crate_version!()) .about(ABOUT) .arg( diff --git a/src/uu/rm/src/rm.rs b/src/uu/rm/src/rm.rs index 259d1ab39..4c44f59ee 100644 --- a/src/uu/rm/src/rm.rs +++ b/src/uu/rm/src/rm.rs @@ -140,7 +140,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> App<'static, 'static> { - App::new(executable!()) + App::new(util_name!()) .version(crate_version!()) .about(ABOUT) diff --git a/src/uu/rmdir/src/rmdir.rs b/src/uu/rmdir/src/rmdir.rs index 8dbaf79a8..3acfca773 100644 --- a/src/uu/rmdir/src/rmdir.rs +++ b/src/uu/rmdir/src/rmdir.rs @@ -53,7 +53,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> App<'static, 'static> { - App::new(executable!()) + App::new(util_name!()) .version(crate_version!()) .about(ABOUT) .arg( diff --git a/src/uu/seq/src/seq.rs b/src/uu/seq/src/seq.rs index 50a93d3af..589e8450d 100644 --- a/src/uu/seq/src/seq.rs +++ b/src/uu/seq/src/seq.rs @@ -163,7 +163,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> App<'static, 'static> { - App::new(executable!()) + App::new(util_name!()) .setting(AppSettings::AllowLeadingHyphen) .version(crate_version!()) .about(ABOUT) diff --git a/src/uu/shred/src/shred.rs b/src/uu/shred/src/shred.rs index 90336ea95..43428caf6 100644 --- a/src/uu/shred/src/shred.rs +++ b/src/uu/shred/src/shred.rs @@ -330,7 +330,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> App<'static, 'static> { - App::new(executable!()) + App::new(util_name!()) .version(crate_version!()) .about(ABOUT) .after_help(AFTER_HELP) diff --git a/src/uu/shuf/src/shuf.rs b/src/uu/shuf/src/shuf.rs index 4690d1c6e..7f69a5fe9 100644 --- a/src/uu/shuf/src/shuf.rs +++ b/src/uu/shuf/src/shuf.rs @@ -115,7 +115,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> App<'static, 'static> { - App::new(executable!()) + App::new(util_name!()) .name(NAME) .version(crate_version!()) .template(TEMPLATE) diff --git a/src/uu/sleep/src/sleep.rs b/src/uu/sleep/src/sleep.rs index 127804a9f..c34d4f7b7 100644 --- a/src/uu/sleep/src/sleep.rs +++ b/src/uu/sleep/src/sleep.rs @@ -49,7 +49,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } pub fn uu_app() -> App<'static, 'static> { - App::new(executable!()) + App::new(util_name!()) .version(crate_version!()) .about(ABOUT) .after_help(LONG_HELP) diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index e0b445782..c31c47eb9 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -1287,7 +1287,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } pub fn uu_app() -> App<'static, 'static> { - App::new(executable!()) + App::new(util_name!()) .version(crate_version!()) .about(ABOUT) .arg( diff --git a/src/uu/split/src/split.rs b/src/uu/split/src/split.rs index ccc98ee5e..8e7cb5d6d 100644 --- a/src/uu/split/src/split.rs +++ b/src/uu/split/src/split.rs @@ -127,7 +127,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> App<'static, 'static> { - App::new(executable!()) + App::new(util_name!()) .version(crate_version!()) .about("Create output files containing consecutive or interleaved sections of input") // strategy (mutually exclusive) diff --git a/src/uu/stat/src/stat.rs b/src/uu/stat/src/stat.rs index c56971f6b..d4e9818d5 100644 --- a/src/uu/stat/src/stat.rs +++ b/src/uu/stat/src/stat.rs @@ -963,7 +963,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> App<'static, 'static> { - App::new(executable!()) + App::new(util_name!()) .version(crate_version!()) .about(ABOUT) .arg( diff --git a/src/uu/stdbuf/src/stdbuf.rs b/src/uu/stdbuf/src/stdbuf.rs index 7460a2cb2..469fffbdc 100644 --- a/src/uu/stdbuf/src/stdbuf.rs +++ b/src/uu/stdbuf/src/stdbuf.rs @@ -185,7 +185,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> App<'static, 'static> { - App::new(executable!()) + App::new(util_name!()) .version(crate_version!()) .about(ABOUT) .after_help(LONG_HELP) diff --git a/src/uu/sum/src/sum.rs b/src/uu/sum/src/sum.rs index 0ce612859..b4b2294aa 100644 --- a/src/uu/sum/src/sum.rs +++ b/src/uu/sum/src/sum.rs @@ -140,7 +140,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> App<'static, 'static> { - App::new(executable!()) + App::new(util_name!()) .name(NAME) .version(crate_version!()) .usage(USAGE) diff --git a/src/uu/sync/src/sync.rs b/src/uu/sync/src/sync.rs index 4fcdf49f9..5109bf24b 100644 --- a/src/uu/sync/src/sync.rs +++ b/src/uu/sync/src/sync.rs @@ -193,7 +193,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> App<'static, 'static> { - App::new(executable!()) + App::new(util_name!()) .version(crate_version!()) .about(ABOUT) .arg( diff --git a/src/uu/tac/src/tac.rs b/src/uu/tac/src/tac.rs index 01cf09215..70fa04622 100644 --- a/src/uu/tac/src/tac.rs +++ b/src/uu/tac/src/tac.rs @@ -51,7 +51,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> App<'static, 'static> { - App::new(executable!()) + App::new(util_name!()) .name(NAME) .version(crate_version!()) .usage(USAGE) diff --git a/src/uu/tail/src/tail.rs b/src/uu/tail/src/tail.rs index 471c1a404..106c1c5db 100644 --- a/src/uu/tail/src/tail.rs +++ b/src/uu/tail/src/tail.rs @@ -213,7 +213,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> App<'static, 'static> { - App::new(executable!()) + App::new(util_name!()) .version(crate_version!()) .about("output the last part of files") // TODO: add usage diff --git a/src/uu/tee/src/tee.rs b/src/uu/tee/src/tee.rs index a207dee63..bf9334677 100644 --- a/src/uu/tee/src/tee.rs +++ b/src/uu/tee/src/tee.rs @@ -57,7 +57,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> App<'static, 'static> { - App::new(executable!()) + App::new(util_name!()) .version(crate_version!()) .about(ABOUT) .after_help("If a FILE is -, it refers to a file named - .") diff --git a/src/uu/test/src/test.rs b/src/uu/test/src/test.rs index bed1472e2..939e13ab9 100644 --- a/src/uu/test/src/test.rs +++ b/src/uu/test/src/test.rs @@ -14,7 +14,7 @@ use clap::{crate_version, App, AppSettings}; use parser::{parse, Symbol}; use std::ffi::{OsStr, OsString}; use std::path::Path; -use uucore::executable; +use uucore::util_name; const USAGE: &str = "test EXPRESSION or: test @@ -87,7 +87,7 @@ the version described here. Please refer to your shell's documentation for details about the options it supports."; pub fn uu_app() -> App<'static, 'static> { - App::new(executable!()) + App::new(util_name!()) .setting(AppSettings::DisableHelpFlags) .setting(AppSettings::DisableVersion) } diff --git a/src/uu/touch/src/touch.rs b/src/uu/touch/src/touch.rs index 49efa676a..92c0080fa 100644 --- a/src/uu/touch/src/touch.rs +++ b/src/uu/touch/src/touch.rs @@ -129,7 +129,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } pub fn uu_app() -> App<'static, 'static> { - App::new(executable!()) + App::new(util_name!()) .version(crate_version!()) .about(ABOUT) .arg( diff --git a/src/uu/tr/src/tr.rs b/src/uu/tr/src/tr.rs index 6dd81badf..390511ec4 100644 --- a/src/uu/tr/src/tr.rs +++ b/src/uu/tr/src/tr.rs @@ -312,7 +312,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> App<'static, 'static> { - App::new(executable!()) + App::new(util_name!()) .version(crate_version!()) .about(ABOUT) .arg( diff --git a/src/uu/true/src/true.rs b/src/uu/true/src/true.rs index f84a89176..07e075175 100644 --- a/src/uu/true/src/true.rs +++ b/src/uu/true/src/true.rs @@ -10,7 +10,7 @@ extern crate uucore; use clap::App; use uucore::error::UResult; -use uucore::executable; +use uucore::util_name; #[uucore_procs::gen_uumain] pub fn uumain(args: impl uucore::Args) -> UResult<()> { @@ -19,5 +19,5 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } pub fn uu_app() -> App<'static, 'static> { - App::new(executable!()) + App::new(util_name!()) } diff --git a/src/uu/truncate/src/truncate.rs b/src/uu/truncate/src/truncate.rs index bb7aa61d4..7b7a55f33 100644 --- a/src/uu/truncate/src/truncate.rs +++ b/src/uu/truncate/src/truncate.rs @@ -133,7 +133,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> App<'static, 'static> { - App::new(executable!()) + App::new(util_name!()) .version(crate_version!()) .about(ABOUT) .arg( diff --git a/src/uu/tsort/src/tsort.rs b/src/uu/tsort/src/tsort.rs index 0a323f837..c2416afb4 100644 --- a/src/uu/tsort/src/tsort.rs +++ b/src/uu/tsort/src/tsort.rs @@ -90,7 +90,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> App<'static, 'static> { - App::new(executable!()) + App::new(util_name!()) .version(crate_version!()) .usage(USAGE) .about(SUMMARY) diff --git a/src/uu/tty/src/tty.rs b/src/uu/tty/src/tty.rs index 7412cdf45..7c6f73dd4 100644 --- a/src/uu/tty/src/tty.rs +++ b/src/uu/tty/src/tty.rs @@ -78,7 +78,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> App<'static, 'static> { - App::new(executable!()) + App::new(util_name!()) .version(crate_version!()) .about(ABOUT) .arg( diff --git a/src/uu/uname/src/uname.rs b/src/uu/uname/src/uname.rs index abd50d1b8..2b16c85ec 100644 --- a/src/uu/uname/src/uname.rs +++ b/src/uu/uname/src/uname.rs @@ -119,7 +119,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> App<'static, 'static> { - App::new(executable!()) + App::new(util_name!()) .version(crate_version!()) .about(ABOUT) .arg(Arg::with_name(options::ALL) diff --git a/src/uu/unexpand/src/unexpand.rs b/src/uu/unexpand/src/unexpand.rs index 50e3f186d..4a4c50feb 100644 --- a/src/uu/unexpand/src/unexpand.rs +++ b/src/uu/unexpand/src/unexpand.rs @@ -102,7 +102,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> App<'static, 'static> { - App::new(executable!()) + App::new(util_name!()) .name(NAME) .version(crate_version!()) .usage(USAGE) diff --git a/src/uu/uniq/src/uniq.rs b/src/uu/uniq/src/uniq.rs index 20639c850..288ecf105 100644 --- a/src/uu/uniq/src/uniq.rs +++ b/src/uu/uniq/src/uniq.rs @@ -281,7 +281,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> App<'static, 'static> { - App::new(executable!()) + App::new(util_name!()) .version(crate_version!()) .about(ABOUT) .arg( diff --git a/src/uu/unlink/src/unlink.rs b/src/uu/unlink/src/unlink.rs index 49f17cb12..7c0e596fb 100644 --- a/src/uu/unlink/src/unlink.rs +++ b/src/uu/unlink/src/unlink.rs @@ -95,7 +95,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> App<'static, 'static> { - App::new(executable!()) + App::new(util_name!()) .version(crate_version!()) .about(ABOUT) .arg(Arg::with_name(OPT_PATH).hidden(true).multiple(true)) diff --git a/src/uu/uptime/src/uptime.rs b/src/uu/uptime/src/uptime.rs index 35270093c..06dd93a24 100644 --- a/src/uu/uptime/src/uptime.rs +++ b/src/uu/uptime/src/uptime.rs @@ -64,7 +64,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> App<'static, 'static> { - App::new(executable!()) + App::new(util_name!()) .version(crate_version!()) .about(ABOUT) .arg( diff --git a/src/uu/users/src/users.rs b/src/uu/users/src/users.rs index ef878497c..91168644c 100644 --- a/src/uu/users/src/users.rs +++ b/src/uu/users/src/users.rs @@ -65,7 +65,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> App<'static, 'static> { - App::new(executable!()) + App::new(util_name!()) .version(crate_version!()) .about(ABOUT) .arg(Arg::with_name(ARG_FILES).takes_value(true).max_values(1)) diff --git a/src/uu/wc/src/wc.rs b/src/uu/wc/src/wc.rs index 0bcc66664..afdf8b7de 100644 --- a/src/uu/wc/src/wc.rs +++ b/src/uu/wc/src/wc.rs @@ -164,7 +164,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> App<'static, 'static> { - App::new(executable!()) + App::new(util_name!()) .version(crate_version!()) .about(ABOUT) .arg( diff --git a/src/uu/who/src/who.rs b/src/uu/who/src/who.rs index 6a9c88710..557a4efd6 100644 --- a/src/uu/who/src/who.rs +++ b/src/uu/who/src/who.rs @@ -160,7 +160,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> App<'static, 'static> { - App::new(executable!()) + App::new(util_name!()) .version(crate_version!()) .about(ABOUT) .arg( From 894d9a068cf83b456e596e35f4316b23ebda2ff2 Mon Sep 17 00:00:00 2001 From: Roy Ivy III Date: Wed, 30 Jun 2021 22:44:43 -0500 Subject: [PATCH 014/206] refactor ~ standardize on 'Try `{} --help`...' messaging (common markdown-type formatting) --- src/uu/basename/src/basename.rs | 4 ++-- src/uu/chroot/src/chroot.rs | 2 +- src/uu/cp/src/cp.rs | 2 +- src/uu/du/src/du.rs | 12 ++++++------ src/uu/ln/src/ln.rs | 2 +- src/uu/mknod/src/mknod.rs | 4 ++-- src/uu/mv/src/mv.rs | 2 +- src/uu/nice/src/nice.rs | 2 +- src/uu/pathchk/src/pathchk.rs | 5 ++++- src/uu/printf/src/printf.rs | 2 +- src/uu/readlink/src/readlink.rs | 2 +- src/uu/seq/src/seq.rs | 6 +++--- src/uu/stdbuf/src/stdbuf.rs | 10 ++++++++-- src/uu/unlink/src/unlink.rs | 4 ++-- src/uucore/src/lib/mods/error.rs | 6 +++--- src/uucore_procs/src/lib.rs | 2 +- 16 files changed, 38 insertions(+), 29 deletions(-) diff --git a/src/uu/basename/src/basename.rs b/src/uu/basename/src/basename.rs index 26fb67c54..aa1e2fa2d 100644 --- a/src/uu/basename/src/basename.rs +++ b/src/uu/basename/src/basename.rs @@ -46,7 +46,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { if !matches.is_present(options::NAME) { crash!( 1, - "{1}\nTry '{0} --help' for more information.", + "{1}\nTry `{0} --help` for more information.", executable!(), "missing operand" ); @@ -60,7 +60,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { if !multiple_paths && matches.occurrences_of(options::NAME) > 2 { crash!( 1, - "extra operand '{1}'\nTry '{0} --help' for more information.", + "extra operand '{1}'\nTry `{0} --help` for more information.", executable!(), matches.values_of(options::NAME).unwrap().nth(2).unwrap() ); diff --git a/src/uu/chroot/src/chroot.rs b/src/uu/chroot/src/chroot.rs index b548b7734..01a7c6b84 100644 --- a/src/uu/chroot/src/chroot.rs +++ b/src/uu/chroot/src/chroot.rs @@ -46,7 +46,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { Some(v) => Path::new(v), None => crash!( 1, - "Missing operand: NEWROOT\nTry '{} --help' for more information.", + "Missing operand: NEWROOT\nTry `{} --help` for more information.", NAME ), }; diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index 8920cd439..542e9bfee 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -99,7 +99,7 @@ quick_error! { NotImplemented(opt: String) { display("Option '{}' not yet implemented.", opt) } /// Invalid arguments to backup - Backup(description: String) { display("{}\nTry 'cp --help' for more information.", description) } + Backup(description: String) { display("{}\nTry `{} --help` for more information.", description, executable!()) } } } diff --git a/src/uu/du/src/du.rs b/src/uu/du/src/du.rs index 02a05b911..6160f5490 100644 --- a/src/uu/du/src/du.rs +++ b/src/uu/du/src/du.rs @@ -70,7 +70,6 @@ mod options { pub const FILE: &str = "FILE"; } -const NAME: &str = "du"; const SUMMARY: &str = "estimate file space usage"; const LONG_HELP: &str = " Display values are in units of the first available SIZE from --block-size, @@ -87,7 +86,7 @@ const UNITS: [(char, u32); 6] = [('E', 6), ('P', 5), ('T', 4), ('G', 3), ('M', 2 struct Options { all: bool, - program_name: String, + util_name: String, max_depth: Option, total: bool, separate_dirs: bool, @@ -295,7 +294,7 @@ fn du( safe_writeln!( stderr(), "{}: cannot read directory '{}': {}", - options.program_name, + options.util_name, my_stat.path.display(), e ); @@ -423,8 +422,9 @@ Valid arguments are: - 'full-iso' - 'long-iso' - 'iso' -Try '{} --help' for more information.", - s, NAME +Try `{} --help` for more information.", + s, + executable!() ), DuError::InvalidTimeArg(s) => write!( f, @@ -466,7 +466,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let options = Options { all: matches.is_present(options::ALL), - program_name: NAME.to_owned(), + util_name: util_name!().to_string(), max_depth, total: matches.is_present(options::TOTAL), separate_dirs: matches.is_present(options::SEPARATE_DIRS), diff --git a/src/uu/ln/src/ln.rs b/src/uu/ln/src/ln.rs index a9a8379a0..568c97e45 100644 --- a/src/uu/ln/src/ln.rs +++ b/src/uu/ln/src/ln.rs @@ -68,7 +68,7 @@ impl Display for LnError { } Self::ExtraOperand(s) => write!( f, - "extra operand '{}'\nTry '{} --help' for more information.", + "extra operand '{}'\nTry `{} --help` for more information.", s, executable!() ), diff --git a/src/uu/mknod/src/mknod.rs b/src/uu/mknod/src/mknod.rs index 72b35cd33..bd26949cd 100644 --- a/src/uu/mknod/src/mknod.rs +++ b/src/uu/mknod/src/mknod.rs @@ -113,7 +113,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { if ch == 'p' { if matches.is_present("major") || matches.is_present("minor") { eprintln!("Fifos do not have major and minor device numbers."); - eprintln!("Try '{} --help' for more information.", NAME); + eprintln!("Try `{} --help` for more information.", NAME); 1 } else { _mknod(file_name, S_IFIFO | mode, 0) @@ -122,7 +122,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { match (matches.value_of("major"), matches.value_of("minor")) { (None, None) | (_, None) | (None, _) => { eprintln!("Special files require major and minor device numbers."); - eprintln!("Try '{} --help' for more information.", NAME); + eprintln!("Try `{} --help` for more information.", NAME); 1 } (Some(major), Some(minor)) => { diff --git a/src/uu/mv/src/mv.rs b/src/uu/mv/src/mv.rs index 5f7431203..c064a2244 100644 --- a/src/uu/mv/src/mv.rs +++ b/src/uu/mv/src/mv.rs @@ -294,7 +294,7 @@ fn exec(files: &[PathBuf], b: Behavior) -> i32 { if b.no_target_dir { show_error!( "mv: extra operand '{}'\n\ - Try '{} --help' for more information.", + Try `{} --help` for more information.", files[2].display(), executable!() ); diff --git a/src/uu/nice/src/nice.rs b/src/uu/nice/src/nice.rs index 62565fc5c..3b62d8058 100644 --- a/src/uu/nice/src/nice.rs +++ b/src/uu/nice/src/nice.rs @@ -53,7 +53,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { Some(nstr) => { if !matches.is_present(options::COMMAND) { show_error!( - "A command must be given with an adjustment.\nTry \"{} --help\" for more information.", + "A command must be given with an adjustment.\nTry `{} --help` for more information.", executable!() ); return 125; diff --git a/src/uu/pathchk/src/pathchk.rs b/src/uu/pathchk/src/pathchk.rs index 44e4ed4ac..46edf0850 100644 --- a/src/uu/pathchk/src/pathchk.rs +++ b/src/uu/pathchk/src/pathchk.rs @@ -69,7 +69,10 @@ pub fn uumain(args: impl uucore::Args) -> i32 { // take necessary actions let paths = matches.values_of(options::PATH); let mut res = if paths.is_none() { - show_error!("missing operand\nTry {} --help for more information", NAME); + show_error!( + "missing operand\nTry `{} --help` for more information", + NAME + ); false } else { true diff --git a/src/uu/printf/src/printf.rs b/src/uu/printf/src/printf.rs index bc672156f..211522fbf 100644 --- a/src/uu/printf/src/printf.rs +++ b/src/uu/printf/src/printf.rs @@ -284,7 +284,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let location = &args[0]; if args.len() <= 1 { println!( - "{0}: missing operand\nTry '{0} --help' for more information.", + "{0}: missing operand\nTry `{0} --help` for more information.", location ); return 1; diff --git a/src/uu/readlink/src/readlink.rs b/src/uu/readlink/src/readlink.rs index 7ae2f9423..4bfac4630 100644 --- a/src/uu/readlink/src/readlink.rs +++ b/src/uu/readlink/src/readlink.rs @@ -59,7 +59,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { if files.is_empty() { crash!( 1, - "missing operand\nTry {} --help for more information", + "missing operand\nTry `{} --help` for more information", NAME ); } diff --git a/src/uu/seq/src/seq.rs b/src/uu/seq/src/seq.rs index 589e8450d..ae81aa71d 100644 --- a/src/uu/seq/src/seq.rs +++ b/src/uu/seq/src/seq.rs @@ -70,13 +70,13 @@ impl FromStr for Number { Ok(n) => Ok(Number::BigInt(n)), Err(_) => match s.parse::() { Ok(value) if value.is_nan() => Err(format!( - "invalid 'not-a-number' argument: '{}'\nTry '{} --help' for more information.", + "invalid 'not-a-number' argument: '{}'\nTry `{} --help` for more information.", s, executable!(), )), Ok(value) => Ok(Number::F64(value)), Err(_) => Err(format!( - "invalid floating point argument: '{}'\nTry '{} --help' for more information.", + "invalid floating point argument: '{}'\nTry `{} --help` for more information.", s, executable!(), )), @@ -121,7 +121,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { }; if increment.is_zero() { show_error!( - "invalid Zero increment value: '{}'\nTry '{} --help' for more information.", + "invalid Zero increment value: '{}'\nTry `{} --help` for more information.", numbers[1], executable!() ); diff --git a/src/uu/stdbuf/src/stdbuf.rs b/src/uu/stdbuf/src/stdbuf.rs index 469fffbdc..60f42102d 100644 --- a/src/uu/stdbuf/src/stdbuf.rs +++ b/src/uu/stdbuf/src/stdbuf.rs @@ -156,8 +156,14 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let matches = uu_app().usage(&usage[..]).get_matches_from(args); - let options = ProgramOptions::try_from(&matches) - .unwrap_or_else(|e| crash!(125, "{}\nTry 'stdbuf --help' for more information.", e.0)); + let options = ProgramOptions::try_from(&matches).unwrap_or_else(|e| { + crash!( + 125, + "{}\nTry `{} --help` for more information.", + e.0, + executable!() + ) + }); let mut command_values = matches.values_of::<&str>(options::COMMAND).unwrap(); let mut command = Command::new(command_values.next().unwrap()); diff --git a/src/uu/unlink/src/unlink.rs b/src/uu/unlink/src/unlink.rs index 7c0e596fb..5f96cf429 100644 --- a/src/uu/unlink/src/unlink.rs +++ b/src/uu/unlink/src/unlink.rs @@ -43,13 +43,13 @@ pub fn uumain(args: impl uucore::Args) -> i32 { if paths.is_empty() { crash!( 1, - "missing operand\nTry '{0} --help' for more information.", + "missing operand\nTry `{0} --help` for more information.", executable!() ); } else if paths.len() > 1 { crash!( 1, - "extra operand: '{1}'\nTry '{0} --help' for more information.", + "extra operand: '{1}'\nTry `{0} --help` for more information.", executable!(), paths[1] ); diff --git a/src/uucore/src/lib/mods/error.rs b/src/uucore/src/lib/mods/error.rs index 664fc9841..c2b3069c8 100644 --- a/src/uucore/src/lib/mods/error.rs +++ b/src/uucore/src/lib/mods/error.rs @@ -203,8 +203,8 @@ pub trait UError: Error + Send { /// Print usage help to a custom error. /// /// Return true or false to control whether a short usage help is printed - /// below the error message. The usage help is in the format: "Try '{name} - /// --help' for more information." and printed only if `true` is returned. + /// below the error message. The usage help is in the format: "Try `{name} + /// --help` for more information." and printed only if `true` is returned. /// /// # Example /// @@ -519,7 +519,7 @@ macro_rules! uio_error( /// let res: UResult<()> = Err(1.into()); /// ``` /// This type is especially useful for a trivial conversion from utils returning [`i32`] to -/// returning [`UResult`]. +/// returning [`UResult`]. #[derive(Debug)] pub struct ExitCode(pub i32); diff --git a/src/uucore_procs/src/lib.rs b/src/uucore_procs/src/lib.rs index f62e4178e..2467ce2c7 100644 --- a/src/uucore_procs/src/lib.rs +++ b/src/uucore_procs/src/lib.rs @@ -104,7 +104,7 @@ pub fn gen_uumain(_args: TokenStream, stream: TokenStream) -> TokenStream { show_error!("{}", s); } if e.usage() { - eprintln!("Try '{} --help' for more information.", executable!()); + eprintln!("Try `{} --help` for more information.", executable!()); } e.code() } From 23b68d80bad807df1dcb6dd8e4d925988744df05 Mon Sep 17 00:00:00 2001 From: Roy Ivy III Date: Wed, 30 Jun 2021 23:03:14 -0500 Subject: [PATCH 015/206] refactor ~ `usage()` instead of `get_usage()` --- src/uu/base32/src/base32.rs | 4 ++-- src/uu/base64/src/base64.rs | 4 ++-- src/uu/basename/src/basename.rs | 4 ++-- src/uu/chgrp/src/chgrp.rs | 4 ++-- src/uu/chmod/src/chmod.rs | 4 ++-- src/uu/chown/src/chown.rs | 4 ++-- src/uu/comm/src/comm.rs | 4 ++-- src/uu/cp/src/cp.rs | 4 ++-- src/uu/csplit/src/csplit.rs | 4 ++-- src/uu/df/src/df.rs | 4 ++-- src/uu/dircolors/src/dircolors.rs | 4 ++-- src/uu/dirname/src/dirname.rs | 4 ++-- src/uu/du/src/du.rs | 4 ++-- src/uu/expand/src/expand.rs | 4 ++-- src/uu/fmt/src/fmt.rs | 4 ++-- src/uu/groups/src/groups.rs | 4 ++-- src/uu/hostname/src/hostname.rs | 5 +++-- src/uu/id/src/id.rs | 4 ++-- src/uu/install/src/install.rs | 4 ++-- src/uu/link/src/link.rs | 4 ++-- src/uu/ln/src/ln.rs | 8 ++++---- src/uu/logname/src/logname.rs | 4 ++-- src/uu/ls/src/ls.rs | 4 ++-- src/uu/mkdir/src/mkdir.rs | 4 ++-- src/uu/mktemp/src/mktemp.rs | 4 ++-- src/uu/mv/src/mv.rs | 4 ++-- src/uu/nice/src/nice.rs | 4 ++-- src/uu/nohup/src/nohup.rs | 4 ++-- src/uu/nproc/src/nproc.rs | 4 ++-- src/uu/numfmt/src/numfmt.rs | 4 ++-- src/uu/pathchk/src/pathchk.rs | 4 ++-- src/uu/pinky/src/pinky.rs | 4 ++-- src/uu/printenv/src/printenv.rs | 4 ++-- src/uu/pwd/src/pwd.rs | 4 ++-- src/uu/readlink/src/readlink.rs | 4 ++-- src/uu/realpath/src/realpath.rs | 4 ++-- src/uu/relpath/src/relpath.rs | 4 ++-- src/uu/rm/src/rm.rs | 4 ++-- src/uu/rmdir/src/rmdir.rs | 4 ++-- src/uu/seq/src/seq.rs | 4 ++-- src/uu/shred/src/shred.rs | 4 ++-- src/uu/sleep/src/sleep.rs | 4 ++-- src/uu/sort/src/sort.rs | 4 ++-- src/uu/split/src/split.rs | 6 +++--- src/uu/stat/src/stat.rs | 4 ++-- src/uu/stdbuf/src/stdbuf.rs | 4 ++-- src/uu/sync/src/sync.rs | 4 ++-- src/uu/tee/src/tee.rs | 4 ++-- src/uu/timeout/src/timeout.rs | 4 ++-- src/uu/touch/src/touch.rs | 4 ++-- src/uu/tr/src/tr.rs | 4 ++-- src/uu/truncate/src/truncate.rs | 4 ++-- src/uu/tty/src/tty.rs | 4 ++-- src/uu/uniq/src/uniq.rs | 4 ++-- src/uu/unlink/src/unlink.rs | 4 ++-- src/uu/uptime/src/uptime.rs | 4 ++-- src/uu/users/src/users.rs | 4 ++-- src/uu/wc/src/wc.rs | 4 ++-- src/uu/who/src/who.rs | 4 ++-- 59 files changed, 122 insertions(+), 121 deletions(-) diff --git a/src/uu/base32/src/base32.rs b/src/uu/base32/src/base32.rs index 9a29717ac..8e02f03e0 100644 --- a/src/uu/base32/src/base32.rs +++ b/src/uu/base32/src/base32.rs @@ -28,13 +28,13 @@ static VERSION: &str = env!("CARGO_PKG_VERSION"); static BASE_CMD_PARSE_ERROR: i32 = 1; -fn get_usage() -> String { +fn usage() -> String { format!("{0} [OPTION]... [FILE]", executable!()) } pub fn uumain(args: impl uucore::Args) -> i32 { let format = Format::Base32; - let usage = get_usage(); + let usage = usage(); let name = executable!(); let config_result: Result = diff --git a/src/uu/base64/src/base64.rs b/src/uu/base64/src/base64.rs index 71ed44e6e..48aee537d 100644 --- a/src/uu/base64/src/base64.rs +++ b/src/uu/base64/src/base64.rs @@ -29,13 +29,13 @@ static VERSION: &str = env!("CARGO_PKG_VERSION"); static BASE_CMD_PARSE_ERROR: i32 = 1; -fn get_usage() -> String { +fn usage() -> String { format!("{0} [OPTION]... [FILE]", executable!()) } pub fn uumain(args: impl uucore::Args) -> i32 { let format = Format::Base64; - let usage = get_usage(); + let usage = usage(); let name = executable!(); let config_result: Result = base_common::parse_base_cmd_args(args, name, VERSION, ABOUT, &usage); diff --git a/src/uu/basename/src/basename.rs b/src/uu/basename/src/basename.rs index aa1e2fa2d..f3c9f5391 100644 --- a/src/uu/basename/src/basename.rs +++ b/src/uu/basename/src/basename.rs @@ -17,7 +17,7 @@ use uucore::InvalidEncodingHandling; static SUMMARY: &str = "Print NAME with any leading directory components removed If specified, also remove a trailing SUFFIX"; -fn get_usage() -> String { +fn usage() -> String { format!( "{0} NAME [SUFFIX] {0} OPTION... NAME...", @@ -36,7 +36,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let args = args .collect_str(InvalidEncodingHandling::ConvertLossy) .accept_any(); - let usage = get_usage(); + let usage = usage(); // // Argument parsing // diff --git a/src/uu/chgrp/src/chgrp.rs b/src/uu/chgrp/src/chgrp.rs index db1e20e57..11a45c90a 100644 --- a/src/uu/chgrp/src/chgrp.rs +++ b/src/uu/chgrp/src/chgrp.rs @@ -59,7 +59,7 @@ const FTS_COMFOLLOW: u8 = 1; const FTS_PHYSICAL: u8 = 1 << 1; const FTS_LOGICAL: u8 = 1 << 2; -fn get_usage() -> String { +fn usage() -> String { format!( "{0} [OPTION]... GROUP FILE...\n {0} [OPTION]... --reference=RFILE FILE...", executable!() @@ -71,7 +71,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .collect_str(InvalidEncodingHandling::ConvertLossy) .accept_any(); - let usage = get_usage(); + let usage = usage(); let mut app = uu_app().usage(&usage[..]); diff --git a/src/uu/chmod/src/chmod.rs b/src/uu/chmod/src/chmod.rs index d39ac4556..e1d8b32e4 100644 --- a/src/uu/chmod/src/chmod.rs +++ b/src/uu/chmod/src/chmod.rs @@ -36,7 +36,7 @@ mod options { pub const FILE: &str = "FILE"; } -fn get_usage() -> String { +fn usage() -> String { format!( "{0} [OPTION]... MODE[,MODE]... FILE... or: {0} [OPTION]... OCTAL-MODE FILE... @@ -58,7 +58,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { // a possible MODE prefix '-' needs to be removed (e.g. "chmod -x FILE"). let mode_had_minus_prefix = strip_minus_from_mode(&mut args); - let usage = get_usage(); + let usage = usage(); let after_help = get_long_usage(); let matches = uu_app() diff --git a/src/uu/chown/src/chown.rs b/src/uu/chown/src/chown.rs index 50c651e1f..a5d7c5ac7 100644 --- a/src/uu/chown/src/chown.rs +++ b/src/uu/chown/src/chown.rs @@ -61,7 +61,7 @@ const FTS_COMFOLLOW: u8 = 1; const FTS_PHYSICAL: u8 = 1 << 1; const FTS_LOGICAL: u8 = 1 << 2; -fn get_usage() -> String { +fn usage() -> String { format!( "{0} [OPTION]... [OWNER][:[GROUP]] FILE...\n{0} [OPTION]... --reference=RFILE FILE...", executable!() @@ -74,7 +74,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { .collect_str(InvalidEncodingHandling::Ignore) .accept_any(); - let usage = get_usage(); + let usage = usage(); let matches = uu_app().usage(&usage[..]).get_matches_from(args); diff --git a/src/uu/comm/src/comm.rs b/src/uu/comm/src/comm.rs index 69db9c556..36238cd5f 100644 --- a/src/uu/comm/src/comm.rs +++ b/src/uu/comm/src/comm.rs @@ -31,7 +31,7 @@ mod options { pub const FILE_2: &str = "FILE2"; } -fn get_usage() -> String { +fn usage() -> String { format!("{} [OPTION]... FILE1 FILE2", executable!()) } @@ -132,7 +132,7 @@ fn open_file(name: &str) -> io::Result { } pub fn uumain(args: impl uucore::Args) -> i32 { - let usage = get_usage(); + let usage = usage(); let args = args .collect_str(InvalidEncodingHandling::ConvertLossy) .accept_any(); diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index 542e9bfee..406f6757d 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -218,7 +218,7 @@ static LONG_HELP: &str = ""; static EXIT_OK: i32 = 0; static EXIT_ERR: i32 = 1; -fn get_usage() -> String { +fn usage() -> String { format!( "{0} [OPTION]... [-T] SOURCE DEST {0} [OPTION]... SOURCE... DIRECTORY @@ -465,7 +465,7 @@ pub fn uu_app() -> App<'static, 'static> { } pub fn uumain(args: impl uucore::Args) -> i32 { - let usage = get_usage(); + let usage = usage(); let matches = uu_app() .after_help(&*format!( "{}\n{}", diff --git a/src/uu/csplit/src/csplit.rs b/src/uu/csplit/src/csplit.rs index 039501401..409b17b98 100644 --- a/src/uu/csplit/src/csplit.rs +++ b/src/uu/csplit/src/csplit.rs @@ -34,7 +34,7 @@ mod options { pub const PATTERN: &str = "pattern"; } -fn get_usage() -> String { +fn usage() -> String { format!("{0} [OPTION]... FILE PATTERN...", executable!()) } @@ -706,7 +706,7 @@ mod tests { } pub fn uumain(args: impl uucore::Args) -> i32 { - let usage = get_usage(); + let usage = usage(); let args = args .collect_str(InvalidEncodingHandling::Ignore) .accept_any(); diff --git a/src/uu/df/src/df.rs b/src/uu/df/src/df.rs index 9977370c4..4ffa8b532 100644 --- a/src/uu/df/src/df.rs +++ b/src/uu/df/src/df.rs @@ -79,7 +79,7 @@ struct Filesystem { usage: FsUsage, } -fn get_usage() -> String { +fn usage() -> String { format!("{0} [OPTION]... [FILE]...", executable!()) } @@ -284,7 +284,7 @@ impl UError for DfError { #[uucore_procs::gen_uumain] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let usage = get_usage(); + let usage = usage(); let matches = uu_app().usage(&usage[..]).get_matches_from(args); let paths: Vec = matches diff --git a/src/uu/dircolors/src/dircolors.rs b/src/uu/dircolors/src/dircolors.rs index 27b43b9a0..90b48c301 100644 --- a/src/uu/dircolors/src/dircolors.rs +++ b/src/uu/dircolors/src/dircolors.rs @@ -62,7 +62,7 @@ pub fn guess_syntax() -> OutputFmt { } } -fn get_usage() -> String { +fn usage() -> String { format!("{0} {1}", executable!(), SYNTAX) } @@ -71,7 +71,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .collect_str(InvalidEncodingHandling::Ignore) .accept_any(); - let usage = get_usage(); + let usage = usage(); let matches = uu_app().usage(&usage[..]).get_matches_from(&args); diff --git a/src/uu/dirname/src/dirname.rs b/src/uu/dirname/src/dirname.rs index d6e09acc1..3162bef48 100644 --- a/src/uu/dirname/src/dirname.rs +++ b/src/uu/dirname/src/dirname.rs @@ -20,7 +20,7 @@ mod options { pub const DIR: &str = "dir"; } -fn get_usage() -> String { +fn usage() -> String { format!("{0} [OPTION] NAME...", executable!()) } @@ -37,7 +37,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { .collect_str(InvalidEncodingHandling::ConvertLossy) .accept_any(); - let usage = get_usage(); + let usage = usage(); let after_help = get_long_usage(); let matches = uu_app() diff --git a/src/uu/du/src/du.rs b/src/uu/du/src/du.rs index 6160f5490..56f412ffb 100644 --- a/src/uu/du/src/du.rs +++ b/src/uu/du/src/du.rs @@ -392,7 +392,7 @@ fn convert_size_other(size: u64, _multiplier: u64, block_size: u64) -> String { format!("{}", ((size as f64) / (block_size as f64)).ceil()) } -fn get_usage() -> String { +fn usage() -> String { format!( "{0} [OPTION]... [FILE]... {0} [OPTION]... --files0-from=F", @@ -456,7 +456,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { .collect_str(InvalidEncodingHandling::Ignore) .accept_any(); - let usage = get_usage(); + let usage = usage(); let matches = uu_app().usage(&usage[..]).get_matches_from(args); diff --git a/src/uu/expand/src/expand.rs b/src/uu/expand/src/expand.rs index a821aed33..29de6ae44 100644 --- a/src/uu/expand/src/expand.rs +++ b/src/uu/expand/src/expand.rs @@ -32,7 +32,7 @@ static LONG_HELP: &str = ""; static DEFAULT_TABSTOP: usize = 8; -fn get_usage() -> String { +fn usage() -> String { format!("{0} [OPTION]... [FILE]...", executable!()) } @@ -170,7 +170,7 @@ impl Options { } pub fn uumain(args: impl uucore::Args) -> i32 { - let usage = get_usage(); + let usage = usage(); let matches = uu_app().usage(&usage[..]).get_matches_from(args); expand(Options::new(&matches)); diff --git a/src/uu/fmt/src/fmt.rs b/src/uu/fmt/src/fmt.rs index 87febb124..80f74cf84 100644 --- a/src/uu/fmt/src/fmt.rs +++ b/src/uu/fmt/src/fmt.rs @@ -50,7 +50,7 @@ static OPT_TAB_WIDTH: &str = "tab-width"; static ARG_FILES: &str = "files"; -fn get_usage() -> String { +fn usage() -> String { format!("{} [OPTION]... [FILE]...", executable!()) } @@ -75,7 +75,7 @@ pub struct FmtOptions { #[allow(clippy::cognitive_complexity)] pub fn uumain(args: impl uucore::Args) -> i32 { - let usage = get_usage(); + let usage = usage(); let matches = uu_app().usage(&usage[..]).get_matches_from(args); diff --git a/src/uu/groups/src/groups.rs b/src/uu/groups/src/groups.rs index a7c989ddf..fe1065cc1 100644 --- a/src/uu/groups/src/groups.rs +++ b/src/uu/groups/src/groups.rs @@ -28,12 +28,12 @@ static ABOUT: &str = "Print group memberships for each USERNAME or, \ if no USERNAME is specified, for\nthe current process \ (which may differ if the groups data‐base has changed)."; -fn get_usage() -> String { +fn usage() -> String { format!("{0} [OPTION]... [USERNAME]...", executable!()) } pub fn uumain(args: impl uucore::Args) -> i32 { - let usage = get_usage(); + let usage = usage(); let matches = uu_app().usage(&usage[..]).get_matches_from(args); diff --git a/src/uu/hostname/src/hostname.rs b/src/uu/hostname/src/hostname.rs index e42629eff..2b7deb565 100644 --- a/src/uu/hostname/src/hostname.rs +++ b/src/uu/hostname/src/hostname.rs @@ -53,11 +53,12 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { result } -fn get_usage() -> String { +fn usage() -> String { format!("{0} [OPTION]... [HOSTNAME]", executable!()) } + fn execute(args: impl uucore::Args) -> UResult<()> { - let usage = get_usage(); + let usage = usage(); let matches = uu_app().usage(&usage[..]).get_matches_from(args); match matches.value_of(OPT_HOST) { diff --git a/src/uu/id/src/id.rs b/src/uu/id/src/id.rs index ce2d8eaf1..16c521ea3 100644 --- a/src/uu/id/src/id.rs +++ b/src/uu/id/src/id.rs @@ -76,7 +76,7 @@ mod options { pub const ARG_USERS: &str = "USER"; } -fn get_usage() -> String { +fn usage() -> String { format!("{0} [OPTION]... [USER]...", executable!()) } @@ -127,7 +127,7 @@ struct State { #[uucore_procs::gen_uumain] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let usage = get_usage(); + let usage = usage(); let after_help = get_description(); let matches = uu_app() diff --git a/src/uu/install/src/install.rs b/src/uu/install/src/install.rs index 33a21be88..5ee103e0f 100644 --- a/src/uu/install/src/install.rs +++ b/src/uu/install/src/install.rs @@ -172,7 +172,7 @@ static OPT_CONTEXT: &str = "context"; static ARG_FILES: &str = "files"; -fn get_usage() -> String { +fn usage() -> String { format!("{0} [OPTION]... [FILE]...", executable!()) } @@ -182,7 +182,7 @@ fn get_usage() -> String { /// #[uucore_procs::gen_uumain] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let usage = get_usage(); + let usage = usage(); let matches = uu_app().usage(&usage[..]).get_matches_from(args); diff --git a/src/uu/link/src/link.rs b/src/uu/link/src/link.rs index 5344eb9c8..d06025815 100644 --- a/src/uu/link/src/link.rs +++ b/src/uu/link/src/link.rs @@ -19,7 +19,7 @@ pub mod options { pub static FILES: &str = "FILES"; } -fn get_usage() -> String { +fn usage() -> String { format!("{0} FILE1 FILE2", executable!()) } @@ -31,7 +31,7 @@ pub fn normalize_error_message(e: Error) -> String { } pub fn uumain(args: impl uucore::Args) -> i32 { - let usage = get_usage(); + let usage = usage(); let matches = uu_app().usage(&usage[..]).get_matches_from(args); let files: Vec<_> = matches diff --git a/src/uu/ln/src/ln.rs b/src/uu/ln/src/ln.rs index 568c97e45..572c68379 100644 --- a/src/uu/ln/src/ln.rs +++ b/src/uu/ln/src/ln.rs @@ -92,7 +92,7 @@ impl UError for LnError { } } -fn get_usage() -> String { +fn usage() -> String { format!( "{0} [OPTION]... [-T] TARGET LINK_NAME (1st form) {0} [OPTION]... TARGET (2nd form) @@ -102,7 +102,7 @@ fn get_usage() -> String { ) } -fn get_long_usage() -> String { +fn long_usage() -> String { String::from( " In the 1st form, create a link to TARGET with the name LINK_NAME. In the 2nd form, create a link to TARGET in the current directory. @@ -136,8 +136,8 @@ static ARG_FILES: &str = "files"; #[uucore_procs::gen_uumain] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let usage = get_usage(); - let long_usage = get_long_usage(); + let usage = usage(); + let long_usage = long_usage(); let matches = uu_app() .usage(&usage[..]) diff --git a/src/uu/logname/src/logname.rs b/src/uu/logname/src/logname.rs index 45187be18..095f3fc84 100644 --- a/src/uu/logname/src/logname.rs +++ b/src/uu/logname/src/logname.rs @@ -35,7 +35,7 @@ fn get_userlogin() -> Option { static SUMMARY: &str = "Print user's login name"; -fn get_usage() -> String { +fn usage() -> String { String::from(executable!()) } @@ -44,7 +44,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .collect_str(InvalidEncodingHandling::Ignore) .accept_any(); - let usage = get_usage(); + let usage = usage(); let _ = uu_app().usage(&usage[..]).get_matches_from(args); match get_userlogin() { diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index 6e49e7ef9..f02a53738 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -46,7 +46,7 @@ use unicode_width::UnicodeWidthStr; use uucore::libc::{S_IXGRP, S_IXOTH, S_IXUSR}; use uucore::{fs::display_permissions, version_cmp::version_cmp}; -fn get_usage() -> String { +fn usage() -> String { format!("{0} [OPTION]... [FILE]...", executable!()) } @@ -603,7 +603,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { .collect_str(InvalidEncodingHandling::Ignore) .accept_any(); - let usage = get_usage(); + let usage = usage(); let app = uu_app().usage(&usage[..]); diff --git a/src/uu/mkdir/src/mkdir.rs b/src/uu/mkdir/src/mkdir.rs index 8d492a93c..e9aa4bc22 100644 --- a/src/uu/mkdir/src/mkdir.rs +++ b/src/uu/mkdir/src/mkdir.rs @@ -22,13 +22,13 @@ mod options { pub const DIRS: &str = "dirs"; } -fn get_usage() -> String { +fn usage() -> String { format!("{0} [OPTION]... [USER]", executable!()) } #[uucore_procs::gen_uumain] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let usage = get_usage(); + let usage = usage(); // Linux-specific options, not implemented // opts.optflag("Z", "context", "set SELinux security context" + diff --git a/src/uu/mktemp/src/mktemp.rs b/src/uu/mktemp/src/mktemp.rs index 645c5ac28..52d60d140 100644 --- a/src/uu/mktemp/src/mktemp.rs +++ b/src/uu/mktemp/src/mktemp.rs @@ -36,7 +36,7 @@ static OPT_T: &str = "t"; static ARG_TEMPLATE: &str = "template"; -fn get_usage() -> String { +fn usage() -> String { format!("{0} [OPTION]... [TEMPLATE]", executable!()) } @@ -74,7 +74,7 @@ impl Display for MkTempError { #[uucore_procs::gen_uumain] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let usage = get_usage(); + let usage = usage(); let matches = uu_app().usage(&usage[..]).get_matches_from(args); diff --git a/src/uu/mv/src/mv.rs b/src/uu/mv/src/mv.rs index c064a2244..1c25e653c 100644 --- a/src/uu/mv/src/mv.rs +++ b/src/uu/mv/src/mv.rs @@ -58,7 +58,7 @@ static OPT_VERBOSE: &str = "verbose"; static ARG_FILES: &str = "files"; -fn get_usage() -> String { +fn usage() -> String { format!( "{0} [OPTION]... [-T] SOURCE DEST {0} [OPTION]... SOURCE... DIRECTORY @@ -68,7 +68,7 @@ fn get_usage() -> String { } pub fn uumain(args: impl uucore::Args) -> i32 { - let usage = get_usage(); + let usage = usage(); let matches = uu_app() .after_help(&*format!( diff --git a/src/uu/nice/src/nice.rs b/src/uu/nice/src/nice.rs index 3b62d8058..c071a875f 100644 --- a/src/uu/nice/src/nice.rs +++ b/src/uu/nice/src/nice.rs @@ -22,7 +22,7 @@ pub mod options { pub static COMMAND: &str = "COMMAND"; } -fn get_usage() -> String { +fn usage() -> String { format!( " {0} [OPTIONS] [COMMAND [ARGS]] @@ -36,7 +36,7 @@ process).", } pub fn uumain(args: impl uucore::Args) -> i32 { - let usage = get_usage(); + let usage = usage(); let matches = uu_app().usage(&usage[..]).get_matches_from(args); diff --git a/src/uu/nohup/src/nohup.rs b/src/uu/nohup/src/nohup.rs index 60654672d..a6f08e806 100644 --- a/src/uu/nohup/src/nohup.rs +++ b/src/uu/nohup/src/nohup.rs @@ -40,7 +40,7 @@ mod options { } pub fn uumain(args: impl uucore::Args) -> i32 { - let usage = get_usage(); + let usage = usage(); let args = args .collect_str(InvalidEncodingHandling::ConvertLossy) .accept_any(); @@ -156,7 +156,7 @@ fn find_stdout() -> File { } } -fn get_usage() -> String { +fn usage() -> String { format!("{0} COMMAND [ARG]...\n {0} FLAG", executable!()) } diff --git a/src/uu/nproc/src/nproc.rs b/src/uu/nproc/src/nproc.rs index ae8e66492..427a3cd2f 100644 --- a/src/uu/nproc/src/nproc.rs +++ b/src/uu/nproc/src/nproc.rs @@ -27,12 +27,12 @@ static OPT_IGNORE: &str = "ignore"; static ABOUT: &str = "Print the number of cores available to the current process."; -fn get_usage() -> String { +fn usage() -> String { format!("{0} [OPTIONS]...", executable!()) } pub fn uumain(args: impl uucore::Args) -> i32 { - let usage = get_usage(); + let usage = usage(); let matches = uu_app().usage(&usage[..]).get_matches_from(args); let mut ignore = match matches.value_of(OPT_IGNORE) { diff --git a/src/uu/numfmt/src/numfmt.rs b/src/uu/numfmt/src/numfmt.rs index 4f9d90a6c..6012bbf82 100644 --- a/src/uu/numfmt/src/numfmt.rs +++ b/src/uu/numfmt/src/numfmt.rs @@ -50,7 +50,7 @@ FIELDS supports cut(1) style field ranges: Multiple fields/ranges can be separated with commas "; -fn get_usage() -> String { +fn usage() -> String { format!("{0} [OPTION]... [NUMBER]...", executable!()) } @@ -154,7 +154,7 @@ fn parse_options(args: &ArgMatches) -> Result { } pub fn uumain(args: impl uucore::Args) -> i32 { - let usage = get_usage(); + let usage = usage(); let matches = uu_app().usage(&usage[..]).get_matches_from(args); diff --git a/src/uu/pathchk/src/pathchk.rs b/src/uu/pathchk/src/pathchk.rs index 46edf0850..4d257a771 100644 --- a/src/uu/pathchk/src/pathchk.rs +++ b/src/uu/pathchk/src/pathchk.rs @@ -39,12 +39,12 @@ mod options { const POSIX_PATH_MAX: usize = 256; const POSIX_NAME_MAX: usize = 14; -fn get_usage() -> String { +fn usage() -> String { format!("{0} [OPTION]... NAME...", executable!()) } pub fn uumain(args: impl uucore::Args) -> i32 { - let usage = get_usage(); + let usage = usage(); let args = args .collect_str(InvalidEncodingHandling::ConvertLossy) .accept_any(); diff --git a/src/uu/pinky/src/pinky.rs b/src/uu/pinky/src/pinky.rs index 58c4f56ed..4aad2c07d 100644 --- a/src/uu/pinky/src/pinky.rs +++ b/src/uu/pinky/src/pinky.rs @@ -40,7 +40,7 @@ mod options { pub const USER: &str = "user"; } -fn get_usage() -> String { +fn usage() -> String { format!("{0} [OPTION]... [USER]...", executable!()) } @@ -57,7 +57,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .collect_str(InvalidEncodingHandling::Ignore) .accept_any(); - let usage = get_usage(); + let usage = usage(); let after_help = get_long_usage(); let matches = uu_app() diff --git a/src/uu/printenv/src/printenv.rs b/src/uu/printenv/src/printenv.rs index 7164f5092..0e86f1012 100644 --- a/src/uu/printenv/src/printenv.rs +++ b/src/uu/printenv/src/printenv.rs @@ -19,12 +19,12 @@ static OPT_NULL: &str = "null"; static ARG_VARIABLES: &str = "variables"; -fn get_usage() -> String { +fn usage() -> String { format!("{0} [VARIABLE]... [OPTION]...", executable!()) } pub fn uumain(args: impl uucore::Args) -> i32 { - let usage = get_usage(); + let usage = usage(); let matches = uu_app().usage(&usage[..]).get_matches_from(args); diff --git a/src/uu/pwd/src/pwd.rs b/src/uu/pwd/src/pwd.rs index f29a3abfa..bfdfafd13 100644 --- a/src/uu/pwd/src/pwd.rs +++ b/src/uu/pwd/src/pwd.rs @@ -34,13 +34,13 @@ pub fn absolute_path(path: &Path) -> io::Result { Ok(path_buf) } -fn get_usage() -> String { +fn usage() -> String { format!("{0} [OPTION]... FILE...", executable!()) } #[uucore_procs::gen_uumain] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let usage = get_usage(); + let usage = usage(); let matches = uu_app().usage(&usage[..]).get_matches_from(args); diff --git a/src/uu/readlink/src/readlink.rs b/src/uu/readlink/src/readlink.rs index 4bfac4630..b86dff162 100644 --- a/src/uu/readlink/src/readlink.rs +++ b/src/uu/readlink/src/readlink.rs @@ -29,12 +29,12 @@ const OPT_ZERO: &str = "zero"; const ARG_FILES: &str = "files"; -fn get_usage() -> String { +fn usage() -> String { format!("{0} [OPTION]... [FILE]...", executable!()) } pub fn uumain(args: impl uucore::Args) -> i32 { - let usage = get_usage(); + let usage = usage(); let matches = uu_app().usage(&usage[..]).get_matches_from(args); let mut no_newline = matches.is_present(OPT_NO_NEWLINE); diff --git a/src/uu/realpath/src/realpath.rs b/src/uu/realpath/src/realpath.rs index 34b2640ab..494fe41dc 100644 --- a/src/uu/realpath/src/realpath.rs +++ b/src/uu/realpath/src/realpath.rs @@ -22,12 +22,12 @@ static OPT_ZERO: &str = "zero"; static ARG_FILES: &str = "files"; -fn get_usage() -> String { +fn usage() -> String { format!("{0} [OPTION]... FILE...", executable!()) } pub fn uumain(args: impl uucore::Args) -> i32 { - let usage = get_usage(); + let usage = usage(); let matches = uu_app().usage(&usage[..]).get_matches_from(args); diff --git a/src/uu/relpath/src/relpath.rs b/src/uu/relpath/src/relpath.rs index e27ee0ce9..b7fa8991c 100644 --- a/src/uu/relpath/src/relpath.rs +++ b/src/uu/relpath/src/relpath.rs @@ -25,7 +25,7 @@ mod options { pub const FROM: &str = "FROM"; } -fn get_usage() -> String { +fn usage() -> String { format!("{} [-d DIR] TO [FROM]", executable!()) } @@ -33,7 +33,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let args = args .collect_str(InvalidEncodingHandling::ConvertLossy) .accept_any(); - let usage = get_usage(); + let usage = usage(); let matches = uu_app().usage(&usage[..]).get_matches_from(args); diff --git a/src/uu/rm/src/rm.rs b/src/uu/rm/src/rm.rs index 4c44f59ee..4b435807b 100644 --- a/src/uu/rm/src/rm.rs +++ b/src/uu/rm/src/rm.rs @@ -52,7 +52,7 @@ static OPT_VERBOSE: &str = "verbose"; static ARG_FILES: &str = "files"; -fn get_usage() -> String { +fn usage() -> String { format!("{0} [OPTION]... FILE...", executable!()) } @@ -74,7 +74,7 @@ fn get_long_usage() -> String { } pub fn uumain(args: impl uucore::Args) -> i32 { - let usage = get_usage(); + let usage = usage(); let long_usage = get_long_usage(); let matches = uu_app() diff --git a/src/uu/rmdir/src/rmdir.rs b/src/uu/rmdir/src/rmdir.rs index 3acfca773..880300a7a 100644 --- a/src/uu/rmdir/src/rmdir.rs +++ b/src/uu/rmdir/src/rmdir.rs @@ -26,12 +26,12 @@ static ENOTDIR: i32 = 20; #[cfg(windows)] static ENOTDIR: i32 = 267; -fn get_usage() -> String { +fn usage() -> String { format!("{0} [OPTION]... DIRECTORY...", executable!()) } pub fn uumain(args: impl uucore::Args) -> i32 { - let usage = get_usage(); + let usage = usage(); let matches = uu_app().usage(&usage[..]).get_matches_from(args); diff --git a/src/uu/seq/src/seq.rs b/src/uu/seq/src/seq.rs index ae81aa71d..2042cf23f 100644 --- a/src/uu/seq/src/seq.rs +++ b/src/uu/seq/src/seq.rs @@ -22,7 +22,7 @@ static OPT_WIDTHS: &str = "widths"; static ARG_NUMBERS: &str = "numbers"; -fn get_usage() -> String { +fn usage() -> String { format!( "{0} [OPTION]... LAST {0} [OPTION]... FIRST LAST @@ -86,7 +86,7 @@ impl FromStr for Number { } pub fn uumain(args: impl uucore::Args) -> i32 { - let usage = get_usage(); + let usage = usage(); let matches = uu_app().usage(&usage[..]).get_matches_from(args); let numbers = matches.values_of(ARG_NUMBERS).unwrap().collect::>(); diff --git a/src/uu/shred/src/shred.rs b/src/uu/shred/src/shred.rs index 43428caf6..936577f01 100644 --- a/src/uu/shred/src/shred.rs +++ b/src/uu/shred/src/shred.rs @@ -213,7 +213,7 @@ static ABOUT: &str = "Overwrite the specified FILE(s) repeatedly, in order to ma for even very expensive hardware probing to recover the data. "; -fn get_usage() -> String { +fn usage() -> String { format!("{} [OPTION]... FILE...", executable!()) } @@ -270,7 +270,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .collect_str(InvalidEncodingHandling::Ignore) .accept_any(); - let usage = get_usage(); + let usage = usage(); let app = uu_app().usage(&usage[..]); diff --git a/src/uu/sleep/src/sleep.rs b/src/uu/sleep/src/sleep.rs index c34d4f7b7..624e65901 100644 --- a/src/uu/sleep/src/sleep.rs +++ b/src/uu/sleep/src/sleep.rs @@ -26,7 +26,7 @@ mod options { pub const NUMBER: &str = "NUMBER"; } -fn get_usage() -> String { +fn usage() -> String { format!( "{0} {1}[SUFFIX]... \n {0} OPTION", executable!(), @@ -36,7 +36,7 @@ fn get_usage() -> String { #[uucore_procs::gen_uumain] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let usage = get_usage(); + let usage = usage(); let matches = uu_app().usage(&usage[..]).get_matches_from(args); diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index c31c47eb9..78b6f8b64 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -1055,7 +1055,7 @@ impl FieldSelector { } } -fn get_usage() -> String { +fn usage() -> String { format!( "{0} [OPTION]... [FILE]... Write the sorted concatenation of all FILE(s) to standard output. @@ -1081,7 +1081,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let args = args .collect_str(InvalidEncodingHandling::Ignore) .accept_any(); - let usage = get_usage(); + let usage = usage(); let mut settings: GlobalSettings = Default::default(); let matches = match uu_app().usage(&usage[..]).get_matches_from_safe(args) { diff --git a/src/uu/split/src/split.rs b/src/uu/split/src/split.rs index 8e7cb5d6d..bc38818b9 100644 --- a/src/uu/split/src/split.rs +++ b/src/uu/split/src/split.rs @@ -36,7 +36,7 @@ static OPT_VERBOSE: &str = "verbose"; static ARG_INPUT: &str = "input"; static ARG_PREFIX: &str = "prefix"; -fn get_usage() -> String { +fn usage() -> String { format!("{0} [OPTION]... [INPUT [PREFIX]]", NAME) } fn get_long_usage() -> String { @@ -47,12 +47,12 @@ fn get_long_usage() -> String { Output fixed-size pieces of INPUT to PREFIXaa, PREFIX ab, ...; default size is 1000, and default PREFIX is 'x'. With no INPUT, or when INPUT is -, read standard input.", - get_usage() + usage() ) } pub fn uumain(args: impl uucore::Args) -> i32 { - let usage = get_usage(); + let usage = usage(); let long_usage = get_long_usage(); let matches = uu_app() diff --git a/src/uu/stat/src/stat.rs b/src/uu/stat/src/stat.rs index d4e9818d5..4a12e4fb7 100644 --- a/src/uu/stat/src/stat.rs +++ b/src/uu/stat/src/stat.rs @@ -882,7 +882,7 @@ impl Stater { } } -fn get_usage() -> String { +fn usage() -> String { format!("{0} [OPTION]... FILE...", executable!()) } @@ -945,7 +945,7 @@ for details about the options it supports. } pub fn uumain(args: impl uucore::Args) -> i32 { - let usage = get_usage(); + let usage = usage(); let long_usage = get_long_usage(); let matches = uu_app() diff --git a/src/uu/stdbuf/src/stdbuf.rs b/src/uu/stdbuf/src/stdbuf.rs index 60f42102d..bac5040cf 100644 --- a/src/uu/stdbuf/src/stdbuf.rs +++ b/src/uu/stdbuf/src/stdbuf.rs @@ -47,7 +47,7 @@ mod options { pub const COMMAND: &str = "command"; } -fn get_usage() -> String { +fn usage() -> String { format!("{0} OPTION... COMMAND", executable!()) } @@ -152,7 +152,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let args = args .collect_str(InvalidEncodingHandling::Ignore) .accept_any(); - let usage = get_usage(); + let usage = usage(); let matches = uu_app().usage(&usage[..]).get_matches_from(args); diff --git a/src/uu/sync/src/sync.rs b/src/uu/sync/src/sync.rs index 5109bf24b..123167db7 100644 --- a/src/uu/sync/src/sync.rs +++ b/src/uu/sync/src/sync.rs @@ -159,12 +159,12 @@ mod platform { } } -fn get_usage() -> String { +fn usage() -> String { format!("{0} [OPTION]... FILE...", executable!()) } pub fn uumain(args: impl uucore::Args) -> i32 { - let usage = get_usage(); + let usage = usage(); let matches = uu_app().usage(&usage[..]).get_matches_from(args); diff --git a/src/uu/tee/src/tee.rs b/src/uu/tee/src/tee.rs index bf9334677..2f1b17ebe 100644 --- a/src/uu/tee/src/tee.rs +++ b/src/uu/tee/src/tee.rs @@ -32,12 +32,12 @@ struct Options { files: Vec, } -fn get_usage() -> String { +fn usage() -> String { format!("{0} [OPTION]... [FILE]...", executable!()) } pub fn uumain(args: impl uucore::Args) -> i32 { - let usage = get_usage(); + let usage = usage(); let matches = uu_app().usage(&usage[..]).get_matches_from(args); diff --git a/src/uu/timeout/src/timeout.rs b/src/uu/timeout/src/timeout.rs index 464414c5e..e0fbe8a54 100644 --- a/src/uu/timeout/src/timeout.rs +++ b/src/uu/timeout/src/timeout.rs @@ -22,7 +22,7 @@ use uucore::InvalidEncodingHandling; static ABOUT: &str = "Start COMMAND, and kill it if still running after DURATION."; -fn get_usage() -> String { +fn usage() -> String { format!("{0} [OPTION] DURATION COMMAND...", executable!()) } @@ -100,7 +100,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .collect_str(InvalidEncodingHandling::ConvertLossy) .accept_any(); - let usage = get_usage(); + let usage = usage(); let app = uu_app().usage(&usage[..]); diff --git a/src/uu/touch/src/touch.rs b/src/uu/touch/src/touch.rs index 92c0080fa..bdb65683f 100644 --- a/src/uu/touch/src/touch.rs +++ b/src/uu/touch/src/touch.rs @@ -47,13 +47,13 @@ fn local_tm_to_filetime(tm: time::Tm) -> FileTime { FileTime::from_unix_time(ts.sec as i64, ts.nsec as u32) } -fn get_usage() -> String { +fn usage() -> String { format!("{0} [OPTION]... [USER]", executable!()) } #[uucore_procs::gen_uumain] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let usage = get_usage(); + let usage = usage(); let matches = uu_app().usage(&usage[..]).get_matches_from(args); diff --git a/src/uu/tr/src/tr.rs b/src/uu/tr/src/tr.rs index 390511ec4..ddb866393 100644 --- a/src/uu/tr/src/tr.rs +++ b/src/uu/tr/src/tr.rs @@ -228,7 +228,7 @@ fn translate_input( } } -fn get_usage() -> String { +fn usage() -> String { format!("{} [OPTION]... SET1 [SET2]", executable!()) } @@ -243,7 +243,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .collect_str(InvalidEncodingHandling::ConvertLossy) .accept_any(); - let usage = get_usage(); + let usage = usage(); let after_help = get_long_usage(); let matches = uu_app() diff --git a/src/uu/truncate/src/truncate.rs b/src/uu/truncate/src/truncate.rs index 7b7a55f33..c5f9fb5b7 100644 --- a/src/uu/truncate/src/truncate.rs +++ b/src/uu/truncate/src/truncate.rs @@ -63,7 +63,7 @@ pub mod options { pub static ARG_FILES: &str = "files"; } -fn get_usage() -> String { +fn usage() -> String { format!("{0} [OPTION]... [FILE]...", executable!()) } @@ -90,7 +90,7 @@ fn get_long_usage() -> String { } pub fn uumain(args: impl uucore::Args) -> i32 { - let usage = get_usage(); + let usage = usage(); let long_usage = get_long_usage(); let matches = uu_app() diff --git a/src/uu/tty/src/tty.rs b/src/uu/tty/src/tty.rs index 7c6f73dd4..df6c43a27 100644 --- a/src/uu/tty/src/tty.rs +++ b/src/uu/tty/src/tty.rs @@ -23,12 +23,12 @@ mod options { pub const SILENT: &str = "silent"; } -fn get_usage() -> String { +fn usage() -> String { format!("{0} [OPTION]...", executable!()) } pub fn uumain(args: impl uucore::Args) -> i32 { - let usage = get_usage(); + let usage = usage(); let args = args .collect_str(InvalidEncodingHandling::ConvertLossy) .accept_any(); diff --git a/src/uu/uniq/src/uniq.rs b/src/uu/uniq/src/uniq.rs index 288ecf105..fd8b4019b 100644 --- a/src/uu/uniq/src/uniq.rs +++ b/src/uu/uniq/src/uniq.rs @@ -221,7 +221,7 @@ fn opt_parsed(opt_name: &str, matches: &ArgMatches) -> Option { }) } -fn get_usage() -> String { +fn usage() -> String { format!("{0} [OPTION]... [INPUT [OUTPUT]]...", executable!()) } @@ -235,7 +235,7 @@ fn get_long_usage() -> String { } pub fn uumain(args: impl uucore::Args) -> i32 { - let usage = get_usage(); + let usage = usage(); let long_usage = get_long_usage(); let matches = uu_app() diff --git a/src/uu/unlink/src/unlink.rs b/src/uu/unlink/src/unlink.rs index 5f96cf429..0601b3e54 100644 --- a/src/uu/unlink/src/unlink.rs +++ b/src/uu/unlink/src/unlink.rs @@ -22,7 +22,7 @@ use uucore::InvalidEncodingHandling; static ABOUT: &str = "Unlink the file at [FILE]."; static OPT_PATH: &str = "FILE"; -fn get_usage() -> String { +fn usage() -> String { format!("{} [OPTION]... FILE", executable!()) } @@ -31,7 +31,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .collect_str(InvalidEncodingHandling::ConvertLossy) .accept_any(); - let usage = get_usage(); + let usage = usage(); let matches = uu_app().usage(&usage[..]).get_matches_from(args); diff --git a/src/uu/uptime/src/uptime.rs b/src/uu/uptime/src/uptime.rs index 06dd93a24..33f3ec965 100644 --- a/src/uu/uptime/src/uptime.rs +++ b/src/uu/uptime/src/uptime.rs @@ -32,12 +32,12 @@ extern "C" { fn GetTickCount() -> uucore::libc::uint32_t; } -fn get_usage() -> String { +fn usage() -> String { format!("{0} [OPTION]...", executable!()) } pub fn uumain(args: impl uucore::Args) -> i32 { - let usage = get_usage(); + let usage = usage(); let matches = uu_app().usage(&usage[..]).get_matches_from(args); let (boot_time, user_count) = process_utmpx(); diff --git a/src/uu/users/src/users.rs b/src/uu/users/src/users.rs index 91168644c..3f081f891 100644 --- a/src/uu/users/src/users.rs +++ b/src/uu/users/src/users.rs @@ -18,7 +18,7 @@ static ABOUT: &str = "Print the user names of users currently logged in to the c static ARG_FILES: &str = "files"; -fn get_usage() -> String { +fn usage() -> String { format!("{0} [FILE]", executable!()) } @@ -31,7 +31,7 @@ If FILE is not specified, use {}. /var/log/wtmp as FILE is common.", } pub fn uumain(args: impl uucore::Args) -> i32 { - let usage = get_usage(); + let usage = usage(); let after_help = get_long_usage(); let matches = uu_app() diff --git a/src/uu/wc/src/wc.rs b/src/uu/wc/src/wc.rs index afdf8b7de..e21eb0b7e 100644 --- a/src/uu/wc/src/wc.rs +++ b/src/uu/wc/src/wc.rs @@ -95,7 +95,7 @@ pub mod options { static ARG_FILES: &str = "files"; -fn get_usage() -> String { +fn usage() -> String { format!( "{0} [OPTION]... [FILE]... With no FILE, or when FILE is -, read standard input.", @@ -132,7 +132,7 @@ impl Input { } pub fn uumain(args: impl uucore::Args) -> i32 { - let usage = get_usage(); + let usage = usage(); let matches = uu_app().usage(&usage[..]).get_matches_from(args); diff --git a/src/uu/who/src/who.rs b/src/uu/who/src/who.rs index 557a4efd6..558d42f1b 100644 --- a/src/uu/who/src/who.rs +++ b/src/uu/who/src/who.rs @@ -44,7 +44,7 @@ static RUNLEVEL_HELP: &str = "print current runlevel"; #[cfg(not(target_os = "linux"))] static RUNLEVEL_HELP: &str = "print current runlevel (This is meaningless on non Linux)"; -fn get_usage() -> String { +fn usage() -> String { format!("{0} [OPTION]... [ FILE | ARG1 ARG2 ]", executable!()) } @@ -61,7 +61,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .collect_str(InvalidEncodingHandling::Ignore) .accept_any(); - let usage = get_usage(); + let usage = usage(); let after_help = get_long_usage(); let matches = uu_app() From eb13533e4ea93814ac2d37fc982699343ecd0672 Mon Sep 17 00:00:00 2001 From: Roy Ivy III Date: Mon, 26 Jul 2021 20:25:41 -0500 Subject: [PATCH 016/206] refactor/uucore ~ replace `executable_name!()` with `util_name!()` in standard messaging --- src/uucore/src/lib/macros.rs | 10 +++++----- src/uucore/src/lib/mods/coreopts.rs | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/uucore/src/lib/macros.rs b/src/uucore/src/lib/macros.rs index 715752c84..06aad6f36 100644 --- a/src/uucore/src/lib/macros.rs +++ b/src/uucore/src/lib/macros.rs @@ -55,7 +55,7 @@ macro_rules! show( ($err:expr) => ({ let e = $err; uucore::error::set_exit_code(e.code()); - eprintln!("{}: {}", $crate::executable_name!(), e); + eprintln!("{}: {}", $crate::util_name!(), e); }) ); @@ -72,7 +72,7 @@ macro_rules! show_if_err( #[macro_export] macro_rules! show_error( ($($args:tt)+) => ({ - eprint!("{}: ", $crate::executable_name!()); + eprint!("{}: ", $crate::util_name!()); eprintln!($($args)+); }) ); @@ -81,7 +81,7 @@ macro_rules! show_error( #[macro_export] macro_rules! show_error_custom_description ( ($err:expr,$($args:tt)+) => ({ - eprint!("{}: {}: ", $crate::executable_name!(), $err); + eprint!("{}: {}: ", $crate::util_name!(), $err); eprintln!($($args)+); }) ); @@ -89,7 +89,7 @@ macro_rules! show_error_custom_description ( #[macro_export] macro_rules! show_warning( ($($args:tt)+) => ({ - eprint!("{}: warning: ", $crate::executable_name!()); + eprint!("{}: warning: ", $crate::util_name!()); eprintln!($($args)+); }) ); @@ -98,7 +98,7 @@ macro_rules! show_warning( #[macro_export] macro_rules! show_usage_error( ($($args:tt)+) => ({ - eprint!("{}: ", $crate::executable_name!()); + eprint!("{}: ", $crate::util_name!()); eprintln!($($args)+); eprintln!("Try `{:?} --help` for more information.", $crate::executable!()); }) diff --git a/src/uucore/src/lib/mods/coreopts.rs b/src/uucore/src/lib/mods/coreopts.rs index f3fb77335..b81ef7e5a 100644 --- a/src/uucore/src/lib/mods/coreopts.rs +++ b/src/uucore/src/lib/mods/coreopts.rs @@ -120,7 +120,7 @@ impl<'a> CoreOptions<'a> { macro_rules! app { ($syntax: expr, $summary: expr, $long_help: expr) => { uucore::coreopts::CoreOptions::new(uucore::coreopts::HelpText { - name: executable!(), + name: util_name!(), version: env!("CARGO_PKG_VERSION"), syntax: $syntax, summary: $summary, @@ -130,7 +130,7 @@ macro_rules! app { }; ($syntax: expr, $summary: expr, $long_help: expr, $display_usage: expr) => { uucore::coreopts::CoreOptions::new(uucore::coreopts::HelpText { - name: executable!(), + name: util_name!(), version: env!("CARGO_PKG_VERSION"), syntax: $syntax, summary: $summary, From 69ce4dc8e5236b45807116953e4c429c8632dd5a Mon Sep 17 00:00:00 2001 From: Roy Ivy III Date: Wed, 28 Jul 2021 16:18:42 -0500 Subject: [PATCH 017/206] refactor/uucore ~ align return values for executable/util macros --- src/uucore/src/lib/macros.rs | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/uucore/src/lib/macros.rs b/src/uucore/src/lib/macros.rs index 06aad6f36..931777220 100644 --- a/src/uucore/src/lib/macros.rs +++ b/src/uucore/src/lib/macros.rs @@ -20,7 +20,7 @@ macro_rules! executable( let exe = match $crate::executable_os!().to_str() { // * UTF-8 Some(s) => s.to_string(), - // * "lossless" debug format if `executable_os!()` is not well-formed UTF-8 + // * "lossless" debug format (if/when `executable_os!()` is not well-formed UTF-8) None => format!("{:?}", $crate::executable_os!()) }; &exe.to_owned() @@ -31,7 +31,14 @@ macro_rules! executable( #[macro_export] macro_rules! executable_name( () => ({ - &std::path::Path::new($crate::executable_os!()).file_stem().unwrap().to_string_lossy() + let stem = &std::path::Path::new($crate::executable_os!()).file_stem().unwrap().to_owned(); + let exe = match stem.to_str() { + // * UTF-8 + Some(s) => s.to_string(), + // * "lossless" debug format (if/when `executable_os!()` is not well-formed UTF-8) + None => format!("{:?}", stem) + }; + &exe.to_owned() }) ); @@ -40,11 +47,12 @@ macro_rules! executable_name( macro_rules! util_name( () => ({ let crate_name = env!("CARGO_PKG_NAME"); - if crate_name.starts_with("uu_") { + let name = if crate_name.starts_with("uu_") { &crate_name[3..] } else { - &crate_name - } + crate_name + }; + &name.to_owned() }) ); From f56fc5bf444f40a0b48101c157574635fccd1bf7 Mon Sep 17 00:00:00 2001 From: Roy Ivy III Date: Wed, 28 Jul 2021 16:20:30 -0500 Subject: [PATCH 018/206] refactor/uucore ~ use uucore args for executable macros --- src/uucore/src/lib/macros.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uucore/src/lib/macros.rs b/src/uucore/src/lib/macros.rs index 931777220..6d74ee260 100644 --- a/src/uucore/src/lib/macros.rs +++ b/src/uucore/src/lib/macros.rs @@ -9,7 +9,7 @@ #[macro_export] macro_rules! executable_os( () => ({ - &std::env::args_os().next().unwrap() + &uucore::args_os().next().unwrap() }) ); From 318f366ace28c496f89496ddf4ea7668d02cacd9 Mon Sep 17 00:00:00 2001 From: Roy Ivy III Date: Wed, 28 Jul 2021 16:22:02 -0500 Subject: [PATCH 019/206] change/uucore ~ add `execution_phrase!()` macro for use with usage messages --- src/uucore/src/lib/macros.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/uucore/src/lib/macros.rs b/src/uucore/src/lib/macros.rs index 6d74ee260..36336a24d 100644 --- a/src/uucore/src/lib/macros.rs +++ b/src/uucore/src/lib/macros.rs @@ -58,6 +58,23 @@ macro_rules! util_name( //==== +/// Derive the complete execution phrase for "usage". +#[macro_export] +macro_rules! execution_phrase( + () => ({ + let exe = if (executable_name!() == util_name!()) { + executable!().to_string() + } else { + format!("{} {}", executable!(), util_name!()) + .as_str() + .to_owned() + }; + &exe.to_owned() + }) +); + +//==== + #[macro_export] macro_rules! show( ($err:expr) => ({ From c0854000d169c386f59acf9f350d622a6cc96c5f Mon Sep 17 00:00:00 2001 From: Roy Ivy III Date: Mon, 26 Jul 2021 22:54:48 -0500 Subject: [PATCH 020/206] refactor ~ use `execution_phrase!()` for usage messaging --- src/uu/base32/src/base32.rs | 6 +++--- src/uu/base64/src/base64.rs | 4 ++-- src/uu/basename/src/basename.rs | 6 +++--- src/uu/chgrp/src/chgrp.rs | 2 +- src/uu/chmod/src/chmod.rs | 2 +- src/uu/chown/src/chown.rs | 2 +- src/uu/chroot/src/chroot.rs | 5 ++--- src/uu/comm/src/comm.rs | 2 +- src/uu/cp/src/cp.rs | 6 +++--- src/uu/csplit/src/csplit.rs | 2 +- src/uu/df/src/df.rs | 4 ++-- src/uu/dircolors/src/dircolors.rs | 2 +- src/uu/dirname/src/dirname.rs | 2 +- src/uu/du/src/du.rs | 4 ++-- src/uu/expand/src/expand.rs | 2 +- src/uu/expr/src/expr.rs | 2 +- src/uu/fmt/src/fmt.rs | 2 +- src/uu/groups/src/groups.rs | 2 +- src/uu/hostname/src/hostname.rs | 2 +- src/uu/id/src/id.rs | 2 +- src/uu/install/src/install.rs | 4 ++-- src/uu/kill/src/kill.rs | 2 +- src/uu/link/src/link.rs | 2 +- src/uu/ln/src/ln.rs | 6 +++--- src/uu/logname/src/logname.rs | 2 +- src/uu/ls/src/ls.rs | 2 +- src/uu/mkdir/src/mkdir.rs | 4 ++-- src/uu/mknod/src/mknod.rs | 8 ++++---- src/uu/mktemp/src/mktemp.rs | 2 +- src/uu/mv/src/mv.rs | 6 +++--- src/uu/nice/src/nice.rs | 4 ++-- src/uu/nohup/src/nohup.rs | 2 +- src/uu/nproc/src/nproc.rs | 2 +- src/uu/numfmt/src/numfmt.rs | 2 +- src/uu/od/src/multifilereader.rs | 13 ++----------- src/uu/pathchk/src/pathchk.rs | 5 ++--- src/uu/pinky/src/pinky.rs | 2 +- src/uu/printenv/src/printenv.rs | 2 +- src/uu/printf/src/printf.rs | 8 ++++---- src/uu/pwd/src/pwd.rs | 2 +- src/uu/readlink/src/readlink.rs | 24 ++++++++++++++++++------ src/uu/realpath/src/realpath.rs | 2 +- src/uu/relpath/src/relpath.rs | 2 +- src/uu/rm/src/rm.rs | 4 ++-- src/uu/rmdir/src/rmdir.rs | 2 +- src/uu/seq/src/seq.rs | 8 ++++---- src/uu/shred/src/shred.rs | 2 +- src/uu/sleep/src/sleep.rs | 2 +- src/uu/sort/src/sort.rs | 3 +-- src/uu/split/src/split.rs | 4 +--- src/uu/stat/src/stat.rs | 2 +- src/uu/stdbuf/src/stdbuf.rs | 4 ++-- src/uu/sync/src/sync.rs | 2 +- src/uu/tee/src/tee.rs | 2 +- src/uu/timeout/src/timeout.rs | 2 +- src/uu/touch/src/touch.rs | 2 +- src/uu/tr/src/tr.rs | 6 +++--- src/uu/truncate/src/truncate.rs | 2 +- src/uu/tty/src/tty.rs | 2 +- src/uu/uname/src/uname.rs | 2 +- src/uu/uniq/src/uniq.rs | 2 +- src/uu/unlink/src/unlink.rs | 6 +++--- src/uu/uptime/src/uptime.rs | 2 +- src/uu/users/src/users.rs | 2 +- src/uu/wc/src/wc.rs | 2 +- src/uu/who/src/who.rs | 2 +- src/uucore/src/lib/macros.rs | 2 +- src/uucore_procs/src/lib.rs | 2 +- 68 files changed, 119 insertions(+), 121 deletions(-) diff --git a/src/uu/base32/src/base32.rs b/src/uu/base32/src/base32.rs index 8e02f03e0..a93b4e18b 100644 --- a/src/uu/base32/src/base32.rs +++ b/src/uu/base32/src/base32.rs @@ -29,13 +29,13 @@ static VERSION: &str = env!("CARGO_PKG_VERSION"); static BASE_CMD_PARSE_ERROR: i32 = 1; fn usage() -> String { - format!("{0} [OPTION]... [FILE]", executable!()) + format!("{0} [OPTION]... [FILE]", execution_phrase!()) } pub fn uumain(args: impl uucore::Args) -> i32 { let format = Format::Base32; let usage = usage(); - let name = executable!(); + let name = util_name!(); let config_result: Result = base_common::parse_base_cmd_args(args, name, VERSION, ABOUT, &usage); @@ -59,5 +59,5 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> App<'static, 'static> { - base_common::base_app(executable!(), VERSION, ABOUT) + base_common::base_app(util_name!(), VERSION, ABOUT) } diff --git a/src/uu/base64/src/base64.rs b/src/uu/base64/src/base64.rs index 48aee537d..b53ec32e9 100644 --- a/src/uu/base64/src/base64.rs +++ b/src/uu/base64/src/base64.rs @@ -30,13 +30,13 @@ static VERSION: &str = env!("CARGO_PKG_VERSION"); static BASE_CMD_PARSE_ERROR: i32 = 1; fn usage() -> String { - format!("{0} [OPTION]... [FILE]", executable!()) + format!("{0} [OPTION]... [FILE]", execution_phrase!()) } pub fn uumain(args: impl uucore::Args) -> i32 { let format = Format::Base64; let usage = usage(); - let name = executable!(); + let name = util_name!(); let config_result: Result = 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)); diff --git a/src/uu/basename/src/basename.rs b/src/uu/basename/src/basename.rs index f3c9f5391..8de55af80 100644 --- a/src/uu/basename/src/basename.rs +++ b/src/uu/basename/src/basename.rs @@ -21,7 +21,7 @@ fn usage() -> String { format!( "{0} NAME [SUFFIX] {0} OPTION... NAME...", - executable!() + execution_phrase!() ) } @@ -47,7 +47,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { crash!( 1, "{1}\nTry `{0} --help` for more information.", - executable!(), + execution_phrase!(), "missing operand" ); } @@ -61,7 +61,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { crash!( 1, "extra operand '{1}'\nTry `{0} --help` for more information.", - executable!(), + execution_phrase!(), matches.values_of(options::NAME).unwrap().nth(2).unwrap() ); } diff --git a/src/uu/chgrp/src/chgrp.rs b/src/uu/chgrp/src/chgrp.rs index 11a45c90a..5dbcc277e 100644 --- a/src/uu/chgrp/src/chgrp.rs +++ b/src/uu/chgrp/src/chgrp.rs @@ -62,7 +62,7 @@ const FTS_LOGICAL: u8 = 1 << 2; fn usage() -> String { format!( "{0} [OPTION]... GROUP FILE...\n {0} [OPTION]... --reference=RFILE FILE...", - executable!() + execution_phrase!() ) } diff --git a/src/uu/chmod/src/chmod.rs b/src/uu/chmod/src/chmod.rs index e1d8b32e4..88ad22969 100644 --- a/src/uu/chmod/src/chmod.rs +++ b/src/uu/chmod/src/chmod.rs @@ -41,7 +41,7 @@ fn usage() -> String { "{0} [OPTION]... MODE[,MODE]... FILE... or: {0} [OPTION]... OCTAL-MODE FILE... or: {0} [OPTION]... --reference=RFILE FILE...", - executable!() + execution_phrase!() ) } diff --git a/src/uu/chown/src/chown.rs b/src/uu/chown/src/chown.rs index a5d7c5ac7..a7824f248 100644 --- a/src/uu/chown/src/chown.rs +++ b/src/uu/chown/src/chown.rs @@ -64,7 +64,7 @@ const FTS_LOGICAL: u8 = 1 << 2; fn usage() -> String { format!( "{0} [OPTION]... [OWNER][:[GROUP]] FILE...\n{0} [OPTION]... --reference=RFILE FILE...", - executable!() + execution_phrase!() ) } diff --git a/src/uu/chroot/src/chroot.rs b/src/uu/chroot/src/chroot.rs index 01a7c6b84..b622b51a1 100644 --- a/src/uu/chroot/src/chroot.rs +++ b/src/uu/chroot/src/chroot.rs @@ -16,9 +16,8 @@ use std::io::Error; use std::path::Path; use std::process::Command; use uucore::libc::{self, chroot, setgid, setgroups, setuid}; -use uucore::{entries, InvalidEncodingHandling}; +use uucore::{entries, execution_phrase, InvalidEncodingHandling}; -static NAME: &str = "chroot"; static ABOUT: &str = "Run COMMAND with root directory set to NEWROOT."; static SYNTAX: &str = "[OPTION]... NEWROOT [COMMAND [ARG]...]"; @@ -47,7 +46,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { None => crash!( 1, "Missing operand: NEWROOT\nTry `{} --help` for more information.", - NAME + execution_phrase!() ), }; diff --git a/src/uu/comm/src/comm.rs b/src/uu/comm/src/comm.rs index 36238cd5f..02c5598be 100644 --- a/src/uu/comm/src/comm.rs +++ b/src/uu/comm/src/comm.rs @@ -32,7 +32,7 @@ mod options { } fn usage() -> String { - format!("{} [OPTION]... FILE1 FILE2", executable!()) + format!("{} [OPTION]... FILE1 FILE2", execution_phrase!()) } fn mkdelim(col: usize, opts: &ArgMatches) -> String { diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index 406f6757d..086b734e8 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -99,7 +99,7 @@ quick_error! { NotImplemented(opt: String) { display("Option '{}' not yet implemented.", opt) } /// Invalid arguments to backup - Backup(description: String) { display("{}\nTry `{} --help` for more information.", description, executable!()) } + Backup(description: String) { display("{}\nTry `{} --help` for more information.", description, execution_phrase!()) } } } @@ -223,7 +223,7 @@ fn usage() -> String { "{0} [OPTION]... [-T] SOURCE DEST {0} [OPTION]... SOURCE... DIRECTORY {0} [OPTION]... -t DIRECTORY SOURCE...", - executable!() + execution_phrase!() ) } @@ -1060,7 +1060,7 @@ impl OverwriteMode { match *self { OverwriteMode::NoClobber => Err(Error::NotAllFilesCopied), OverwriteMode::Interactive(_) => { - if prompt_yes!("{}: overwrite {}? ", executable!(), path.display()) { + if prompt_yes!("{}: overwrite {}? ", util_name!(), path.display()) { Ok(()) } else { Err(Error::Skipped(format!( diff --git a/src/uu/csplit/src/csplit.rs b/src/uu/csplit/src/csplit.rs index 409b17b98..c89d06cf3 100644 --- a/src/uu/csplit/src/csplit.rs +++ b/src/uu/csplit/src/csplit.rs @@ -35,7 +35,7 @@ mod options { } fn usage() -> String { - format!("{0} [OPTION]... FILE PATTERN...", executable!()) + format!("{0} [OPTION]... FILE PATTERN...", execution_phrase!()) } /// Command line options for csplit. diff --git a/src/uu/df/src/df.rs b/src/uu/df/src/df.rs index 4ffa8b532..52fae4702 100644 --- a/src/uu/df/src/df.rs +++ b/src/uu/df/src/df.rs @@ -80,7 +80,7 @@ struct Filesystem { } fn usage() -> String { - format!("{0} [OPTION]... [FILE]...", executable!()) + format!("{0} [OPTION]... [FILE]...", execution_phrase!()) } impl FsSelector { @@ -295,7 +295,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { #[cfg(windows)] { if matches.is_present(OPT_INODES) { - println!("{}: doesn't support -i option", executable!()); + println!("{}: doesn't support -i option", util_name!()); return Ok(()); } } diff --git a/src/uu/dircolors/src/dircolors.rs b/src/uu/dircolors/src/dircolors.rs index 90b48c301..c70fd97ee 100644 --- a/src/uu/dircolors/src/dircolors.rs +++ b/src/uu/dircolors/src/dircolors.rs @@ -63,7 +63,7 @@ pub fn guess_syntax() -> OutputFmt { } fn usage() -> String { - format!("{0} {1}", executable!(), SYNTAX) + format!("{0} {1}", execution_phrase!(), SYNTAX) } pub fn uumain(args: impl uucore::Args) -> i32 { diff --git a/src/uu/dirname/src/dirname.rs b/src/uu/dirname/src/dirname.rs index 3162bef48..4600d67ac 100644 --- a/src/uu/dirname/src/dirname.rs +++ b/src/uu/dirname/src/dirname.rs @@ -21,7 +21,7 @@ mod options { } fn usage() -> String { - format!("{0} [OPTION] NAME...", executable!()) + format!("{0} [OPTION] NAME...", execution_phrase!()) } fn get_long_usage() -> String { diff --git a/src/uu/du/src/du.rs b/src/uu/du/src/du.rs index 56f412ffb..2c430d693 100644 --- a/src/uu/du/src/du.rs +++ b/src/uu/du/src/du.rs @@ -396,7 +396,7 @@ fn usage() -> String { format!( "{0} [OPTION]... [FILE]... {0} [OPTION]... --files0-from=F", - executable!() + execution_phrase!() ) } @@ -424,7 +424,7 @@ Valid arguments are: - 'iso' Try `{} --help` for more information.", s, - executable!() + execution_phrase!() ), DuError::InvalidTimeArg(s) => write!( f, diff --git a/src/uu/expand/src/expand.rs b/src/uu/expand/src/expand.rs index 29de6ae44..0cd4b2719 100644 --- a/src/uu/expand/src/expand.rs +++ b/src/uu/expand/src/expand.rs @@ -33,7 +33,7 @@ static LONG_HELP: &str = ""; static DEFAULT_TABSTOP: usize = 8; fn usage() -> String { - format!("{0} [OPTION]... [FILE]...", executable!()) + format!("{0} [OPTION]... [FILE]...", execution_phrase!()) } /// The mode to use when replacing tabs beyond the last one specified in diff --git a/src/uu/expr/src/expr.rs b/src/uu/expr/src/expr.rs index b37e1ced6..0a9d76f67 100644 --- a/src/uu/expr/src/expr.rs +++ b/src/uu/expr/src/expr.rs @@ -140,5 +140,5 @@ Environment variables: } fn print_version() { - println!("{} {}", executable!(), crate_version!()); + println!("{} {}", util_name!(), crate_version!()); } diff --git a/src/uu/fmt/src/fmt.rs b/src/uu/fmt/src/fmt.rs index 80f74cf84..d42151503 100644 --- a/src/uu/fmt/src/fmt.rs +++ b/src/uu/fmt/src/fmt.rs @@ -51,7 +51,7 @@ static OPT_TAB_WIDTH: &str = "tab-width"; static ARG_FILES: &str = "files"; fn usage() -> String { - format!("{} [OPTION]... [FILE]...", executable!()) + format!("{} [OPTION]... [FILE]...", execution_phrase!()) } pub type FileOrStdReader = BufReader>; diff --git a/src/uu/groups/src/groups.rs b/src/uu/groups/src/groups.rs index fe1065cc1..83be0932b 100644 --- a/src/uu/groups/src/groups.rs +++ b/src/uu/groups/src/groups.rs @@ -29,7 +29,7 @@ static ABOUT: &str = "Print group memberships for each USERNAME or, \ (which may differ if the groups data‐base has changed)."; fn usage() -> String { - format!("{0} [OPTION]... [USERNAME]...", executable!()) + format!("{0} [OPTION]... [USERNAME]...", execution_phrase!()) } pub fn uumain(args: impl uucore::Args) -> i32 { diff --git a/src/uu/hostname/src/hostname.rs b/src/uu/hostname/src/hostname.rs index 2b7deb565..2dc68abfb 100644 --- a/src/uu/hostname/src/hostname.rs +++ b/src/uu/hostname/src/hostname.rs @@ -54,7 +54,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } fn usage() -> String { - format!("{0} [OPTION]... [HOSTNAME]", executable!()) + format!("{0} [OPTION]... [HOSTNAME]", execution_phrase!()) } fn execute(args: impl uucore::Args) -> UResult<()> { diff --git a/src/uu/id/src/id.rs b/src/uu/id/src/id.rs index 16c521ea3..74523213e 100644 --- a/src/uu/id/src/id.rs +++ b/src/uu/id/src/id.rs @@ -77,7 +77,7 @@ mod options { } fn usage() -> String { - format!("{0} [OPTION]... [USER]...", executable!()) + format!("{0} [OPTION]... [USER]...", execution_phrase!()) } fn get_description() -> String { diff --git a/src/uu/install/src/install.rs b/src/uu/install/src/install.rs index 5ee103e0f..a1c6d4225 100644 --- a/src/uu/install/src/install.rs +++ b/src/uu/install/src/install.rs @@ -89,7 +89,7 @@ impl Display for InstallError { IE::DirNeedsArg() => write!( f, "{} with -d requires at least one argument.", - executable!() + util_name!() ), IE::CreateDirFailed(dir, e) => { Display::fmt(&uio_error!(e, "failed to create {}", dir.display()), f) @@ -173,7 +173,7 @@ static OPT_CONTEXT: &str = "context"; static ARG_FILES: &str = "files"; fn usage() -> String { - format!("{0} [OPTION]... [FILE]...", executable!()) + format!("{0} [OPTION]... [FILE]...", execution_phrase!()) } /// Main install utility function, called from main.rs. diff --git a/src/uu/kill/src/kill.rs b/src/uu/kill/src/kill.rs index e9ec70964..08dff87d4 100644 --- a/src/uu/kill/src/kill.rs +++ b/src/uu/kill/src/kill.rs @@ -41,7 +41,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { .accept_any(); let (args, obs_signal) = handle_obsolete(args); - let usage = format!("{} [OPTIONS]... PID...", executable!()); + let usage = format!("{} [OPTIONS]... PID...", execution_phrase!()); let matches = uu_app().usage(&usage[..]).get_matches_from(args); let mode = if matches.is_present(options::TABLE) || matches.is_present(options::TABLE_OLD) { diff --git a/src/uu/link/src/link.rs b/src/uu/link/src/link.rs index d06025815..03dd46aee 100644 --- a/src/uu/link/src/link.rs +++ b/src/uu/link/src/link.rs @@ -20,7 +20,7 @@ pub mod options { } fn usage() -> String { - format!("{0} FILE1 FILE2", executable!()) + format!("{0} FILE1 FILE2", execution_phrase!()) } pub fn normalize_error_message(e: Error) -> String { diff --git a/src/uu/ln/src/ln.rs b/src/uu/ln/src/ln.rs index 572c68379..62748ae78 100644 --- a/src/uu/ln/src/ln.rs +++ b/src/uu/ln/src/ln.rs @@ -70,7 +70,7 @@ impl Display for LnError { f, "extra operand '{}'\nTry `{} --help` for more information.", s, - executable!() + execution_phrase!() ), Self::InvalidBackupMode(s) => write!(f, "{}", s), } @@ -98,7 +98,7 @@ fn usage() -> String { {0} [OPTION]... TARGET (2nd form) {0} [OPTION]... TARGET... DIRECTORY (3rd form) {0} [OPTION]... -t DIRECTORY TARGET... (4th form)", - executable!() + execution_phrase!() ) } @@ -431,7 +431,7 @@ fn link(src: &Path, dst: &Path, settings: &Settings) -> Result<()> { match settings.overwrite { OverwriteMode::NoClobber => {} OverwriteMode::Interactive => { - print!("{}: overwrite '{}'? ", executable!(), dst.display()); + print!("{}: overwrite '{}'? ", util_name!(), dst.display()); if !read_yes() { return Ok(()); } diff --git a/src/uu/logname/src/logname.rs b/src/uu/logname/src/logname.rs index 095f3fc84..e9952ddef 100644 --- a/src/uu/logname/src/logname.rs +++ b/src/uu/logname/src/logname.rs @@ -36,7 +36,7 @@ fn get_userlogin() -> Option { static SUMMARY: &str = "Print user's login name"; fn usage() -> String { - String::from(executable!()) + execution_phrase!().to_string() } pub fn uumain(args: impl uucore::Args) -> i32 { diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index f02a53738..6ba3ec4f1 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -47,7 +47,7 @@ use uucore::libc::{S_IXGRP, S_IXOTH, S_IXUSR}; use uucore::{fs::display_permissions, version_cmp::version_cmp}; fn usage() -> String { - format!("{0} [OPTION]... [FILE]...", executable!()) + format!("{0} [OPTION]... [FILE]...", execution_phrase!()) } pub mod options { diff --git a/src/uu/mkdir/src/mkdir.rs b/src/uu/mkdir/src/mkdir.rs index e9aa4bc22..046628f3a 100644 --- a/src/uu/mkdir/src/mkdir.rs +++ b/src/uu/mkdir/src/mkdir.rs @@ -23,7 +23,7 @@ mod options { } fn usage() -> String { - format!("{0} [OPTION]... [USER]", executable!()) + format!("{0} [OPTION]... [USER]", execution_phrase!()) } #[uucore_procs::gen_uumain] @@ -103,7 +103,7 @@ fn mkdir(path: &Path, recursive: bool, mode: u16, verbose: bool) -> UResult<()> create_dir(path).map_err_context(|| format!("cannot create directory '{}'", path.display()))?; if verbose { - println!("{}: created directory '{}'", executable!(), path.display()); + println!("{}: created directory '{}'", util_name!(), path.display()); } chmod(path, mode) diff --git a/src/uu/mknod/src/mknod.rs b/src/uu/mknod/src/mknod.rs index bd26949cd..19c61bcfe 100644 --- a/src/uu/mknod/src/mknod.rs +++ b/src/uu/mknod/src/mknod.rs @@ -18,7 +18,6 @@ use libc::{S_IFBLK, S_IFCHR, S_IFIFO, S_IRGRP, S_IROTH, S_IRUSR, S_IWGRP, S_IWOT use uucore::InvalidEncodingHandling; -static NAME: &str = "mknod"; static ABOUT: &str = "Create the special file NAME of the given TYPE."; static USAGE: &str = "mknod [OPTION]... NAME TYPE [MAJOR MINOR]"; static LONG_HELP: &str = "Mandatory arguments to long options are mandatory for short options too. @@ -72,7 +71,8 @@ fn _mknod(file_name: &str, mode: mode_t, dev: dev_t) -> i32 { } if errno == -1 { - let c_str = CString::new(NAME).expect("Failed to convert to CString"); + let c_str = + CString::new(execution_phrase!().as_bytes()).expect("Failed to convert to CString"); // shows the error from the mknod syscall libc::perror(c_str.as_ptr()); } @@ -113,7 +113,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { if ch == 'p' { if matches.is_present("major") || matches.is_present("minor") { eprintln!("Fifos do not have major and minor device numbers."); - eprintln!("Try `{} --help` for more information.", NAME); + eprintln!("Try `{} --help` for more information.", execution_phrase!()); 1 } else { _mknod(file_name, S_IFIFO | mode, 0) @@ -122,7 +122,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { match (matches.value_of("major"), matches.value_of("minor")) { (None, None) | (_, None) | (None, _) => { eprintln!("Special files require major and minor device numbers."); - eprintln!("Try `{} --help` for more information.", NAME); + eprintln!("Try `{} --help` for more information.", execution_phrase!()); 1 } (Some(major), Some(minor)) => { diff --git a/src/uu/mktemp/src/mktemp.rs b/src/uu/mktemp/src/mktemp.rs index 52d60d140..2d42d38ba 100644 --- a/src/uu/mktemp/src/mktemp.rs +++ b/src/uu/mktemp/src/mktemp.rs @@ -37,7 +37,7 @@ static OPT_T: &str = "t"; static ARG_TEMPLATE: &str = "template"; fn usage() -> String { - format!("{0} [OPTION]... [TEMPLATE]", executable!()) + format!("{0} [OPTION]... [TEMPLATE]", execution_phrase!()) } #[derive(Debug)] diff --git a/src/uu/mv/src/mv.rs b/src/uu/mv/src/mv.rs index 1c25e653c..ecd57ee5a 100644 --- a/src/uu/mv/src/mv.rs +++ b/src/uu/mv/src/mv.rs @@ -63,7 +63,7 @@ fn usage() -> String { "{0} [OPTION]... [-T] SOURCE DEST {0} [OPTION]... SOURCE... DIRECTORY {0} [OPTION]... -t DIRECTORY SOURCE...", - executable!() + execution_phrase!() ) } @@ -296,7 +296,7 @@ fn exec(files: &[PathBuf], b: Behavior) -> i32 { "mv: extra operand '{}'\n\ Try `{} --help` for more information.", files[2].display(), - executable!() + execution_phrase!() ); return 1; } @@ -353,7 +353,7 @@ fn rename(from: &Path, to: &Path, b: &Behavior) -> io::Result<()> { match b.overwrite { OverwriteMode::NoClobber => return Ok(()), OverwriteMode::Interactive => { - println!("{}: overwrite '{}'? ", executable!(), to.display()); + println!("{}: overwrite '{}'? ", util_name!(), to.display()); if !read_yes() { return Ok(()); } diff --git a/src/uu/nice/src/nice.rs b/src/uu/nice/src/nice.rs index c071a875f..835034b74 100644 --- a/src/uu/nice/src/nice.rs +++ b/src/uu/nice/src/nice.rs @@ -31,7 +31,7 @@ Run COMMAND with an adjusted niceness, which affects process scheduling. With no COMMAND, print the current niceness. Niceness values range from at least -20 (most favorable to the process) to 19 (least favorable to the process).", - executable!() + execution_phrase!() ) } @@ -54,7 +54,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { if !matches.is_present(options::COMMAND) { show_error!( "A command must be given with an adjustment.\nTry `{} --help` for more information.", - executable!() + execution_phrase!() ); return 125; } diff --git a/src/uu/nohup/src/nohup.rs b/src/uu/nohup/src/nohup.rs index a6f08e806..17b06301f 100644 --- a/src/uu/nohup/src/nohup.rs +++ b/src/uu/nohup/src/nohup.rs @@ -157,7 +157,7 @@ fn find_stdout() -> File { } fn usage() -> String { - format!("{0} COMMAND [ARG]...\n {0} FLAG", executable!()) + format!("{0} COMMAND [ARG]...\n {0} FLAG", execution_phrase!()) } #[cfg(target_vendor = "apple")] diff --git a/src/uu/nproc/src/nproc.rs b/src/uu/nproc/src/nproc.rs index 427a3cd2f..c10d79d40 100644 --- a/src/uu/nproc/src/nproc.rs +++ b/src/uu/nproc/src/nproc.rs @@ -28,7 +28,7 @@ static OPT_IGNORE: &str = "ignore"; static ABOUT: &str = "Print the number of cores available to the current process."; fn usage() -> String { - format!("{0} [OPTIONS]...", executable!()) + format!("{0} [OPTIONS]...", execution_phrase!()) } pub fn uumain(args: impl uucore::Args) -> i32 { diff --git a/src/uu/numfmt/src/numfmt.rs b/src/uu/numfmt/src/numfmt.rs index 6012bbf82..849abeb71 100644 --- a/src/uu/numfmt/src/numfmt.rs +++ b/src/uu/numfmt/src/numfmt.rs @@ -51,7 +51,7 @@ Multiple fields/ranges can be separated with commas "; fn usage() -> String { - format!("{0} [OPTION]... [NUMBER]...", executable!()) + format!("{0} [OPTION]... [NUMBER]...", execution_phrase!()) } fn handle_args<'a>(args: impl Iterator, options: NumfmtOptions) -> Result<()> { diff --git a/src/uu/od/src/multifilereader.rs b/src/uu/od/src/multifilereader.rs index 1255da66d..303093b01 100644 --- a/src/uu/od/src/multifilereader.rs +++ b/src/uu/od/src/multifilereader.rs @@ -57,12 +57,7 @@ impl<'b> MultifileReader<'b> { // print an error at the time that the file is needed, // then move on the the next file. // This matches the behavior of the original `od` - eprintln!( - "{}: '{}': {}", - executable!().split("::").next().unwrap(), // remove module - fname, - e - ); + eprintln!("{}: '{}': {}", util_name!(), fname, e); self.any_err = true } } @@ -95,11 +90,7 @@ impl<'b> io::Read for MultifileReader<'b> { Ok(0) => break, Ok(n) => n, Err(e) => { - eprintln!( - "{}: I/O: {}", - executable!().split("::").next().unwrap(), // remove module - e - ); + eprintln!("{}: I/O: {}", util_name!(), e); self.any_err = true; break; } diff --git a/src/uu/pathchk/src/pathchk.rs b/src/uu/pathchk/src/pathchk.rs index 4d257a771..65c917ac0 100644 --- a/src/uu/pathchk/src/pathchk.rs +++ b/src/uu/pathchk/src/pathchk.rs @@ -25,7 +25,6 @@ enum Mode { Both, // a combination of `Basic` and `Extra` } -static NAME: &str = "pathchk"; static ABOUT: &str = "Check whether file names are valid or portable"; mod options { @@ -40,7 +39,7 @@ const POSIX_PATH_MAX: usize = 256; const POSIX_NAME_MAX: usize = 14; fn usage() -> String { - format!("{0} [OPTION]... NAME...", executable!()) + format!("{0} [OPTION]... NAME...", execution_phrase!()) } pub fn uumain(args: impl uucore::Args) -> i32 { @@ -71,7 +70,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let mut res = if paths.is_none() { show_error!( "missing operand\nTry `{} --help` for more information", - NAME + execution_phrase!() ); false } else { diff --git a/src/uu/pinky/src/pinky.rs b/src/uu/pinky/src/pinky.rs index 4aad2c07d..9268ffd2b 100644 --- a/src/uu/pinky/src/pinky.rs +++ b/src/uu/pinky/src/pinky.rs @@ -41,7 +41,7 @@ mod options { } fn usage() -> String { - format!("{0} [OPTION]... [USER]...", executable!()) + format!("{0} [OPTION]... [USER]...", execution_phrase!()) } fn get_long_usage() -> String { diff --git a/src/uu/printenv/src/printenv.rs b/src/uu/printenv/src/printenv.rs index 0e86f1012..da46f7667 100644 --- a/src/uu/printenv/src/printenv.rs +++ b/src/uu/printenv/src/printenv.rs @@ -20,7 +20,7 @@ static OPT_NULL: &str = "null"; static ARG_VARIABLES: &str = "variables"; fn usage() -> String { - format!("{0} [VARIABLE]... [OPTION]...", executable!()) + format!("{0} [VARIABLE]... [OPTION]...", execution_phrase!()) } pub fn uumain(args: impl uucore::Args) -> i32 { diff --git a/src/uu/printf/src/printf.rs b/src/uu/printf/src/printf.rs index 211522fbf..26a1b29b2 100644 --- a/src/uu/printf/src/printf.rs +++ b/src/uu/printf/src/printf.rs @@ -281,11 +281,11 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .collect_str(InvalidEncodingHandling::Ignore) .accept_any(); - let location = &args[0]; if args.len() <= 1 { println!( - "{0}: missing operand\nTry `{0} --help` for more information.", - location + "{0}: missing operand\nTry `{1} --help` for more information.", + util_name!(), + execution_phrase!() ); return 1; } @@ -294,7 +294,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { if formatstr == "--help" { print!("{} {}", LONGHELP_LEAD, LONGHELP_BODY); } else if formatstr == "--version" { - println!("{} {}", executable!(), crate_version!()); + println!("{} {}", util_name!(), crate_version!()); } else { let printf_args = &args[2..]; memo::Memo::run_all(formatstr, printf_args); diff --git a/src/uu/pwd/src/pwd.rs b/src/uu/pwd/src/pwd.rs index bfdfafd13..388a10463 100644 --- a/src/uu/pwd/src/pwd.rs +++ b/src/uu/pwd/src/pwd.rs @@ -35,7 +35,7 @@ pub fn absolute_path(path: &Path) -> io::Result { } fn usage() -> String { - format!("{0} [OPTION]... FILE...", executable!()) + format!("{0} [OPTION]... FILE...", execution_phrase!()) } #[uucore_procs::gen_uumain] diff --git a/src/uu/readlink/src/readlink.rs b/src/uu/readlink/src/readlink.rs index b86dff162..cf788c1fe 100644 --- a/src/uu/readlink/src/readlink.rs +++ b/src/uu/readlink/src/readlink.rs @@ -16,7 +16,6 @@ use std::io::{stdout, Write}; use std::path::{Path, PathBuf}; use uucore::fs::{canonicalize, CanonicalizeMode}; -const NAME: &str = "readlink"; const ABOUT: &str = "Print value of a symbolic link or canonical file name."; const OPT_CANONICALIZE: &str = "canonicalize"; const OPT_CANONICALIZE_MISSING: &str = "canonicalize-missing"; @@ -30,7 +29,7 @@ const OPT_ZERO: &str = "zero"; const ARG_FILES: &str = "files"; fn usage() -> String { - format!("{0} [OPTION]... [FILE]...", executable!()) + format!("{0} [OPTION]... [FILE]...", execution_phrase!()) } pub fn uumain(args: impl uucore::Args) -> i32 { @@ -60,12 +59,15 @@ pub fn uumain(args: impl uucore::Args) -> i32 { crash!( 1, "missing operand\nTry `{} --help` for more information", - NAME + execution_phrase!() ); } if no_newline && files.len() > 1 && !silent { - eprintln!("{}: ignoring --no-newline with multiple arguments", NAME); + eprintln!( + "{}: ignoring --no-newline with multiple arguments", + util_name!() + ); no_newline = false; } @@ -76,7 +78,12 @@ pub fn uumain(args: impl uucore::Args) -> i32 { Ok(path) => show(&path, no_newline, use_zero), Err(err) => { if verbose { - eprintln!("{}: {}: errno {}", NAME, f, err.raw_os_error().unwrap()); + eprintln!( + "{}: {}: errno {}", + util_name!(), + f, + err.raw_os_error().unwrap() + ); } return 1; } @@ -86,7 +93,12 @@ pub fn uumain(args: impl uucore::Args) -> i32 { Ok(path) => show(&path, no_newline, use_zero), Err(err) => { if verbose { - eprintln!("{}: {}: errno {:?}", NAME, f, err.raw_os_error().unwrap()); + eprintln!( + "{}: {}: errno {:?}", + util_name!(), + f, + err.raw_os_error().unwrap() + ); } return 1; } diff --git a/src/uu/realpath/src/realpath.rs b/src/uu/realpath/src/realpath.rs index 494fe41dc..7ce8fc179 100644 --- a/src/uu/realpath/src/realpath.rs +++ b/src/uu/realpath/src/realpath.rs @@ -23,7 +23,7 @@ static OPT_ZERO: &str = "zero"; static ARG_FILES: &str = "files"; fn usage() -> String { - format!("{0} [OPTION]... FILE...", executable!()) + format!("{0} [OPTION]... FILE...", execution_phrase!()) } pub fn uumain(args: impl uucore::Args) -> i32 { diff --git a/src/uu/relpath/src/relpath.rs b/src/uu/relpath/src/relpath.rs index b7fa8991c..941a6a3f2 100644 --- a/src/uu/relpath/src/relpath.rs +++ b/src/uu/relpath/src/relpath.rs @@ -26,7 +26,7 @@ mod options { } fn usage() -> String { - format!("{} [-d DIR] TO [FROM]", executable!()) + format!("{} [-d DIR] TO [FROM]", execution_phrase!()) } pub fn uumain(args: impl uucore::Args) -> i32 { diff --git a/src/uu/rm/src/rm.rs b/src/uu/rm/src/rm.rs index 4b435807b..03977bc3e 100644 --- a/src/uu/rm/src/rm.rs +++ b/src/uu/rm/src/rm.rs @@ -53,7 +53,7 @@ static OPT_VERBOSE: &str = "verbose"; static ARG_FILES: &str = "files"; fn usage() -> String { - format!("{0} [OPTION]... FILE...", executable!()) + format!("{0} [OPTION]... FILE...", execution_phrase!()) } fn get_long_usage() -> String { @@ -93,7 +93,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { // Still check by hand and not use clap // Because "rm -f" is a thing show_error!("missing an argument"); - show_error!("for help, try '{0} --help'", executable!()); + show_error!("for help, try '{0} --help'", execution_phrase!()); return 1; } else { let options = Options { diff --git a/src/uu/rmdir/src/rmdir.rs b/src/uu/rmdir/src/rmdir.rs index 880300a7a..135350a8e 100644 --- a/src/uu/rmdir/src/rmdir.rs +++ b/src/uu/rmdir/src/rmdir.rs @@ -27,7 +27,7 @@ static ENOTDIR: i32 = 20; static ENOTDIR: i32 = 267; fn usage() -> String { - format!("{0} [OPTION]... DIRECTORY...", executable!()) + format!("{0} [OPTION]... DIRECTORY...", execution_phrase!()) } pub fn uumain(args: impl uucore::Args) -> i32 { diff --git a/src/uu/seq/src/seq.rs b/src/uu/seq/src/seq.rs index 2042cf23f..f3e57881e 100644 --- a/src/uu/seq/src/seq.rs +++ b/src/uu/seq/src/seq.rs @@ -27,7 +27,7 @@ fn usage() -> String { "{0} [OPTION]... LAST {0} [OPTION]... FIRST LAST {0} [OPTION]... FIRST INCREMENT LAST", - executable!() + execution_phrase!() ) } #[derive(Clone)] @@ -72,13 +72,13 @@ impl FromStr for Number { Ok(value) if value.is_nan() => Err(format!( "invalid 'not-a-number' argument: '{}'\nTry `{} --help` for more information.", s, - executable!(), + execution_phrase!(), )), Ok(value) => Ok(Number::F64(value)), Err(_) => Err(format!( "invalid floating point argument: '{}'\nTry `{} --help` for more information.", s, - executable!(), + execution_phrase!(), )), }, } @@ -123,7 +123,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { show_error!( "invalid Zero increment value: '{}'\nTry `{} --help` for more information.", numbers[1], - executable!() + execution_phrase!() ); return 1; } diff --git a/src/uu/shred/src/shred.rs b/src/uu/shred/src/shred.rs index 936577f01..521cb8dc6 100644 --- a/src/uu/shred/src/shred.rs +++ b/src/uu/shred/src/shred.rs @@ -214,7 +214,7 @@ for even very expensive hardware probing to recover the data. "; fn usage() -> String { - format!("{} [OPTION]... FILE...", executable!()) + format!("{} [OPTION]... FILE...", execution_phrase!()) } static AFTER_HELP: &str = diff --git a/src/uu/sleep/src/sleep.rs b/src/uu/sleep/src/sleep.rs index 624e65901..136d92e4d 100644 --- a/src/uu/sleep/src/sleep.rs +++ b/src/uu/sleep/src/sleep.rs @@ -29,7 +29,7 @@ mod options { fn usage() -> String { format!( "{0} {1}[SUFFIX]... \n {0} OPTION", - executable!(), + execution_phrase!(), options::NUMBER ) } diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index 78b6f8b64..049be5352 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -52,7 +52,6 @@ use uucore::InvalidEncodingHandling; use crate::tmp_dir::TmpDirWrapper; -const NAME: &str = "sort"; const ABOUT: &str = "Display sorted concatenation of all FILE(s)."; const LONG_HELP_KEYS: &str = "The key format is FIELD[.CHAR][OPTIONS][,FIELD[.CHAR]][OPTIONS]. @@ -1061,7 +1060,7 @@ fn usage() -> String { Write the sorted concatenation of all FILE(s) to standard output. Mandatory arguments for long options are mandatory for short options too. With no FILE, or when FILE is -, read standard input.", - NAME + execution_phrase!() ) } diff --git a/src/uu/split/src/split.rs b/src/uu/split/src/split.rs index bc38818b9..ce21361af 100644 --- a/src/uu/split/src/split.rs +++ b/src/uu/split/src/split.rs @@ -21,8 +21,6 @@ use std::path::Path; use std::{char, fs::remove_file}; use uucore::parse_size::parse_size; -static NAME: &str = "split"; - static OPT_BYTES: &str = "bytes"; static OPT_LINE_BYTES: &str = "line-bytes"; static OPT_LINES: &str = "lines"; @@ -37,7 +35,7 @@ static ARG_INPUT: &str = "input"; static ARG_PREFIX: &str = "prefix"; fn usage() -> String { - format!("{0} [OPTION]... [INPUT [PREFIX]]", NAME) + format!("{0} [OPTION]... [INPUT [PREFIX]]", execution_phrase!()) } fn get_long_usage() -> String { format!( diff --git a/src/uu/stat/src/stat.rs b/src/uu/stat/src/stat.rs index 4a12e4fb7..d3783d725 100644 --- a/src/uu/stat/src/stat.rs +++ b/src/uu/stat/src/stat.rs @@ -883,7 +883,7 @@ impl Stater { } fn usage() -> String { - format!("{0} [OPTION]... FILE...", executable!()) + format!("{0} [OPTION]... FILE...", execution_phrase!()) } fn get_long_usage() -> String { diff --git a/src/uu/stdbuf/src/stdbuf.rs b/src/uu/stdbuf/src/stdbuf.rs index bac5040cf..87e478b73 100644 --- a/src/uu/stdbuf/src/stdbuf.rs +++ b/src/uu/stdbuf/src/stdbuf.rs @@ -48,7 +48,7 @@ mod options { } fn usage() -> String { - format!("{0} OPTION... COMMAND", executable!()) + format!("{0} OPTION... COMMAND", execution_phrase!()) } const STDBUF_INJECT: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/libstdbuf.so")); @@ -161,7 +161,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { 125, "{}\nTry `{} --help` for more information.", e.0, - executable!() + execution_phrase!() ) }); diff --git a/src/uu/sync/src/sync.rs b/src/uu/sync/src/sync.rs index 123167db7..7dd37d780 100644 --- a/src/uu/sync/src/sync.rs +++ b/src/uu/sync/src/sync.rs @@ -160,7 +160,7 @@ mod platform { } fn usage() -> String { - format!("{0} [OPTION]... FILE...", executable!()) + format!("{0} [OPTION]... FILE...", execution_phrase!()) } pub fn uumain(args: impl uucore::Args) -> i32 { diff --git a/src/uu/tee/src/tee.rs b/src/uu/tee/src/tee.rs index 2f1b17ebe..461600003 100644 --- a/src/uu/tee/src/tee.rs +++ b/src/uu/tee/src/tee.rs @@ -33,7 +33,7 @@ struct Options { } fn usage() -> String { - format!("{0} [OPTION]... [FILE]...", executable!()) + format!("{0} [OPTION]... [FILE]...", execution_phrase!()) } pub fn uumain(args: impl uucore::Args) -> i32 { diff --git a/src/uu/timeout/src/timeout.rs b/src/uu/timeout/src/timeout.rs index e0fbe8a54..8b169b5b5 100644 --- a/src/uu/timeout/src/timeout.rs +++ b/src/uu/timeout/src/timeout.rs @@ -23,7 +23,7 @@ use uucore::InvalidEncodingHandling; static ABOUT: &str = "Start COMMAND, and kill it if still running after DURATION."; fn usage() -> String { - format!("{0} [OPTION] DURATION COMMAND...", executable!()) + format!("{0} [OPTION] DURATION COMMAND...", execution_phrase!()) } const ERR_EXIT_STATUS: i32 = 125; diff --git a/src/uu/touch/src/touch.rs b/src/uu/touch/src/touch.rs index bdb65683f..8ddfcfa74 100644 --- a/src/uu/touch/src/touch.rs +++ b/src/uu/touch/src/touch.rs @@ -48,7 +48,7 @@ fn local_tm_to_filetime(tm: time::Tm) -> FileTime { } fn usage() -> String { - format!("{0} [OPTION]... [USER]", executable!()) + format!("{0} [OPTION]... [USER]", execution_phrase!()) } #[uucore_procs::gen_uumain] diff --git a/src/uu/tr/src/tr.rs b/src/uu/tr/src/tr.rs index ddb866393..0a277d663 100644 --- a/src/uu/tr/src/tr.rs +++ b/src/uu/tr/src/tr.rs @@ -229,7 +229,7 @@ fn translate_input( } fn usage() -> String { - format!("{} [OPTION]... SET1 [SET2]", executable!()) + format!("{} [OPTION]... SET1 [SET2]", execution_phrase!()) } fn get_long_usage() -> String { @@ -264,7 +264,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { if sets.is_empty() { show_error!( "missing operand\nTry `{} --help` for more information.", - executable!() + execution_phrase!() ); return 1; } @@ -273,7 +273,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { show_error!( "missing operand after '{}'\nTry `{} --help` for more information.", sets[0], - executable!() + execution_phrase!() ); return 1; } diff --git a/src/uu/truncate/src/truncate.rs b/src/uu/truncate/src/truncate.rs index c5f9fb5b7..fdcff3fd5 100644 --- a/src/uu/truncate/src/truncate.rs +++ b/src/uu/truncate/src/truncate.rs @@ -64,7 +64,7 @@ pub mod options { } fn usage() -> String { - format!("{0} [OPTION]... [FILE]...", executable!()) + format!("{0} [OPTION]... [FILE]...", execution_phrase!()) } fn get_long_usage() -> String { diff --git a/src/uu/tty/src/tty.rs b/src/uu/tty/src/tty.rs index df6c43a27..284916050 100644 --- a/src/uu/tty/src/tty.rs +++ b/src/uu/tty/src/tty.rs @@ -24,7 +24,7 @@ mod options { } fn usage() -> String { - format!("{0} [OPTION]...", executable!()) + format!("{0} [OPTION]...", execution_phrase!()) } pub fn uumain(args: impl uucore::Args) -> i32 { diff --git a/src/uu/uname/src/uname.rs b/src/uu/uname/src/uname.rs index 2b16c85ec..b15d950bf 100644 --- a/src/uu/uname/src/uname.rs +++ b/src/uu/uname/src/uname.rs @@ -50,7 +50,7 @@ const HOST_OS: &str = "Fuchsia"; const HOST_OS: &str = "Redox"; pub fn uumain(args: impl uucore::Args) -> i32 { - let usage = format!("{} [OPTION]...", executable!()); + let usage = format!("{} [OPTION]...", execution_phrase!()); let matches = uu_app().usage(&usage[..]).get_matches_from(args); let uname = return_if_err!(1, PlatformInfo::new()); diff --git a/src/uu/uniq/src/uniq.rs b/src/uu/uniq/src/uniq.rs index fd8b4019b..10cb7a4ce 100644 --- a/src/uu/uniq/src/uniq.rs +++ b/src/uu/uniq/src/uniq.rs @@ -222,7 +222,7 @@ fn opt_parsed(opt_name: &str, matches: &ArgMatches) -> Option { } fn usage() -> String { - format!("{0} [OPTION]... [INPUT [OUTPUT]]...", executable!()) + format!("{0} [OPTION]... [INPUT [OUTPUT]]...", execution_phrase!()) } fn get_long_usage() -> String { diff --git a/src/uu/unlink/src/unlink.rs b/src/uu/unlink/src/unlink.rs index 0601b3e54..182729fd4 100644 --- a/src/uu/unlink/src/unlink.rs +++ b/src/uu/unlink/src/unlink.rs @@ -23,7 +23,7 @@ static ABOUT: &str = "Unlink the file at [FILE]."; static OPT_PATH: &str = "FILE"; fn usage() -> String { - format!("{} [OPTION]... FILE", executable!()) + format!("{} [OPTION]... FILE", execution_phrase!()) } pub fn uumain(args: impl uucore::Args) -> i32 { @@ -44,13 +44,13 @@ pub fn uumain(args: impl uucore::Args) -> i32 { crash!( 1, "missing operand\nTry `{0} --help` for more information.", - executable!() + execution_phrase!() ); } else if paths.len() > 1 { crash!( 1, "extra operand: '{1}'\nTry `{0} --help` for more information.", - executable!(), + execution_phrase!(), paths[1] ); } diff --git a/src/uu/uptime/src/uptime.rs b/src/uu/uptime/src/uptime.rs index 33f3ec965..b19566ec3 100644 --- a/src/uu/uptime/src/uptime.rs +++ b/src/uu/uptime/src/uptime.rs @@ -33,7 +33,7 @@ extern "C" { } fn usage() -> String { - format!("{0} [OPTION]...", executable!()) + format!("{0} [OPTION]...", execution_phrase!()) } pub fn uumain(args: impl uucore::Args) -> i32 { diff --git a/src/uu/users/src/users.rs b/src/uu/users/src/users.rs index 3f081f891..7aed00507 100644 --- a/src/uu/users/src/users.rs +++ b/src/uu/users/src/users.rs @@ -19,7 +19,7 @@ static ABOUT: &str = "Print the user names of users currently logged in to the c static ARG_FILES: &str = "files"; fn usage() -> String { - format!("{0} [FILE]", executable!()) + format!("{0} [FILE]", execution_phrase!()) } fn get_long_usage() -> String { diff --git a/src/uu/wc/src/wc.rs b/src/uu/wc/src/wc.rs index e21eb0b7e..3077d31d9 100644 --- a/src/uu/wc/src/wc.rs +++ b/src/uu/wc/src/wc.rs @@ -99,7 +99,7 @@ fn usage() -> String { format!( "{0} [OPTION]... [FILE]... With no FILE, or when FILE is -, read standard input.", - executable!() + execution_phrase!() ) } diff --git a/src/uu/who/src/who.rs b/src/uu/who/src/who.rs index 558d42f1b..59e8b7c36 100644 --- a/src/uu/who/src/who.rs +++ b/src/uu/who/src/who.rs @@ -45,7 +45,7 @@ static RUNLEVEL_HELP: &str = "print current runlevel"; static RUNLEVEL_HELP: &str = "print current runlevel (This is meaningless on non Linux)"; fn usage() -> String { - format!("{0} [OPTION]... [ FILE | ARG1 ARG2 ]", executable!()) + format!("{0} [OPTION]... [ FILE | ARG1 ARG2 ]", execution_phrase!()) } fn get_long_usage() -> String { diff --git a/src/uucore/src/lib/macros.rs b/src/uucore/src/lib/macros.rs index 36336a24d..df3834f7e 100644 --- a/src/uucore/src/lib/macros.rs +++ b/src/uucore/src/lib/macros.rs @@ -125,7 +125,7 @@ macro_rules! show_usage_error( ($($args:tt)+) => ({ eprint!("{}: ", $crate::util_name!()); eprintln!($($args)+); - eprintln!("Try `{:?} --help` for more information.", $crate::executable!()); + eprintln!("Try `{} --help` for more information.", $crate::execution_phrase!()); }) ); diff --git a/src/uucore_procs/src/lib.rs b/src/uucore_procs/src/lib.rs index 2467ce2c7..74ac60220 100644 --- a/src/uucore_procs/src/lib.rs +++ b/src/uucore_procs/src/lib.rs @@ -104,7 +104,7 @@ pub fn gen_uumain(_args: TokenStream, stream: TokenStream) -> TokenStream { show_error!("{}", s); } if e.usage() { - eprintln!("Try `{} --help` for more information.", executable!()); + eprintln!("Try `{} --help` for more information.", execution_phrase!()); } e.code() } From 4da46d93c7bb8269eaa28fad2cb09aeecc255c5e Mon Sep 17 00:00:00 2001 From: Roy Ivy III Date: Tue, 27 Jul 2021 00:21:12 -0500 Subject: [PATCH 021/206] tests ~ fix tests for new `execution_phrase!()` and usage phrasing --- tests/by-util/test_base32.rs | 14 +++++++++----- tests/by-util/test_basename.rs | 19 ++++++++++++------- tests/by-util/test_cp.rs | 13 ++++++++----- tests/by-util/test_more.rs | 12 ++++++++---- tests/by-util/test_mv.rs | 9 ++++++--- tests/by-util/test_nice.rs | 9 +++++++-- tests/by-util/test_rm.rs | 10 ++++++---- tests/by-util/test_seq.rs | 20 ++++++++++++++------ tests/by-util/test_stdbuf.rs | 23 +++++++++++++++++------ tests/by-util/test_unlink.rs | 13 ++++++++----- tests/common/util.rs | 2 +- 11 files changed, 96 insertions(+), 48 deletions(-) diff --git a/tests/by-util/test_base32.rs b/tests/by-util/test_base32.rs index 178341f44..5c74c5b59 100644 --- a/tests/by-util/test_base32.rs +++ b/tests/by-util/test_base32.rs @@ -85,11 +85,15 @@ fn test_wrap() { #[test] fn test_wrap_no_arg() { for wrap_param in &["-w", "--wrap"] { - let expected_stderr = "error: The argument '--wrap \' requires a value but none was \ - supplied\n\nUSAGE:\n base32 [OPTION]... [FILE]\n\nFor more \ - information try --help" - .to_string(); - new_ucmd!() + let ts = TestScenario::new(util_name!()); + let expected_stderr = &format!( + "error: The argument '--wrap \' requires a value but none was \ + supplied\n\nUSAGE:\n {1} {0} [OPTION]... [FILE]\n\nFor more \ + information try --help", + ts.util_name, + ts.bin_path.to_string_lossy() + ); + ts.ucmd() .arg(wrap_param) .fails() .stderr_only(expected_stderr); diff --git a/tests/by-util/test_basename.rs b/tests/by-util/test_basename.rs index d9632106e..9701e9973 100644 --- a/tests/by-util/test_basename.rs +++ b/tests/by-util/test_basename.rs @@ -114,9 +114,12 @@ fn test_no_args() { #[test] fn test_no_args_output() { - new_ucmd!() - .fails() - .stderr_is("basename: missing operand\nTry 'basename --help' for more information."); + let ts = TestScenario::new(util_name!()); + ts.ucmd().fails().stderr_is(&format!( + "{0}: missing operand\nTry `{1} {0} --help` for more information.", + ts.util_name, + ts.bin_path.to_string_lossy() + )); } #[test] @@ -126,10 +129,12 @@ fn test_too_many_args() { #[test] fn test_too_many_args_output() { - new_ucmd!() - .args(&["a", "b", "c"]) - .fails() - .stderr_is("basename: extra operand 'c'\nTry 'basename --help' for more information."); + let ts = TestScenario::new(util_name!()); + ts.ucmd().args(&["a", "b", "c"]).fails().stderr_is(format!( + "{0}: extra operand 'c'\nTry `{1} {0} --help` for more information.", + ts.util_name, + ts.bin_path.to_string_lossy() + )); } #[cfg(any(unix, target_os = "redox"))] diff --git a/tests/by-util/test_cp.rs b/tests/by-util/test_cp.rs index 541e6b5d9..6d739f448 100644 --- a/tests/by-util/test_cp.rs +++ b/tests/by-util/test_cp.rs @@ -561,14 +561,17 @@ fn test_cp_backup_off() { #[test] fn test_cp_backup_no_clobber_conflicting_options() { - let (_, mut ucmd) = at_and_ucmd!(); - - ucmd.arg("--backup") + let ts = TestScenario::new(util_name!()); + ts.ucmd() + .arg("--backup") .arg("--no-clobber") .arg(TEST_HELLO_WORLD_SOURCE) .arg(TEST_HOW_ARE_YOU_SOURCE) - .fails() - .stderr_is("cp: options --backup and --no-clobber are mutually exclusive\nTry 'cp --help' for more information."); + .fails().stderr_is(&format!( + "{0}: options --backup and --no-clobber are mutually exclusive\nTry `{1} {0} --help` for more information.", + ts.util_name, + ts.bin_path.to_string_lossy() + )); } #[test] diff --git a/tests/by-util/test_more.rs b/tests/by-util/test_more.rs index 9b28ee24e..a422b23a5 100644 --- a/tests/by-util/test_more.rs +++ b/tests/by-util/test_more.rs @@ -15,11 +15,15 @@ fn test_more_dir_arg() { // Maybe we could capture the error, i.e. "Device not found" in that case // but I am leaving this for later if atty::is(atty::Stream::Stdout) { - let result = new_ucmd!().arg(".").run(); + let ts = TestScenario::new(util_name!()); + let result = ts.ucmd().arg(".").run(); result.failure(); - const EXPECTED_ERROR_MESSAGE: &str = - "more: '.' is a directory.\nTry 'more --help' for more information."; - assert_eq!(result.stderr_str().trim(), EXPECTED_ERROR_MESSAGE); + let expected_error_message = &format!( + "{0}: '.' is a directory.\nTry `{1} {0} --help` for more information.", + ts.util_name, + ts.bin_path.to_string_lossy() + ); + assert_eq!(result.stderr_str().trim(), expected_error_message); } else { } } diff --git a/tests/by-util/test_mv.rs b/tests/by-util/test_mv.rs index 02c65f68d..8596f8477 100644 --- a/tests/by-util/test_mv.rs +++ b/tests/by-util/test_mv.rs @@ -522,14 +522,17 @@ fn test_mv_backup_off() { #[test] fn test_mv_backup_no_clobber_conflicting_options() { - let (_, mut ucmd) = at_and_ucmd!(); + let ts = TestScenario::new(util_name!()); - ucmd.arg("--backup") + ts.ucmd().arg("--backup") .arg("--no-clobber") .arg("file1") .arg("file2") .fails() - .stderr_is("mv: options --backup and --no-clobber are mutually exclusive\nTry 'mv --help' for more information."); + .stderr_is(&format!("{0}: options --backup and --no-clobber are mutually exclusive\nTry `{1} {0} --help` for more information.", + ts.util_name, + ts.bin_path.to_string_lossy() + )); } #[test] diff --git a/tests/by-util/test_nice.rs b/tests/by-util/test_nice.rs index 25886de78..639fa5697 100644 --- a/tests/by-util/test_nice.rs +++ b/tests/by-util/test_nice.rs @@ -22,10 +22,15 @@ fn test_negative_adjustment() { #[test] fn test_adjustment_with_no_command_should_error() { - new_ucmd!() + let ts = TestScenario::new(util_name!()); + + ts.ucmd() .args(&["-n", "19"]) .run() - .stderr_is("nice: A command must be given with an adjustment.\nTry \"nice --help\" for more information.\n"); + .stderr_is(&format!("{0}: A command must be given with an adjustment.\nTry `{1} {0} --help` for more information.\n", + ts.util_name, + ts.bin_path.to_string_lossy() + )); } #[test] diff --git a/tests/by-util/test_rm.rs b/tests/by-util/test_rm.rs index 0592be244..32bc43837 100644 --- a/tests/by-util/test_rm.rs +++ b/tests/by-util/test_rm.rs @@ -255,10 +255,12 @@ fn test_rm_force_no_operand() { #[test] fn test_rm_no_operand() { - let mut ucmd = new_ucmd!(); - - ucmd.fails() - .stderr_is("rm: missing an argument\nrm: for help, try 'rm --help'\n"); + let ts = TestScenario::new(util_name!()); + ts.ucmd().fails().stderr_is(&format!( + "{0}: missing an argument\n{0}: for help, try '{1} {0} --help'\n", + ts.util_name, + ts.bin_path.to_string_lossy() + )); } #[test] diff --git a/tests/by-util/test_seq.rs b/tests/by-util/test_seq.rs index be04bf1fd..cc94d4b1f 100644 --- a/tests/by-util/test_seq.rs +++ b/tests/by-util/test_seq.rs @@ -2,16 +2,24 @@ use crate::common::util::*; #[test] fn test_rejects_nan() { - new_ucmd!().args(&["NaN"]).fails().stderr_only( - "seq: invalid 'not-a-number' argument: 'NaN'\nTry 'seq --help' for more information.", - ); + let ts = TestScenario::new(util_name!()); + + ts.ucmd().args(&["NaN"]).fails().stderr_only(format!( + "{0}: invalid 'not-a-number' argument: 'NaN'\nTry `{1} {0} --help` for more information.", + ts.util_name, + ts.bin_path.to_string_lossy() + )); } #[test] fn test_rejects_non_floats() { - new_ucmd!().args(&["foo"]).fails().stderr_only( - "seq: invalid floating point argument: 'foo'\nTry 'seq --help' for more information.", - ); + let ts = TestScenario::new(util_name!()); + + ts.ucmd().args(&["foo"]).fails().stderr_only(&format!( + "{0}: invalid floating point argument: 'foo'\nTry `{1} {0} --help` for more information.", + ts.util_name, + ts.bin_path.to_string_lossy() + )); } // ---- Tests for the big integer based path ---- diff --git a/tests/by-util/test_stdbuf.rs b/tests/by-util/test_stdbuf.rs index 66892ea0f..d705c0854 100644 --- a/tests/by-util/test_stdbuf.rs +++ b/tests/by-util/test_stdbuf.rs @@ -25,15 +25,19 @@ fn test_stdbuf_line_buffered_stdout() { #[cfg(not(target_os = "windows"))] #[test] fn test_stdbuf_no_buffer_option_fails() { - new_ucmd!().args(&["head"]).fails().stderr_is( + let ts = TestScenario::new(util_name!()); + + ts.ucmd().args(&["head"]).fails().stderr_is(&format!( "error: The following required arguments were not provided:\n \ --error \n \ --input \n \ --output \n\n\ USAGE:\n \ - stdbuf OPTION... COMMAND\n\n\ + {1} {0} OPTION... COMMAND\n\n\ For more information try --help", - ); + ts.util_name, + ts.bin_path.to_string_lossy() + )); } #[cfg(not(target_os = "windows"))] @@ -49,9 +53,16 @@ fn test_stdbuf_trailing_var_arg() { #[cfg(not(target_os = "windows"))] #[test] fn test_stdbuf_line_buffering_stdin_fails() { - new_ucmd!().args(&["-i", "L", "head"]).fails().stderr_is( - "stdbuf: line buffering stdin is meaningless\nTry 'stdbuf --help' for more information.", - ); + let ts = TestScenario::new(util_name!()); + + ts.ucmd() + .args(&["-i", "L", "head"]) + .fails() + .stderr_is(&format!( + "{0}: line buffering stdin is meaningless\nTry `{1} {0} --help` for more information.", + ts.util_name, + ts.bin_path.to_string_lossy() + )); } #[cfg(not(target_os = "windows"))] diff --git a/tests/by-util/test_unlink.rs b/tests/by-util/test_unlink.rs index 1999e965c..0c82b6a39 100644 --- a/tests/by-util/test_unlink.rs +++ b/tests/by-util/test_unlink.rs @@ -14,17 +14,20 @@ fn test_unlink_file() { #[test] fn test_unlink_multiple_files() { - let (at, mut ucmd) = at_and_ucmd!(); + let ts = TestScenario::new(util_name!()); + + let (at, mut ucmd) = (ts.fixtures.clone(), ts.ucmd()); let file_a = "test_unlink_multiple_file_a"; let file_b = "test_unlink_multiple_file_b"; at.touch(file_a); at.touch(file_b); - ucmd.arg(file_a).arg(file_b).fails().stderr_is( - "unlink: extra operand: 'test_unlink_multiple_file_b'\nTry 'unlink --help' \ - for more information.\n", - ); + ucmd.arg(file_a).arg(file_b).fails().stderr_is(&format!( + "{0}: extra operand: 'test_unlink_multiple_file_b'\nTry `{1} {0} --help` for more information.", + ts.util_name, + ts.bin_path.to_string_lossy() + )); } #[test] diff --git a/tests/common/util.rs b/tests/common/util.rs index 41389d567..5441b82c2 100644 --- a/tests/common/util.rs +++ b/tests/common/util.rs @@ -713,7 +713,7 @@ impl AtPath { /// /// Fixtures can be found under `tests/fixtures/$util_name/` pub struct TestScenario { - bin_path: PathBuf, + pub bin_path: PathBuf, pub util_name: String, pub fixtures: AtPath, tmpd: Rc, From 38f3d13f7fb46463619df6c701ce63f5fba7bfa2 Mon Sep 17 00:00:00 2001 From: Roy Ivy III Date: Tue, 27 Jul 2021 22:00:45 -0500 Subject: [PATCH 022/206] maint/CICD ~ pin code coverage utility `grcov` at v0.8.0 (v0.8.1 uses unstable `take()`) --- .github/workflows/CICD.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/CICD.yml b/.github/workflows/CICD.yml index 8a1e142df..90b836c0f 100644 --- a/.github/workflows/CICD.yml +++ b/.github/workflows/CICD.yml @@ -625,7 +625,7 @@ jobs: uses: actions-rs/install@v0.1 with: crate: grcov - version: latest + version: 0.8.0 use-tool-cache: false - name: Generate coverage data (via `grcov`) id: coverage From 44981cab0123dfbb92476f3e1c07aaefcbb81d33 Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Sat, 14 Aug 2021 13:50:53 +0200 Subject: [PATCH 023/206] refactor/uucore ~ correct implementation of executable!() for multicall - Use an atomic bool to track whether the utility name is the second or the first argument. - Add tests --- src/bin/coreutils.rs | 1 + src/uu/base32/src/base32.rs | 6 +-- src/uu/base64/src/base64.rs | 4 +- src/uucore/src/lib/lib.rs | 9 ++++ src/uucore/src/lib/macros.rs | 59 ++++++++++---------------- tests/test_util_name.rs | 81 ++++++++++++++++++++++++++++++++++++ 6 files changed, 117 insertions(+), 43 deletions(-) create mode 100644 tests/test_util_name.rs diff --git a/src/bin/coreutils.rs b/src/bin/coreutils.rs index 3e8df57f7..477931dbd 100644 --- a/src/bin/coreutils.rs +++ b/src/bin/coreutils.rs @@ -70,6 +70,7 @@ fn main() { Some(OsString::from(*util)) } else { // unmatched binary name => regard as multi-binary container and advance argument list + uucore::set_utility_is_second_arg(); args.next() }; diff --git a/src/uu/base32/src/base32.rs b/src/uu/base32/src/base32.rs index a93b4e18b..4d6c634e5 100644 --- a/src/uu/base32/src/base32.rs +++ b/src/uu/base32/src/base32.rs @@ -38,7 +38,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let name = util_name!(); let config_result: Result = - base_common::parse_base_cmd_args(args, name, VERSION, ABOUT, &usage); + 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)); // Create a reference to stdin so we can return a locked stdin from @@ -52,12 +52,12 @@ pub fn uumain(args: impl uucore::Args) -> i32 { config.wrap_cols, config.ignore_garbage, config.decode, - name, + &name, ); 0 } pub fn uu_app() -> App<'static, 'static> { - base_common::base_app(util_name!(), VERSION, ABOUT) + base_common::base_app(&util_name!(), VERSION, ABOUT) } diff --git a/src/uu/base64/src/base64.rs b/src/uu/base64/src/base64.rs index b53ec32e9..689940a07 100644 --- a/src/uu/base64/src/base64.rs +++ b/src/uu/base64/src/base64.rs @@ -38,7 +38,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let usage = usage(); let name = util_name!(); let config_result: Result = - base_common::parse_base_cmd_args(args, name, VERSION, ABOUT, &usage); + 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)); // Create a reference to stdin so we can return a locked stdin from @@ -52,7 +52,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { config.wrap_cols, config.ignore_garbage, config.decode, - name, + &name, ); 0 diff --git a/src/uucore/src/lib/lib.rs b/src/uucore/src/lib/lib.rs index aa96a1086..8920b226d 100644 --- a/src/uucore/src/lib/lib.rs +++ b/src/uucore/src/lib/lib.rs @@ -77,6 +77,15 @@ pub use crate::features::wide; //## core functions use std::ffi::OsString; +use std::sync::atomic::Ordering; + +pub fn get_utility_is_second_arg() -> bool { + crate::macros::UTILITY_IS_SECOND_ARG.load(Ordering::SeqCst) +} + +pub fn set_utility_is_second_arg() { + crate::macros::UTILITY_IS_SECOND_ARG.store(true, Ordering::SeqCst) +} pub enum InvalidEncodingHandling { Ignore, diff --git a/src/uucore/src/lib/macros.rs b/src/uucore/src/lib/macros.rs index df3834f7e..72826be5f 100644 --- a/src/uucore/src/lib/macros.rs +++ b/src/uucore/src/lib/macros.rs @@ -1,3 +1,5 @@ +use std::sync::atomic::AtomicBool; + // This file is part of the uutils coreutils package. // // (c) Alex Lyon @@ -5,40 +7,22 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. +/// Whether we were called as a multicall binary ("coreutils ") +pub static UTILITY_IS_SECOND_ARG: AtomicBool = AtomicBool::new(false); + /// Get the executable path (as `OsString`). #[macro_export] macro_rules! executable_os( () => ({ - &uucore::args_os().next().unwrap() + $crate::args_os().next().unwrap() }) ); -/// Get the executable path (as `String`; lossless). +/// Get the executable path (as `String`). #[macro_export] macro_rules! executable( () => ({ - let exe = match $crate::executable_os!().to_str() { - // * UTF-8 - Some(s) => s.to_string(), - // * "lossless" debug format (if/when `executable_os!()` is not well-formed UTF-8) - None => format!("{:?}", $crate::executable_os!()) - }; - &exe.to_owned() - }) -); - -/// Get the executable name. -#[macro_export] -macro_rules! executable_name( - () => ({ - let stem = &std::path::Path::new($crate::executable_os!()).file_stem().unwrap().to_owned(); - let exe = match stem.to_str() { - // * UTF-8 - Some(s) => s.to_string(), - // * "lossless" debug format (if/when `executable_os!()` is not well-formed UTF-8) - None => format!("{:?}", stem) - }; - &exe.to_owned() + $crate::executable_os!().to_string_lossy().to_string() }) ); @@ -46,13 +30,11 @@ macro_rules! executable_name( #[macro_export] macro_rules! util_name( () => ({ - let crate_name = env!("CARGO_PKG_NAME"); - let name = if crate_name.starts_with("uu_") { - &crate_name[3..] + if $crate::get_utility_is_second_arg() { + $crate::args_os().nth(1).unwrap().to_string_lossy().to_string() } else { - crate_name - }; - &name.to_owned() + $crate::executable!() + } }) ); @@ -62,14 +44,15 @@ macro_rules! util_name( #[macro_export] macro_rules! execution_phrase( () => ({ - let exe = if (executable_name!() == util_name!()) { - executable!().to_string() - } else { - format!("{} {}", executable!(), util_name!()) - .as_str() - .to_owned() - }; - &exe.to_owned() + if $crate::get_utility_is_second_arg() { + $crate::args_os() + .take(2) + .map(|os_str| os_str.to_string_lossy().to_string()) + .collect::>() + .join(" ") + } else { + $crate::executable!() + } }) ); diff --git a/tests/test_util_name.rs b/tests/test_util_name.rs new file mode 100644 index 000000000..640fecd44 --- /dev/null +++ b/tests/test_util_name.rs @@ -0,0 +1,81 @@ +mod common; + +use common::util::TestScenario; + +#[test] +#[cfg(feature = "ls")] +fn execution_phrase_double() { + use std::process::Command; + + let scenario = TestScenario::new("ls"); + let output = Command::new(&scenario.bin_path) + .arg("ls") + .arg("--some-invalid-arg") + .output() + .unwrap(); + assert!(String::from_utf8(output.stderr) + .unwrap() + .contains(&format!("USAGE:\n {} ls", scenario.bin_path.display(),))); +} + +#[test] +#[cfg(feature = "ls")] +fn execution_phrase_single() { + use std::process::Command; + + let scenario = TestScenario::new("ls"); + std::fs::copy(scenario.bin_path, scenario.fixtures.plus("uu-ls")).unwrap(); + let output = Command::new(scenario.fixtures.plus("uu-ls")) + .arg("--some-invalid-arg") + .output() + .unwrap(); + assert!(String::from_utf8(output.stderr).unwrap().contains(&format!( + "USAGE:\n {}", + scenario.fixtures.plus("uu-ls").display() + ))); +} + +#[test] +#[cfg(feature = "sort")] +fn util_name_double() { + use std::{ + io::Write, + process::{Command, Stdio}, + }; + + let scenario = TestScenario::new("sort"); + let mut child = Command::new(&scenario.bin_path) + .arg("sort") + .stdin(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .unwrap(); + // input invalid utf8 to cause an error + child.stdin.take().unwrap().write_all(&[255]).unwrap(); + let output = child.wait_with_output().unwrap(); + assert!(String::from_utf8(output.stderr).unwrap().contains("sort: ")); +} + +#[test] +#[cfg(feature = "sort")] +fn util_name_single() { + use std::{ + io::Write, + process::{Command, Stdio}, + }; + + let scenario = TestScenario::new("sort"); + std::fs::copy(scenario.bin_path, scenario.fixtures.plus("uu-sort")).unwrap(); + let mut child = Command::new(scenario.fixtures.plus("uu-sort")) + .stdin(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .unwrap(); + // input invalid utf8 to cause an error + child.stdin.take().unwrap().write_all(&[255]).unwrap(); + let output = child.wait_with_output().unwrap(); + assert!(String::from_utf8(output.stderr).unwrap().contains(&format!( + "{}: ", + scenario.fixtures.plus("uu-sort").display() + ))); +} From 813b477859f4ca82ea8060431f84b55f150fee62 Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Sat, 14 Aug 2021 14:10:35 +0200 Subject: [PATCH 024/206] fix ~ fixes for newly added utilities --- src/uu/basenc/src/basenc.rs | 12 ++++++------ src/uu/cat/src/cat.rs | 2 +- src/uu/chcon/src/chcon.rs | 8 ++++---- src/uu/dd/src/dd.rs | 2 +- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/uu/basenc/src/basenc.rs b/src/uu/basenc/src/basenc.rs index e1daea4e6..ef9779512 100644 --- a/src/uu/basenc/src/basenc.rs +++ b/src/uu/basenc/src/basenc.rs @@ -42,12 +42,12 @@ const ENCODINGS: &[(&str, Format)] = &[ ("base2m", Format::Base2Msbf), ]; -fn get_usage() -> String { - format!("{0} [OPTION]... [FILE]", executable!()) +fn usage() -> String { + format!("{0} [OPTION]... [FILE]", execution_phrase!()) } pub fn uu_app() -> App<'static, 'static> { - let mut app = base_common::base_app(executable!(), crate_version!(), ABOUT); + let mut app = base_common::base_app(&util_name!(), crate_version!(), ABOUT); for encoding in ENCODINGS { app = app.arg(Arg::with_name(encoding.0).long(encoding.0)); } @@ -55,7 +55,7 @@ pub fn uu_app() -> App<'static, 'static> { } fn parse_cmd_args(args: impl uucore::Args) -> (Config, Format) { - let usage = get_usage(); + let usage = usage(); let matches = uu_app().usage(&usage[..]).get_matches_from( args.collect_str(InvalidEncodingHandling::ConvertLossy) .accept_any(), @@ -75,7 +75,7 @@ fn parse_cmd_args(args: impl uucore::Args) -> (Config, Format) { } pub fn uumain(args: impl uucore::Args) -> i32 { - let name = executable!(); + let name = util_name!(); let (config, format) = parse_cmd_args(args); // Create a reference to stdin so we can return a locked stdin from // parse_base_cmd_args @@ -88,7 +88,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { config.wrap_cols, config.ignore_garbage, config.decode, - name, + &name, ); 0 diff --git a/src/uu/cat/src/cat.rs b/src/uu/cat/src/cat.rs index cb12830a4..e0c7d2226 100644 --- a/src/uu/cat/src/cat.rs +++ b/src/uu/cat/src/cat.rs @@ -396,7 +396,7 @@ fn cat_files(files: Vec, options: &OutputOptions) -> UResult<()> { Ok(()) } else { // each next line is expected to display "cat: …" - let line_joiner = format!("\n{}: ", executable!()); + let line_joiner = format!("\n{}: ", util_name!()); Err(uucore::error::USimpleError::new( error_messages.len() as i32, diff --git a/src/uu/chcon/src/chcon.rs b/src/uu/chcon/src/chcon.rs index 2a5efba46..6f45dcd92 100644 --- a/src/uu/chcon/src/chcon.rs +++ b/src/uu/chcon/src/chcon.rs @@ -2,7 +2,7 @@ #![allow(clippy::upper_case_acronyms)] -use uucore::{executable, show_error, show_usage_error, show_warning}; +use uucore::{execution_phrase, show_error, show_usage_error, show_warning, util_name}; use clap::{App, Arg}; use selinux::{OpaqueSecurityContext, SecurityContext}; @@ -56,7 +56,7 @@ fn get_usage() -> String { "{0} [OPTION]... CONTEXT FILE... \n \ {0} [OPTION]... [-u USER] [-r ROLE] [-l RANGE] [-t TYPE] FILE... \n \ {0} [OPTION]... --reference=RFILE FILE...", - executable!() + execution_phrase!() ) } @@ -152,7 +152,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> App<'static, 'static> { - App::new(executable!()) + App::new(util_name!()) .version(VERSION) .about(ABOUT) .arg( @@ -563,7 +563,7 @@ fn process_file( if options.verbose { println!( "{}: Changing security context of: {}", - executable!(), + util_name!(), file_full_name.to_string_lossy() ); } diff --git a/src/uu/dd/src/dd.rs b/src/uu/dd/src/dd.rs index b300dff2d..660f62f7a 100644 --- a/src/uu/dd/src/dd.rs +++ b/src/uu/dd/src/dd.rs @@ -1046,7 +1046,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> clap::App<'static, 'static> { - clap::App::new(executable!()) + clap::App::new(util_name!()) .version(crate_version!()) .about(ABOUT) .arg( From 303908352109b549e860aa93313055bf457f7ec1 Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Sat, 14 Aug 2021 14:19:05 +0200 Subject: [PATCH 025/206] refactor/uucore ~ mark executable!() as deprecated Make sure that utilities do not use it anymore. It is only allowed as an implementation detail of util_name!() and execution_phrase!() --- src/uucore/src/lib/macros.rs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/uucore/src/lib/macros.rs b/src/uucore/src/lib/macros.rs index 72826be5f..3d389d83d 100644 --- a/src/uucore/src/lib/macros.rs +++ b/src/uucore/src/lib/macros.rs @@ -20,6 +20,7 @@ macro_rules! executable_os( /// Get the executable path (as `String`). #[macro_export] +#[deprecated = "Use util_name!() or execution_phrase!() instead"] macro_rules! executable( () => ({ $crate::executable_os!().to_string_lossy().to_string() @@ -33,7 +34,10 @@ macro_rules! util_name( if $crate::get_utility_is_second_arg() { $crate::args_os().nth(1).unwrap().to_string_lossy().to_string() } else { - $crate::executable!() + #[allow(deprecated)] + { + $crate::executable!() + } } }) ); @@ -51,7 +55,10 @@ macro_rules! execution_phrase( .collect::>() .join(" ") } else { - $crate::executable!() + #[allow(deprecated)] + { + $crate::executable!() + } } }) ); From 6ab3d27c4e0ef105a6007c926175f8dbdc968507 Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Sat, 14 Aug 2021 14:48:38 +0200 Subject: [PATCH 026/206] fix ~ remove redundant clone() util_name!() and execution_phrare!() now return a String directly --- src/uu/du/src/du.rs | 2 +- src/uu/logname/src/logname.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/uu/du/src/du.rs b/src/uu/du/src/du.rs index 2c430d693..629247f29 100644 --- a/src/uu/du/src/du.rs +++ b/src/uu/du/src/du.rs @@ -466,7 +466,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let options = Options { all: matches.is_present(options::ALL), - util_name: util_name!().to_string(), + util_name: util_name!(), max_depth, total: matches.is_present(options::TOTAL), separate_dirs: matches.is_present(options::SEPARATE_DIRS), diff --git a/src/uu/logname/src/logname.rs b/src/uu/logname/src/logname.rs index e9952ddef..1a69543c1 100644 --- a/src/uu/logname/src/logname.rs +++ b/src/uu/logname/src/logname.rs @@ -36,7 +36,7 @@ fn get_userlogin() -> Option { static SUMMARY: &str = "Print user's login name"; fn usage() -> String { - execution_phrase!().to_string() + execution_phrase!() } pub fn uumain(args: impl uucore::Args) -> i32 { From 5f2335829a846b964829579de7a424116a57e550 Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Sat, 14 Aug 2021 17:21:18 +0200 Subject: [PATCH 027/206] refactor ~ revert to single quotes for "Try '{0 --help'" This is a test expectation for gnu. --- src/uu/basename/src/basename.rs | 4 ++-- src/uu/chroot/src/chroot.rs | 2 +- src/uu/cp/src/cp.rs | 2 +- src/uu/du/src/du.rs | 2 +- src/uu/ln/src/ln.rs | 2 +- src/uu/mknod/src/mknod.rs | 4 ++-- src/uu/mv/src/mv.rs | 2 +- src/uu/nice/src/nice.rs | 2 +- src/uu/pathchk/src/pathchk.rs | 2 +- src/uu/printf/src/printf.rs | 2 +- src/uu/readlink/src/readlink.rs | 2 +- src/uu/seq/src/seq.rs | 6 +++--- src/uu/stdbuf/src/stdbuf.rs | 2 +- src/uu/tr/src/tr.rs | 4 ++-- src/uu/unlink/src/unlink.rs | 4 ++-- src/uucore/src/lib/macros.rs | 2 +- src/uucore_procs/src/lib.rs | 2 +- tests/by-util/test_basename.rs | 4 ++-- tests/by-util/test_cp.rs | 2 +- tests/by-util/test_more.rs | 2 +- tests/by-util/test_mv.rs | 2 +- tests/by-util/test_nice.rs | 2 +- tests/by-util/test_seq.rs | 4 ++-- tests/by-util/test_stdbuf.rs | 2 +- tests/by-util/test_unlink.rs | 2 +- 25 files changed, 33 insertions(+), 33 deletions(-) diff --git a/src/uu/basename/src/basename.rs b/src/uu/basename/src/basename.rs index 8de55af80..d7aa52289 100644 --- a/src/uu/basename/src/basename.rs +++ b/src/uu/basename/src/basename.rs @@ -46,7 +46,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { if !matches.is_present(options::NAME) { crash!( 1, - "{1}\nTry `{0} --help` for more information.", + "{1}\nTry '{0} --help' for more information.", execution_phrase!(), "missing operand" ); @@ -60,7 +60,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { if !multiple_paths && matches.occurrences_of(options::NAME) > 2 { crash!( 1, - "extra operand '{1}'\nTry `{0} --help` for more information.", + "extra operand '{1}'\nTry '{0} --help' for more information.", execution_phrase!(), matches.values_of(options::NAME).unwrap().nth(2).unwrap() ); diff --git a/src/uu/chroot/src/chroot.rs b/src/uu/chroot/src/chroot.rs index b622b51a1..71a12b052 100644 --- a/src/uu/chroot/src/chroot.rs +++ b/src/uu/chroot/src/chroot.rs @@ -45,7 +45,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { Some(v) => Path::new(v), None => crash!( 1, - "Missing operand: NEWROOT\nTry `{} --help` for more information.", + "Missing operand: NEWROOT\nTry '{} --help' for more information.", execution_phrase!() ), }; diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index 086b734e8..a3b3db9a5 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -99,7 +99,7 @@ quick_error! { NotImplemented(opt: String) { display("Option '{}' not yet implemented.", opt) } /// Invalid arguments to backup - Backup(description: String) { display("{}\nTry `{} --help` for more information.", description, execution_phrase!()) } + Backup(description: String) { display("{}\nTry '{} --help' for more information.", description, execution_phrase!()) } } } diff --git a/src/uu/du/src/du.rs b/src/uu/du/src/du.rs index 629247f29..52750cb95 100644 --- a/src/uu/du/src/du.rs +++ b/src/uu/du/src/du.rs @@ -422,7 +422,7 @@ Valid arguments are: - 'full-iso' - 'long-iso' - 'iso' -Try `{} --help` for more information.", +Try '{} --help' for more information.", s, execution_phrase!() ), diff --git a/src/uu/ln/src/ln.rs b/src/uu/ln/src/ln.rs index 62748ae78..817aa91cb 100644 --- a/src/uu/ln/src/ln.rs +++ b/src/uu/ln/src/ln.rs @@ -68,7 +68,7 @@ impl Display for LnError { } Self::ExtraOperand(s) => write!( f, - "extra operand '{}'\nTry `{} --help` for more information.", + "extra operand '{}'\nTry '{} --help' for more information.", s, execution_phrase!() ), diff --git a/src/uu/mknod/src/mknod.rs b/src/uu/mknod/src/mknod.rs index 19c61bcfe..1a52de14f 100644 --- a/src/uu/mknod/src/mknod.rs +++ b/src/uu/mknod/src/mknod.rs @@ -113,7 +113,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { if ch == 'p' { if matches.is_present("major") || matches.is_present("minor") { eprintln!("Fifos do not have major and minor device numbers."); - eprintln!("Try `{} --help` for more information.", execution_phrase!()); + eprintln!("Try '{} --help' for more information.", execution_phrase!()); 1 } else { _mknod(file_name, S_IFIFO | mode, 0) @@ -122,7 +122,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { match (matches.value_of("major"), matches.value_of("minor")) { (None, None) | (_, None) | (None, _) => { eprintln!("Special files require major and minor device numbers."); - eprintln!("Try `{} --help` for more information.", execution_phrase!()); + eprintln!("Try '{} --help' for more information.", execution_phrase!()); 1 } (Some(major), Some(minor)) => { diff --git a/src/uu/mv/src/mv.rs b/src/uu/mv/src/mv.rs index ecd57ee5a..1cf45c36a 100644 --- a/src/uu/mv/src/mv.rs +++ b/src/uu/mv/src/mv.rs @@ -294,7 +294,7 @@ fn exec(files: &[PathBuf], b: Behavior) -> i32 { if b.no_target_dir { show_error!( "mv: extra operand '{}'\n\ - Try `{} --help` for more information.", + Try '{} --help' for more information.", files[2].display(), execution_phrase!() ); diff --git a/src/uu/nice/src/nice.rs b/src/uu/nice/src/nice.rs index 835034b74..a93137c10 100644 --- a/src/uu/nice/src/nice.rs +++ b/src/uu/nice/src/nice.rs @@ -53,7 +53,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { Some(nstr) => { if !matches.is_present(options::COMMAND) { show_error!( - "A command must be given with an adjustment.\nTry `{} --help` for more information.", + "A command must be given with an adjustment.\nTry '{} --help' for more information.", execution_phrase!() ); return 125; diff --git a/src/uu/pathchk/src/pathchk.rs b/src/uu/pathchk/src/pathchk.rs index 65c917ac0..c1ea76bf9 100644 --- a/src/uu/pathchk/src/pathchk.rs +++ b/src/uu/pathchk/src/pathchk.rs @@ -69,7 +69,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let paths = matches.values_of(options::PATH); let mut res = if paths.is_none() { show_error!( - "missing operand\nTry `{} --help` for more information", + "missing operand\nTry '{} --help' for more information", execution_phrase!() ); false diff --git a/src/uu/printf/src/printf.rs b/src/uu/printf/src/printf.rs index 26a1b29b2..22e02ffe2 100644 --- a/src/uu/printf/src/printf.rs +++ b/src/uu/printf/src/printf.rs @@ -283,7 +283,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { if args.len() <= 1 { println!( - "{0}: missing operand\nTry `{1} --help` for more information.", + "{0}: missing operand\nTry '{1} --help' for more information.", util_name!(), execution_phrase!() ); diff --git a/src/uu/readlink/src/readlink.rs b/src/uu/readlink/src/readlink.rs index cf788c1fe..81d8376f6 100644 --- a/src/uu/readlink/src/readlink.rs +++ b/src/uu/readlink/src/readlink.rs @@ -58,7 +58,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { if files.is_empty() { crash!( 1, - "missing operand\nTry `{} --help` for more information", + "missing operand\nTry '{} --help' for more information", execution_phrase!() ); } diff --git a/src/uu/seq/src/seq.rs b/src/uu/seq/src/seq.rs index f3e57881e..db5537b8b 100644 --- a/src/uu/seq/src/seq.rs +++ b/src/uu/seq/src/seq.rs @@ -70,13 +70,13 @@ impl FromStr for Number { Ok(n) => Ok(Number::BigInt(n)), Err(_) => match s.parse::() { Ok(value) if value.is_nan() => Err(format!( - "invalid 'not-a-number' argument: '{}'\nTry `{} --help` for more information.", + "invalid 'not-a-number' argument: '{}'\nTry '{} --help' for more information.", s, execution_phrase!(), )), Ok(value) => Ok(Number::F64(value)), Err(_) => Err(format!( - "invalid floating point argument: '{}'\nTry `{} --help` for more information.", + "invalid floating point argument: '{}'\nTry '{} --help' for more information.", s, execution_phrase!(), )), @@ -121,7 +121,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { }; if increment.is_zero() { show_error!( - "invalid Zero increment value: '{}'\nTry `{} --help` for more information.", + "invalid Zero increment value: '{}'\nTry '{} --help' for more information.", numbers[1], execution_phrase!() ); diff --git a/src/uu/stdbuf/src/stdbuf.rs b/src/uu/stdbuf/src/stdbuf.rs index 87e478b73..a16a40ce5 100644 --- a/src/uu/stdbuf/src/stdbuf.rs +++ b/src/uu/stdbuf/src/stdbuf.rs @@ -159,7 +159,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let options = ProgramOptions::try_from(&matches).unwrap_or_else(|e| { crash!( 125, - "{}\nTry `{} --help` for more information.", + "{}\nTry '{} --help' for more information.", e.0, execution_phrase!() ) diff --git a/src/uu/tr/src/tr.rs b/src/uu/tr/src/tr.rs index 0a277d663..7b6fbd6f2 100644 --- a/src/uu/tr/src/tr.rs +++ b/src/uu/tr/src/tr.rs @@ -263,7 +263,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { if sets.is_empty() { show_error!( - "missing operand\nTry `{} --help` for more information.", + "missing operand\nTry '{} --help' for more information.", execution_phrase!() ); return 1; @@ -271,7 +271,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { if !(delete_flag || squeeze_flag) && sets.len() < 2 { show_error!( - "missing operand after '{}'\nTry `{} --help` for more information.", + "missing operand after '{}'\nTry '{} --help' for more information.", sets[0], execution_phrase!() ); diff --git a/src/uu/unlink/src/unlink.rs b/src/uu/unlink/src/unlink.rs index 182729fd4..e0a743c1c 100644 --- a/src/uu/unlink/src/unlink.rs +++ b/src/uu/unlink/src/unlink.rs @@ -43,13 +43,13 @@ pub fn uumain(args: impl uucore::Args) -> i32 { if paths.is_empty() { crash!( 1, - "missing operand\nTry `{0} --help` for more information.", + "missing operand\nTry '{0} --help' for more information.", execution_phrase!() ); } else if paths.len() > 1 { crash!( 1, - "extra operand: '{1}'\nTry `{0} --help` for more information.", + "extra operand: '{1}'\nTry '{0} --help' for more information.", execution_phrase!(), paths[1] ); diff --git a/src/uucore/src/lib/macros.rs b/src/uucore/src/lib/macros.rs index 3d389d83d..c9fff455a 100644 --- a/src/uucore/src/lib/macros.rs +++ b/src/uucore/src/lib/macros.rs @@ -115,7 +115,7 @@ macro_rules! show_usage_error( ($($args:tt)+) => ({ eprint!("{}: ", $crate::util_name!()); eprintln!($($args)+); - eprintln!("Try `{} --help` for more information.", $crate::execution_phrase!()); + eprintln!("Try '{} --help' for more information.", $crate::execution_phrase!()); }) ); diff --git a/src/uucore_procs/src/lib.rs b/src/uucore_procs/src/lib.rs index 74ac60220..7f7460283 100644 --- a/src/uucore_procs/src/lib.rs +++ b/src/uucore_procs/src/lib.rs @@ -104,7 +104,7 @@ pub fn gen_uumain(_args: TokenStream, stream: TokenStream) -> TokenStream { show_error!("{}", s); } if e.usage() { - eprintln!("Try `{} --help` for more information.", execution_phrase!()); + eprintln!("Try '{} --help' for more information.", execution_phrase!()); } e.code() } diff --git a/tests/by-util/test_basename.rs b/tests/by-util/test_basename.rs index 9701e9973..02dfebeda 100644 --- a/tests/by-util/test_basename.rs +++ b/tests/by-util/test_basename.rs @@ -116,7 +116,7 @@ fn test_no_args() { fn test_no_args_output() { let ts = TestScenario::new(util_name!()); ts.ucmd().fails().stderr_is(&format!( - "{0}: missing operand\nTry `{1} {0} --help` for more information.", + "{0}: missing operand\nTry '{1} {0} --help' for more information.", ts.util_name, ts.bin_path.to_string_lossy() )); @@ -131,7 +131,7 @@ fn test_too_many_args() { fn test_too_many_args_output() { let ts = TestScenario::new(util_name!()); ts.ucmd().args(&["a", "b", "c"]).fails().stderr_is(format!( - "{0}: extra operand 'c'\nTry `{1} {0} --help` for more information.", + "{0}: extra operand 'c'\nTry '{1} {0} --help' for more information.", ts.util_name, ts.bin_path.to_string_lossy() )); diff --git a/tests/by-util/test_cp.rs b/tests/by-util/test_cp.rs index 6d739f448..b6b4c3311 100644 --- a/tests/by-util/test_cp.rs +++ b/tests/by-util/test_cp.rs @@ -568,7 +568,7 @@ fn test_cp_backup_no_clobber_conflicting_options() { .arg(TEST_HELLO_WORLD_SOURCE) .arg(TEST_HOW_ARE_YOU_SOURCE) .fails().stderr_is(&format!( - "{0}: options --backup and --no-clobber are mutually exclusive\nTry `{1} {0} --help` for more information.", + "{0}: options --backup and --no-clobber are mutually exclusive\nTry '{1} {0} --help' for more information.", ts.util_name, ts.bin_path.to_string_lossy() )); diff --git a/tests/by-util/test_more.rs b/tests/by-util/test_more.rs index a422b23a5..4b2719d8f 100644 --- a/tests/by-util/test_more.rs +++ b/tests/by-util/test_more.rs @@ -19,7 +19,7 @@ fn test_more_dir_arg() { let result = ts.ucmd().arg(".").run(); result.failure(); let expected_error_message = &format!( - "{0}: '.' is a directory.\nTry `{1} {0} --help` for more information.", + "{0}: '.' is a directory.\nTry '{1} {0} --help' for more information.", ts.util_name, ts.bin_path.to_string_lossy() ); diff --git a/tests/by-util/test_mv.rs b/tests/by-util/test_mv.rs index 8596f8477..8d9b00664 100644 --- a/tests/by-util/test_mv.rs +++ b/tests/by-util/test_mv.rs @@ -529,7 +529,7 @@ fn test_mv_backup_no_clobber_conflicting_options() { .arg("file1") .arg("file2") .fails() - .stderr_is(&format!("{0}: options --backup and --no-clobber are mutually exclusive\nTry `{1} {0} --help` for more information.", + .stderr_is(&format!("{0}: options --backup and --no-clobber are mutually exclusive\nTry '{1} {0} --help' for more information.", ts.util_name, ts.bin_path.to_string_lossy() )); diff --git a/tests/by-util/test_nice.rs b/tests/by-util/test_nice.rs index 639fa5697..7a99a333d 100644 --- a/tests/by-util/test_nice.rs +++ b/tests/by-util/test_nice.rs @@ -27,7 +27,7 @@ fn test_adjustment_with_no_command_should_error() { ts.ucmd() .args(&["-n", "19"]) .run() - .stderr_is(&format!("{0}: A command must be given with an adjustment.\nTry `{1} {0} --help` for more information.\n", + .stderr_is(&format!("{0}: A command must be given with an adjustment.\nTry '{1} {0} --help' for more information.\n", ts.util_name, ts.bin_path.to_string_lossy() )); diff --git a/tests/by-util/test_seq.rs b/tests/by-util/test_seq.rs index cc94d4b1f..eab7e16ce 100644 --- a/tests/by-util/test_seq.rs +++ b/tests/by-util/test_seq.rs @@ -5,7 +5,7 @@ fn test_rejects_nan() { let ts = TestScenario::new(util_name!()); ts.ucmd().args(&["NaN"]).fails().stderr_only(format!( - "{0}: invalid 'not-a-number' argument: 'NaN'\nTry `{1} {0} --help` for more information.", + "{0}: invalid 'not-a-number' argument: 'NaN'\nTry '{1} {0} --help' for more information.", ts.util_name, ts.bin_path.to_string_lossy() )); @@ -16,7 +16,7 @@ fn test_rejects_non_floats() { let ts = TestScenario::new(util_name!()); ts.ucmd().args(&["foo"]).fails().stderr_only(&format!( - "{0}: invalid floating point argument: 'foo'\nTry `{1} {0} --help` for more information.", + "{0}: invalid floating point argument: 'foo'\nTry '{1} {0} --help' for more information.", ts.util_name, ts.bin_path.to_string_lossy() )); diff --git a/tests/by-util/test_stdbuf.rs b/tests/by-util/test_stdbuf.rs index d705c0854..c05b65d70 100644 --- a/tests/by-util/test_stdbuf.rs +++ b/tests/by-util/test_stdbuf.rs @@ -59,7 +59,7 @@ fn test_stdbuf_line_buffering_stdin_fails() { .args(&["-i", "L", "head"]) .fails() .stderr_is(&format!( - "{0}: line buffering stdin is meaningless\nTry `{1} {0} --help` for more information.", + "{0}: line buffering stdin is meaningless\nTry '{1} {0} --help' for more information.", ts.util_name, ts.bin_path.to_string_lossy() )); diff --git a/tests/by-util/test_unlink.rs b/tests/by-util/test_unlink.rs index 0c82b6a39..36c978734 100644 --- a/tests/by-util/test_unlink.rs +++ b/tests/by-util/test_unlink.rs @@ -24,7 +24,7 @@ fn test_unlink_multiple_files() { at.touch(file_b); ucmd.arg(file_a).arg(file_b).fails().stderr_is(&format!( - "{0}: extra operand: 'test_unlink_multiple_file_b'\nTry `{1} {0} --help` for more information.", + "{0}: extra operand: 'test_unlink_multiple_file_b'\nTry '{1} {0} --help' for more information.", ts.util_name, ts.bin_path.to_string_lossy() )); From 252220e9eb5b97719f74e1ed163d88eaa20f8463 Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Sat, 14 Aug 2021 17:55:14 +0200 Subject: [PATCH 028/206] refactor/uucore ~ make util_name and execution_phrase functions Since util_name and execution_phrase no longer rely on features that are only available to macros, they may as well be plain functions. --- src/uu/arch/src/arch.rs | 2 +- src/uu/base32/src/base32.rs | 6 +-- src/uu/base64/src/base64.rs | 4 +- src/uu/basename/src/basename.rs | 8 ++-- src/uu/basenc/src/basenc.rs | 6 +-- src/uu/cat/src/cat.rs | 4 +- src/uu/chcon/src/chcon.rs | 8 ++-- src/uu/chgrp/src/chgrp.rs | 4 +- src/uu/chmod/src/chmod.rs | 4 +- src/uu/chown/src/chown.rs | 4 +- src/uu/chroot/src/chroot.rs | 6 +-- src/uu/cksum/src/cksum.rs | 2 +- src/uu/comm/src/comm.rs | 7 +-- src/uu/cp/src/cp.rs | 8 ++-- src/uu/csplit/src/csplit.rs | 7 ++- src/uu/cut/src/cut.rs | 2 +- src/uu/date/src/date.rs | 5 +-- src/uu/dd/src/dd.rs | 4 +- src/uu/df/src/df.rs | 6 +-- src/uu/dircolors/src/dircolors.rs | 4 +- src/uu/dirname/src/dirname.rs | 4 +- src/uu/du/src/du.rs | 8 ++-- src/uu/echo/src/echo.rs | 2 +- src/uu/expand/src/expand.rs | 4 +- src/uu/expr/src/expr.rs | 4 +- src/uu/factor/src/cli.rs | 2 +- src/uu/false/src/false.rs | 3 +- src/uu/fmt/src/fmt.rs | 4 +- src/uu/fold/src/fold.rs | 2 +- src/uu/groups/src/groups.rs | 4 +- src/uu/hashsum/src/hashsum.rs | 2 +- src/uu/head/src/head.rs | 4 +- src/uu/hostid/src/hostid.rs | 2 +- src/uu/hostname/src/hostname.rs | 4 +- src/uu/id/src/id.rs | 4 +- src/uu/install/src/install.rs | 16 ++++--- src/uu/kill/src/kill.rs | 4 +- src/uu/link/src/link.rs | 4 +- src/uu/ln/src/ln.rs | 8 ++-- src/uu/logname/src/logname.rs | 4 +- src/uu/ls/src/ls.rs | 4 +- src/uu/mkdir/src/mkdir.rs | 10 +++-- src/uu/mkfifo/src/mkfifo.rs | 2 +- src/uu/mknod/src/mknod.rs | 16 ++++--- src/uu/mktemp/src/mktemp.rs | 4 +- src/uu/more/src/more.rs | 2 +- src/uu/mv/src/mv.rs | 8 ++-- src/uu/nice/src/nice.rs | 6 +-- src/uu/nl/src/nl.rs | 2 +- src/uu/nohup/src/nohup.rs | 7 ++- src/uu/nproc/src/nproc.rs | 4 +- src/uu/numfmt/src/numfmt.rs | 4 +- src/uu/od/src/multifilereader.rs | 4 +- src/uu/od/src/od.rs | 2 +- src/uu/paste/src/paste.rs | 2 +- src/uu/pathchk/src/pathchk.rs | 6 +-- src/uu/pinky/src/pinky.rs | 4 +- src/uu/pr/src/pr.rs | 3 +- src/uu/printenv/src/printenv.rs | 7 +-- src/uu/printf/src/printf.rs | 11 ++--- src/uu/ptx/src/ptx.rs | 2 +- src/uu/pwd/src/pwd.rs | 4 +- src/uu/readlink/src/readlink.rs | 12 +++--- src/uu/realpath/src/realpath.rs | 4 +- src/uu/relpath/src/relpath.rs | 7 +-- src/uu/rm/src/rm.rs | 6 +-- src/uu/rmdir/src/rmdir.rs | 4 +- src/uu/seq/src/seq.rs | 10 ++--- src/uu/shred/src/shred.rs | 4 +- src/uu/shuf/src/shuf.rs | 2 +- src/uu/sleep/src/sleep.rs | 4 +- src/uu/sort/src/sort.rs | 4 +- src/uu/split/src/split.rs | 7 ++- src/uu/stat/src/stat.rs | 4 +- src/uu/stdbuf/src/stdbuf.rs | 6 +-- src/uu/sum/src/sum.rs | 2 +- src/uu/sync/src/sync.rs | 4 +- src/uu/tac/src/tac.rs | 2 +- src/uu/tail/src/tail.rs | 2 +- src/uu/tee/src/tee.rs | 4 +- src/uu/test/src/test.rs | 3 +- src/uu/timeout/src/timeout.rs | 5 ++- src/uu/touch/src/touch.rs | 4 +- src/uu/tr/src/tr.rs | 8 ++-- src/uu/true/src/true.rs | 3 +- src/uu/truncate/src/truncate.rs | 4 +- src/uu/tsort/src/tsort.rs | 2 +- src/uu/tty/src/tty.rs | 7 +-- src/uu/uname/src/uname.rs | 4 +- src/uu/unexpand/src/unexpand.rs | 2 +- src/uu/uniq/src/uniq.rs | 7 ++- src/uu/unlink/src/unlink.rs | 8 ++-- src/uu/uptime/src/uptime.rs | 4 +- src/uu/users/src/users.rs | 7 +-- src/uu/wc/src/wc.rs | 4 +- src/uu/who/src/who.rs | 7 ++- src/uucore/src/lib/lib.rs | 32 ++++++++++++++ src/uucore/src/lib/macros.rs | 67 +++-------------------------- src/uucore/src/lib/mods/coreopts.rs | 4 +- src/uucore_procs/src/lib.rs | 2 +- 100 files changed, 279 insertions(+), 297 deletions(-) diff --git a/src/uu/arch/src/arch.rs b/src/uu/arch/src/arch.rs index 30adbaad9..478fef6f1 100644 --- a/src/uu/arch/src/arch.rs +++ b/src/uu/arch/src/arch.rs @@ -27,7 +27,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } pub fn uu_app() -> App<'static, 'static> { - App::new(util_name!()) + App::new(uucore::util_name()) .version(crate_version!()) .about(ABOUT) .after_help(SUMMARY) diff --git a/src/uu/base32/src/base32.rs b/src/uu/base32/src/base32.rs index 4d6c634e5..ac9ed1075 100644 --- a/src/uu/base32/src/base32.rs +++ b/src/uu/base32/src/base32.rs @@ -29,13 +29,13 @@ static VERSION: &str = env!("CARGO_PKG_VERSION"); static BASE_CMD_PARSE_ERROR: i32 = 1; fn usage() -> String { - format!("{0} [OPTION]... [FILE]", execution_phrase!()) + format!("{0} [OPTION]... [FILE]", uucore::execution_phrase()) } pub fn uumain(args: impl uucore::Args) -> i32 { let format = Format::Base32; let usage = usage(); - let name = util_name!(); + let name = uucore::util_name(); let config_result: Result = base_common::parse_base_cmd_args(args, &name, VERSION, ABOUT, &usage); @@ -59,5 +59,5 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> App<'static, 'static> { - base_common::base_app(&util_name!(), VERSION, ABOUT) + base_common::base_app(&uucore::util_name(), VERSION, ABOUT) } diff --git a/src/uu/base64/src/base64.rs b/src/uu/base64/src/base64.rs index 689940a07..e303f9d29 100644 --- a/src/uu/base64/src/base64.rs +++ b/src/uu/base64/src/base64.rs @@ -30,13 +30,13 @@ static VERSION: &str = env!("CARGO_PKG_VERSION"); static BASE_CMD_PARSE_ERROR: i32 = 1; fn usage() -> String { - format!("{0} [OPTION]... [FILE]", execution_phrase!()) + format!("{0} [OPTION]... [FILE]", uucore::execution_phrase()) } pub fn uumain(args: impl uucore::Args) -> i32 { let format = Format::Base64; let usage = usage(); - let name = util_name!(); + let name = uucore::util_name(); let config_result: Result = 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)); diff --git a/src/uu/basename/src/basename.rs b/src/uu/basename/src/basename.rs index d7aa52289..2d2f649d1 100644 --- a/src/uu/basename/src/basename.rs +++ b/src/uu/basename/src/basename.rs @@ -21,7 +21,7 @@ fn usage() -> String { format!( "{0} NAME [SUFFIX] {0} OPTION... NAME...", - execution_phrase!() + uucore::execution_phrase() ) } @@ -47,7 +47,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { crash!( 1, "{1}\nTry '{0} --help' for more information.", - execution_phrase!(), + uucore::execution_phrase(), "missing operand" ); } @@ -61,7 +61,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { crash!( 1, "extra operand '{1}'\nTry '{0} --help' for more information.", - execution_phrase!(), + uucore::execution_phrase(), matches.values_of(options::NAME).unwrap().nth(2).unwrap() ); } @@ -93,7 +93,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> App<'static, 'static> { - App::new(util_name!()) + App::new(uucore::util_name()) .version(crate_version!()) .about(SUMMARY) .arg( diff --git a/src/uu/basenc/src/basenc.rs b/src/uu/basenc/src/basenc.rs index ef9779512..2b3193d49 100644 --- a/src/uu/basenc/src/basenc.rs +++ b/src/uu/basenc/src/basenc.rs @@ -43,11 +43,11 @@ const ENCODINGS: &[(&str, Format)] = &[ ]; fn usage() -> String { - format!("{0} [OPTION]... [FILE]", execution_phrase!()) + format!("{0} [OPTION]... [FILE]", uucore::execution_phrase()) } pub fn uu_app() -> App<'static, 'static> { - let mut app = base_common::base_app(&util_name!(), crate_version!(), ABOUT); + let mut app = base_common::base_app(&uucore::util_name(), crate_version!(), ABOUT); for encoding in ENCODINGS { app = app.arg(Arg::with_name(encoding.0).long(encoding.0)); } @@ -75,7 +75,7 @@ fn parse_cmd_args(args: impl uucore::Args) -> (Config, Format) { } pub fn uumain(args: impl uucore::Args) -> i32 { - let name = util_name!(); + let name = uucore::util_name(); let (config, format) = parse_cmd_args(args); // Create a reference to stdin so we can return a locked stdin from // parse_base_cmd_args diff --git a/src/uu/cat/src/cat.rs b/src/uu/cat/src/cat.rs index e0c7d2226..a176b8c5d 100644 --- a/src/uu/cat/src/cat.rs +++ b/src/uu/cat/src/cat.rs @@ -234,7 +234,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } pub fn uu_app() -> App<'static, 'static> { - App::new(util_name!()) + App::new(uucore::util_name()) .name(NAME) .version(crate_version!()) .usage(SYNTAX) @@ -396,7 +396,7 @@ fn cat_files(files: Vec, options: &OutputOptions) -> UResult<()> { Ok(()) } else { // each next line is expected to display "cat: …" - let line_joiner = format!("\n{}: ", util_name!()); + let line_joiner = format!("\n{}: ", uucore::util_name()); Err(uucore::error::USimpleError::new( error_messages.len() as i32, diff --git a/src/uu/chcon/src/chcon.rs b/src/uu/chcon/src/chcon.rs index 6f45dcd92..5f6a80bde 100644 --- a/src/uu/chcon/src/chcon.rs +++ b/src/uu/chcon/src/chcon.rs @@ -2,7 +2,7 @@ #![allow(clippy::upper_case_acronyms)] -use uucore::{execution_phrase, show_error, show_usage_error, show_warning, util_name}; +use uucore::{show_error, show_usage_error, show_warning}; use clap::{App, Arg}; use selinux::{OpaqueSecurityContext, SecurityContext}; @@ -56,7 +56,7 @@ fn get_usage() -> String { "{0} [OPTION]... CONTEXT FILE... \n \ {0} [OPTION]... [-u USER] [-r ROLE] [-l RANGE] [-t TYPE] FILE... \n \ {0} [OPTION]... --reference=RFILE FILE...", - execution_phrase!() + uucore::execution_phrase() ) } @@ -152,7 +152,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> App<'static, 'static> { - App::new(util_name!()) + App::new(uucore::util_name()) .version(VERSION) .about(ABOUT) .arg( @@ -563,7 +563,7 @@ fn process_file( if options.verbose { println!( "{}: Changing security context of: {}", - util_name!(), + uucore::util_name(), file_full_name.to_string_lossy() ); } diff --git a/src/uu/chgrp/src/chgrp.rs b/src/uu/chgrp/src/chgrp.rs index 5dbcc277e..7840ba829 100644 --- a/src/uu/chgrp/src/chgrp.rs +++ b/src/uu/chgrp/src/chgrp.rs @@ -62,7 +62,7 @@ const FTS_LOGICAL: u8 = 1 << 2; fn usage() -> String { format!( "{0} [OPTION]... GROUP FILE...\n {0} [OPTION]... --reference=RFILE FILE...", - execution_phrase!() + uucore::execution_phrase() ) } @@ -197,7 +197,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> App<'static, 'static> { - App::new(util_name!()) + App::new(uucore::util_name()) .version(VERSION) .about(ABOUT) .arg( diff --git a/src/uu/chmod/src/chmod.rs b/src/uu/chmod/src/chmod.rs index 88ad22969..5108ec924 100644 --- a/src/uu/chmod/src/chmod.rs +++ b/src/uu/chmod/src/chmod.rs @@ -41,7 +41,7 @@ fn usage() -> String { "{0} [OPTION]... MODE[,MODE]... FILE... or: {0} [OPTION]... OCTAL-MODE FILE... or: {0} [OPTION]... --reference=RFILE FILE...", - execution_phrase!() + uucore::execution_phrase() ) } @@ -116,7 +116,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> App<'static, 'static> { - App::new(util_name!()) + App::new(uucore::util_name()) .version(crate_version!()) .about(ABOUT) .arg( diff --git a/src/uu/chown/src/chown.rs b/src/uu/chown/src/chown.rs index a7824f248..8813c07e2 100644 --- a/src/uu/chown/src/chown.rs +++ b/src/uu/chown/src/chown.rs @@ -64,7 +64,7 @@ const FTS_LOGICAL: u8 = 1 << 2; fn usage() -> String { format!( "{0} [OPTION]... [OWNER][:[GROUP]] FILE...\n{0} [OPTION]... --reference=RFILE FILE...", - execution_phrase!() + uucore::execution_phrase() ) } @@ -165,7 +165,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } pub fn uu_app() -> App<'static, 'static> { - App::new(util_name!()) + App::new(uucore::util_name()) .version(crate_version!()) .about(ABOUT) .arg( diff --git a/src/uu/chroot/src/chroot.rs b/src/uu/chroot/src/chroot.rs index 71a12b052..c0f392913 100644 --- a/src/uu/chroot/src/chroot.rs +++ b/src/uu/chroot/src/chroot.rs @@ -16,7 +16,7 @@ use std::io::Error; use std::path::Path; use std::process::Command; use uucore::libc::{self, chroot, setgid, setgroups, setuid}; -use uucore::{entries, execution_phrase, InvalidEncodingHandling}; +use uucore::{entries, InvalidEncodingHandling}; static ABOUT: &str = "Run COMMAND with root directory set to NEWROOT."; static SYNTAX: &str = "[OPTION]... NEWROOT [COMMAND [ARG]...]"; @@ -46,7 +46,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { None => crash!( 1, "Missing operand: NEWROOT\nTry '{} --help' for more information.", - execution_phrase!() + uucore::execution_phrase() ), }; @@ -91,7 +91,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> App<'static, 'static> { - App::new(util_name!()) + App::new(uucore::util_name()) .version(crate_version!()) .about(ABOUT) .usage(SYNTAX) diff --git a/src/uu/cksum/src/cksum.rs b/src/uu/cksum/src/cksum.rs index 8173fa451..a5beec368 100644 --- a/src/uu/cksum/src/cksum.rs +++ b/src/uu/cksum/src/cksum.rs @@ -213,7 +213,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> App<'static, 'static> { - App::new(util_name!()) + App::new(uucore::util_name()) .name(NAME) .version(crate_version!()) .about(SUMMARY) diff --git a/src/uu/comm/src/comm.rs b/src/uu/comm/src/comm.rs index 02c5598be..56af42fd9 100644 --- a/src/uu/comm/src/comm.rs +++ b/src/uu/comm/src/comm.rs @@ -7,9 +7,6 @@ // spell-checker:ignore (ToDO) delim mkdelim -#[macro_use] -extern crate uucore; - use std::cmp::Ordering; use std::fs::File; use std::io::{self, stdin, BufRead, BufReader, Stdin}; @@ -32,7 +29,7 @@ mod options { } fn usage() -> String { - format!("{} [OPTION]... FILE1 FILE2", execution_phrase!()) + format!("{} [OPTION]... FILE1 FILE2", uucore::execution_phrase()) } fn mkdelim(col: usize, opts: &ArgMatches) -> String { @@ -148,7 +145,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> App<'static, 'static> { - App::new(util_name!()) + App::new(uucore::util_name()) .version(crate_version!()) .about(ABOUT) .after_help(LONG_HELP) diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index a3b3db9a5..01d7d11be 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -99,7 +99,7 @@ quick_error! { NotImplemented(opt: String) { display("Option '{}' not yet implemented.", opt) } /// Invalid arguments to backup - Backup(description: String) { display("{}\nTry '{} --help' for more information.", description, execution_phrase!()) } + Backup(description: String) { display("{}\nTry '{} --help' for more information.", description, uucore::execution_phrase()) } } } @@ -223,7 +223,7 @@ fn usage() -> String { "{0} [OPTION]... [-T] SOURCE DEST {0} [OPTION]... SOURCE... DIRECTORY {0} [OPTION]... -t DIRECTORY SOURCE...", - execution_phrase!() + uucore::execution_phrase() ) } @@ -293,7 +293,7 @@ static DEFAULT_ATTRIBUTES: &[Attribute] = &[ ]; pub fn uu_app() -> App<'static, 'static> { - App::new(util_name!()) + App::new(uucore::util_name()) .version(crate_version!()) .about(ABOUT) .arg(Arg::with_name(options::TARGET_DIRECTORY) @@ -1060,7 +1060,7 @@ impl OverwriteMode { match *self { OverwriteMode::NoClobber => Err(Error::NotAllFilesCopied), OverwriteMode::Interactive(_) => { - if prompt_yes!("{}: overwrite {}? ", util_name!(), path.display()) { + if prompt_yes!("{}: overwrite {}? ", uucore::util_name(), path.display()) { Ok(()) } else { Err(Error::Skipped(format!( diff --git a/src/uu/csplit/src/csplit.rs b/src/uu/csplit/src/csplit.rs index c89d06cf3..9e93bbe42 100644 --- a/src/uu/csplit/src/csplit.rs +++ b/src/uu/csplit/src/csplit.rs @@ -35,7 +35,10 @@ mod options { } fn usage() -> String { - format!("{0} [OPTION]... FILE PATTERN...", execution_phrase!()) + format!( + "{0} [OPTION]... FILE PATTERN...", + uucore::execution_phrase() + ) } /// Command line options for csplit. @@ -739,7 +742,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> App<'static, 'static> { - App::new(util_name!()) + App::new(uucore::util_name()) .version(crate_version!()) .about(SUMMARY) .arg( diff --git a/src/uu/cut/src/cut.rs b/src/uu/cut/src/cut.rs index 6410b9efc..783502e3d 100644 --- a/src/uu/cut/src/cut.rs +++ b/src/uu/cut/src/cut.rs @@ -548,7 +548,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> App<'static, 'static> { - App::new(util_name!()) + App::new(uucore::util_name()) .name(NAME) .version(crate_version!()) .usage(SYNTAX) diff --git a/src/uu/date/src/date.rs b/src/uu/date/src/date.rs index 3d2a8c6d0..7bf6298c0 100644 --- a/src/uu/date/src/date.rs +++ b/src/uu/date/src/date.rs @@ -8,9 +8,6 @@ // spell-checker:ignore (chrono) Datelike Timelike ; (format) DATEFILE MMDDhhmm ; (vars) datetime datetimes -#[macro_use] -extern crate uucore; - use chrono::{DateTime, FixedOffset, Local, Offset, Utc}; #[cfg(windows)] use chrono::{Datelike, Timelike}; @@ -253,7 +250,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> App<'static, 'static> { - App::new(util_name!()) + App::new(uucore::util_name()) .version(crate_version!()) .about(ABOUT) .arg( diff --git a/src/uu/dd/src/dd.rs b/src/uu/dd/src/dd.rs index 660f62f7a..c339398e6 100644 --- a/src/uu/dd/src/dd.rs +++ b/src/uu/dd/src/dd.rs @@ -7,8 +7,6 @@ // spell-checker:ignore fname, tname, fpath, specfile, testfile, unspec, ifile, ofile, outfile, fullblock, urand, fileio, atoe, atoibm, behaviour, bmax, bremain, btotal, cflags, creat, ctable, ctty, datastructures, doesnt, etoa, fileout, fname, gnudd, iconvflags, nocache, noctty, noerror, nofollow, nolinks, nonblock, oconvflags, outfile, parseargs, rlen, rmax, rposition, rremain, rsofar, rstat, sigusr, sigval, wlen, wstat -#[macro_use] -extern crate uucore; use uucore::InvalidEncodingHandling; #[cfg(test)] @@ -1046,7 +1044,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> clap::App<'static, 'static> { - clap::App::new(util_name!()) + clap::App::new(uucore::util_name()) .version(crate_version!()) .about(ABOUT) .arg( diff --git a/src/uu/df/src/df.rs b/src/uu/df/src/df.rs index 52fae4702..e7f3944a0 100644 --- a/src/uu/df/src/df.rs +++ b/src/uu/df/src/df.rs @@ -80,7 +80,7 @@ struct Filesystem { } fn usage() -> String { - format!("{0} [OPTION]... [FILE]...", execution_phrase!()) + format!("{0} [OPTION]... [FILE]...", uucore::execution_phrase()) } impl FsSelector { @@ -295,7 +295,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { #[cfg(windows)] { if matches.is_present(OPT_INODES) { - println!("{}: doesn't support -i option", util_name!()); + println!("{}: doesn't support -i option", uucore::util_name()); return Ok(()); } } @@ -427,7 +427,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } pub fn uu_app() -> App<'static, 'static> { - App::new(util_name!()) + App::new(uucore::util_name()) .version(crate_version!()) .about(ABOUT) .arg( diff --git a/src/uu/dircolors/src/dircolors.rs b/src/uu/dircolors/src/dircolors.rs index c70fd97ee..2b9c25ba3 100644 --- a/src/uu/dircolors/src/dircolors.rs +++ b/src/uu/dircolors/src/dircolors.rs @@ -63,7 +63,7 @@ pub fn guess_syntax() -> OutputFmt { } fn usage() -> String { - format!("{0} {1}", execution_phrase!(), SYNTAX) + format!("{0} {1}", uucore::execution_phrase(), SYNTAX) } pub fn uumain(args: impl uucore::Args) -> i32 { @@ -153,7 +153,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> App<'static, 'static> { - App::new(util_name!()) + App::new(uucore::util_name()) .version(crate_version!()) .about(SUMMARY) .after_help(LONG_HELP) diff --git a/src/uu/dirname/src/dirname.rs b/src/uu/dirname/src/dirname.rs index 4600d67ac..3e91d598a 100644 --- a/src/uu/dirname/src/dirname.rs +++ b/src/uu/dirname/src/dirname.rs @@ -21,7 +21,7 @@ mod options { } fn usage() -> String { - format!("{0} [OPTION] NAME...", execution_phrase!()) + format!("{0} [OPTION] NAME...", uucore::execution_phrase()) } fn get_long_usage() -> String { @@ -86,7 +86,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } pub fn uu_app() -> App<'static, 'static> { - App::new(util_name!()) + App::new(uucore::util_name()) .about(ABOUT) .version(crate_version!()) .arg( diff --git a/src/uu/du/src/du.rs b/src/uu/du/src/du.rs index 52750cb95..258d58bae 100644 --- a/src/uu/du/src/du.rs +++ b/src/uu/du/src/du.rs @@ -396,7 +396,7 @@ fn usage() -> String { format!( "{0} [OPTION]... [FILE]... {0} [OPTION]... --files0-from=F", - execution_phrase!() + uucore::execution_phrase() ) } @@ -424,7 +424,7 @@ Valid arguments are: - 'iso' Try '{} --help' for more information.", s, - execution_phrase!() + uucore::execution_phrase() ), DuError::InvalidTimeArg(s) => write!( f, @@ -466,7 +466,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let options = Options { all: matches.is_present(options::ALL), - util_name: util_name!(), + util_name: uucore::util_name(), max_depth, total: matches.is_present(options::TOTAL), separate_dirs: matches.is_present(options::SEPARATE_DIRS), @@ -625,7 +625,7 @@ fn parse_depth(max_depth_str: Option<&str>, summarize: bool) -> UResult App<'static, 'static> { - App::new(util_name!()) + App::new(uucore::util_name()) .version(crate_version!()) .about(SUMMARY) .after_help(LONG_HELP) diff --git a/src/uu/echo/src/echo.rs b/src/uu/echo/src/echo.rs index d34460df0..601fd8d48 100644 --- a/src/uu/echo/src/echo.rs +++ b/src/uu/echo/src/echo.rs @@ -132,7 +132,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } pub fn uu_app() -> App<'static, 'static> { - App::new(util_name!()) + App::new(uucore::util_name()) .name(NAME) // TrailingVarArg specifies the final positional argument is a VarArg // and it doesn't attempts the parse any further args. diff --git a/src/uu/expand/src/expand.rs b/src/uu/expand/src/expand.rs index 0cd4b2719..04cb42649 100644 --- a/src/uu/expand/src/expand.rs +++ b/src/uu/expand/src/expand.rs @@ -33,7 +33,7 @@ static LONG_HELP: &str = ""; static DEFAULT_TABSTOP: usize = 8; fn usage() -> String { - format!("{0} [OPTION]... [FILE]...", execution_phrase!()) + format!("{0} [OPTION]... [FILE]...", uucore::execution_phrase()) } /// The mode to use when replacing tabs beyond the last one specified in @@ -178,7 +178,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> App<'static, 'static> { - App::new(util_name!()) + App::new(uucore::util_name()) .version(crate_version!()) .about(ABOUT) .after_help(LONG_HELP) diff --git a/src/uu/expr/src/expr.rs b/src/uu/expr/src/expr.rs index 0a9d76f67..2d82300ff 100644 --- a/src/uu/expr/src/expr.rs +++ b/src/uu/expr/src/expr.rs @@ -18,7 +18,7 @@ const VERSION: &str = "version"; const HELP: &str = "help"; pub fn uu_app() -> App<'static, 'static> { - App::new(util_name!()) + App::new(uucore::util_name()) .arg(Arg::with_name(VERSION).long(VERSION)) .arg(Arg::with_name(HELP).long(HELP)) } @@ -140,5 +140,5 @@ Environment variables: } fn print_version() { - println!("{} {}", util_name!(), crate_version!()); + println!("{} {}", uucore::util_name(), crate_version!()); } diff --git a/src/uu/factor/src/cli.rs b/src/uu/factor/src/cli.rs index 628ded969..95b8bb866 100644 --- a/src/uu/factor/src/cli.rs +++ b/src/uu/factor/src/cli.rs @@ -75,7 +75,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> App<'static, 'static> { - App::new(util_name!()) + App::new(uucore::util_name()) .version(crate_version!()) .about(SUMMARY) .arg(Arg::with_name(options::NUMBER).multiple(true)) diff --git a/src/uu/false/src/false.rs b/src/uu/false/src/false.rs index 8f7710912..88ec1af06 100644 --- a/src/uu/false/src/false.rs +++ b/src/uu/false/src/false.rs @@ -10,7 +10,6 @@ extern crate uucore; use clap::App; use uucore::error::UResult; -use uucore::util_name; #[uucore_procs::gen_uumain] pub fn uumain(args: impl uucore::Args) -> UResult<()> { @@ -19,5 +18,5 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } pub fn uu_app() -> App<'static, 'static> { - App::new(util_name!()) + App::new(uucore::util_name()) } diff --git a/src/uu/fmt/src/fmt.rs b/src/uu/fmt/src/fmt.rs index d42151503..5203745a1 100644 --- a/src/uu/fmt/src/fmt.rs +++ b/src/uu/fmt/src/fmt.rs @@ -51,7 +51,7 @@ static OPT_TAB_WIDTH: &str = "tab-width"; static ARG_FILES: &str = "files"; fn usage() -> String { - format!("{} [OPTION]... [FILE]...", execution_phrase!()) + format!("{} [OPTION]... [FILE]...", uucore::execution_phrase()) } pub type FileOrStdReader = BufReader>; @@ -211,7 +211,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> App<'static, 'static> { - App::new(util_name!()) + App::new(uucore::util_name()) .version(crate_version!()) .about(ABOUT) .arg( diff --git a/src/uu/fold/src/fold.rs b/src/uu/fold/src/fold.rs index a2321d5ee..c5628125d 100644 --- a/src/uu/fold/src/fold.rs +++ b/src/uu/fold/src/fold.rs @@ -64,7 +64,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> App<'static, 'static> { - App::new(util_name!()) + App::new(uucore::util_name()) .name(NAME) .version(crate_version!()) .usage(SYNTAX) diff --git a/src/uu/groups/src/groups.rs b/src/uu/groups/src/groups.rs index 83be0932b..c2af5c4b0 100644 --- a/src/uu/groups/src/groups.rs +++ b/src/uu/groups/src/groups.rs @@ -29,7 +29,7 @@ static ABOUT: &str = "Print group memberships for each USERNAME or, \ (which may differ if the groups data‐base has changed)."; fn usage() -> String { - format!("{0} [OPTION]... [USERNAME]...", execution_phrase!()) + format!("{0} [OPTION]... [USERNAME]...", uucore::execution_phrase()) } pub fn uumain(args: impl uucore::Args) -> i32 { @@ -85,7 +85,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> App<'static, 'static> { - App::new(util_name!()) + App::new(uucore::util_name()) .version(crate_version!()) .about(ABOUT) .arg( diff --git a/src/uu/hashsum/src/hashsum.rs b/src/uu/hashsum/src/hashsum.rs index 332e08009..77cc0d558 100644 --- a/src/uu/hashsum/src/hashsum.rs +++ b/src/uu/hashsum/src/hashsum.rs @@ -342,7 +342,7 @@ pub fn uu_app_common() -> App<'static, 'static> { const TEXT_HELP: &str = "read in text mode"; #[cfg(not(windows))] const TEXT_HELP: &str = "read in text mode (default)"; - App::new(util_name!()) + App::new(uucore::util_name()) .version(crate_version!()) .about("Compute and check message digests.") .arg( diff --git a/src/uu/head/src/head.rs b/src/uu/head/src/head.rs index 11ea56449..e47488ac4 100644 --- a/src/uu/head/src/head.rs +++ b/src/uu/head/src/head.rs @@ -9,7 +9,7 @@ use clap::{crate_version, App, Arg}; use std::convert::TryFrom; use std::ffi::OsString; use std::io::{self, ErrorKind, Read, Seek, SeekFrom, Write}; -use uucore::{crash, show_error_custom_description, util_name}; +use uucore::{crash, show_error_custom_description}; const EXIT_FAILURE: i32 = 1; const EXIT_SUCCESS: i32 = 0; @@ -41,7 +41,7 @@ use lines::zlines; use take::take_all_but; pub fn uu_app() -> App<'static, 'static> { - App::new(util_name!()) + App::new(uucore::util_name()) .version(crate_version!()) .about(ABOUT) .usage(USAGE) diff --git a/src/uu/hostid/src/hostid.rs b/src/uu/hostid/src/hostid.rs index e593ff7ee..4c9cafa35 100644 --- a/src/uu/hostid/src/hostid.rs +++ b/src/uu/hostid/src/hostid.rs @@ -29,7 +29,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } pub fn uu_app() -> App<'static, 'static> { - App::new(util_name!()) + App::new(uucore::util_name()) .version(crate_version!()) .usage(SYNTAX) } diff --git a/src/uu/hostname/src/hostname.rs b/src/uu/hostname/src/hostname.rs index 2dc68abfb..8852e0f43 100644 --- a/src/uu/hostname/src/hostname.rs +++ b/src/uu/hostname/src/hostname.rs @@ -54,7 +54,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } fn usage() -> String { - format!("{0} [OPTION]... [HOSTNAME]", execution_phrase!()) + format!("{0} [OPTION]... [HOSTNAME]", uucore::execution_phrase()) } fn execute(args: impl uucore::Args) -> UResult<()> { @@ -74,7 +74,7 @@ fn execute(args: impl uucore::Args) -> UResult<()> { } pub fn uu_app() -> App<'static, 'static> { - App::new(util_name!()) + App::new(uucore::util_name()) .version(crate_version!()) .about(ABOUT) .arg( diff --git a/src/uu/id/src/id.rs b/src/uu/id/src/id.rs index 74523213e..4e8e30e52 100644 --- a/src/uu/id/src/id.rs +++ b/src/uu/id/src/id.rs @@ -77,7 +77,7 @@ mod options { } fn usage() -> String { - format!("{0} [OPTION]... [USER]...", execution_phrase!()) + format!("{0} [OPTION]... [USER]...", uucore::execution_phrase()) } fn get_description() -> String { @@ -347,7 +347,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } pub fn uu_app() -> App<'static, 'static> { - App::new(util_name!()) + App::new(uucore::util_name()) .version(crate_version!()) .about(ABOUT) .arg( diff --git a/src/uu/install/src/install.rs b/src/uu/install/src/install.rs index a1c6d4225..5c951ad5b 100644 --- a/src/uu/install/src/install.rs +++ b/src/uu/install/src/install.rs @@ -86,11 +86,13 @@ impl Display for InstallError { use InstallError as IE; match self { IE::Unimplemented(opt) => write!(f, "Unimplemented feature: {}", opt), - IE::DirNeedsArg() => write!( - f, - "{} with -d requires at least one argument.", - util_name!() - ), + IE::DirNeedsArg() => { + write!( + f, + "{} with -d requires at least one argument.", + uucore::util_name() + ) + } IE::CreateDirFailed(dir, e) => { Display::fmt(&uio_error!(e, "failed to create {}", dir.display()), f) } @@ -173,7 +175,7 @@ static OPT_CONTEXT: &str = "context"; static ARG_FILES: &str = "files"; fn usage() -> String { - format!("{0} [OPTION]... [FILE]...", execution_phrase!()) + format!("{0} [OPTION]... [FILE]...", uucore::execution_phrase()) } /// Main install utility function, called from main.rs. @@ -202,7 +204,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } pub fn uu_app() -> App<'static, 'static> { - App::new(util_name!()) + App::new(uucore::util_name()) .version(crate_version!()) .about(ABOUT) .arg( diff --git a/src/uu/kill/src/kill.rs b/src/uu/kill/src/kill.rs index 08dff87d4..47bd97dbc 100644 --- a/src/uu/kill/src/kill.rs +++ b/src/uu/kill/src/kill.rs @@ -41,7 +41,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { .accept_any(); let (args, obs_signal) = handle_obsolete(args); - let usage = format!("{} [OPTIONS]... PID...", execution_phrase!()); + let usage = format!("{} [OPTIONS]... PID...", uucore::execution_phrase()); let matches = uu_app().usage(&usage[..]).get_matches_from(args); let mode = if matches.is_present(options::TABLE) || matches.is_present(options::TABLE_OLD) { @@ -76,7 +76,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } pub fn uu_app() -> App<'static, 'static> { - App::new(util_name!()) + App::new(uucore::util_name()) .version(crate_version!()) .about(ABOUT) .arg( diff --git a/src/uu/link/src/link.rs b/src/uu/link/src/link.rs index 03dd46aee..73e81b107 100644 --- a/src/uu/link/src/link.rs +++ b/src/uu/link/src/link.rs @@ -20,7 +20,7 @@ pub mod options { } fn usage() -> String { - format!("{0} FILE1 FILE2", execution_phrase!()) + format!("{0} FILE1 FILE2", uucore::execution_phrase()) } pub fn normalize_error_message(e: Error) -> String { @@ -51,7 +51,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> App<'static, 'static> { - App::new(util_name!()) + App::new(uucore::util_name()) .version(crate_version!()) .about(ABOUT) .arg( diff --git a/src/uu/ln/src/ln.rs b/src/uu/ln/src/ln.rs index 817aa91cb..c24db2172 100644 --- a/src/uu/ln/src/ln.rs +++ b/src/uu/ln/src/ln.rs @@ -70,7 +70,7 @@ impl Display for LnError { f, "extra operand '{}'\nTry '{} --help' for more information.", s, - execution_phrase!() + uucore::execution_phrase() ), Self::InvalidBackupMode(s) => write!(f, "{}", s), } @@ -98,7 +98,7 @@ fn usage() -> String { {0} [OPTION]... TARGET (2nd form) {0} [OPTION]... TARGET... DIRECTORY (3rd form) {0} [OPTION]... -t DIRECTORY TARGET... (4th form)", - execution_phrase!() + uucore::execution_phrase() ) } @@ -196,7 +196,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } pub fn uu_app() -> App<'static, 'static> { - App::new(util_name!()) + App::new(uucore::util_name()) .version(crate_version!()) .about(ABOUT) .arg( @@ -431,7 +431,7 @@ fn link(src: &Path, dst: &Path, settings: &Settings) -> Result<()> { match settings.overwrite { OverwriteMode::NoClobber => {} OverwriteMode::Interactive => { - print!("{}: overwrite '{}'? ", util_name!(), dst.display()); + print!("{}: overwrite '{}'? ", uucore::util_name(), dst.display()); if !read_yes() { return Ok(()); } diff --git a/src/uu/logname/src/logname.rs b/src/uu/logname/src/logname.rs index 1a69543c1..f8dd3fc5d 100644 --- a/src/uu/logname/src/logname.rs +++ b/src/uu/logname/src/logname.rs @@ -36,7 +36,7 @@ fn get_userlogin() -> Option { static SUMMARY: &str = "Print user's login name"; fn usage() -> String { - execution_phrase!() + uucore::execution_phrase() } pub fn uumain(args: impl uucore::Args) -> i32 { @@ -56,7 +56,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> App<'static, 'static> { - App::new(util_name!()) + App::new(uucore::util_name()) .version(crate_version!()) .about(SUMMARY) } diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index 6ba3ec4f1..da8955152 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -47,7 +47,7 @@ use uucore::libc::{S_IXGRP, S_IXOTH, S_IXUSR}; use uucore::{fs::display_permissions, version_cmp::version_cmp}; fn usage() -> String { - format!("{0} [OPTION]... [FILE]...", execution_phrase!()) + format!("{0} [OPTION]... [FILE]...", uucore::execution_phrase()) } pub mod options { @@ -618,7 +618,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } pub fn uu_app() -> App<'static, 'static> { - App::new(util_name!()) + App::new(uucore::util_name()) .version(crate_version!()) .about( "By default, ls will list the files and contents of any directories on \ diff --git a/src/uu/mkdir/src/mkdir.rs b/src/uu/mkdir/src/mkdir.rs index 046628f3a..848f79988 100644 --- a/src/uu/mkdir/src/mkdir.rs +++ b/src/uu/mkdir/src/mkdir.rs @@ -23,7 +23,7 @@ mod options { } fn usage() -> String { - format!("{0} [OPTION]... [USER]", execution_phrase!()) + format!("{0} [OPTION]... [USER]", uucore::execution_phrase()) } #[uucore_procs::gen_uumain] @@ -51,7 +51,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } pub fn uu_app() -> App<'static, 'static> { - App::new(util_name!()) + App::new(uucore::util_name()) .version(crate_version!()) .about(ABOUT) .arg( @@ -103,7 +103,11 @@ fn mkdir(path: &Path, recursive: bool, mode: u16, verbose: bool) -> UResult<()> create_dir(path).map_err_context(|| format!("cannot create directory '{}'", path.display()))?; if verbose { - println!("{}: created directory '{}'", util_name!(), path.display()); + println!( + "{}: created directory '{}'", + uucore::util_name(), + path.display() + ); } chmod(path, mode) diff --git a/src/uu/mkfifo/src/mkfifo.rs b/src/uu/mkfifo/src/mkfifo.rs index 10ea8337c..009675811 100644 --- a/src/uu/mkfifo/src/mkfifo.rs +++ b/src/uu/mkfifo/src/mkfifo.rs @@ -70,7 +70,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> App<'static, 'static> { - App::new(util_name!()) + App::new(uucore::util_name()) .name(NAME) .version(crate_version!()) .usage(USAGE) diff --git a/src/uu/mknod/src/mknod.rs b/src/uu/mknod/src/mknod.rs index 1a52de14f..f8fb9c469 100644 --- a/src/uu/mknod/src/mknod.rs +++ b/src/uu/mknod/src/mknod.rs @@ -71,8 +71,8 @@ fn _mknod(file_name: &str, mode: mode_t, dev: dev_t) -> i32 { } if errno == -1 { - let c_str = - CString::new(execution_phrase!().as_bytes()).expect("Failed to convert to CString"); + let c_str = CString::new(uucore::execution_phrase().as_bytes()) + .expect("Failed to convert to CString"); // shows the error from the mknod syscall libc::perror(c_str.as_ptr()); } @@ -113,7 +113,10 @@ pub fn uumain(args: impl uucore::Args) -> i32 { if ch == 'p' { if matches.is_present("major") || matches.is_present("minor") { eprintln!("Fifos do not have major and minor device numbers."); - eprintln!("Try '{} --help' for more information.", execution_phrase!()); + eprintln!( + "Try '{} --help' for more information.", + uucore::execution_phrase() + ); 1 } else { _mknod(file_name, S_IFIFO | mode, 0) @@ -122,7 +125,10 @@ pub fn uumain(args: impl uucore::Args) -> i32 { match (matches.value_of("major"), matches.value_of("minor")) { (None, None) | (_, None) | (None, _) => { eprintln!("Special files require major and minor device numbers."); - eprintln!("Try '{} --help' for more information.", execution_phrase!()); + eprintln!( + "Try '{} --help' for more information.", + uucore::execution_phrase() + ); 1 } (Some(major), Some(minor)) => { @@ -145,7 +151,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> App<'static, 'static> { - App::new(util_name!()) + App::new(uucore::util_name()) .version(crate_version!()) .usage(USAGE) .after_help(LONG_HELP) diff --git a/src/uu/mktemp/src/mktemp.rs b/src/uu/mktemp/src/mktemp.rs index 2d42d38ba..0b30f0087 100644 --- a/src/uu/mktemp/src/mktemp.rs +++ b/src/uu/mktemp/src/mktemp.rs @@ -37,7 +37,7 @@ static OPT_T: &str = "t"; static ARG_TEMPLATE: &str = "template"; fn usage() -> String { - format!("{0} [OPTION]... [TEMPLATE]", execution_phrase!()) + format!("{0} [OPTION]... [TEMPLATE]", uucore::execution_phrase()) } #[derive(Debug)] @@ -134,7 +134,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } pub fn uu_app() -> App<'static, 'static> { - App::new(util_name!()) + App::new(uucore::util_name()) .version(crate_version!()) .about(ABOUT) .arg( diff --git a/src/uu/more/src/more.rs b/src/uu/more/src/more.rs index ee03c0d8a..8097d1402 100644 --- a/src/uu/more/src/more.rs +++ b/src/uu/more/src/more.rs @@ -93,7 +93,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> App<'static, 'static> { - App::new(util_name!()) + App::new(uucore::util_name()) .about("A file perusal filter for CRT viewing.") .version(crate_version!()) .arg( diff --git a/src/uu/mv/src/mv.rs b/src/uu/mv/src/mv.rs index 1cf45c36a..5f825113b 100644 --- a/src/uu/mv/src/mv.rs +++ b/src/uu/mv/src/mv.rs @@ -63,7 +63,7 @@ fn usage() -> String { "{0} [OPTION]... [-T] SOURCE DEST {0} [OPTION]... SOURCE... DIRECTORY {0} [OPTION]... -t DIRECTORY SOURCE...", - execution_phrase!() + uucore::execution_phrase() ) } @@ -133,7 +133,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> App<'static, 'static> { - App::new(util_name!()) + App::new(uucore::util_name()) .version(crate_version!()) .about(ABOUT) .arg( @@ -296,7 +296,7 @@ fn exec(files: &[PathBuf], b: Behavior) -> i32 { "mv: extra operand '{}'\n\ Try '{} --help' for more information.", files[2].display(), - execution_phrase!() + uucore::execution_phrase() ); return 1; } @@ -353,7 +353,7 @@ fn rename(from: &Path, to: &Path, b: &Behavior) -> io::Result<()> { match b.overwrite { OverwriteMode::NoClobber => return Ok(()), OverwriteMode::Interactive => { - println!("{}: overwrite '{}'? ", util_name!(), to.display()); + println!("{}: overwrite '{}'? ", uucore::util_name(), to.display()); if !read_yes() { return Ok(()); } diff --git a/src/uu/nice/src/nice.rs b/src/uu/nice/src/nice.rs index a93137c10..fbc2be0e5 100644 --- a/src/uu/nice/src/nice.rs +++ b/src/uu/nice/src/nice.rs @@ -31,7 +31,7 @@ Run COMMAND with an adjusted niceness, which affects process scheduling. With no COMMAND, print the current niceness. Niceness values range from at least -20 (most favorable to the process) to 19 (least favorable to the process).", - execution_phrase!() + uucore::execution_phrase() ) } @@ -54,7 +54,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { if !matches.is_present(options::COMMAND) { show_error!( "A command must be given with an adjustment.\nTry '{} --help' for more information.", - execution_phrase!() + uucore::execution_phrase() ); return 125; } @@ -101,7 +101,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> App<'static, 'static> { - App::new(util_name!()) + App::new(uucore::util_name()) .setting(AppSettings::TrailingVarArg) .version(crate_version!()) .arg( diff --git a/src/uu/nl/src/nl.rs b/src/uu/nl/src/nl.rs index d5ea18f8a..600ebace0 100644 --- a/src/uu/nl/src/nl.rs +++ b/src/uu/nl/src/nl.rs @@ -143,7 +143,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> App<'static, 'static> { - App::new(util_name!()) + App::new(uucore::util_name()) .name(NAME) .version(crate_version!()) .usage(USAGE) diff --git a/src/uu/nohup/src/nohup.rs b/src/uu/nohup/src/nohup.rs index 17b06301f..1ecb9914f 100644 --- a/src/uu/nohup/src/nohup.rs +++ b/src/uu/nohup/src/nohup.rs @@ -71,7 +71,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> App<'static, 'static> { - App::new(util_name!()) + App::new(uucore::util_name()) .version(crate_version!()) .about(ABOUT) .after_help(LONG_HELP) @@ -157,7 +157,10 @@ fn find_stdout() -> File { } fn usage() -> String { - format!("{0} COMMAND [ARG]...\n {0} FLAG", execution_phrase!()) + format!( + "{0} COMMAND [ARG]...\n {0} FLAG", + uucore::execution_phrase() + ) } #[cfg(target_vendor = "apple")] diff --git a/src/uu/nproc/src/nproc.rs b/src/uu/nproc/src/nproc.rs index c10d79d40..16b8d8c3a 100644 --- a/src/uu/nproc/src/nproc.rs +++ b/src/uu/nproc/src/nproc.rs @@ -28,7 +28,7 @@ static OPT_IGNORE: &str = "ignore"; static ABOUT: &str = "Print the number of cores available to the current process."; fn usage() -> String { - format!("{0} [OPTIONS]...", execution_phrase!()) + format!("{0} [OPTIONS]...", uucore::execution_phrase()) } pub fn uumain(args: impl uucore::Args) -> i32 { @@ -70,7 +70,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> App<'static, 'static> { - App::new(util_name!()) + App::new(uucore::util_name()) .version(crate_version!()) .about(ABOUT) .arg( diff --git a/src/uu/numfmt/src/numfmt.rs b/src/uu/numfmt/src/numfmt.rs index 849abeb71..1798975dc 100644 --- a/src/uu/numfmt/src/numfmt.rs +++ b/src/uu/numfmt/src/numfmt.rs @@ -51,7 +51,7 @@ Multiple fields/ranges can be separated with commas "; fn usage() -> String { - format!("{0} [OPTION]... [NUMBER]...", execution_phrase!()) + format!("{0} [OPTION]... [NUMBER]...", uucore::execution_phrase()) } fn handle_args<'a>(args: impl Iterator, options: NumfmtOptions) -> Result<()> { @@ -175,7 +175,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> App<'static, 'static> { - App::new(util_name!()) + App::new(uucore::util_name()) .version(crate_version!()) .about(ABOUT) .after_help(LONG_HELP) diff --git a/src/uu/od/src/multifilereader.rs b/src/uu/od/src/multifilereader.rs index 303093b01..2f44da803 100644 --- a/src/uu/od/src/multifilereader.rs +++ b/src/uu/od/src/multifilereader.rs @@ -57,7 +57,7 @@ impl<'b> MultifileReader<'b> { // print an error at the time that the file is needed, // then move on the the next file. // This matches the behavior of the original `od` - eprintln!("{}: '{}': {}", util_name!(), fname, e); + eprintln!("{}: '{}': {}", uucore::util_name(), fname, e); self.any_err = true } } @@ -90,7 +90,7 @@ impl<'b> io::Read for MultifileReader<'b> { Ok(0) => break, Ok(n) => n, Err(e) => { - eprintln!("{}: I/O: {}", util_name!(), e); + eprintln!("{}: I/O: {}", uucore::util_name(), e); self.any_err = true; break; } diff --git a/src/uu/od/src/od.rs b/src/uu/od/src/od.rs index b37990fd5..6c1110362 100644 --- a/src/uu/od/src/od.rs +++ b/src/uu/od/src/od.rs @@ -252,7 +252,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> clap::App<'static, 'static> { - clap::App::new(util_name!()) + clap::App::new(uucore::util_name()) .version(crate_version!()) .about(ABOUT) .usage(USAGE) diff --git a/src/uu/paste/src/paste.rs b/src/uu/paste/src/paste.rs index 801de50dc..9ac5507df 100644 --- a/src/uu/paste/src/paste.rs +++ b/src/uu/paste/src/paste.rs @@ -52,7 +52,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> App<'static, 'static> { - App::new(util_name!()) + App::new(uucore::util_name()) .version(crate_version!()) .about(ABOUT) .arg( diff --git a/src/uu/pathchk/src/pathchk.rs b/src/uu/pathchk/src/pathchk.rs index c1ea76bf9..863bb6bf2 100644 --- a/src/uu/pathchk/src/pathchk.rs +++ b/src/uu/pathchk/src/pathchk.rs @@ -39,7 +39,7 @@ const POSIX_PATH_MAX: usize = 256; const POSIX_NAME_MAX: usize = 14; fn usage() -> String { - format!("{0} [OPTION]... NAME...", execution_phrase!()) + format!("{0} [OPTION]... NAME...", uucore::execution_phrase()) } pub fn uumain(args: impl uucore::Args) -> i32 { @@ -70,7 +70,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let mut res = if paths.is_none() { show_error!( "missing operand\nTry '{} --help' for more information", - execution_phrase!() + uucore::execution_phrase() ); false } else { @@ -98,7 +98,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> App<'static, 'static> { - App::new(util_name!()) + App::new(uucore::util_name()) .version(crate_version!()) .about(ABOUT) .arg( diff --git a/src/uu/pinky/src/pinky.rs b/src/uu/pinky/src/pinky.rs index 9268ffd2b..02573c994 100644 --- a/src/uu/pinky/src/pinky.rs +++ b/src/uu/pinky/src/pinky.rs @@ -41,7 +41,7 @@ mod options { } fn usage() -> String { - format!("{0} [OPTION]... [USER]...", execution_phrase!()) + format!("{0} [OPTION]... [USER]...", uucore::execution_phrase()) } fn get_long_usage() -> String { @@ -130,7 +130,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> App<'static, 'static> { - App::new(util_name!()) + App::new(uucore::util_name()) .version(crate_version!()) .about(ABOUT) .arg( diff --git a/src/uu/pr/src/pr.rs b/src/uu/pr/src/pr.rs index 9ee5158ec..1358cef6c 100644 --- a/src/uu/pr/src/pr.rs +++ b/src/uu/pr/src/pr.rs @@ -23,7 +23,6 @@ use std::fs::{metadata, File}; use std::io::{stdin, stdout, BufRead, BufReader, Lines, Read, Stdout, Write}; #[cfg(unix)] use std::os::unix::fs::FileTypeExt; -use uucore::util_name; type IOError = std::io::Error; @@ -170,7 +169,7 @@ quick_error! { pub fn uu_app() -> clap::App<'static, 'static> { // TODO: migrate to clap to get more shell completions - clap::App::new(util_name!()) + clap::App::new(uucore::util_name()) } pub fn uumain(args: impl uucore::Args) -> i32 { diff --git a/src/uu/printenv/src/printenv.rs b/src/uu/printenv/src/printenv.rs index da46f7667..5d32cfbcc 100644 --- a/src/uu/printenv/src/printenv.rs +++ b/src/uu/printenv/src/printenv.rs @@ -7,9 +7,6 @@ /* last synced with: printenv (GNU coreutils) 8.13 */ -#[macro_use] -extern crate uucore; - use clap::{crate_version, App, Arg}; use std::env; @@ -20,7 +17,7 @@ static OPT_NULL: &str = "null"; static ARG_VARIABLES: &str = "variables"; fn usage() -> String { - format!("{0} [VARIABLE]... [OPTION]...", execution_phrase!()) + format!("{0} [VARIABLE]... [OPTION]...", uucore::execution_phrase()) } pub fn uumain(args: impl uucore::Args) -> i32 { @@ -55,7 +52,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> App<'static, 'static> { - App::new(util_name!()) + App::new(uucore::util_name()) .version(crate_version!()) .about(ABOUT) .arg( diff --git a/src/uu/printf/src/printf.rs b/src/uu/printf/src/printf.rs index 22e02ffe2..d3c8dca90 100644 --- a/src/uu/printf/src/printf.rs +++ b/src/uu/printf/src/printf.rs @@ -2,9 +2,6 @@ // spell-checker:ignore (change!) each's // spell-checker:ignore (ToDO) LONGHELP FORMATSTRING templating parameterizing formatstr -#[macro_use] -extern crate uucore; - use clap::{crate_version, App, Arg}; use uucore::InvalidEncodingHandling; @@ -284,8 +281,8 @@ pub fn uumain(args: impl uucore::Args) -> i32 { if args.len() <= 1 { println!( "{0}: missing operand\nTry '{1} --help' for more information.", - util_name!(), - execution_phrase!() + uucore::util_name(), + uucore::execution_phrase() ); return 1; } @@ -294,7 +291,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { if formatstr == "--help" { print!("{} {}", LONGHELP_LEAD, LONGHELP_BODY); } else if formatstr == "--version" { - println!("{} {}", util_name!(), crate_version!()); + println!("{} {}", uucore::util_name(), crate_version!()); } else { let printf_args = &args[2..]; memo::Memo::run_all(formatstr, printf_args); @@ -303,7 +300,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> App<'static, 'static> { - App::new(util_name!()) + App::new(uucore::util_name()) .arg(Arg::with_name(VERSION).long(VERSION)) .arg(Arg::with_name(HELP).long(HELP)) } diff --git a/src/uu/ptx/src/ptx.rs b/src/uu/ptx/src/ptx.rs index 4f16af470..264a37d72 100644 --- a/src/uu/ptx/src/ptx.rs +++ b/src/uu/ptx/src/ptx.rs @@ -659,7 +659,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> App<'static, 'static> { - App::new(util_name!()) + App::new(uucore::util_name()) .name(NAME) .version(crate_version!()) .usage(BRIEF) diff --git a/src/uu/pwd/src/pwd.rs b/src/uu/pwd/src/pwd.rs index 388a10463..75dc637e6 100644 --- a/src/uu/pwd/src/pwd.rs +++ b/src/uu/pwd/src/pwd.rs @@ -35,7 +35,7 @@ pub fn absolute_path(path: &Path) -> io::Result { } fn usage() -> String { - format!("{0} [OPTION]... FILE...", execution_phrase!()) + format!("{0} [OPTION]... FILE...", uucore::execution_phrase()) } #[uucore_procs::gen_uumain] @@ -66,7 +66,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } pub fn uu_app() -> App<'static, 'static> { - App::new(util_name!()) + App::new(uucore::util_name()) .version(crate_version!()) .about(ABOUT) .arg( diff --git a/src/uu/readlink/src/readlink.rs b/src/uu/readlink/src/readlink.rs index 81d8376f6..aab368af1 100644 --- a/src/uu/readlink/src/readlink.rs +++ b/src/uu/readlink/src/readlink.rs @@ -29,7 +29,7 @@ const OPT_ZERO: &str = "zero"; const ARG_FILES: &str = "files"; fn usage() -> String { - format!("{0} [OPTION]... [FILE]...", execution_phrase!()) + format!("{0} [OPTION]... [FILE]...", uucore::execution_phrase()) } pub fn uumain(args: impl uucore::Args) -> i32 { @@ -59,14 +59,14 @@ pub fn uumain(args: impl uucore::Args) -> i32 { crash!( 1, "missing operand\nTry '{} --help' for more information", - execution_phrase!() + uucore::execution_phrase() ); } if no_newline && files.len() > 1 && !silent { eprintln!( "{}: ignoring --no-newline with multiple arguments", - util_name!() + uucore::util_name() ); no_newline = false; } @@ -80,7 +80,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { if verbose { eprintln!( "{}: {}: errno {}", - util_name!(), + uucore::util_name(), f, err.raw_os_error().unwrap() ); @@ -95,7 +95,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { if verbose { eprintln!( "{}: {}: errno {:?}", - util_name!(), + uucore::util_name(), f, err.raw_os_error().unwrap() ); @@ -110,7 +110,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> App<'static, 'static> { - App::new(util_name!()) + App::new(uucore::util_name()) .version(crate_version!()) .about(ABOUT) .arg( diff --git a/src/uu/realpath/src/realpath.rs b/src/uu/realpath/src/realpath.rs index 7ce8fc179..b12fef3d9 100644 --- a/src/uu/realpath/src/realpath.rs +++ b/src/uu/realpath/src/realpath.rs @@ -23,7 +23,7 @@ static OPT_ZERO: &str = "zero"; static ARG_FILES: &str = "files"; fn usage() -> String { - format!("{0} [OPTION]... FILE...", execution_phrase!()) + format!("{0} [OPTION]... FILE...", uucore::execution_phrase()) } pub fn uumain(args: impl uucore::Args) -> i32 { @@ -55,7 +55,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> App<'static, 'static> { - App::new(util_name!()) + App::new(uucore::util_name()) .version(crate_version!()) .about(ABOUT) .arg( diff --git a/src/uu/relpath/src/relpath.rs b/src/uu/relpath/src/relpath.rs index 941a6a3f2..1878e2efa 100644 --- a/src/uu/relpath/src/relpath.rs +++ b/src/uu/relpath/src/relpath.rs @@ -7,9 +7,6 @@ // spell-checker:ignore (ToDO) subpath absto absfrom absbase -#[macro_use] -extern crate uucore; - use clap::{crate_version, App, Arg}; use std::env; use std::path::{Path, PathBuf}; @@ -26,7 +23,7 @@ mod options { } fn usage() -> String { - format!("{} [-d DIR] TO [FROM]", execution_phrase!()) + format!("{} [-d DIR] TO [FROM]", uucore::execution_phrase()) } pub fn uumain(args: impl uucore::Args) -> i32 { @@ -82,7 +79,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> App<'static, 'static> { - App::new(util_name!()) + App::new(uucore::util_name()) .version(crate_version!()) .about(ABOUT) .arg( diff --git a/src/uu/rm/src/rm.rs b/src/uu/rm/src/rm.rs index 03977bc3e..0613ff857 100644 --- a/src/uu/rm/src/rm.rs +++ b/src/uu/rm/src/rm.rs @@ -53,7 +53,7 @@ static OPT_VERBOSE: &str = "verbose"; static ARG_FILES: &str = "files"; fn usage() -> String { - format!("{0} [OPTION]... FILE...", execution_phrase!()) + format!("{0} [OPTION]... FILE...", uucore::execution_phrase()) } fn get_long_usage() -> String { @@ -93,7 +93,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { // Still check by hand and not use clap // Because "rm -f" is a thing show_error!("missing an argument"); - show_error!("for help, try '{0} --help'", execution_phrase!()); + show_error!("for help, try '{0} --help'", uucore::execution_phrase()); return 1; } else { let options = Options { @@ -140,7 +140,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> App<'static, 'static> { - App::new(util_name!()) + App::new(uucore::util_name()) .version(crate_version!()) .about(ABOUT) diff --git a/src/uu/rmdir/src/rmdir.rs b/src/uu/rmdir/src/rmdir.rs index 135350a8e..cafd8e982 100644 --- a/src/uu/rmdir/src/rmdir.rs +++ b/src/uu/rmdir/src/rmdir.rs @@ -27,7 +27,7 @@ static ENOTDIR: i32 = 20; static ENOTDIR: i32 = 267; fn usage() -> String { - format!("{0} [OPTION]... DIRECTORY...", execution_phrase!()) + format!("{0} [OPTION]... DIRECTORY...", uucore::execution_phrase()) } pub fn uumain(args: impl uucore::Args) -> i32 { @@ -53,7 +53,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> App<'static, 'static> { - App::new(util_name!()) + App::new(uucore::util_name()) .version(crate_version!()) .about(ABOUT) .arg( diff --git a/src/uu/seq/src/seq.rs b/src/uu/seq/src/seq.rs index db5537b8b..05388e7a1 100644 --- a/src/uu/seq/src/seq.rs +++ b/src/uu/seq/src/seq.rs @@ -27,7 +27,7 @@ fn usage() -> String { "{0} [OPTION]... LAST {0} [OPTION]... FIRST LAST {0} [OPTION]... FIRST INCREMENT LAST", - execution_phrase!() + uucore::execution_phrase() ) } #[derive(Clone)] @@ -72,13 +72,13 @@ impl FromStr for Number { Ok(value) if value.is_nan() => Err(format!( "invalid 'not-a-number' argument: '{}'\nTry '{} --help' for more information.", s, - execution_phrase!(), + uucore::execution_phrase(), )), Ok(value) => Ok(Number::F64(value)), Err(_) => Err(format!( "invalid floating point argument: '{}'\nTry '{} --help' for more information.", s, - execution_phrase!(), + uucore::execution_phrase(), )), }, } @@ -123,7 +123,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { show_error!( "invalid Zero increment value: '{}'\nTry '{} --help' for more information.", numbers[1], - execution_phrase!() + uucore::execution_phrase() ); return 1; } @@ -163,7 +163,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> App<'static, 'static> { - App::new(util_name!()) + App::new(uucore::util_name()) .setting(AppSettings::AllowLeadingHyphen) .version(crate_version!()) .about(ABOUT) diff --git a/src/uu/shred/src/shred.rs b/src/uu/shred/src/shred.rs index 521cb8dc6..25cf704bf 100644 --- a/src/uu/shred/src/shred.rs +++ b/src/uu/shred/src/shred.rs @@ -214,7 +214,7 @@ for even very expensive hardware probing to recover the data. "; fn usage() -> String { - format!("{} [OPTION]... FILE...", execution_phrase!()) + format!("{} [OPTION]... FILE...", uucore::execution_phrase()) } static AFTER_HELP: &str = @@ -330,7 +330,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> App<'static, 'static> { - App::new(util_name!()) + App::new(uucore::util_name()) .version(crate_version!()) .about(ABOUT) .after_help(AFTER_HELP) diff --git a/src/uu/shuf/src/shuf.rs b/src/uu/shuf/src/shuf.rs index 7f69a5fe9..a193b702b 100644 --- a/src/uu/shuf/src/shuf.rs +++ b/src/uu/shuf/src/shuf.rs @@ -115,7 +115,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> App<'static, 'static> { - App::new(util_name!()) + App::new(uucore::util_name()) .name(NAME) .version(crate_version!()) .template(TEMPLATE) diff --git a/src/uu/sleep/src/sleep.rs b/src/uu/sleep/src/sleep.rs index 136d92e4d..a70a524c4 100644 --- a/src/uu/sleep/src/sleep.rs +++ b/src/uu/sleep/src/sleep.rs @@ -29,7 +29,7 @@ mod options { fn usage() -> String { format!( "{0} {1}[SUFFIX]... \n {0} OPTION", - execution_phrase!(), + uucore::execution_phrase(), options::NUMBER ) } @@ -49,7 +49,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } pub fn uu_app() -> App<'static, 'static> { - App::new(util_name!()) + App::new(uucore::util_name()) .version(crate_version!()) .about(ABOUT) .after_help(LONG_HELP) diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index 049be5352..9aae23bb8 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -1060,7 +1060,7 @@ fn usage() -> String { Write the sorted concatenation of all FILE(s) to standard output. Mandatory arguments for long options are mandatory for short options too. With no FILE, or when FILE is -, read standard input.", - execution_phrase!() + uucore::execution_phrase() ) } @@ -1286,7 +1286,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } pub fn uu_app() -> App<'static, 'static> { - App::new(util_name!()) + App::new(uucore::util_name()) .version(crate_version!()) .about(ABOUT) .arg( diff --git a/src/uu/split/src/split.rs b/src/uu/split/src/split.rs index ce21361af..070df81f6 100644 --- a/src/uu/split/src/split.rs +++ b/src/uu/split/src/split.rs @@ -35,7 +35,10 @@ static ARG_INPUT: &str = "input"; static ARG_PREFIX: &str = "prefix"; fn usage() -> String { - format!("{0} [OPTION]... [INPUT [PREFIX]]", execution_phrase!()) + format!( + "{0} [OPTION]... [INPUT [PREFIX]]", + uucore::execution_phrase() + ) } fn get_long_usage() -> String { format!( @@ -125,7 +128,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> App<'static, 'static> { - App::new(util_name!()) + App::new(uucore::util_name()) .version(crate_version!()) .about("Create output files containing consecutive or interleaved sections of input") // strategy (mutually exclusive) diff --git a/src/uu/stat/src/stat.rs b/src/uu/stat/src/stat.rs index d3783d725..d017999b4 100644 --- a/src/uu/stat/src/stat.rs +++ b/src/uu/stat/src/stat.rs @@ -883,7 +883,7 @@ impl Stater { } fn usage() -> String { - format!("{0} [OPTION]... FILE...", execution_phrase!()) + format!("{0} [OPTION]... FILE...", uucore::execution_phrase()) } fn get_long_usage() -> String { @@ -963,7 +963,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> App<'static, 'static> { - App::new(util_name!()) + App::new(uucore::util_name()) .version(crate_version!()) .about(ABOUT) .arg( diff --git a/src/uu/stdbuf/src/stdbuf.rs b/src/uu/stdbuf/src/stdbuf.rs index a16a40ce5..e87b3a225 100644 --- a/src/uu/stdbuf/src/stdbuf.rs +++ b/src/uu/stdbuf/src/stdbuf.rs @@ -48,7 +48,7 @@ mod options { } fn usage() -> String { - format!("{0} OPTION... COMMAND", execution_phrase!()) + format!("{0} OPTION... COMMAND", uucore::execution_phrase()) } const STDBUF_INJECT: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/libstdbuf.so")); @@ -161,7 +161,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { 125, "{}\nTry '{} --help' for more information.", e.0, - execution_phrase!() + uucore::execution_phrase() ) }); @@ -191,7 +191,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> App<'static, 'static> { - App::new(util_name!()) + App::new(uucore::util_name()) .version(crate_version!()) .about(ABOUT) .after_help(LONG_HELP) diff --git a/src/uu/sum/src/sum.rs b/src/uu/sum/src/sum.rs index b4b2294aa..f1147ca2e 100644 --- a/src/uu/sum/src/sum.rs +++ b/src/uu/sum/src/sum.rs @@ -140,7 +140,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> App<'static, 'static> { - App::new(util_name!()) + App::new(uucore::util_name()) .name(NAME) .version(crate_version!()) .usage(USAGE) diff --git a/src/uu/sync/src/sync.rs b/src/uu/sync/src/sync.rs index 7dd37d780..f49f51728 100644 --- a/src/uu/sync/src/sync.rs +++ b/src/uu/sync/src/sync.rs @@ -160,7 +160,7 @@ mod platform { } fn usage() -> String { - format!("{0} [OPTION]... FILE...", execution_phrase!()) + format!("{0} [OPTION]... FILE...", uucore::execution_phrase()) } pub fn uumain(args: impl uucore::Args) -> i32 { @@ -193,7 +193,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> App<'static, 'static> { - App::new(util_name!()) + App::new(uucore::util_name()) .version(crate_version!()) .about(ABOUT) .arg( diff --git a/src/uu/tac/src/tac.rs b/src/uu/tac/src/tac.rs index 70fa04622..67b361a76 100644 --- a/src/uu/tac/src/tac.rs +++ b/src/uu/tac/src/tac.rs @@ -51,7 +51,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> App<'static, 'static> { - App::new(util_name!()) + App::new(uucore::util_name()) .name(NAME) .version(crate_version!()) .usage(USAGE) diff --git a/src/uu/tail/src/tail.rs b/src/uu/tail/src/tail.rs index 106c1c5db..44f910ea0 100644 --- a/src/uu/tail/src/tail.rs +++ b/src/uu/tail/src/tail.rs @@ -213,7 +213,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> App<'static, 'static> { - App::new(util_name!()) + App::new(uucore::util_name()) .version(crate_version!()) .about("output the last part of files") // TODO: add usage diff --git a/src/uu/tee/src/tee.rs b/src/uu/tee/src/tee.rs index 461600003..d98835265 100644 --- a/src/uu/tee/src/tee.rs +++ b/src/uu/tee/src/tee.rs @@ -33,7 +33,7 @@ struct Options { } fn usage() -> String { - format!("{0} [OPTION]... [FILE]...", execution_phrase!()) + format!("{0} [OPTION]... [FILE]...", uucore::execution_phrase()) } pub fn uumain(args: impl uucore::Args) -> i32 { @@ -57,7 +57,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> App<'static, 'static> { - App::new(util_name!()) + App::new(uucore::util_name()) .version(crate_version!()) .about(ABOUT) .after_help("If a FILE is -, it refers to a file named - .") diff --git a/src/uu/test/src/test.rs b/src/uu/test/src/test.rs index 939e13ab9..6eea00080 100644 --- a/src/uu/test/src/test.rs +++ b/src/uu/test/src/test.rs @@ -14,7 +14,6 @@ use clap::{crate_version, App, AppSettings}; use parser::{parse, Symbol}; use std::ffi::{OsStr, OsString}; use std::path::Path; -use uucore::util_name; const USAGE: &str = "test EXPRESSION or: test @@ -87,7 +86,7 @@ the version described here. Please refer to your shell's documentation for details about the options it supports."; pub fn uu_app() -> App<'static, 'static> { - App::new(util_name!()) + App::new(uucore::util_name()) .setting(AppSettings::DisableHelpFlags) .setting(AppSettings::DisableVersion) } diff --git a/src/uu/timeout/src/timeout.rs b/src/uu/timeout/src/timeout.rs index 8b169b5b5..89fde82ca 100644 --- a/src/uu/timeout/src/timeout.rs +++ b/src/uu/timeout/src/timeout.rs @@ -23,7 +23,10 @@ use uucore::InvalidEncodingHandling; static ABOUT: &str = "Start COMMAND, and kill it if still running after DURATION."; fn usage() -> String { - format!("{0} [OPTION] DURATION COMMAND...", execution_phrase!()) + format!( + "{0} [OPTION] DURATION COMMAND...", + uucore::execution_phrase() + ) } const ERR_EXIT_STATUS: i32 = 125; diff --git a/src/uu/touch/src/touch.rs b/src/uu/touch/src/touch.rs index 8ddfcfa74..7c0903665 100644 --- a/src/uu/touch/src/touch.rs +++ b/src/uu/touch/src/touch.rs @@ -48,7 +48,7 @@ fn local_tm_to_filetime(tm: time::Tm) -> FileTime { } fn usage() -> String { - format!("{0} [OPTION]... [USER]", execution_phrase!()) + format!("{0} [OPTION]... [USER]", uucore::execution_phrase()) } #[uucore_procs::gen_uumain] @@ -129,7 +129,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } pub fn uu_app() -> App<'static, 'static> { - App::new(util_name!()) + App::new(uucore::util_name()) .version(crate_version!()) .about(ABOUT) .arg( diff --git a/src/uu/tr/src/tr.rs b/src/uu/tr/src/tr.rs index 7b6fbd6f2..d46318e38 100644 --- a/src/uu/tr/src/tr.rs +++ b/src/uu/tr/src/tr.rs @@ -229,7 +229,7 @@ fn translate_input( } fn usage() -> String { - format!("{} [OPTION]... SET1 [SET2]", execution_phrase!()) + format!("{} [OPTION]... SET1 [SET2]", uucore::execution_phrase()) } fn get_long_usage() -> String { @@ -264,7 +264,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { if sets.is_empty() { show_error!( "missing operand\nTry '{} --help' for more information.", - execution_phrase!() + uucore::execution_phrase() ); return 1; } @@ -273,7 +273,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { show_error!( "missing operand after '{}'\nTry '{} --help' for more information.", sets[0], - execution_phrase!() + uucore::execution_phrase() ); return 1; } @@ -312,7 +312,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> App<'static, 'static> { - App::new(util_name!()) + App::new(uucore::util_name()) .version(crate_version!()) .about(ABOUT) .arg( diff --git a/src/uu/true/src/true.rs b/src/uu/true/src/true.rs index 07e075175..6b4a87bf1 100644 --- a/src/uu/true/src/true.rs +++ b/src/uu/true/src/true.rs @@ -10,7 +10,6 @@ extern crate uucore; use clap::App; use uucore::error::UResult; -use uucore::util_name; #[uucore_procs::gen_uumain] pub fn uumain(args: impl uucore::Args) -> UResult<()> { @@ -19,5 +18,5 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } pub fn uu_app() -> App<'static, 'static> { - App::new(util_name!()) + App::new(uucore::util_name()) } diff --git a/src/uu/truncate/src/truncate.rs b/src/uu/truncate/src/truncate.rs index fdcff3fd5..062ef3811 100644 --- a/src/uu/truncate/src/truncate.rs +++ b/src/uu/truncate/src/truncate.rs @@ -64,7 +64,7 @@ pub mod options { } fn usage() -> String { - format!("{0} [OPTION]... [FILE]...", execution_phrase!()) + format!("{0} [OPTION]... [FILE]...", uucore::execution_phrase()) } fn get_long_usage() -> String { @@ -133,7 +133,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> App<'static, 'static> { - App::new(util_name!()) + App::new(uucore::util_name()) .version(crate_version!()) .about(ABOUT) .arg( diff --git a/src/uu/tsort/src/tsort.rs b/src/uu/tsort/src/tsort.rs index c2416afb4..c0ef66598 100644 --- a/src/uu/tsort/src/tsort.rs +++ b/src/uu/tsort/src/tsort.rs @@ -90,7 +90,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> App<'static, 'static> { - App::new(util_name!()) + App::new(uucore::util_name()) .version(crate_version!()) .usage(USAGE) .about(SUMMARY) diff --git a/src/uu/tty/src/tty.rs b/src/uu/tty/src/tty.rs index 284916050..94e2e6b24 100644 --- a/src/uu/tty/src/tty.rs +++ b/src/uu/tty/src/tty.rs @@ -9,9 +9,6 @@ // spell-checker:ignore (ToDO) ttyname filedesc -#[macro_use] -extern crate uucore; - use clap::{crate_version, App, Arg}; use std::ffi::CStr; use std::io::Write; @@ -24,7 +21,7 @@ mod options { } fn usage() -> String { - format!("{0} [OPTION]...", execution_phrase!()) + format!("{0} [OPTION]...", uucore::execution_phrase()) } pub fn uumain(args: impl uucore::Args) -> i32 { @@ -78,7 +75,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> App<'static, 'static> { - App::new(util_name!()) + App::new(uucore::util_name()) .version(crate_version!()) .about(ABOUT) .arg( diff --git a/src/uu/uname/src/uname.rs b/src/uu/uname/src/uname.rs index b15d950bf..5f8dfd8a8 100644 --- a/src/uu/uname/src/uname.rs +++ b/src/uu/uname/src/uname.rs @@ -50,7 +50,7 @@ const HOST_OS: &str = "Fuchsia"; const HOST_OS: &str = "Redox"; pub fn uumain(args: impl uucore::Args) -> i32 { - let usage = format!("{} [OPTION]...", execution_phrase!()); + let usage = format!("{} [OPTION]...", uucore::execution_phrase()); let matches = uu_app().usage(&usage[..]).get_matches_from(args); let uname = return_if_err!(1, PlatformInfo::new()); @@ -119,7 +119,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> App<'static, 'static> { - App::new(util_name!()) + App::new(uucore::util_name()) .version(crate_version!()) .about(ABOUT) .arg(Arg::with_name(options::ALL) diff --git a/src/uu/unexpand/src/unexpand.rs b/src/uu/unexpand/src/unexpand.rs index 4a4c50feb..cb8541048 100644 --- a/src/uu/unexpand/src/unexpand.rs +++ b/src/uu/unexpand/src/unexpand.rs @@ -102,7 +102,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> App<'static, 'static> { - App::new(util_name!()) + App::new(uucore::util_name()) .name(NAME) .version(crate_version!()) .usage(USAGE) diff --git a/src/uu/uniq/src/uniq.rs b/src/uu/uniq/src/uniq.rs index 10cb7a4ce..5c3fa3b1e 100644 --- a/src/uu/uniq/src/uniq.rs +++ b/src/uu/uniq/src/uniq.rs @@ -222,7 +222,10 @@ fn opt_parsed(opt_name: &str, matches: &ArgMatches) -> Option { } fn usage() -> String { - format!("{0} [OPTION]... [INPUT [OUTPUT]]...", execution_phrase!()) + format!( + "{0} [OPTION]... [INPUT [OUTPUT]]...", + uucore::execution_phrase() + ) } fn get_long_usage() -> String { @@ -281,7 +284,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> App<'static, 'static> { - App::new(util_name!()) + App::new(uucore::util_name()) .version(crate_version!()) .about(ABOUT) .arg( diff --git a/src/uu/unlink/src/unlink.rs b/src/uu/unlink/src/unlink.rs index e0a743c1c..c7fc00639 100644 --- a/src/uu/unlink/src/unlink.rs +++ b/src/uu/unlink/src/unlink.rs @@ -23,7 +23,7 @@ static ABOUT: &str = "Unlink the file at [FILE]."; static OPT_PATH: &str = "FILE"; fn usage() -> String { - format!("{} [OPTION]... FILE", execution_phrase!()) + format!("{} [OPTION]... FILE", uucore::execution_phrase()) } pub fn uumain(args: impl uucore::Args) -> i32 { @@ -44,13 +44,13 @@ pub fn uumain(args: impl uucore::Args) -> i32 { crash!( 1, "missing operand\nTry '{0} --help' for more information.", - execution_phrase!() + uucore::execution_phrase() ); } else if paths.len() > 1 { crash!( 1, "extra operand: '{1}'\nTry '{0} --help' for more information.", - execution_phrase!(), + uucore::execution_phrase(), paths[1] ); } @@ -95,7 +95,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> App<'static, 'static> { - App::new(util_name!()) + App::new(uucore::util_name()) .version(crate_version!()) .about(ABOUT) .arg(Arg::with_name(OPT_PATH).hidden(true).multiple(true)) diff --git a/src/uu/uptime/src/uptime.rs b/src/uu/uptime/src/uptime.rs index b19566ec3..e58f398db 100644 --- a/src/uu/uptime/src/uptime.rs +++ b/src/uu/uptime/src/uptime.rs @@ -33,7 +33,7 @@ extern "C" { } fn usage() -> String { - format!("{0} [OPTION]...", execution_phrase!()) + format!("{0} [OPTION]...", uucore::execution_phrase()) } pub fn uumain(args: impl uucore::Args) -> i32 { @@ -64,7 +64,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> App<'static, 'static> { - App::new(util_name!()) + App::new(uucore::util_name()) .version(crate_version!()) .about(ABOUT) .arg( diff --git a/src/uu/users/src/users.rs b/src/uu/users/src/users.rs index 7aed00507..866093189 100644 --- a/src/uu/users/src/users.rs +++ b/src/uu/users/src/users.rs @@ -8,9 +8,6 @@ // spell-checker:ignore (paths) wtmp -#[macro_use] -extern crate uucore; - use clap::{crate_version, App, Arg}; use uucore::utmpx::{self, Utmpx}; @@ -19,7 +16,7 @@ static ABOUT: &str = "Print the user names of users currently logged in to the c static ARG_FILES: &str = "files"; fn usage() -> String { - format!("{0} [FILE]", execution_phrase!()) + format!("{0} [FILE]", uucore::execution_phrase()) } fn get_long_usage() -> String { @@ -65,7 +62,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> App<'static, 'static> { - App::new(util_name!()) + App::new(uucore::util_name()) .version(crate_version!()) .about(ABOUT) .arg(Arg::with_name(ARG_FILES).takes_value(true).max_values(1)) diff --git a/src/uu/wc/src/wc.rs b/src/uu/wc/src/wc.rs index 3077d31d9..ff586a6f6 100644 --- a/src/uu/wc/src/wc.rs +++ b/src/uu/wc/src/wc.rs @@ -99,7 +99,7 @@ fn usage() -> String { format!( "{0} [OPTION]... [FILE]... With no FILE, or when FILE is -, read standard input.", - execution_phrase!() + uucore::execution_phrase() ) } @@ -164,7 +164,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> App<'static, 'static> { - App::new(util_name!()) + App::new(uucore::util_name()) .version(crate_version!()) .about(ABOUT) .arg( diff --git a/src/uu/who/src/who.rs b/src/uu/who/src/who.rs index 59e8b7c36..b0ef7b3fa 100644 --- a/src/uu/who/src/who.rs +++ b/src/uu/who/src/who.rs @@ -45,7 +45,10 @@ static RUNLEVEL_HELP: &str = "print current runlevel"; static RUNLEVEL_HELP: &str = "print current runlevel (This is meaningless on non Linux)"; fn usage() -> String { - format!("{0} [OPTION]... [ FILE | ARG1 ARG2 ]", execution_phrase!()) + format!( + "{0} [OPTION]... [ FILE | ARG1 ARG2 ]", + uucore::execution_phrase() + ) } fn get_long_usage() -> String { @@ -160,7 +163,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } pub fn uu_app() -> App<'static, 'static> { - App::new(util_name!()) + App::new(uucore::util_name()) .version(crate_version!()) .about(ABOUT) .arg( diff --git a/src/uucore/src/lib/lib.rs b/src/uucore/src/lib/lib.rs index 8920b226d..6acd4e017 100644 --- a/src/uucore/src/lib/lib.rs +++ b/src/uucore/src/lib/lib.rs @@ -87,6 +87,38 @@ pub fn set_utility_is_second_arg() { crate::macros::UTILITY_IS_SECOND_ARG.store(true, Ordering::SeqCst) } +/// Get the executable path (as `OsString`). +fn executable_os() -> OsString { + args_os().next().unwrap() +} + +/// Get the executable path (as `String`). +fn executable() -> String { + executable_os().to_string_lossy().into_owned() +} + +/// Derive the utility name. +pub fn util_name() -> String { + if get_utility_is_second_arg() { + args_os().nth(1).unwrap().to_string_lossy().into_owned() + } else { + executable() + } +} + +/// Derive the complete execution phrase for "usage". +pub fn execution_phrase() -> String { + if get_utility_is_second_arg() { + args_os() + .take(2) + .map(|os_str| os_str.to_string_lossy().into_owned()) + .collect::>() + .join(" ") + } else { + executable() + } +} + pub enum InvalidEncodingHandling { Ignore, ConvertLossy, diff --git a/src/uucore/src/lib/macros.rs b/src/uucore/src/lib/macros.rs index c9fff455a..d16f04504 100644 --- a/src/uucore/src/lib/macros.rs +++ b/src/uucore/src/lib/macros.rs @@ -10,67 +10,14 @@ use std::sync::atomic::AtomicBool; /// Whether we were called as a multicall binary ("coreutils ") pub static UTILITY_IS_SECOND_ARG: AtomicBool = AtomicBool::new(false); -/// Get the executable path (as `OsString`). -#[macro_export] -macro_rules! executable_os( - () => ({ - $crate::args_os().next().unwrap() - }) -); - -/// Get the executable path (as `String`). -#[macro_export] -#[deprecated = "Use util_name!() or execution_phrase!() instead"] -macro_rules! executable( - () => ({ - $crate::executable_os!().to_string_lossy().to_string() - }) -); - -/// Derive the utility name. -#[macro_export] -macro_rules! util_name( - () => ({ - if $crate::get_utility_is_second_arg() { - $crate::args_os().nth(1).unwrap().to_string_lossy().to_string() - } else { - #[allow(deprecated)] - { - $crate::executable!() - } - } - }) -); - -//==== - -/// Derive the complete execution phrase for "usage". -#[macro_export] -macro_rules! execution_phrase( - () => ({ - if $crate::get_utility_is_second_arg() { - $crate::args_os() - .take(2) - .map(|os_str| os_str.to_string_lossy().to_string()) - .collect::>() - .join(" ") - } else { - #[allow(deprecated)] - { - $crate::executable!() - } - } - }) -); - //==== #[macro_export] macro_rules! show( ($err:expr) => ({ let e = $err; - uucore::error::set_exit_code(e.code()); - eprintln!("{}: {}", $crate::util_name!(), e); + $crate::error::set_exit_code(e.code()); + eprintln!("{}: {}", $crate::util_name(), e); }) ); @@ -87,7 +34,7 @@ macro_rules! show_if_err( #[macro_export] macro_rules! show_error( ($($args:tt)+) => ({ - eprint!("{}: ", $crate::util_name!()); + eprint!("{}: ", $crate::util_name()); eprintln!($($args)+); }) ); @@ -96,7 +43,7 @@ macro_rules! show_error( #[macro_export] macro_rules! show_error_custom_description ( ($err:expr,$($args:tt)+) => ({ - eprint!("{}: {}: ", $crate::util_name!(), $err); + eprint!("{}: {}: ", $crate::util_name(), $err); eprintln!($($args)+); }) ); @@ -104,7 +51,7 @@ macro_rules! show_error_custom_description ( #[macro_export] macro_rules! show_warning( ($($args:tt)+) => ({ - eprint!("{}: warning: ", $crate::util_name!()); + eprint!("{}: warning: ", $crate::util_name()); eprintln!($($args)+); }) ); @@ -113,9 +60,9 @@ macro_rules! show_warning( #[macro_export] macro_rules! show_usage_error( ($($args:tt)+) => ({ - eprint!("{}: ", $crate::util_name!()); + eprint!("{}: ", $crate::util_name()); eprintln!($($args)+); - eprintln!("Try '{} --help' for more information.", $crate::execution_phrase!()); + eprintln!("Try '{} --help' for more information.", $crate::execution_phrase()); }) ); diff --git a/src/uucore/src/lib/mods/coreopts.rs b/src/uucore/src/lib/mods/coreopts.rs index b81ef7e5a..b534ff902 100644 --- a/src/uucore/src/lib/mods/coreopts.rs +++ b/src/uucore/src/lib/mods/coreopts.rs @@ -120,7 +120,7 @@ impl<'a> CoreOptions<'a> { macro_rules! app { ($syntax: expr, $summary: expr, $long_help: expr) => { uucore::coreopts::CoreOptions::new(uucore::coreopts::HelpText { - name: util_name!(), + name: uucore::util_name(), version: env!("CARGO_PKG_VERSION"), syntax: $syntax, summary: $summary, @@ -130,7 +130,7 @@ macro_rules! app { }; ($syntax: expr, $summary: expr, $long_help: expr, $display_usage: expr) => { uucore::coreopts::CoreOptions::new(uucore::coreopts::HelpText { - name: util_name!(), + name: uucore::util_name(), version: env!("CARGO_PKG_VERSION"), syntax: $syntax, summary: $summary, diff --git a/src/uucore_procs/src/lib.rs b/src/uucore_procs/src/lib.rs index 7f7460283..092a4a66c 100644 --- a/src/uucore_procs/src/lib.rs +++ b/src/uucore_procs/src/lib.rs @@ -104,7 +104,7 @@ pub fn gen_uumain(_args: TokenStream, stream: TokenStream) -> TokenStream { show_error!("{}", s); } if e.usage() { - eprintln!("Try '{} --help' for more information.", execution_phrase!()); + eprintln!("Try '{} --help' for more information.", uucore::execution_phrase()); } e.code() } From 6f6f6251e920cb6f1290f62bb2142a313cc82f98 Mon Sep 17 00:00:00 2001 From: Tyler Date: Wed, 18 Aug 2021 17:08:01 -0700 Subject: [PATCH 029/206] dd: apply @miDeb patch to address issue #2572 --- src/uu/dd/src/dd.rs | 302 ++++++------------ .../dd/src/dd_unit_tests/conversion_tests.rs | 4 +- src/uu/dd/src/dd_unit_tests/mod.rs | 2 +- 3 files changed, 109 insertions(+), 199 deletions(-) diff --git a/src/uu/dd/src/dd.rs b/src/uu/dd/src/dd.rs index b300dff2d..6be986649 100644 --- a/src/uu/dd/src/dd.rs +++ b/src/uu/dd/src/dd.rs @@ -275,13 +275,19 @@ impl Input { } } +trait OutputTrait: Sized + Write { + fn new(matches: &Matches) -> Result>; + fn fsync(&mut self) -> io::Result<()>; + fn fdatasync(&mut self) -> io::Result<()>; +} + struct Output { dst: W, obs: usize, cflags: OConvFlags, } -impl Output { +impl OutputTrait for Output { fn new(matches: &Matches) -> Result> { let obs = parseargs::parse_obs(matches)?; let cflags = parseargs::parse_conv_flag_output(matches)?; @@ -300,6 +306,100 @@ impl Output { } } +impl Output +where + Self: OutputTrait, +{ + fn write_blocks(&mut self, buf: Vec) -> io::Result { + let mut writes_complete = 0; + let mut writes_partial = 0; + let mut bytes_total = 0; + + for chunk in buf.chunks(self.obs) { + match self.write(chunk)? { + wlen if wlen < chunk.len() => { + writes_partial += 1; + bytes_total += wlen; + } + wlen => { + writes_complete += 1; + bytes_total += wlen; + } + } + } + + Ok(WriteStat { + writes_complete, + writes_partial, + bytes_total: bytes_total.try_into().unwrap_or(0u128), + }) + } + + fn dd_out(mut self, mut i: Input) -> Result<(), Box> { + let mut rstat = ReadStat { + reads_complete: 0, + reads_partial: 0, + records_truncated: 0, + }; + let mut wstat = WriteStat { + writes_complete: 0, + writes_partial: 0, + bytes_total: 0, + }; + let start = time::Instant::now(); + let bsize = calc_bsize(i.ibs, self.obs); + + let prog_tx = { + let (tx, rx) = mpsc::channel(); + thread::spawn(gen_prog_updater(rx, i.print_level)); + tx + }; + + while below_count_limit(&i.count, &rstat, &wstat) { + // Read/Write + let loop_bsize = calc_loop_bsize(&i.count, &rstat, &wstat, i.ibs, bsize); + match read_helper(&mut i, loop_bsize)? { + ( + ReadStat { + reads_complete: 0, + reads_partial: 0, + .. + }, + _, + ) => break, + (rstat_update, buf) => { + let wstat_update = self.write_blocks(buf)?; + + rstat += rstat_update; + wstat += wstat_update; + } + }; + // Update Prog + prog_tx.send(ProgUpdate { + read_stat: rstat, + write_stat: wstat, + duration: start.elapsed(), + })?; + } + + if self.cflags.fsync { + self.fsync()?; + } else if self.cflags.fdatasync { + self.fdatasync()?; + } + + match i.print_level { + Some(StatusLevel::Noxfer) | Some(StatusLevel::None) => {} + _ => print_transfer_stats(&ProgUpdate { + read_stat: rstat, + write_stat: wstat, + duration: start.elapsed(), + }), + } + Ok(()) + } +} + #[cfg(target_os = "linux")] fn make_linux_oflags(oflags: &OFlags) -> Option { let mut flag = 0; @@ -340,7 +440,7 @@ fn make_linux_oflags(oflags: &OFlags) -> Option { } } -impl Output { +impl OutputTrait for Output { fn new(matches: &Matches) -> Result> { fn open_dst(path: &Path, cflags: &OConvFlags, oflags: &OFlags) -> Result { let mut opts = OpenOptions::new(); @@ -430,62 +530,6 @@ impl Write for Output { } } -impl Output { - /// Write all data in the given buffer in writes of size obs. - fn write_blocks(&mut self, buf: Vec) -> io::Result { - let mut writes_complete = 0; - let mut writes_partial = 0; - let mut bytes_total = 0; - - for chunk in buf.chunks(self.obs) { - match self.write(chunk)? { - wlen if wlen < chunk.len() => { - writes_partial += 1; - bytes_total += wlen; - } - wlen => { - writes_complete += 1; - bytes_total += wlen; - } - } - } - - Ok(WriteStat { - writes_complete, - writes_partial, - bytes_total: bytes_total.try_into().unwrap_or(0u128), - }) - } -} - -impl Output { - /// Write all data in the given buffer in writes of size obs. - fn write_blocks(&mut self, buf: Vec) -> io::Result { - let mut writes_complete = 0; - let mut writes_partial = 0; - let mut bytes_total = 0; - - for chunk in buf.chunks(self.obs) { - match self.write(chunk)? { - wlen if wlen < chunk.len() => { - writes_partial += 1; - bytes_total += wlen; - } - wlen => { - writes_complete += 1; - bytes_total += wlen; - } - } - } - - Ok(WriteStat { - writes_complete, - writes_partial, - bytes_total: bytes_total.try_into().unwrap_or(0u128), - }) - } -} - /// Splits the content of buf into cbs-length blocks /// Appends padding as specified by conv=block and cbs=N /// Expects ascii encoded data @@ -827,140 +871,6 @@ fn below_count_limit(count: &Option, rstat: &ReadStat, wstat: &WriteS } } -/// Perform the copy/convert operations. Stdout version -/// Note: The body of this function should be kept identical to dd_fileout. This is definitely a problem from a maintenance perspective -/// and should be addressed (TODO). The problem exists because some of dd's functionality depends on whether the output is a file or stdout. -fn dd_stdout(mut i: Input, mut o: Output) -> Result<(), Box> { - let mut rstat = ReadStat { - reads_complete: 0, - reads_partial: 0, - records_truncated: 0, - }; - let mut wstat = WriteStat { - writes_complete: 0, - writes_partial: 0, - bytes_total: 0, - }; - let start = time::Instant::now(); - let bsize = calc_bsize(i.ibs, o.obs); - - let prog_tx = { - let (tx, rx) = mpsc::channel(); - thread::spawn(gen_prog_updater(rx, i.print_level)); - tx - }; - - while below_count_limit(&i.count, &rstat, &wstat) { - // Read/Write - let loop_bsize = calc_loop_bsize(&i.count, &rstat, &wstat, i.ibs, bsize); - match read_helper(&mut i, loop_bsize)? { - ( - ReadStat { - reads_complete: 0, - reads_partial: 0, - .. - }, - _, - ) => break, - (rstat_update, buf) => { - let wstat_update = o.write_blocks(buf)?; - - rstat += rstat_update; - wstat += wstat_update; - } - }; - // Update Prog - prog_tx.send(ProgUpdate { - read_stat: rstat, - write_stat: wstat, - duration: start.elapsed(), - })?; - } - - if o.cflags.fsync { - o.fsync()?; - } else if o.cflags.fdatasync { - o.fdatasync()?; - } - - match i.print_level { - Some(StatusLevel::Noxfer) | Some(StatusLevel::None) => {} - _ => print_transfer_stats(&ProgUpdate { - read_stat: rstat, - write_stat: wstat, - duration: start.elapsed(), - }), - } - Ok(()) -} - -/// Perform the copy/convert operations. File backed output version -/// Note: The body of this function should be kept identical to dd_stdout. This is definitely a problem from a maintenance perspective -/// and should be addressed (TODO). The problem exists because some of dd's functionality depends on whether the output is a file or stdout. -fn dd_fileout(mut i: Input, mut o: Output) -> Result<(), Box> { - let mut rstat = ReadStat { - reads_complete: 0, - reads_partial: 0, - records_truncated: 0, - }; - let mut wstat = WriteStat { - writes_complete: 0, - writes_partial: 0, - bytes_total: 0, - }; - let start = time::Instant::now(); - let bsize = calc_bsize(i.ibs, o.obs); - - let prog_tx = { - let (tx, rx) = mpsc::channel(); - thread::spawn(gen_prog_updater(rx, i.print_level)); - tx - }; - - while below_count_limit(&i.count, &rstat, &wstat) { - // Read/Write - let loop_bsize = calc_loop_bsize(&i.count, &rstat, &wstat, i.ibs, bsize); - match read_helper(&mut i, loop_bsize)? { - ( - ReadStat { - reads_complete: 0, - reads_partial: 0, - .. - }, - _, - ) => break, - (rstat_update, buf) => { - let wstat_update = o.write_blocks(buf)?; - - rstat += rstat_update; - wstat += wstat_update; - } - }; - // Update Prog - prog_tx.send(ProgUpdate { - read_stat: rstat, - write_stat: wstat, - duration: start.elapsed(), - })?; - } - - if o.cflags.fsync { - o.fsync()?; - } else if o.cflags.fdatasync { - o.fdatasync()?; - } - - match i.print_level { - Some(StatusLevel::Noxfer) | Some(StatusLevel::None) => {} - _ => print_transfer_stats(&ProgUpdate { - read_stat: rstat, - write_stat: wstat, - duration: start.elapsed(), - }), - } - Ok(()) -} - fn append_dashes_if_not_present(mut acc: Vec, mut s: String) -> Vec { if !s.starts_with("--") && !s.starts_with('-') { s.insert_str(0, "--"); @@ -1009,7 +919,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let (i, o) = unpack_or_rtn!(Input::::new(&matches), Output::::new(&matches)); - dd_fileout(i, o) + o.dd_out(i) } (false, true) => { let (i, o) = unpack_or_rtn!( @@ -1017,7 +927,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { Output::::new(&matches) ); - dd_fileout(i, o) + o.dd_out(i) } (true, false) => { let (i, o) = unpack_or_rtn!( @@ -1025,7 +935,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { Output::::new(&matches) ); - dd_stdout(i, o) + o.dd_out(i) } (false, false) => { let (i, o) = unpack_or_rtn!( @@ -1033,7 +943,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { Output::::new(&matches) ); - dd_stdout(i, o) + o.dd_out(i) } }; match result { diff --git a/src/uu/dd/src/dd_unit_tests/conversion_tests.rs b/src/uu/dd/src/dd_unit_tests/conversion_tests.rs index c4515d3a2..9255a1a89 100644 --- a/src/uu/dd/src/dd_unit_tests/conversion_tests.rs +++ b/src/uu/dd/src/dd_unit_tests/conversion_tests.rs @@ -153,7 +153,7 @@ fn all_valid_ascii_ebcdic_ascii_roundtrip_conv_test() { cflags: OConvFlags::default(), }; - dd_fileout(i, o).unwrap(); + o.dd_out(i).unwrap(); // EBCDIC->ASCII let test_name = "all-valid-ebcdic-to-ascii"; @@ -175,7 +175,7 @@ fn all_valid_ascii_ebcdic_ascii_roundtrip_conv_test() { cflags: OConvFlags::default(), }; - dd_fileout(i, o).unwrap(); + o.dd_out(i).unwrap(); // Final Comparison let res = File::open(&tmp_fname_ea).unwrap(); diff --git a/src/uu/dd/src/dd_unit_tests/mod.rs b/src/uu/dd/src/dd_unit_tests/mod.rs index 27b5a18ad..9641c9bba 100644 --- a/src/uu/dd/src/dd_unit_tests/mod.rs +++ b/src/uu/dd/src/dd_unit_tests/mod.rs @@ -67,7 +67,7 @@ macro_rules! make_spec_test ( #[test] fn $test_id() { - dd_fileout($i,$o).unwrap(); + $o.dd_out($i).unwrap(); let res = File::open($tmp_fname).unwrap(); // Check test file isn't empty (unless spec file is too) From a9bab842ecb06ba03df3bcb16f93b6db6d994187 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Thu, 19 Aug 2021 12:41:14 +0200 Subject: [PATCH 030/206] Use grcov latest again (grcov 0.8.2 fixed the take issue) --- .github/workflows/CICD.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/CICD.yml b/.github/workflows/CICD.yml index 90b836c0f..8a1e142df 100644 --- a/.github/workflows/CICD.yml +++ b/.github/workflows/CICD.yml @@ -625,7 +625,7 @@ jobs: uses: actions-rs/install@v0.1 with: crate: grcov - version: 0.8.0 + version: latest use-tool-cache: false - name: Generate coverage data (via `grcov`) id: coverage From 1eb7193ee89d031d5dd8538ceedcdc1106af14f5 Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Tue, 17 Aug 2021 15:42:31 +0200 Subject: [PATCH 031/206] wc: fix clippy lint --- src/uu/wc/src/wc.rs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/uu/wc/src/wc.rs b/src/uu/wc/src/wc.rs index ff586a6f6..d77cd6b4b 100644 --- a/src/uu/wc/src/wc.rs +++ b/src/uu/wc/src/wc.rs @@ -350,13 +350,8 @@ fn digit_width(input: &Input) -> WcResult> { fn max_width(inputs: &[Input]) -> usize { let mut result = 1; for input in inputs { - match digit_width(input) { - Ok(maybe_n) => { - if let Some(n) = maybe_n { - result = result.max(n); - } - } - Err(_) => continue, + if let Ok(Some(n)) = digit_width(input) { + result = result.max(n); } } result From 0062e54b5ed7a3fecfb760c306f8be4a77583e60 Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Fri, 20 Aug 2021 00:20:56 +0200 Subject: [PATCH 032/206] test_util_name: symlink the xecutable instead of copying Speculative fix for "text file busy" errors. --- tests/test_util_name.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/tests/test_util_name.rs b/tests/test_util_name.rs index 640fecd44..b0a78a2e8 100644 --- a/tests/test_util_name.rs +++ b/tests/test_util_name.rs @@ -2,6 +2,11 @@ mod common; use common::util::TestScenario; +#[cfg(unix)] +use std::os::unix::fs::symlink as symlink_file; +#[cfg(windows)] +use std::os::windows::fs::symlink_file; + #[test] #[cfg(feature = "ls")] fn execution_phrase_double() { @@ -24,7 +29,7 @@ fn execution_phrase_single() { use std::process::Command; let scenario = TestScenario::new("ls"); - std::fs::copy(scenario.bin_path, scenario.fixtures.plus("uu-ls")).unwrap(); + symlink_file(scenario.bin_path, scenario.fixtures.plus("uu-ls")).unwrap(); let output = Command::new(scenario.fixtures.plus("uu-ls")) .arg("--some-invalid-arg") .output() @@ -65,7 +70,7 @@ fn util_name_single() { }; let scenario = TestScenario::new("sort"); - std::fs::copy(scenario.bin_path, scenario.fixtures.plus("uu-sort")).unwrap(); + symlink_file(scenario.bin_path, scenario.fixtures.plus("uu-sort")).unwrap(); let mut child = Command::new(scenario.fixtures.plus("uu-sort")) .stdin(Stdio::piped()) .stderr(Stdio::piped()) From ecf59b173d84522abc10df5ca948b715315f90c8 Mon Sep 17 00:00:00 2001 From: Jeffrey Finkelstein Date: Sat, 21 Aug 2021 17:24:10 -0400 Subject: [PATCH 033/206] wc: fix collapsible match clippy warning --- src/uu/wc/src/wc.rs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/uu/wc/src/wc.rs b/src/uu/wc/src/wc.rs index 0bcc66664..ab14cb7d2 100644 --- a/src/uu/wc/src/wc.rs +++ b/src/uu/wc/src/wc.rs @@ -350,13 +350,8 @@ fn digit_width(input: &Input) -> WcResult> { fn max_width(inputs: &[Input]) -> usize { let mut result = 1; for input in inputs { - match digit_width(input) { - Ok(maybe_n) => { - if let Some(n) = maybe_n { - result = result.max(n); - } - } - Err(_) => continue, + if let Ok(Some(n)) = digit_width(input) { + result = result.max(n); } } result From 8337aeb4d6fda559fb095a9b688e43cadc303e61 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sat, 21 Aug 2021 22:02:49 +0200 Subject: [PATCH 034/206] bump a few crates to simplify Debian packaging --- Cargo.lock | 82 ++++++++++++++++++++++++++++++--------- src/uu/cp/Cargo.toml | 2 +- src/uu/csplit/Cargo.toml | 2 +- src/uu/env/Cargo.toml | 2 +- src/uu/env/src/env.rs | 2 +- src/uu/more/Cargo.toml | 2 +- src/uu/shred/Cargo.toml | 2 +- src/uu/shred/src/shred.rs | 8 ++-- src/uu/shuf/src/shuf.rs | 14 ++++--- src/uucore/Cargo.toml | 4 +- 10 files changed, 84 insertions(+), 36 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8cf7cddcb..88de76e8c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,6 +8,12 @@ version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" +[[package]] +name = "ahash" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "739f4a8db6605981345c5654f3a85b056ce52f37a39d34da03f25bf2151ea16e" + [[package]] name = "aho-corasick" version = "0.7.18" @@ -234,7 +240,7 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "853eda514c284c2287f4bf20ae614f8781f40a81d32ecda6e91449304dfe077c" dependencies = [ - "glob 0.3.0", + "glob", "libc", "libloading", ] @@ -294,7 +300,7 @@ dependencies = [ "clap", "conv", "filetime", - "glob 0.3.0", + "glob", "lazy_static", "libc", "nix 0.20.0", @@ -652,6 +658,15 @@ dependencies = [ "generic-array", ] +[[package]] +name = "dlv-list" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68df3f2b690c1b86e65ef7830956aededf3cb0a16f898f79b9a6f421a7b6211b" +dependencies = [ + "rand 0.8.4", +] + [[package]] name = "dns-lookup" version = "1.0.5" @@ -804,12 +819,6 @@ dependencies = [ "wasi 0.10.2+wasi-snapshot-preview1", ] -[[package]] -name = "glob" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8be18de09a56b60ed0edf84bc9df007e30040691af7acd1c41874faac5895bfb" - [[package]] name = "glob" version = "0.3.0" @@ -835,6 +844,15 @@ version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62aca2aba2d62b4a7f5b33f3712cb1b0692779a56fb510499d5c0aa594daeaf3" +[[package]] +name = "hashbrown" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04" +dependencies = [ + "ahash", +] + [[package]] name = "heck" version = "0.3.3" @@ -893,9 +911,9 @@ dependencies = [ [[package]] name = "ioctl-sys" -version = "0.5.2" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e2c4b26352496eaaa8ca7cfa9bd99e93419d3f7983dc6e99c2a35fe9e33504a" +checksum = "1c429fffa658f288669529fc26565f728489a2e39bc7b24a428aaaf51355182e" [[package]] name = "itertools" @@ -1066,6 +1084,18 @@ dependencies = [ "void", ] +[[package]] +name = "nix" +version = "0.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2ccba0cfe4fdf15982d1674c69b1fd80bad427d293849982668dfe454bd61f2" +dependencies = [ + "bitflags", + "cc", + "cfg-if 1.0.0", + "libc", +] + [[package]] name = "nix" version = "0.20.0" @@ -1185,6 +1215,16 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "ordered-multimap" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c672c7ad9ec066e428c00eb917124a06f08db19e2584de982cc34b1f4c12485" +dependencies = [ + "dlv-list", + "hashbrown", +] + [[package]] name = "ouroboros" version = "0.10.1" @@ -1604,9 +1644,13 @@ dependencies = [ [[package]] name = "rust-ini" -version = "0.13.0" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e52c148ef37f8c375d49d5a73aa70713125b7f19095948a923f80afdeb22ec2" +checksum = "63471c4aa97a1cf8332a5f97709a79a4234698de6a1f5087faf66f2dae810e22" +dependencies = [ + "cfg-if 1.0.0", + "ordered-multimap", +] [[package]] name = "rustc-hash" @@ -2065,7 +2109,7 @@ name = "uu_chown" version = "0.0.7" dependencies = [ "clap", - "glob 0.3.0", + "glob", "uucore", "uucore_procs", "walkdir", @@ -2121,7 +2165,7 @@ name = "uu_csplit" version = "0.0.7" dependencies = [ "clap", - "glob 0.2.11", + "glob", "regex", "thiserror", "uucore", @@ -2181,7 +2225,7 @@ name = "uu_dircolors" version = "0.0.7" dependencies = [ "clap", - "glob 0.3.0", + "glob", "uucore", "uucore_procs", ] @@ -2493,7 +2537,7 @@ dependencies = [ "atty", "clap", "crossterm", - "nix 0.13.1", + "nix 0.19.1", "redox_syscall", "redox_termios", "unicode-segmentation", @@ -2732,7 +2776,7 @@ dependencies = [ "clap", "filetime", "libc", - "rand 0.5.6", + "rand 0.7.3", "time", "uucore", "uucore_procs", @@ -3064,7 +3108,7 @@ dependencies = [ "getopts", "lazy_static", "libc", - "nix 0.13.1", + "nix 0.19.1", "platform-info", "termion", "thiserror", @@ -3139,7 +3183,7 @@ version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "035793abb854745033f01a07647a79831eba29ec0be377205f2a25b0aa830020" dependencies = [ - "glob 0.3.0", + "glob", ] [[package]] diff --git a/src/uu/cp/Cargo.toml b/src/uu/cp/Cargo.toml index b7b3809a7..ca292c9a1 100644 --- a/src/uu/cp/Cargo.toml +++ b/src/uu/cp/Cargo.toml @@ -28,7 +28,7 @@ uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_p walkdir = "2.2" [target.'cfg(target_os = "linux")'.dependencies] -ioctl-sys = "0.5.2" +ioctl-sys = "0.6" [target.'cfg(target_os = "windows")'.dependencies] winapi = { version="0.3", features=["fileapi"] } diff --git a/src/uu/csplit/Cargo.toml b/src/uu/csplit/Cargo.toml index 2ddc3b89c..82389a93b 100644 --- a/src/uu/csplit/Cargo.toml +++ b/src/uu/csplit/Cargo.toml @@ -18,7 +18,7 @@ path = "src/csplit.rs" clap = { version = "2.33", features = ["wrap_help"] } thiserror = "1.0" regex = "1.0.0" -glob = "0.2.11" +glob = "0.3" uucore = { version=">=0.0.9", package="uucore", path="../../uucore", features=["entries", "fs"] } uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_procs" } diff --git a/src/uu/env/Cargo.toml b/src/uu/env/Cargo.toml index 9f778aa3f..c368cfbac 100644 --- a/src/uu/env/Cargo.toml +++ b/src/uu/env/Cargo.toml @@ -17,7 +17,7 @@ path = "src/env.rs" [dependencies] clap = { version = "2.33", features = ["wrap_help"] } libc = "0.2.42" -rust-ini = "0.13.0" +rust-ini = "0.17.0" uucore = { version=">=0.0.9", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_procs" } diff --git a/src/uu/env/src/env.rs b/src/uu/env/src/env.rs index 51ff92801..9fcdf84ea 100644 --- a/src/uu/env/src/env.rs +++ b/src/uu/env/src/env.rs @@ -89,7 +89,7 @@ fn load_config_file(opts: &mut Options) -> Result<(), i32> { for (_, prop) in &conf { // ignore all INI section lines (treat them as comments) - for (key, value) in prop { + for (key, value) in prop.iter() { env::set_var(key, value); } } diff --git a/src/uu/more/Cargo.toml b/src/uu/more/Cargo.toml index 2d66dc950..d7bbe5c75 100644 --- a/src/uu/more/Cargo.toml +++ b/src/uu/more/Cargo.toml @@ -28,7 +28,7 @@ redox_termios = "0.1" redox_syscall = "0.2" [target.'cfg(all(unix, not(target_os = "fuchsia")))'.dependencies] -nix = "<=0.13" +nix = "0.19" [[bin]] name = "more" diff --git a/src/uu/shred/Cargo.toml b/src/uu/shred/Cargo.toml index 89ed980c1..f515044c0 100644 --- a/src/uu/shred/Cargo.toml +++ b/src/uu/shred/Cargo.toml @@ -18,7 +18,7 @@ path = "src/shred.rs" clap = { version = "2.33", features = ["wrap_help"] } filetime = "0.2.1" libc = "0.2.42" -rand = "0.5" +rand = "0.7" time = "0.1.40" uucore = { version=">=0.0.9", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_procs" } diff --git a/src/uu/shred/src/shred.rs b/src/uu/shred/src/shred.rs index 90336ea95..856908de2 100644 --- a/src/uu/shred/src/shred.rs +++ b/src/uu/shred/src/shred.rs @@ -9,7 +9,8 @@ // spell-checker:ignore (words) writeback wipesync use clap::{crate_version, App, Arg}; -use rand::{Rng, ThreadRng}; +use rand::prelude::SliceRandom; +use rand::Rng; use std::cell::{Cell, RefCell}; use std::fs; use std::fs::{File, OpenOptions}; @@ -119,7 +120,7 @@ struct BytesGenerator<'a> { block_size: usize, exact: bool, // if false, every block's size is block_size gen_type: PassType<'a>, - rng: Option>, + rng: Option>, bytes: [u8; BLOCK_SIZE], } @@ -499,7 +500,8 @@ fn wipe_file( for pattern in PATTERNS.iter().take(remainder) { pass_sequence.push(PassType::Pattern(pattern)); } - rand::thread_rng().shuffle(&mut pass_sequence[..]); // randomize the order of application + let mut rng = rand::thread_rng(); + pass_sequence.shuffle(&mut rng); // randomize the order of application let n_random = 3 + n_passes / 10; // Minimum 3 random passes; ratio of 10 after // Evenly space random passes; ensures one at the beginning and end diff --git a/src/uu/shuf/src/shuf.rs b/src/uu/shuf/src/shuf.rs index 4690d1c6e..f43e41338 100644 --- a/src/uu/shuf/src/shuf.rs +++ b/src/uu/shuf/src/shuf.rs @@ -240,10 +240,12 @@ fn shuf_bytes(input: &mut Vec<&[u8]>, opts: Options) { }); let mut rng = match opts.random_source { - Some(r) => WrappedRng::RngFile(rand::read::ReadRng::new(match File::open(&r[..]) { - Ok(f) => f, - Err(e) => crash!(1, "failed to open random source '{}': {}", &r[..], e), - })), + Some(r) => WrappedRng::RngFile(rand::rngs::adapter::ReadRng::new( + match File::open(&r[..]) { + Ok(f) => f, + Err(e) => crash!(1, "failed to open random source '{}': {}", &r[..], e), + }, + )), None => WrappedRng::RngDefault(rand::thread_rng()), }; @@ -299,8 +301,8 @@ fn parse_range(input_range: &str) -> Result<(usize, usize), String> { } enum WrappedRng { - RngFile(rand::read::ReadRng), - RngDefault(rand::ThreadRng), + RngFile(rand::rngs::adapter::ReadRng), + RngDefault(rand::rngs::ThreadRng), } impl WrappedRng { diff --git a/src/uucore/Cargo.toml b/src/uucore/Cargo.toml index 60399b9be..28cba3a61 100644 --- a/src/uucore/Cargo.toml +++ b/src/uucore/Cargo.toml @@ -19,11 +19,11 @@ path="src/lib/lib.rs" dns-lookup = { version="1.0.5", optional=true } dunce = "1.0.0" getopts = "<= 0.2.21" -wild = "2.0.4" +wild = "2.0" # * optional thiserror = { version="1.0", optional=true } lazy_static = { version="1.3", optional=true } -nix = { version="<= 0.13", optional=true } +nix = { version="<= 0.19", optional=true } platform-info = { version="<= 0.1", optional=true } time = { version="<= 0.1.43", optional=true } # * "problem" dependencies (pinned) From 5089214832348c7254b7ecb49f6720bcd74930bc Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sun, 22 Aug 2021 00:42:03 +0200 Subject: [PATCH 035/206] bump a few crates to simplify Debian packaging (second) --- Cargo.lock | 49 ++++++++++++++++++++++++++++++++++++------ Cargo.toml | 2 +- src/uu/uniq/Cargo.toml | 4 ++-- 3 files changed, 46 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 88de76e8c..b41affd7f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -256,7 +256,7 @@ dependencies = [ "bitflags", "strsim", "term_size", - "textwrap", + "textwrap 0.11.0", "unicode-width", "vec_map", ] @@ -311,7 +311,7 @@ dependencies = [ "selinux", "sha1", "tempfile", - "textwrap", + "textwrap 0.14.2", "time", "unindent", "unix_socket", @@ -1781,6 +1781,12 @@ version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e" +[[package]] +name = "smawk" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f67ad224767faa3c7d8b6d91985b78e70a1324408abcb1cfcc2be4c06bc06043" + [[package]] name = "socket2" version = "0.3.19" @@ -1806,15 +1812,15 @@ checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" [[package]] name = "strum" -version = "0.20.0" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7318c509b5ba57f18533982607f24070a55d353e90d4cae30c467cdb2ad5ac5c" +checksum = "aaf86bbcfd1fa9670b7a129f64fc0c9fcbbfe4f1bc4210e9e98fe71ffc12cde2" [[package]] name = "strum_macros" -version = "0.20.1" +version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee8bc6b87a5112aeeab1f4a9f7ab634fe6cbefc4850006df31267f4cfb9e3149" +checksum = "d06aaeeee809dbc59eb4556183dd927df67db1540de5be8d3ec0b6636358a5ec" dependencies = [ "heck", "proc-macro2", @@ -1881,6 +1887,16 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "terminal_size" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "633c1a546cee861a1a6d0dc69ebeca693bf4296661ba7852b9d21d159e0506df" +dependencies = [ + "libc", + "winapi 0.3.9", +] + [[package]] name = "termion" version = "1.5.6" @@ -1916,6 +1932,18 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "textwrap" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0066c8d12af8b5acd21e00547c3797fde4e8677254a7ee429176ccebbe93dd80" +dependencies = [ + "smawk", + "terminal_size", + "unicode-linebreak", + "unicode-width", +] + [[package]] name = "thiserror" version = "1.0.26" @@ -1952,6 +1980,15 @@ version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "879f6906492a7cd215bfa4cf595b600146ccfac0c79bcbd1f3000162af5e8b06" +[[package]] +name = "unicode-linebreak" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a52dcaab0c48d931f7cc8ef826fa51690a08e1ea55117ef26f89864f532383f" +dependencies = [ + "regex", +] + [[package]] name = "unicode-segmentation" version = "1.8.0" diff --git a/Cargo.toml b/Cargo.toml index abf6a42de..a7e191554 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -239,7 +239,7 @@ test = [ "uu_test" ] [dependencies] clap = { version = "2.33", features = ["wrap_help"] } lazy_static = { version="1.3" } -textwrap = { version="=0.11.0", features=["term_size"] } # !maint: [2020-05-10; rivy] unstable crate using undocumented features; pinned currently, will review +textwrap = { version="0.14", features=["terminal_size"] } uucore = { version=">=0.0.9", package="uucore", path="src/uucore" } selinux = { version="0.2.1", optional = true } # * uutils diff --git a/src/uu/uniq/Cargo.toml b/src/uu/uniq/Cargo.toml index aed487b59..856da9a63 100644 --- a/src/uu/uniq/Cargo.toml +++ b/src/uu/uniq/Cargo.toml @@ -16,8 +16,8 @@ path = "src/uniq.rs" [dependencies] clap = { version = "2.33", features = ["wrap_help"] } -strum = "0.20" -strum_macros = "0.20" +strum = "0.21" +strum_macros = "0.21" uucore = { version=">=0.0.9", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_procs" } From fbb81f7088c9ec5ff641ce4735667610ed7ba8ce Mon Sep 17 00:00:00 2001 From: Jeffrey Finkelstein Date: Sat, 21 Aug 2021 22:59:59 -0400 Subject: [PATCH 036/206] tests(head): change file mode from 755 to 644 --- tests/by-util/test_head.rs | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100755 => 100644 tests/by-util/test_head.rs diff --git a/tests/by-util/test_head.rs b/tests/by-util/test_head.rs old mode 100755 new mode 100644 From 4ef35d4a96260f7a45fa5cc4ba3a61d47136e610 Mon Sep 17 00:00:00 2001 From: jfinkels Date: Sun, 22 Aug 2021 15:01:17 -0400 Subject: [PATCH 037/206] tac: correct behavior of -b option (#2523) * tac: correct behavior of -b option Correct the behavior of `tac -b` to match that of GNU coreutils `tac`. Specifically, this changes `tac -b` to assume *leading* line separators instead of the default *trailing* line separators. Before this commit, the (incorrect) behavior was $ printf "/abc/def" | tac -b -s "/" def/abc/ After this commit, the behavior is $ printf "/abc/def" | tac -b -s "/" /def/abc Fixes #2262. * fixup! tac: correct behavior of -b option * fixup! tac: correct behavior of -b option Co-authored-by: Sylvestre Ledru --- src/uu/tac/src/tac.rs | 104 ++++++++---------- tests/by-util/test_tac.rs | 53 ++++++++- .../tac/delimited_primes_before.expected | 2 +- 3 files changed, 96 insertions(+), 63 deletions(-) diff --git a/src/uu/tac/src/tac.rs b/src/uu/tac/src/tac.rs index 67b361a76..4e1b38687 100644 --- a/src/uu/tac/src/tac.rs +++ b/src/uu/tac/src/tac.rs @@ -5,13 +5,13 @@ // * 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 +// spell-checker:ignore (ToDO) sbytes slen dlen #[macro_use] extern crate uucore; use clap::{crate_version, App, Arg}; -use std::io::{stdin, stdout, BufReader, Read, Stdout, Write}; +use std::io::{stdin, stdout, BufReader, Read, Write}; use std::{fs::File, path::Path}; use uucore::InvalidEncodingHandling; @@ -80,12 +80,49 @@ pub fn uu_app() -> App<'static, 'static> { .arg(Arg::with_name(options::FILE).hidden(true).multiple(true)) } -fn tac(filenames: Vec, before: bool, _: bool, separator: &str) -> i32 { - let mut exit_code = 0; +fn buffer_tac(data: &[u8], before: bool, separator: &str) -> std::io::Result<()> { let mut out = stdout(); + + // Convert the line separator to a byte sequence. let sbytes = separator.as_bytes(); let slen = sbytes.len(); + // If there are more characters in the separator than in the data, + // we can't possibly split the data on the separator. Write the + // entire buffer to stdout. + let dlen = data.len(); + if dlen < slen { + return out.write_all(data); + } + + // Iterate over each byte in the buffer in reverse. When we find a + // line separator, write the line to stdout. + // + // The `before` flag controls whether the line separator appears at + // the end of the line (as in "abc\ndef\n") or at the beginning of + // the line (as in "/abc/def"). + let mut following_line_start = data.len(); + for i in (0..dlen - slen + 1).rev() { + if &data[i..i + slen] == sbytes { + if before { + out.write_all(&data[i..following_line_start])?; + following_line_start = i; + } else { + out.write_all(&data[i + slen..following_line_start])?; + following_line_start = i + slen; + } + } + } + + // After the loop terminates, write whatever bytes are remaining at + // the beginning of the buffer. + out.write_all(&data[0..following_line_start])?; + Ok(()) +} + +fn tac(filenames: Vec, before: bool, _: 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 @@ -120,63 +157,8 @@ fn tac(filenames: Vec, before: bool, _: bool, separator: &str) -> i32 { continue; }; - // find offsets in string of all separators - let mut offsets = Vec::new(); - let mut i = 0; - loop { - if i + slen > data.len() { - break; - } - - if &data[i..i + slen] == sbytes { - offsets.push(i); - i += slen; - } else { - i += 1; - } - } - // If the file contains no line separators, then simply write - // the contents of the file directly to stdout. - if offsets.is_empty() { - out.write_all(&data) - .unwrap_or_else(|e| crash!(1, "failed to write to stdout: {}", e)); - return exit_code; - } - - // if there isn't a separator at the end of the file, fake it - if *offsets.last().unwrap() < data.len() - slen { - offsets.push(data.len()); - } - - let mut prev = *offsets.last().unwrap(); - let mut start = true; - for off in offsets.iter().rev().skip(1) { - // correctly handle case of no final separator in file - if start && prev == data.len() { - show_line(&mut out, &[], &data[*off + slen..prev], before); - start = false; - } else { - show_line(&mut out, sbytes, &data[*off + slen..prev], before); - } - prev = *off; - } - show_line(&mut out, sbytes, &data[0..prev], before); + buffer_tac(&data, before, separator) + .unwrap_or_else(|e| crash!(1, "failed to write to stdout: {}", e)); } - exit_code } - -fn show_line(out: &mut Stdout, sep: &[u8], dat: &[u8], before: bool) { - if before { - out.write_all(sep) - .unwrap_or_else(|e| crash!(1, "failed to write to stdout: {}", e)); - } - - out.write_all(dat) - .unwrap_or_else(|e| crash!(1, "failed to write to stdout: {}", e)); - - if !before { - out.write_all(sep) - .unwrap_or_else(|e| crash!(1, "failed to write to stdout: {}", e)); - } -} diff --git a/tests/by-util/test_tac.rs b/tests/by-util/test_tac.rs index 599bc19c7..c6e32fef0 100644 --- a/tests/by-util/test_tac.rs +++ b/tests/by-util/test_tac.rs @@ -1,3 +1,4 @@ +// spell-checker:ignore axxbxx bxxaxx use crate::common::util::*; #[test] @@ -23,7 +24,7 @@ fn test_stdin_non_newline_separator_before() { .args(&["-b", "-s", ":"]) .pipe_in("100:200:300:400:500") .run() - .stdout_is("500:400:300:200:100"); + .stdout_is(":500:400:300:200100"); } #[test] @@ -74,6 +75,56 @@ fn test_no_line_separators() { new_ucmd!().pipe_in("a").succeeds().stdout_is("a"); } +#[test] +fn test_before_trailing_separator_no_leading_separator() { + new_ucmd!() + .arg("-b") + .pipe_in("a\nb\n") + .succeeds() + .stdout_is("\n\nba"); +} + +#[test] +fn test_before_trailing_separator_and_leading_separator() { + new_ucmd!() + .arg("-b") + .pipe_in("\na\nb\n") + .succeeds() + .stdout_is("\n\nb\na"); +} + +#[test] +fn test_before_leading_separator_no_trailing_separator() { + new_ucmd!() + .arg("-b") + .pipe_in("\na\nb") + .succeeds() + .stdout_is("\nb\na"); +} + +#[test] +fn test_before_no_separator() { + new_ucmd!() + .arg("-b") + .pipe_in("ab") + .succeeds() + .stdout_is("ab"); +} + +#[test] +fn test_before_empty_file() { + new_ucmd!().arg("-b").pipe_in("").succeeds().stdout_is(""); +} + +#[test] +fn test_multi_char_separator() { + new_ucmd!() + .args(&["-s", "xx"]) + .pipe_in("axxbxx") + .succeeds() + .stdout_is("bxxaxx"); +} + #[test] fn test_null_separator() { new_ucmd!() diff --git a/tests/fixtures/tac/delimited_primes_before.expected b/tests/fixtures/tac/delimited_primes_before.expected index 13cb1be06..1417a0150 100644 --- a/tests/fixtures/tac/delimited_primes_before.expected +++ b/tests/fixtures/tac/delimited_primes_before.expected @@ -1 +1 @@ -97:89:83:79:73:71:67:61:59:53:47:43:41:37:31:29:23:19:17:13:11:7:5:3:2 \ No newline at end of file +:97:89:83:79:73:71:67:61:59:53:47:43:41:37:31:29:23:19:17:13:11:7:5:32 \ No newline at end of file From 7010dfd9390f0a97434b1d6785e9df7bbbc2ded5 Mon Sep 17 00:00:00 2001 From: Koutheir Attouchi Date: Thu, 19 Aug 2021 21:38:57 -0400 Subject: [PATCH 038/206] runcon: added implementation and tests. --- .../cspell.dictionaries/shell.wordlist.txt | 1 + .../workspace.wordlist.txt | 1 + Cargo.lock | 22 +- Cargo.toml | 4 +- GNUmakefile | 4 +- README.md | 5 +- src/uu/runcon/Cargo.toml | 27 ++ src/uu/runcon/src/errors.rs | 73 +++ src/uu/runcon/src/main.rs | 1 + src/uu/runcon/src/runcon.rs | 450 ++++++++++++++++++ tests/by-util/test_runcon.rs | 151 ++++++ 11 files changed, 731 insertions(+), 8 deletions(-) create mode 100644 src/uu/runcon/Cargo.toml create mode 100644 src/uu/runcon/src/errors.rs create mode 100644 src/uu/runcon/src/main.rs create mode 100644 src/uu/runcon/src/runcon.rs create mode 100644 tests/by-util/test_runcon.rs diff --git a/.vscode/cspell.dictionaries/shell.wordlist.txt b/.vscode/cspell.dictionaries/shell.wordlist.txt index 88ecee35b..07c2364ac 100644 --- a/.vscode/cspell.dictionaries/shell.wordlist.txt +++ b/.vscode/cspell.dictionaries/shell.wordlist.txt @@ -91,6 +91,7 @@ rerast rollup sed selinuxenabled +sestatus wslpath xargs diff --git a/.vscode/cspell.dictionaries/workspace.wordlist.txt b/.vscode/cspell.dictionaries/workspace.wordlist.txt index b506f6847..9c4b1c82f 100644 --- a/.vscode/cspell.dictionaries/workspace.wordlist.txt +++ b/.vscode/cspell.dictionaries/workspace.wordlist.txt @@ -162,6 +162,7 @@ blocksize canonname chroot dlsym +execvp fdatasync freeaddrinfo getaddrinfo diff --git a/Cargo.lock b/Cargo.lock index b41affd7f..2a5b46153 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -125,9 +125,9 @@ checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" [[package]] name = "bitflags" -version = "1.2.1" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitvec" @@ -384,6 +384,7 @@ dependencies = [ "uu_relpath", "uu_rm", "uu_rmdir", + "uu_runcon", "uu_seq", "uu_shred", "uu_shuf", @@ -1675,9 +1676,9 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] name = "selinux" -version = "0.2.1" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1aa2f705dd871c2eb90888bb2d44b13218b34f5c7318c3971df62f799d0143eb" +checksum = "1cf704a543fe60d898f3253f1cc37655d0f0e9cdb68ef6230557e0e031b80608" dependencies = [ "bitflags", "libc", @@ -2795,6 +2796,19 @@ dependencies = [ "uucore_procs", ] +[[package]] +name = "uu_runcon" +version = "0.0.7" +dependencies = [ + "clap", + "fts-sys", + "libc", + "selinux", + "thiserror", + "uucore", + "uucore_procs", +] + [[package]] name = "uu_seq" version = "0.0.7" diff --git a/Cargo.toml b/Cargo.toml index a7e191554..dd500214f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -189,6 +189,7 @@ feat_require_unix_utmpx = [ # "feat_require_selinux" == set of utilities depending on SELinux. feat_require_selinux = [ "chcon", + "runcon", ] ## (alternate/newer/smaller platforms) feature sets # "feat_os_unix_fuchsia" == set of utilities which can be built/run on the "Fuchsia" OS (refs: ; ) @@ -241,7 +242,7 @@ clap = { version = "2.33", features = ["wrap_help"] } lazy_static = { version="1.3" } textwrap = { version="0.14", features=["terminal_size"] } uucore = { version=">=0.0.9", package="uucore", path="src/uucore" } -selinux = { version="0.2.1", optional = true } +selinux = { version="0.2.3", optional = true } # * uutils uu_test = { optional=true, version="0.0.7", package="uu_test", path="src/uu/test" } # @@ -313,6 +314,7 @@ realpath = { optional=true, version="0.0.7", package="uu_realpath", path="src/uu relpath = { optional=true, version="0.0.7", package="uu_relpath", path="src/uu/relpath" } rm = { optional=true, version="0.0.7", package="uu_rm", path="src/uu/rm" } rmdir = { optional=true, version="0.0.7", package="uu_rmdir", path="src/uu/rmdir" } +runcon = { optional=true, version="0.0.7", package="uu_runcon", path="src/uu/runcon" } seq = { optional=true, version="0.0.7", package="uu_seq", path="src/uu/seq" } shred = { optional=true, version="0.0.7", package="uu_shred", path="src/uu/shred" } shuf = { optional=true, version="0.0.7", package="uu_shuf", path="src/uu/shuf" } diff --git a/GNUmakefile b/GNUmakefile index 4c550dadc..367568ca8 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -157,7 +157,8 @@ UNIX_PROGS := \ who SELINUX_PROGS := \ - chcon + chcon \ + runcon ifneq ($(OS),Windows_NT) PROGS := $(PROGS) $(UNIX_PROGS) @@ -216,6 +217,7 @@ TEST_PROGS := \ realpath \ rm \ rmdir \ + runcon \ seq \ sort \ split \ diff --git a/README.md b/README.md index bc3cc0a98..7e420bb33 100644 --- a/README.md +++ b/README.md @@ -365,8 +365,8 @@ To contribute to uutils, please see [CONTRIBUTING](CONTRIBUTING.md). | Done | Semi-Done | To Do | |-----------|-----------|--------| -| arch | cp | runcon | -| base32 | date | stty | +| arch | cp | stty | +| base32 | date | | | base64 | dd | | | basename | df | | | basenc | expr | | @@ -426,6 +426,7 @@ To contribute to uutils, please see [CONTRIBUTING](CONTRIBUTING.md). | relpath | | | | rm | | | | rmdir | | | +| runcon | | | | seq | | | | shred | | | | shuf | | | diff --git a/src/uu/runcon/Cargo.toml b/src/uu/runcon/Cargo.toml new file mode 100644 index 000000000..4e4c0bed6 --- /dev/null +++ b/src/uu/runcon/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "uu_runcon" +version = "0.0.7" +authors = ["uutils developers"] +license = "MIT" +description = "runcon ~ (uutils) run command with specified security context" +homepage = "https://github.com/uutils/coreutils" +repository = "https://github.com/uutils/coreutils/tree/master/src/uu/runcon" +keywords = ["coreutils", "uutils", "cli", "utility"] +categories = ["command-line-utilities"] +edition = "2018" + +[lib] +path = "src/runcon.rs" + +[dependencies] +clap = { version = "2.33", features = ["wrap_help"] } +uucore = { version = ">=0.0.9", package="uucore", path="../../uucore", features=["entries", "fs", "perms"] } +uucore_procs = { version = ">=0.0.6", package="uucore_procs", path="../../uucore_procs" } +selinux = { version = "0.2" } +fts-sys = { version = "0.2" } +thiserror = { version = "1.0" } +libc = { version = "0.2" } + +[[bin]] +name = "runcon" +path = "src/main.rs" diff --git a/src/uu/runcon/src/errors.rs b/src/uu/runcon/src/errors.rs new file mode 100644 index 000000000..bc10a2f3e --- /dev/null +++ b/src/uu/runcon/src/errors.rs @@ -0,0 +1,73 @@ +use std::ffi::OsString; +use std::fmt::Write; +use std::io; +use std::str::Utf8Error; + +pub(crate) type Result = std::result::Result; + +#[derive(thiserror::Error, Debug)] +pub(crate) enum Error { + #[error("No command is specified")] + MissingCommand, + + #[error("SELinux is not enabled")] + SELinuxNotEnabled, + + #[error(transparent)] + NotUTF8(#[from] Utf8Error), + + #[error(transparent)] + CommandLine(#[from] clap::Error), + + #[error("{operation} failed")] + SELinux { + operation: &'static str, + source: selinux::errors::Error, + }, + + #[error("{operation} failed")] + Io { + operation: &'static str, + source: io::Error, + }, + + #[error("{operation} failed on '{}'", .operand1.to_string_lossy())] + Io1 { + operation: &'static str, + operand1: OsString, + source: io::Error, + }, +} + +impl Error { + pub(crate) fn from_io(operation: &'static str, source: io::Error) -> Self { + Self::Io { operation, source } + } + + pub(crate) fn from_io1( + operation: &'static str, + operand1: impl Into, + source: io::Error, + ) -> Self { + Self::Io1 { + operation, + operand1: operand1.into(), + source, + } + } + + pub(crate) fn from_selinux(operation: &'static str, source: selinux::errors::Error) -> Self { + Self::SELinux { operation, source } + } +} + +pub(crate) fn report_full_error(mut err: &dyn std::error::Error) -> String { + let mut desc = String::with_capacity(256); + write!(&mut desc, "{}", err).unwrap(); + while let Some(source) = err.source() { + err = source; + write!(&mut desc, ": {}", err).unwrap(); + } + desc.push('.'); + desc +} diff --git a/src/uu/runcon/src/main.rs b/src/uu/runcon/src/main.rs new file mode 100644 index 000000000..86aae54e5 --- /dev/null +++ b/src/uu/runcon/src/main.rs @@ -0,0 +1 @@ +uucore_procs::main!(uu_runcon); diff --git a/src/uu/runcon/src/runcon.rs b/src/uu/runcon/src/runcon.rs new file mode 100644 index 000000000..b2f1468bd --- /dev/null +++ b/src/uu/runcon/src/runcon.rs @@ -0,0 +1,450 @@ +// spell-checker:ignore (vars) RFILE + +use uucore::{show_error, show_usage_error}; + +use clap::{App, Arg}; +use selinux::{OpaqueSecurityContext, SecurityClass, SecurityContext}; + +use std::borrow::Cow; +use std::ffi::{CStr, CString, OsStr, OsString}; +use std::os::raw::c_char; +use std::os::unix::ffi::OsStrExt; +use std::{io, ptr}; + +mod errors; + +use errors::{report_full_error, Error, Result}; + +const VERSION: &str = env!("CARGO_PKG_VERSION"); +const ABOUT: &str = "Run command with specified security context."; +const DESCRIPTION: &str = "Run COMMAND with completely-specified CONTEXT, or with current or \ + transitioned security context modified by one or more of \ + LEVEL, ROLE, TYPE, and USER.\n\n\ + If none of --compute, --type, --user, --role or --range is specified, \ + then the first argument is used as the complete context.\n\n\ + Note that only carefully-chosen contexts are likely to successfully run.\n\n\ + With neither CONTEXT nor COMMAND are specified, \ + then this prints the current security context."; + +pub mod options { + pub const COMPUTE: &str = "compute"; + + pub const USER: &str = "user"; + pub const ROLE: &str = "role"; + pub const TYPE: &str = "type"; + pub const RANGE: &str = "range"; +} + +// This list is NOT exhaustive. This command might perform an `execvp()` to run +// a different program. When that happens successfully, the exit status of this +// process will be the exit status of that program. +mod error_exit_status { + pub const SUCCESS: i32 = libc::EXIT_SUCCESS; + pub const NOT_FOUND: i32 = 127; + pub const COULD_NOT_EXECUTE: i32 = 126; + pub const ANOTHER_ERROR: i32 = libc::EXIT_FAILURE; +} + +fn get_usage() -> String { + format!( + "{0} [CONTEXT COMMAND [ARG...]]\n \ + {0} [-c] [-u USER] [-r ROLE] [-t TYPE] [-l RANGE] COMMAND [ARG...]", + uucore::execution_phrase() + ) +} + +pub fn uumain(args: impl uucore::Args) -> i32 { + let usage = get_usage(); + + let config = uu_app().usage(usage.as_ref()); + + let options = match parse_command_line(config, args) { + Ok(r) => r, + Err(r) => { + if let Error::CommandLine(ref r) = r { + match r.kind { + clap::ErrorKind::HelpDisplayed | clap::ErrorKind::VersionDisplayed => { + println!("{}", r); + return error_exit_status::SUCCESS; + } + _ => {} + } + } + + show_usage_error!("{}.\n", r); + return error_exit_status::ANOTHER_ERROR; + } + }; + + match &options.mode { + CommandLineMode::Print => { + if let Err(r) = print_current_context() { + show_error!("{}", report_full_error(&r)); + return error_exit_status::ANOTHER_ERROR; + } + } + + CommandLineMode::PlainContext { context, command } => { + let (exit_status, err) = + if let Err(err) = get_plain_context(context).and_then(set_next_exec_context) { + (error_exit_status::ANOTHER_ERROR, err) + } else { + // On successful execution, the following call never returns, + // and this process image is replaced. + execute_command(command, &options.arguments) + }; + + show_error!("{}", report_full_error(&err)); + return exit_status; + } + + CommandLineMode::CustomContext { + compute_transition_context, + user, + role, + the_type, + range, + command, + } => { + if let Some(command) = command { + let (exit_status, err) = if let Err(err) = get_custom_context( + *compute_transition_context, + user.as_deref(), + role.as_deref(), + the_type.as_deref(), + range.as_deref(), + command, + ) + .and_then(set_next_exec_context) + { + (error_exit_status::ANOTHER_ERROR, err) + } else { + // On successful execution, the following call never returns, + // and this process image is replaced. + execute_command(command, &options.arguments) + }; + + show_error!("{}", report_full_error(&err)); + return exit_status; + } else if let Err(r) = print_current_context() { + show_error!("{}", report_full_error(&r)); + return error_exit_status::ANOTHER_ERROR; + } + } + } + + error_exit_status::SUCCESS +} + +pub fn uu_app() -> App<'static, 'static> { + App::new(uucore::util_name()) + .version(VERSION) + .about(ABOUT) + .after_help(DESCRIPTION) + .arg( + Arg::with_name(options::COMPUTE) + .short("c") + .long(options::COMPUTE) + .takes_value(false) + .help("Compute process transition context before modifying."), + ) + .arg( + Arg::with_name(options::USER) + .short("u") + .long(options::USER) + .takes_value(true) + .value_name("USER") + .help("Set user USER in the target security context."), + ) + .arg( + Arg::with_name(options::ROLE) + .short("r") + .long(options::ROLE) + .takes_value(true) + .value_name("ROLE") + .help("Set role ROLE in the target security context."), + ) + .arg( + Arg::with_name(options::TYPE) + .short("t") + .long(options::TYPE) + .takes_value(true) + .value_name("TYPE") + .help("Set type TYPE in the target security context."), + ) + .arg( + Arg::with_name(options::RANGE) + .short("l") + .long(options::RANGE) + .takes_value(true) + .value_name("RANGE") + .help("Set range RANGE in the target security context."), + ) + .arg(Arg::with_name("ARG").multiple(true)) + // Once "ARG" is parsed, everything after that belongs to it. + // + // This is not how POSIX does things, but this is how the GNU implementation + // parses its command line. + .setting(clap::AppSettings::TrailingVarArg) +} + +#[derive(Debug)] +enum CommandLineMode { + Print, + + PlainContext { + context: OsString, + command: OsString, + }, + + CustomContext { + /// Compute process transition context before modifying. + compute_transition_context: bool, + + /// Use the current context with the specified user. + user: Option, + + /// Use the current context with the specified role. + role: Option, + + /// Use the current context with the specified type. + the_type: Option, + + /// Use the current context with the specified range. + range: Option, + + // `command` can be `None`, in which case we're dealing with this syntax: + // runcon [-c] [-u USER] [-r ROLE] [-t TYPE] [-l RANGE] + // + // This syntax is undocumented, but it is accepted by the GNU implementation, + // so we do the same for compatibility. + command: Option, + }, +} + +#[derive(Debug)] +struct Options { + mode: CommandLineMode, + arguments: Vec, +} + +fn parse_command_line(config: App, args: impl uucore::Args) -> Result { + let matches = config.get_matches_from_safe(args)?; + + let compute_transition_context = matches.is_present(options::COMPUTE); + + let mut args = matches + .values_of_os("ARG") + .unwrap_or_default() + .map(OsString::from); + + if compute_transition_context + || matches.is_present(options::USER) + || matches.is_present(options::ROLE) + || matches.is_present(options::TYPE) + || matches.is_present(options::RANGE) + { + // runcon [-c] [-u USER] [-r ROLE] [-t TYPE] [-l RANGE] [COMMAND [args]] + + let mode = CommandLineMode::CustomContext { + compute_transition_context, + user: matches.value_of_os(options::USER).map(Into::into), + role: matches.value_of_os(options::ROLE).map(Into::into), + the_type: matches.value_of_os(options::TYPE).map(Into::into), + range: matches.value_of_os(options::RANGE).map(Into::into), + command: args.next(), + }; + + Ok(Options { + mode, + arguments: args.collect(), + }) + } else if let Some(context) = args.next() { + // runcon CONTEXT COMMAND [args] + + args.next() + .ok_or(Error::MissingCommand) + .map(move |command| Options { + mode: CommandLineMode::PlainContext { context, command }, + arguments: args.collect(), + }) + } else { + // runcon + + Ok(Options { + mode: CommandLineMode::Print, + arguments: Vec::default(), + }) + } +} + +fn print_current_context() -> Result<()> { + let op = "Getting security context of the current process"; + let context = SecurityContext::current(false).map_err(|r| Error::from_selinux(op, r))?; + + let context = context + .to_c_string() + .map_err(|r| Error::from_selinux(op, r))?; + + if let Some(context) = context { + let context = context.as_ref().to_str()?; + println!("{}", context); + } else { + println!(); + } + Ok(()) +} + +fn set_next_exec_context(context: OpaqueSecurityContext) -> Result<()> { + let c_context = context + .to_c_string() + .map_err(|r| Error::from_selinux("Creating new context", r))?; + + let sc = SecurityContext::from_c_str(&c_context, false); + + if sc.check() != Some(true) { + let ctx = OsStr::from_bytes(c_context.as_bytes()); + let err = io::ErrorKind::InvalidInput.into(); + return Err(Error::from_io1("Checking security context", ctx, err)); + } + + sc.set_for_next_exec() + .map_err(|r| Error::from_selinux("Setting new security context", r)) +} + +fn get_plain_context(context: &OsStr) -> Result { + if selinux::kernel_support() == selinux::KernelSupport::Unsupported { + return Err(Error::SELinuxNotEnabled); + } + + let c_context = os_str_to_c_string(context)?; + + OpaqueSecurityContext::from_c_str(&c_context) + .map_err(|r| Error::from_selinux("Creating new context", r)) +} + +fn get_transition_context(command: &OsStr) -> Result { + // Generate context based on process transition. + let sec_class = SecurityClass::from_name("process") + .map_err(|r| Error::from_selinux("Getting process security class", r))?; + + // Get context of file to be executed. + let file_context = match SecurityContext::of_path(command, true, false) { + Ok(Some(context)) => context, + + Ok(None) => { + let err = io::Error::from_raw_os_error(libc::ENODATA); + return Err(Error::from_io1("getfilecon", command, err)); + } + + Err(r) => { + let op = "Getting security context of command file"; + return Err(Error::from_selinux(op, r)); + } + }; + + let process_context = SecurityContext::current(false) + .map_err(|r| Error::from_selinux("Getting security context of the current process", r))?; + + // Compute result of process transition. + process_context + .of_labeling_decision(&file_context, sec_class, "") + .map_err(|r| Error::from_selinux("Computing result of process transition", r)) +} + +fn get_initial_custom_opaque_context( + compute_transition_context: bool, + command: &OsStr, +) -> Result { + let context = if compute_transition_context { + get_transition_context(command)? + } else { + SecurityContext::current(false).map_err(|r| { + Error::from_selinux("Getting security context of the current process", r) + })? + }; + + let c_context = context + .to_c_string() + .map_err(|r| Error::from_selinux("Getting security context", r))? + .unwrap_or_else(|| Cow::Owned(CString::default())); + + OpaqueSecurityContext::from_c_str(c_context.as_ref()) + .map_err(|r| Error::from_selinux("Creating new context", r)) +} + +fn get_custom_context( + compute_transition_context: bool, + user: Option<&OsStr>, + role: Option<&OsStr>, + the_type: Option<&OsStr>, + range: Option<&OsStr>, + command: &OsStr, +) -> Result { + use OpaqueSecurityContext as OSC; + type SetNewValueProc = fn(&OSC, &CStr) -> selinux::errors::Result<()>; + + if selinux::kernel_support() == selinux::KernelSupport::Unsupported { + return Err(Error::SELinuxNotEnabled); + } + + let osc = get_initial_custom_opaque_context(compute_transition_context, command)?; + + let list: &[(Option<&OsStr>, SetNewValueProc, &'static str)] = &[ + (user, OSC::set_user, "Setting security context user"), + (role, OSC::set_role, "Setting security context role"), + (the_type, OSC::set_type, "Setting security context type"), + (range, OSC::set_range, "Setting security context range"), + ]; + + for &(new_value, method, op) in list { + if let Some(new_value) = new_value { + let c_new_value = os_str_to_c_string(new_value)?; + method(&osc, &c_new_value).map_err(|r| Error::from_selinux(op, r))?; + } + } + Ok(osc) +} + +/// The actual return type of this function should be `Result` +/// However, until the *never* type is stabilized, one way to indicate to the +/// compiler the only valid return type is to say "if this returns, it will +/// always return an error". +fn execute_command(command: &OsStr, arguments: &[OsString]) -> (i32, Error) { + let c_command = match os_str_to_c_string(command) { + Ok(v) => v, + Err(r) => return (error_exit_status::ANOTHER_ERROR, r), + }; + + let argv_storage: Vec = match arguments + .iter() + .map(AsRef::as_ref) + .map(os_str_to_c_string) + .collect::>() + { + Ok(v) => v, + Err(r) => return (error_exit_status::ANOTHER_ERROR, r), + }; + + let mut argv: Vec<*const c_char> = Vec::with_capacity(arguments.len().saturating_add(2)); + argv.push(c_command.as_ptr()); + argv.extend(argv_storage.iter().map(AsRef::as_ref).map(CStr::as_ptr)); + argv.push(ptr::null()); + + unsafe { libc::execvp(c_command.as_ptr(), argv.as_ptr()) }; + + let err = io::Error::last_os_error(); + let exit_status = if err.kind() == io::ErrorKind::NotFound { + error_exit_status::NOT_FOUND + } else { + error_exit_status::COULD_NOT_EXECUTE + }; + + let err = Error::from_io1("Executing command", command, err); + (exit_status, err) +} + +fn os_str_to_c_string(s: &OsStr) -> Result { + CString::new(s.as_bytes()) + .map_err(|_r| Error::from_io("CString::new()", io::ErrorKind::InvalidInput.into())) +} diff --git a/tests/by-util/test_runcon.rs b/tests/by-util/test_runcon.rs new file mode 100644 index 000000000..047ce5769 --- /dev/null +++ b/tests/by-util/test_runcon.rs @@ -0,0 +1,151 @@ +// spell-checker:ignore (jargon) xattributes + +#![cfg(feature = "feat_selinux")] + +use crate::common::util::*; + +// TODO: Check the implementation of `--compute` somehow. + +#[test] +fn version() { + new_ucmd!().arg("--version").succeeds(); + new_ucmd!().arg("-V").succeeds(); +} + +#[test] +fn help() { + new_ucmd!().arg("--help").succeeds(); + new_ucmd!().arg("-h").succeeds(); +} + +#[test] +fn print() { + new_ucmd!().succeeds(); + + for &flag in &["-c", "--compute"] { + new_ucmd!().arg(flag).succeeds(); + } + + for &flag in &[ + "-t", "--type", "-u", "--user", "-r", "--role", "-l", "--range", + ] { + new_ucmd!().args(&[flag, "example"]).succeeds(); + new_ucmd!().args(&[flag, "example1,example2"]).succeeds(); + } +} + +#[test] +fn invalid() { + new_ucmd!().arg("invalid").fails().code_is(1); + + let args = &[ + "unconfined_u:unconfined_r:unconfined_t:s0", + "inexistent-file", + ]; + new_ucmd!().args(args).fails().code_is(127); + + let args = &["invalid", "/bin/true"]; + new_ucmd!().args(args).fails().code_is(1); + + let args = &["--compute", "inexistent-file"]; + new_ucmd!().args(args).fails().code_is(1); + + let args = &["--compute", "--compute"]; + new_ucmd!().args(args).fails().code_is(1); + + // clap has an issue that makes this test fail: https://github.com/clap-rs/clap/issues/1543 + // TODO: Enable this code once the issue is fixed in the clap version we're using. + //new_ucmd!().arg("--compute=example").fails().code_is(1); + + for &flag in &[ + "-t", "--type", "-u", "--user", "-r", "--role", "-l", "--range", + ] { + new_ucmd!().arg(flag).fails().code_is(1); + + let args = &[flag, "example", flag, "example"]; + new_ucmd!().args(args).fails().code_is(1); + } +} + +#[test] +fn plain_context() { + let ctx = "unconfined_u:unconfined_r:unconfined_t:s0-s0"; + new_ucmd!().args(&[ctx, "/bin/true"]).succeeds(); + new_ucmd!().args(&[ctx, "/bin/false"]).fails().code_is(1); + + let output = new_ucmd!().args(&[ctx, "sestatus", "-v"]).succeeds(); + let r = get_sestatus_context(output.stdout()); + assert_eq!(r, "unconfined_u:unconfined_r:unconfined_t:s0"); + + let ctx = "system_u:unconfined_r:unconfined_t:s0-s0"; + new_ucmd!().args(&[ctx, "/bin/true"]).succeeds(); + + let ctx = "system_u:system_r:unconfined_t:s0"; + let output = new_ucmd!().args(&[ctx, "sestatus", "-v"]).succeeds(); + assert_eq!(get_sestatus_context(output.stdout()), ctx); +} + +#[test] +fn custom_context() { + let t_ud = "unconfined_t"; + let u_ud = "unconfined_u"; + let r_ud = "unconfined_r"; + + new_ucmd!().args(&["--compute", "/bin/true"]).succeeds(); + + let args = &["--compute", "/bin/false"]; + new_ucmd!().args(args).fails().code_is(1); + + let args = &["--type", t_ud, "/bin/true"]; + new_ucmd!().args(args).succeeds(); + + let args = &["--compute", "--type", t_ud, "/bin/true"]; + new_ucmd!().args(args).succeeds(); + + let args = &["--user=system_u", "/bin/true"]; + new_ucmd!().args(args).succeeds(); + + let args = &["--compute", "--user=system_u", "/bin/true"]; + new_ucmd!().args(args).succeeds(); + + let args = &["--role=system_r", "/bin/true"]; + new_ucmd!().args(args).succeeds(); + + let args = &["--compute", "--role=system_r", "/bin/true"]; + new_ucmd!().args(args).succeeds(); + + new_ucmd!().args(&["--range=s0", "/bin/true"]).succeeds(); + + let args = &["--compute", "--range=s0", "/bin/true"]; + new_ucmd!().args(args).succeeds(); + + for &(ctx, u, r) in &[ + ("unconfined_u:unconfined_r:unconfined_t:s0", u_ud, r_ud), + ("system_u:unconfined_r:unconfined_t:s0", "system_u", r_ud), + ("unconfined_u:system_r:unconfined_t:s0", u_ud, "system_r"), + ("system_u:system_r:unconfined_t:s0", "system_u", "system_r"), + ] { + let args = &["-t", t_ud, "-u", u, "-r", r, "-l", "s0", "sestatus", "-v"]; + + let output = new_ucmd!().args(args).succeeds(); + assert_eq!(get_sestatus_context(output.stdout()), ctx); + } +} + +fn get_sestatus_context(output: &[u8]) -> &str { + let re = regex::bytes::Regex::new(r#"Current context:\s*(\S+)\s*"#) + .expect("Invalid regular expression"); + + output + .split(|&b| b == b'\n') + .find(|&b| b.starts_with(b"Current context:")) + .and_then(|line| { + re.captures_iter(line) + .next() + .and_then(|c| c.get(1)) + .as_ref() + .map(regex::bytes::Match::as_bytes) + }) + .and_then(|bytes| std::str::from_utf8(bytes).ok()) + .expect("Output of sestatus is unexpected") +} From 554d53c0edd1d3513eb615ea15e95aed389d155e Mon Sep 17 00:00:00 2001 From: David Carlier Date: Mon, 23 Aug 2021 16:21:18 +0100 Subject: [PATCH 039/206] uucore netbsd update fsext build fix and utmpx implementation proposal. --- src/uucore/src/lib/features/fsext.rs | 38 ++++++++++++++++++++-------- src/uucore/src/lib/features/utmpx.rs | 26 ++++++++++++++++++- 2 files changed, 52 insertions(+), 12 deletions(-) diff --git a/src/uucore/src/lib/features/fsext.rs b/src/uucore/src/lib/features/fsext.rs index 56e078c56..75375c7e2 100644 --- a/src/uucore/src/lib/features/fsext.rs +++ b/src/uucore/src/lib/features/fsext.rs @@ -309,9 +309,9 @@ impl MountInfo { } } -#[cfg(any(target_vendor = "apple", target_os = "freebsd"))] +#[cfg(any(target_vendor = "apple", target_os = "freebsd", target_os = "netbsd"))] use std::ffi::CStr; -#[cfg(any(target_os = "freebsd", target_vendor = "apple"))] +#[cfg(any(target_os = "freebsd", target_vendor = "apple", target_os = "netbsd"))] impl From for MountInfo { fn from(statfs: StatFs) -> Self { let mut info = MountInfo { @@ -344,9 +344,9 @@ impl From for MountInfo { } } -#[cfg(any(target_os = "freebsd", target_vendor = "apple"))] +#[cfg(any(target_os = "freebsd", target_vendor = "apple", target_os = "netbsd"))] use libc::c_int; -#[cfg(any(target_os = "freebsd", target_vendor = "apple"))] +#[cfg(any(target_os = "freebsd", target_vendor = "apple", target_os = "netbsd"))] extern "C" { #[cfg(all(target_vendor = "apple", target_arch = "x86_64"))] #[link_name = "getmntinfo$INODE64"] // spell-checker:disable-line @@ -354,6 +354,7 @@ extern "C" { #[cfg(any( all(target_os = "freebsd"), + all(target_os = "netbsd"), all(target_vendor = "apple", target_arch = "aarch64") ))] #[link_name = "getmntinfo"] // spell-checker:disable-line @@ -364,9 +365,14 @@ extern "C" { use std::fs::File; #[cfg(target_os = "linux")] use std::io::{BufRead, BufReader}; -#[cfg(any(target_vendor = "apple", target_os = "freebsd", target_os = "windows"))] +#[cfg(any( + target_vendor = "apple", + target_os = "freebsd", + target_os = "windows", + target_os = "netbsd" +))] use std::ptr; -#[cfg(any(target_vendor = "apple", target_os = "freebsd"))] +#[cfg(any(target_vendor = "apple", target_os = "freebsd", target_os = "netbsd"))] use std::slice; /// Read file system list. pub fn read_fs_list() -> Vec { @@ -386,7 +392,7 @@ pub fn read_fs_list() -> Vec { }) .collect::>() } - #[cfg(any(target_os = "freebsd", target_vendor = "apple"))] + #[cfg(any(target_os = "freebsd", target_vendor = "apple", target_os = "netbsd"))] { let mut mount_buffer_ptr: *mut StatFs = ptr::null_mut(); let len = unsafe { get_mount_info(&mut mount_buffer_ptr, 1_i32) }; @@ -582,12 +588,17 @@ impl FsMeta for StatFs { fn io_size(&self) -> u64 { self.f_frsize as u64 } - #[cfg(any(target_vendor = "apple", target_os = "freebsd"))] + #[cfg(any(target_vendor = "apple", target_os = "freebsd", target_os = "netbsd"))] fn io_size(&self) -> u64 { self.f_iosize as u64 } // XXX: dunno if this is right - #[cfg(not(any(target_vendor = "apple", target_os = "freebsd", target_os = "linux")))] + #[cfg(not(any( + target_vendor = "apple", + target_os = "freebsd", + target_os = "linux", + target_os = "netbsd" + )))] fn io_size(&self) -> u64 { self.f_bsize as u64 } @@ -617,12 +628,17 @@ impl FsMeta for StatFs { fn namelen(&self) -> u64 { 1024 } - #[cfg(target_os = "freebsd")] + #[cfg(any(target_os = "freebsd", target_os = "netbsd"))] fn namelen(&self) -> u64 { self.f_namemax as u64 // spell-checker:disable-line } // XXX: should everything just use statvfs? - #[cfg(not(any(target_vendor = "apple", target_os = "freebsd", target_os = "linux")))] + #[cfg(not(any( + target_vendor = "apple", + target_os = "freebsd", + target_os = "linux", + target_os = "netbsd" + )))] fn namelen(&self) -> u64 { self.f_namemax as u64 // spell-checker:disable-line } diff --git a/src/uucore/src/lib/features/utmpx.rs b/src/uucore/src/lib/features/utmpx.rs index 5077d9e59..8f43ba769 100644 --- a/src/uucore/src/lib/features/utmpx.rs +++ b/src/uucore/src/lib/features/utmpx.rs @@ -47,7 +47,7 @@ use libc::utmpx; pub use libc::endutxent; pub use libc::getutxent; pub use libc::setutxent; -#[cfg(any(target_vendor = "apple", target_os = "linux"))] +#[cfg(any(target_vendor = "apple", target_os = "linux", target_os = "netbsd"))] pub use libc::utmpxname; #[cfg(target_os = "freebsd")] pub unsafe extern "C" fn utmpxname(_file: *const libc::c_char) -> libc::c_int { @@ -130,6 +130,30 @@ mod ut { pub use libc::USER_PROCESS; } +#[cfg(target_os = "netbsd")] +mod ut { + pub static DEFAULT_FILE: &str = "/var/run/utmpx"; + + pub const ACCOUNTING: usize = 9; + pub const SHUTDOWN_TIME: usize = 11; + + pub use libc::_UTX_HOSTSIZE as UT_HOSTSIZE; + pub use libc::_UTX_IDSIZE as UT_IDSIZE; + pub use libc::_UTX_LINESIZE as UT_LINESIZE; + pub use libc::_UTX_USERSIZE as UT_NAMESIZE; + + pub use libc::ACCOUNTING; + pub use libc::DEAD_PROCESS; + pub use libc::EMPTY; + pub use libc::INIT_PROCESS; + pub use libc::LOGIN_PROCESS; + pub use libc::NEW_TIME; + pub use libc::OLD_TIME; + pub use libc::RUN_LVL; + pub use libc::SIGNATURE; + pub use libc::USER_PROCESS; +} + pub struct Utmpx { inner: utmpx, } From bdc0f4b7c3e48a1f0b7a37c74a2a48c33ab6657b Mon Sep 17 00:00:00 2001 From: jfinkels Date: Mon, 23 Aug 2021 12:35:19 -0400 Subject: [PATCH 040/206] hashsum: support --check for algorithms with variable output length (#2583) * hashsum: support --check for var. length outputs Add the ability for `hashsum --check` to work with algorithms with variable output length. Previously, the program would terminate with an error due to constructing an invalid regular expression. * fixup! hashsum: support --check for var. length outputs --- src/uu/hashsum/src/hashsum.rs | 19 +++++++++++++++---- tests/by-util/test_hashsum.rs | 12 ++++++++++++ tests/fixtures/hashsum/b2sum.checkfile | 1 + tests/fixtures/hashsum/md5.checkfile | 1 + tests/fixtures/hashsum/sha1.checkfile | 1 + tests/fixtures/hashsum/sha224.checkfile | 1 + tests/fixtures/hashsum/sha256.checkfile | 1 + tests/fixtures/hashsum/sha384.checkfile | 1 + tests/fixtures/hashsum/sha3_224.checkfile | 1 + tests/fixtures/hashsum/sha3_256.checkfile | 1 + tests/fixtures/hashsum/sha3_384.checkfile | 1 + tests/fixtures/hashsum/sha3_512.checkfile | 1 + tests/fixtures/hashsum/sha512.checkfile | 1 + tests/fixtures/hashsum/shake128_256.checkfile | 1 + tests/fixtures/hashsum/shake256_512.checkfile | 1 + 15 files changed, 40 insertions(+), 4 deletions(-) create mode 100644 tests/fixtures/hashsum/b2sum.checkfile create mode 100644 tests/fixtures/hashsum/md5.checkfile create mode 100644 tests/fixtures/hashsum/sha1.checkfile create mode 100644 tests/fixtures/hashsum/sha224.checkfile create mode 100644 tests/fixtures/hashsum/sha256.checkfile create mode 100644 tests/fixtures/hashsum/sha384.checkfile create mode 100644 tests/fixtures/hashsum/sha3_224.checkfile create mode 100644 tests/fixtures/hashsum/sha3_256.checkfile create mode 100644 tests/fixtures/hashsum/sha3_384.checkfile create mode 100644 tests/fixtures/hashsum/sha3_512.checkfile create mode 100644 tests/fixtures/hashsum/sha512.checkfile create mode 100644 tests/fixtures/hashsum/shake128_256.checkfile create mode 100644 tests/fixtures/hashsum/shake256_512.checkfile diff --git a/src/uu/hashsum/src/hashsum.rs b/src/uu/hashsum/src/hashsum.rs index 77cc0d558..1e677358e 100644 --- a/src/uu/hashsum/src/hashsum.rs +++ b/src/uu/hashsum/src/hashsum.rs @@ -473,15 +473,26 @@ where }); if options.check { // Set up Regexes for line validation and parsing + // + // First, we compute the number of bytes we expect to be in + // the digest string. If the algorithm has a variable number + // of output bits, then we use the `+` modifier in the + // regular expression, otherwise we use the `{n}` modifier, + // where `n` is the number of bytes. let bytes = options.digest.output_bits() / 4; + let modifier = if bytes > 0 { + format!("{{{}}}", bytes) + } else { + "+".to_string() + }; let gnu_re = safe_unwrap!(Regex::new(&format!( - r"^(?P[a-fA-F0-9]{{{}}}) (?P[ \*])(?P.*)", - bytes + r"^(?P[a-fA-F0-9]{}) (?P[ \*])(?P.*)", + modifier, ))); let bsd_re = safe_unwrap!(Regex::new(&format!( - r"^{algorithm} \((?P.*)\) = (?P[a-fA-F0-9]{{{digest_size}}})", + r"^{algorithm} \((?P.*)\) = (?P[a-fA-F0-9]{digest_size})", algorithm = options.algoname, - digest_size = bytes + digest_size = modifier, ))); let buffer = file; diff --git a/tests/by-util/test_hashsum.rs b/tests/by-util/test_hashsum.rs index f059e53f3..f2cf91d45 100644 --- a/tests/by-util/test_hashsum.rs +++ b/tests/by-util/test_hashsum.rs @@ -1,3 +1,4 @@ +// spell-checker:ignore checkfile macro_rules! get_hash( ($str:expr) => ( $str.split(' ').collect::>()[0] @@ -12,6 +13,7 @@ macro_rules! test_digest { static DIGEST_ARG: &'static str = concat!("--", stringify!($t)); static BITS_ARG: &'static str = concat!("--bits=", stringify!($size)); static EXPECTED_FILE: &'static str = concat!(stringify!($id), ".expected"); + static CHECK_FILE: &'static str = concat!(stringify!($id), ".checkfile"); #[test] fn test_single_file() { @@ -26,6 +28,16 @@ macro_rules! test_digest { assert_eq!(ts.fixtures.read(EXPECTED_FILE), get_hash!(ts.ucmd().arg(DIGEST_ARG).arg(BITS_ARG).pipe_in_fixture("input.txt").succeeds().no_stderr().stdout_str())); } + + #[test] + fn test_check() { + let ts = TestScenario::new("hashsum"); + ts.ucmd() + .args(&[DIGEST_ARG, BITS_ARG, "--check", CHECK_FILE]) + .succeeds() + .no_stderr() + .stdout_is("input.txt: OK\n"); + } } )*) } diff --git a/tests/fixtures/hashsum/b2sum.checkfile b/tests/fixtures/hashsum/b2sum.checkfile new file mode 100644 index 000000000..9d6781cc8 --- /dev/null +++ b/tests/fixtures/hashsum/b2sum.checkfile @@ -0,0 +1 @@ +7355dd5276c21cfe0c593b5063b96af3f96a454b33216f58314f44c3ade92e9cd6cec4210a0836246780e9baf927cc50b9a3d7073e8f9bd12780fddbcb930c6d input.txt diff --git a/tests/fixtures/hashsum/md5.checkfile b/tests/fixtures/hashsum/md5.checkfile new file mode 100644 index 000000000..328e1bd94 --- /dev/null +++ b/tests/fixtures/hashsum/md5.checkfile @@ -0,0 +1 @@ +e4d7f1b4ed2e42d15898f4b27b019da4 input.txt diff --git a/tests/fixtures/hashsum/sha1.checkfile b/tests/fixtures/hashsum/sha1.checkfile new file mode 100644 index 000000000..02c35969f --- /dev/null +++ b/tests/fixtures/hashsum/sha1.checkfile @@ -0,0 +1 @@ +b7e23ec29af22b0b4e41da31e868d57226121c84 input.txt diff --git a/tests/fixtures/hashsum/sha224.checkfile b/tests/fixtures/hashsum/sha224.checkfile new file mode 100644 index 000000000..6e3402094 --- /dev/null +++ b/tests/fixtures/hashsum/sha224.checkfile @@ -0,0 +1 @@ +6e1a93e32fb44081a401f3db3ef2e6e108b7bbeeb5705afdaf01fb27 input.txt diff --git a/tests/fixtures/hashsum/sha256.checkfile b/tests/fixtures/hashsum/sha256.checkfile new file mode 100644 index 000000000..db1d2be15 --- /dev/null +++ b/tests/fixtures/hashsum/sha256.checkfile @@ -0,0 +1 @@ +09ca7e4eaa6e8ae9c7d261167129184883644d07dfba7cbfbc4c8a2e08360d5b input.txt diff --git a/tests/fixtures/hashsum/sha384.checkfile b/tests/fixtures/hashsum/sha384.checkfile new file mode 100644 index 000000000..c53326b1d --- /dev/null +++ b/tests/fixtures/hashsum/sha384.checkfile @@ -0,0 +1 @@ +1fcdb6059ce05172a26bbe2a3ccc88ed5a8cd5fc53edfd9053304d429296a6da23b1cd9e5c9ed3bb34f00418a70cdb7e input.txt diff --git a/tests/fixtures/hashsum/sha3_224.checkfile b/tests/fixtures/hashsum/sha3_224.checkfile new file mode 100644 index 000000000..3a93cd056 --- /dev/null +++ b/tests/fixtures/hashsum/sha3_224.checkfile @@ -0,0 +1 @@ +927b362eaf84a75785bbec3370d1c9711349e93f1104eda060784221 input.txt diff --git a/tests/fixtures/hashsum/sha3_256.checkfile b/tests/fixtures/hashsum/sha3_256.checkfile new file mode 100644 index 000000000..b8bec0924 --- /dev/null +++ b/tests/fixtures/hashsum/sha3_256.checkfile @@ -0,0 +1 @@ +bfb3959527d7a3f2f09def2f6915452d55a8f122df9e164d6f31c7fcf6093e14 input.txt diff --git a/tests/fixtures/hashsum/sha3_384.checkfile b/tests/fixtures/hashsum/sha3_384.checkfile new file mode 100644 index 000000000..6b014fdd2 --- /dev/null +++ b/tests/fixtures/hashsum/sha3_384.checkfile @@ -0,0 +1 @@ +fbd0c5931195aaa9517869972b372f717bb69f7f9f72bfc0884ed0531c36a16fc2db5dd6d82131968b23ffe0e90757e5 input.txt diff --git a/tests/fixtures/hashsum/sha3_512.checkfile b/tests/fixtures/hashsum/sha3_512.checkfile new file mode 100644 index 000000000..125e2dfba --- /dev/null +++ b/tests/fixtures/hashsum/sha3_512.checkfile @@ -0,0 +1 @@ +2ed3a863a12e2f8ff140aa86232ff3603a7f24af62f0e2ca74672494ade175a9a3de42a351b5019d931a1deae0499609038d9b47268779d76198e1d410d20974 input.txt diff --git a/tests/fixtures/hashsum/sha512.checkfile b/tests/fixtures/hashsum/sha512.checkfile new file mode 100644 index 000000000..41a55cabb --- /dev/null +++ b/tests/fixtures/hashsum/sha512.checkfile @@ -0,0 +1 @@ +8710339dcb6814d0d9d2290ef422285c9322b7163951f9a0ca8f883d3305286f44139aa374848e4174f5aada663027e4548637b6d19894aec4fb6c46a139fbf9 input.txt diff --git a/tests/fixtures/hashsum/shake128_256.checkfile b/tests/fixtures/hashsum/shake128_256.checkfile new file mode 100644 index 000000000..a3769f78e --- /dev/null +++ b/tests/fixtures/hashsum/shake128_256.checkfile @@ -0,0 +1 @@ +83d41db453072caa9953f2f316480fbbcb84a5f3505460a18b3a36a814ae8e9e input.txt diff --git a/tests/fixtures/hashsum/shake256_512.checkfile b/tests/fixtures/hashsum/shake256_512.checkfile new file mode 100644 index 000000000..e16601f30 --- /dev/null +++ b/tests/fixtures/hashsum/shake256_512.checkfile @@ -0,0 +1 @@ +7c9896ea84a2a1b80b2183a3f2b4e43cd59b7d48471dc213bcedaccb699d6e6f7ad5d304928ab79329f1fc62f6db072d95b51209eb807683f5c9371872a2dd4e input.txt From d1f4d74a6e5c19e16793489343e1497dcf359987 Mon Sep 17 00:00:00 2001 From: Jan Verbeek Date: Mon, 23 Aug 2021 23:11:12 +0200 Subject: [PATCH 041/206] Check .vscode/settings.json out of source control Having it in git causes it to get in the way if local changes are made. /.vscode/ as a whole is already in .gitignore so it doesn't have to be added there. --- .vscode/settings.json | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 7a73a41bf..000000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,2 +0,0 @@ -{ -} \ No newline at end of file From 0e689e78aa5395c278b349716c982c41f9b5b3d2 Mon Sep 17 00:00:00 2001 From: Jeffrey Finkelstein Date: Sun, 22 Aug 2021 16:21:08 -0400 Subject: [PATCH 042/206] tac: support multi-char separator with overlap Fix a bug in `tac` where multi-character line separators would cause incorrect behavior when there was overlap between candidate matches in the input string. This commit adds a dependency on `memchr` in order to use the `memchr::memmem::rfind_iter()` function to scan for non-overlapping instances of the specified line separator characters, scanning from right to left. Fixes #2580. --- Cargo.lock | 1 + src/uu/tac/Cargo.toml | 1 + src/uu/tac/src/tac.rs | 53 +++++++++++++++++----------- tests/by-util/test_tac.rs | 74 ++++++++++++++++++++++++++++++++++++++- 4 files changed, 107 insertions(+), 22 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2a5b46153..29be47dfb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2937,6 +2937,7 @@ name = "uu_tac" version = "0.0.7" dependencies = [ "clap", + "memchr 2.4.0", "uucore", "uucore_procs", ] diff --git a/src/uu/tac/Cargo.toml b/src/uu/tac/Cargo.toml index 60e5d29ec..3ba1497a0 100644 --- a/src/uu/tac/Cargo.toml +++ b/src/uu/tac/Cargo.toml @@ -15,6 +15,7 @@ edition = "2018" path = "src/tac.rs" [dependencies] +memchr = "2" clap = { version = "2.33", features = ["wrap_help"] } uucore = { version=">=0.0.9", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_procs" } diff --git a/src/uu/tac/src/tac.rs b/src/uu/tac/src/tac.rs index 4e1b38687..0568f1601 100644 --- a/src/uu/tac/src/tac.rs +++ b/src/uu/tac/src/tac.rs @@ -5,12 +5,13 @@ // * 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 +// spell-checker:ignore (ToDO) sbytes slen dlen memmem #[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 uucore::InvalidEncodingHandling; @@ -80,20 +81,33 @@ pub fn uu_app() -> App<'static, 'static> { .arg(Arg::with_name(options::FILE).hidden(true).multiple(true)) } +/// Write lines from `data` to stdout in reverse. +/// +/// This function writes to [`stdout`] each line appearing in `data`, +/// starting with the last line and ending with the first line. The +/// `separator` parameter defines what characters to use as a line +/// separator. +/// +/// If `before` is `false`, then this function assumes that the +/// `separator` appears at the end of each line, as in `"abc\ndef\n"`. +/// If `before` is `true`, then this function assumes that the +/// `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(); - // Convert the line separator to a byte sequence. - let sbytes = separator.as_bytes(); - let slen = sbytes.len(); + // The number of bytes in the line separator. + let slen = separator.as_bytes().len(); - // If there are more characters in the separator than in the data, - // we can't possibly split the data on the separator. Write the - // entire buffer to stdout. - let dlen = data.len(); - if dlen < slen { - return out.write_all(data); - } + // The index of the start of the next line in the `data`. + // + // As we scan through the `data` from right to left, we update this + // variable each time we find a new line. + // + // If `before` is `true`, then each line starts immediately before + // the line separator. Otherwise, each line starts immediately after + // the line separator. + let mut following_line_start = data.len(); // Iterate over each byte in the buffer in reverse. When we find a // line separator, write the line to stdout. @@ -101,16 +115,13 @@ fn buffer_tac(data: &[u8], before: bool, separator: &str) -> std::io::Result<()> // The `before` flag controls whether the line separator appears at // the end of the line (as in "abc\ndef\n") or at the beginning of // the line (as in "/abc/def"). - let mut following_line_start = data.len(); - for i in (0..dlen - slen + 1).rev() { - if &data[i..i + slen] == sbytes { - if before { - out.write_all(&data[i..following_line_start])?; - following_line_start = i; - } else { - out.write_all(&data[i + slen..following_line_start])?; - following_line_start = i + slen; - } + for i in memmem::rfind_iter(data, separator) { + if before { + out.write_all(&data[i..following_line_start])?; + following_line_start = i; + } else { + out.write_all(&data[i + slen..following_line_start])?; + following_line_start = i + slen; } } diff --git a/tests/by-util/test_tac.rs b/tests/by-util/test_tac.rs index c6e32fef0..202f76d66 100644 --- a/tests/by-util/test_tac.rs +++ b/tests/by-util/test_tac.rs @@ -1,4 +1,4 @@ -// spell-checker:ignore axxbxx bxxaxx +// spell-checker:ignore axxbxx bxxaxx axxx axxxx xxaxx xxax xxxxa use crate::common::util::*; #[test] @@ -125,6 +125,78 @@ fn test_multi_char_separator() { .stdout_is("bxxaxx"); } +#[test] +fn test_multi_char_separator_overlap() { + // The right-most pair of "x" characters in the input is treated as + // the only line separator. That is, "axxx" is interpreted as having + // one line comprising the string "ax" followed by the line + // separator "xx". + new_ucmd!() + .args(&["-s", "xx"]) + .pipe_in("axxx") + .succeeds() + .stdout_is("axxx"); + + // Each non-overlapping pair of "x" characters in the input is + // treated as a line separator. That is, "axxxx" is interpreted as + // having two lines: + // + // * the second line is the empty string "" followed by the line + // separator "xx", + // * the first line is the string "a" followed by the line separator + // "xx". + // + // The lines are printed in reverse, resulting in "xx" followed by + // "axx". + new_ucmd!() + .args(&["-s", "xx"]) + .pipe_in("axxxx") + .succeeds() + .stdout_is("xxaxx"); +} + +#[test] +fn test_multi_char_separator_overlap_before() { + // With the "-b" option, the line separator is assumed to be at the + // beginning of the line. In this case, That is, "axxx" is + // interpreted as having two lines: + // + // * the second line is the empty string "" preceded by the line + // separator "xx", + // * the first line is the string "ax" preceded by no line + // separator, since there are no more characters preceding it. + // + // The lines are printed in reverse, resulting in "xx" followed by + // "ax". + new_ucmd!() + .args(&["-b", "-s", "xx"]) + .pipe_in("axxx") + .succeeds() + .stdout_is("xxax"); + + // With the "-b" option, the line separator is assumed to be at the + // beginning of the line. Each non-overlapping pair of "x" + // characters in the input is treated as a line separator. That is, + // "axxxx" is interpreted as having three lines: + // + // * the third line is the empty string "" preceded by the line + // separator "xx" (the last two "x" characters in the input + // string), + // * the second line is the empty string "" preceded by the line + // separator "xx" (the first two "x" characters in the input + // string), + // * the first line is the string "a" preceded by no line separator, + // since there are no more characters preceding it. + // + // The lines are printed in reverse, resulting in "xx" followed by + // "xx" followed by "a". + new_ucmd!() + .args(&["-b", "-s", "xx"]) + .pipe_in("axxxx") + .succeeds() + .stdout_is("xxxxa"); +} + #[test] fn test_null_separator() { new_ucmd!() From 86c610a84b8b6c925a0a1351a676b2be168ec63f Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sun, 22 Aug 2021 23:05:35 +0200 Subject: [PATCH 043/206] enable freebsd in the CI on gh actions and use --features feat_os_unix (wasn't done before) --- .github/workflows/CICD.yml | 45 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/.github/workflows/CICD.yml b/.github/workflows/CICD.yml index 8a1e142df..d2a164f2d 100644 --- a/.github/workflows/CICD.yml +++ b/.github/workflows/CICD.yml @@ -521,6 +521,51 @@ jobs: n_fails=$(echo "$output" | grep "^FAIL:\s" | wc --lines) if [ $n_fails -gt 0 ] ; then echo "::warning ::${n_fails}+ test failures" ; fi + test_freebsd: + runs-on: macos-latest + name: Tests/FreeBSD test suite + env: + mem: 2048 + steps: + - uses: actions/checkout@v2 + - name: Prepare, build and test + id: test + uses: vmactions/freebsd-vm@v0.1.4 + with: + usesh: true + prepare: pkg install -y curl gmake sudo + run: | + # Need to be run in the same block. Otherwise, we are back on the mac host. + set -e + pw adduser -n cuuser -d /root/ -g wheel -c "Coreutils user to build" -w random + chown -R cuuser:wheel /root/ /Users/runner/work/coreutils/ + whoami + + # Needs to be done in a sudo as we are changing users + sudo -i -u cuuser sh << EOF + whoami + curl https://sh.rustup.rs -sSf --output rustup.sh + sh rustup.sh -y --profile=minimal + ## Info + # environment + echo "## environment" + echo "CI='${CI}'" + # tooling info display + echo "## tooling" + . $HOME/.cargo/env + cargo -V + rustc -V + env + + # where the files are resynced + cd /Users/runner/work/coreutils/coreutils/ + cargo build + cargo test --features feat_os_unix -p uucore -p coreutils + # Clean to avoid to rsync back the files + cargo clean + EOF + + coverage: name: Code Coverage runs-on: ${{ matrix.job.os }} From 1126013dbbd9965e507c9d2ee3bb776866af9a48 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Mon, 23 Aug 2021 13:25:23 +0200 Subject: [PATCH 044/206] Remove unused variables on Freebsd --- src/uu/df/src/df.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/uu/df/src/df.rs b/src/uu/df/src/df.rs index e7f3944a0..310d3c664 100644 --- a/src/uu/df/src/df.rs +++ b/src/uu/df/src/df.rs @@ -28,9 +28,6 @@ use std::fmt::Display; #[cfg(unix)] use std::mem; -#[cfg(target_os = "freebsd")] -use uucore::libc::{c_char, fsid_t, uid_t}; - #[cfg(windows)] use std::path::Path; From 78e28060a0a7a23ec63e7463039e511c137ac65c Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Mon, 23 Aug 2021 13:27:49 +0200 Subject: [PATCH 045/206] NORMAL_FORMAT_STR is also used for freebsd --- tests/by-util/test_stat.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/by-util/test_stat.rs b/tests/by-util/test_stat.rs index af9e3de45..9bbb1c1ca 100644 --- a/tests/by-util/test_stat.rs +++ b/tests/by-util/test_stat.rs @@ -102,7 +102,7 @@ fn test_invalid_option() { new_ucmd!().arg("-w").arg("-q").arg("/").fails(); } -#[cfg(any(target_os = "linux", target_vendor = "apple"))] +#[cfg(unix)] const NORMAL_FORMAT_STR: &str = "%a %A %b %B %d %D %f %F %g %G %h %i %m %n %o %s %u %U %x %X %y %Y %z %Z"; // avoid "%w %W" (birth/creation) due to `stat` limitations and linux kernel & rust version capability variations #[cfg(any(target_os = "linux"))] From 29aa4b668e0ae542bdb878784a0c3ce54709f437 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sun, 22 Aug 2021 23:18:11 +0200 Subject: [PATCH 046/206] remove cirrus ci --- .cirrus.yml | 21 --------------------- 1 file changed, 21 deletions(-) delete mode 100644 .cirrus.yml diff --git a/.cirrus.yml b/.cirrus.yml deleted file mode 100644 index 50f8a25b1..000000000 --- a/.cirrus.yml +++ /dev/null @@ -1,21 +0,0 @@ -env: - # Temporary workaround for error `error: sysinfo not supported on - # this platform` seen on FreeBSD platforms, affecting Rustup - # - # References: https://github.com/rust-lang/rustup/issues/2774 - RUSTUP_IO_THREADS: 1 - -task: - name: stable x86_64-unknown-freebsd-12 - freebsd_instance: - image: freebsd-12-2-release-amd64 - setup_script: - - pkg install -y curl gmake - - curl https://sh.rustup.rs -sSf --output rustup.sh - - sh rustup.sh -y --profile=minimal - build_script: - - . $HOME/.cargo/env - - cargo build - test_script: - - . $HOME/.cargo/env - - cargo test -p uucore -p coreutils From 697dca25cbd97ad06d9e6ec4931cb44b44a50428 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Tue, 24 Aug 2021 08:53:20 +0200 Subject: [PATCH 047/206] silent the failed tests for now on freebsd --- tests/by-util/test_chown.rs | 11 +++++++++-- tests/by-util/test_hostname.rs | 4 ++-- tests/by-util/test_install.rs | 6 ++++-- tests/by-util/test_test.rs | 4 +++- tests/by-util/test_touch.rs | 2 ++ tests/by-util/test_tty.rs | 3 ++- 6 files changed, 22 insertions(+), 8 deletions(-) diff --git a/tests/by-util/test_chown.rs b/tests/by-util/test_chown.rs index 86365f51b..84a0d1c97 100644 --- a/tests/by-util/test_chown.rs +++ b/tests/by-util/test_chown.rs @@ -195,6 +195,8 @@ fn test_chown_failed_stdout() { } #[test] +// FixME: Fails on freebsd because of chown: invalid group: 'root:root' +#[cfg(not(target_os = "freebsd"))] fn test_chown_owner_group() { // test chown username:group file.txt @@ -242,8 +244,11 @@ fn test_chown_owner_group() { } #[test] -// TODO: on macos group name is not recognized correctly: "chown: invalid group: ':groupname' -#[cfg(any(windows, all(unix, not(target_os = "macos"))))] +// FixME: on macos & freebsd group name is not recognized correctly: "chown: invalid group: ':groupname' +#[cfg(any( + windows, + all(unix, not(any(target_os = "macos", target_os = "freebsd"))) +))] fn test_chown_only_group() { // test chown :group file.txt @@ -408,6 +413,8 @@ fn test_chown_owner_group_id() { } #[test] +// FixME: Fails on freebsd because of chown: invalid group: '0:root' +#[cfg(not(target_os = "freebsd"))] fn test_chown_owner_group_mix() { // test chown 1111:group file.txt diff --git a/tests/by-util/test_hostname.rs b/tests/by-util/test_hostname.rs index 3fcb1ae8b..45acff1b5 100644 --- a/tests/by-util/test_hostname.rs +++ b/tests/by-util/test_hostname.rs @@ -10,8 +10,8 @@ fn test_hostname() { assert!(ls_default_res.stdout().len() >= ls_domain_res.stdout().len()); } -// FixME: fails for "MacOS" -#[cfg(not(target_vendor = "apple"))] +// FixME: fails for "MacOS" and "freebsd" "failed to lookup address information: Name does not resolve" +#[cfg(not(any(target_os = "macos", target_os = "freebsd")))] #[test] fn test_hostname_ip() { let result = new_ucmd!().arg("-i").succeeds(); diff --git a/tests/by-util/test_install.rs b/tests/by-util/test_install.rs index 06808db6b..846dc5836 100644 --- a/tests/by-util/test_install.rs +++ b/tests/by-util/test_install.rs @@ -563,7 +563,8 @@ fn strip_source_file() -> &'static str { } #[test] -#[cfg(not(windows))] +// FixME: Freebsd fails on 'No such file or directory' +#[cfg(not(any(windows, target_os = "freebsd")))] fn test_install_and_strip() { let scene = TestScenario::new(util_name!()); let at = &scene.fixtures; @@ -586,7 +587,8 @@ fn test_install_and_strip() { } #[test] -#[cfg(not(windows))] +// FixME: Freebsd fails on 'No such file or directory' +#[cfg(not(any(windows, target_os = "freebsd")))] fn test_install_and_strip_with_program() { let scene = TestScenario::new(util_name!()); let at = &scene.fixtures; diff --git a/tests/by-util/test_test.rs b/tests/by-util/test_test.rs index 79c24651a..c5f1e43ed 100644 --- a/tests/by-util/test_test.rs +++ b/tests/by-util/test_test.rs @@ -477,7 +477,9 @@ fn test_nonexistent_file_is_not_symlink() { } #[test] -#[cfg(not(windows))] // Windows has no concept of sticky bit +// FixME: freebsd fails with 'chmod: sticky_file: Inappropriate file type or format' +// Windows has no concept of sticky bit +#[cfg(not(any(windows, target_os = "freebsd")))] fn test_file_is_sticky() { let scenario = TestScenario::new(util_name!()); let mut ucmd = scenario.ucmd(); diff --git a/tests/by-util/test_touch.rs b/tests/by-util/test_touch.rs index 3ed7f3bb2..c7261fad3 100644 --- a/tests/by-util/test_touch.rs +++ b/tests/by-util/test_touch.rs @@ -290,6 +290,8 @@ fn test_touch_set_both() { } #[test] +// FixME: Fails on freebsd because of a different nanos +#[cfg(not(target_os = "freebsd"))] fn test_touch_no_dereference() { let (at, mut ucmd) = at_and_ucmd!(); let file_a = "test_touch_no_dereference_a"; diff --git a/tests/by-util/test_tty.rs b/tests/by-util/test_tty.rs index 6ba8cd029..f31aa67ee 100644 --- a/tests/by-util/test_tty.rs +++ b/tests/by-util/test_tty.rs @@ -65,7 +65,8 @@ fn test_wrong_argument() { } #[test] -#[cfg(not(windows))] +// FixME: freebsd panic +#[cfg(not(any(windows, target_os = "freebsd")))] fn test_stdout_fail() { let mut child = new_ucmd!().run_no_wait(); drop(child.stdout.take()); From 516c5311f7be7191bbc8b68588d014f01ab92e02 Mon Sep 17 00:00:00 2001 From: Jan Verbeek Date: Tue, 24 Aug 2021 12:00:07 +0200 Subject: [PATCH 048/206] Close file descriptors of pipes after use (#2591) * Close file descriptors of pipes after use * cat: Test file descriptor exhaustion * fixup! cat: Test file descriptor exhaustion --- src/uu/cat/src/splice.rs | 15 +++++++-------- src/uu/wc/src/count_bytes.rs | 8 ++++++-- tests/by-util/test_cat.rs | 22 ++++++++++++++++++++++ 3 files changed, 35 insertions(+), 10 deletions(-) diff --git a/src/uu/cat/src/splice.rs b/src/uu/cat/src/splice.rs index 0b46fc662..108997c4a 100644 --- a/src/uu/cat/src/splice.rs +++ b/src/uu/cat/src/splice.rs @@ -2,8 +2,9 @@ use super::{CatResult, InputHandle}; use nix::fcntl::{splice, SpliceFFlags}; use nix::unistd::{self, pipe}; +use std::fs::File; use std::io::Read; -use std::os::unix::io::RawFd; +use std::os::unix::io::{FromRawFd, RawFd}; const BUF_SIZE: usize = 1024 * 16; @@ -19,13 +20,11 @@ pub(super) fn write_fast_using_splice( handle: &mut InputHandle, write_fd: RawFd, ) -> CatResult { - let (pipe_rd, pipe_wr) = match pipe() { - Ok(r) => r, - Err(_) => { - // It is very rare that creating a pipe fails, but it can happen. - return Ok(true); - } - }; + let (pipe_rd, pipe_wr) = pipe()?; + + // Ensure the pipe is closed when the function returns. + // SAFETY: The file descriptors do not have other owners. + let _handles = unsafe { (File::from_raw_fd(pipe_rd), File::from_raw_fd(pipe_wr)) }; loop { match splice( diff --git a/src/uu/wc/src/count_bytes.rs b/src/uu/wc/src/count_bytes.rs index 7f06f8171..83cc71ac4 100644 --- a/src/uu/wc/src/count_bytes.rs +++ b/src/uu/wc/src/count_bytes.rs @@ -1,7 +1,7 @@ use super::{WcResult, WordCountable}; #[cfg(any(target_os = "linux", target_os = "android"))] -use std::fs::OpenOptions; +use std::fs::{File, OpenOptions}; use std::io::ErrorKind; #[cfg(unix)] @@ -9,7 +9,7 @@ use libc::S_IFREG; #[cfg(unix)] use nix::sys::stat::fstat; #[cfg(any(target_os = "linux", target_os = "android"))] -use std::os::unix::io::{AsRawFd, RawFd}; +use std::os::unix::io::{AsRawFd, FromRawFd, RawFd}; #[cfg(any(target_os = "linux", target_os = "android"))] use libc::S_IFIFO; @@ -47,6 +47,10 @@ fn count_bytes_using_splice(fd: RawFd) -> nix::Result { let null = null_file.as_raw_fd(); let (pipe_rd, pipe_wr) = pipe()?; + // Ensure the pipe is closed when the function returns. + // SAFETY: The file descriptors do not have other owners. + let _handles = unsafe { (File::from_raw_fd(pipe_rd), File::from_raw_fd(pipe_wr)) }; + let mut byte_count = 0; loop { let res = splice(fd, None, pipe_wr, None, BUF_SIZE, SpliceFFlags::empty())?; diff --git a/tests/by-util/test_cat.rs b/tests/by-util/test_cat.rs index e52be9506..b629a06e6 100644 --- a/tests/by-util/test_cat.rs +++ b/tests/by-util/test_cat.rs @@ -1,8 +1,13 @@ +// spell-checker:ignore NOFILE + use crate::common::util::*; use std::fs::OpenOptions; #[cfg(unix)] use std::io::Read; +#[cfg(target_os = "linux")] +use rlimit::Resource; + #[test] fn test_output_simple() { new_ucmd!() @@ -87,6 +92,23 @@ fn test_fifo_symlink() { thread.join().unwrap(); } +#[test] +#[cfg(target_os = "linux")] +fn test_closes_file_descriptors() { + // Each file creates a pipe, which has two file descriptors. + // If they are not closed then five is certainly too many. + new_ucmd!() + .args(&[ + "alpha.txt", + "alpha.txt", + "alpha.txt", + "alpha.txt", + "alpha.txt", + ]) + .with_limit(Resource::NOFILE, 9, 9) + .succeeds(); +} + #[test] #[cfg(unix)] fn test_piped_to_regular_file() { From 7153a595c6f4dbfbd68dd08fcbfd5231d2b18c6d Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Sat, 14 Aug 2021 20:55:04 +0200 Subject: [PATCH 049/206] chgrp: forward to chown chgrp does mostly the same as chown. By making chown a bit more configurable we can reuse its code for chgrp. --- Cargo.lock | 2 +- src/uu/chgrp/Cargo.toml | 2 +- src/uu/chgrp/src/chgrp.rs | 222 +++------------------------ src/uu/chown/src/chown.rs | 52 ++++--- src/uu/install/src/install.rs | 19 ++- src/uucore/src/lib/features/perms.rs | 179 +++++++++------------ tests/by-util/test_chgrp.rs | 2 +- 7 files changed, 141 insertions(+), 337 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 29be47dfb..477895e15 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2126,9 +2126,9 @@ name = "uu_chgrp" version = "0.0.7" dependencies = [ "clap", + "uu_chown", "uucore", "uucore_procs", - "walkdir", ] [[package]] diff --git a/src/uu/chgrp/Cargo.toml b/src/uu/chgrp/Cargo.toml index 5a2591f56..cc160626c 100644 --- a/src/uu/chgrp/Cargo.toml +++ b/src/uu/chgrp/Cargo.toml @@ -18,7 +18,7 @@ path = "src/chgrp.rs" clap = { version = "2.33", features = ["wrap_help"] } uucore = { version=">=0.0.9", package="uucore", path="../../uucore", features=["entries", "fs", "perms"] } uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_procs" } -walkdir = "2.2" +uu_chown = { version=">=0.0.6", package="uu_chown", path="../chown"} [[bin]] name = "chgrp" diff --git a/src/uu/chgrp/src/chgrp.rs b/src/uu/chgrp/src/chgrp.rs index 7840ba829..5ae97955d 100644 --- a/src/uu/chgrp/src/chgrp.rs +++ b/src/uu/chgrp/src/chgrp.rs @@ -5,25 +5,20 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -// spell-checker:ignore (ToDO) COMFOLLOW Chgrper RFILE RFILE's derefer dgid nonblank nonprint nonprinting +// spell-checker:ignore (ToDO) COMFOLLOW Chowner RFILE RFILE's derefer dgid nonblank nonprint nonprinting #[macro_use] extern crate uucore; +use uu_chown::{Chowner, IfFrom}; pub use uucore::entries; -use uucore::fs::resolve_relative_path; -use uucore::libc::gid_t; -use uucore::perms::{wrap_chgrp, Verbosity}; +use uucore::error::{FromIo, UResult, USimpleError}; +use uucore::perms::{Verbosity, VerbosityLevel}; use clap::{App, Arg}; -extern crate walkdir; -use walkdir::WalkDir; - use std::fs; -use std::fs::Metadata; use std::os::unix::fs::MetadataExt; -use std::path::Path; use uucore::InvalidEncodingHandling; static ABOUT: &str = "Change the group of each FILE to GROUP."; @@ -66,7 +61,8 @@ fn 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::ConvertLossy) .accept_any(); @@ -140,8 +136,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { if recursive { if bit_flag == FTS_PHYSICAL { if derefer == 1 { - show_error!("-R --dereference requires -H or -L"); - return 1; + return Err(USimpleError::new(1, "-R --dereference requires -H or -L")); } derefer = 0; } @@ -149,26 +144,22 @@ pub fn uumain(args: impl uucore::Args) -> i32 { bit_flag = FTS_PHYSICAL; } - let verbosity = if matches.is_present(options::verbosity::CHANGES) { - Verbosity::Changes + let verbosity_level = if matches.is_present(options::verbosity::CHANGES) { + VerbosityLevel::Changes } else if matches.is_present(options::verbosity::SILENT) || matches.is_present(options::verbosity::QUIET) { - Verbosity::Silent + VerbosityLevel::Silent } else if matches.is_present(options::verbosity::VERBOSE) { - Verbosity::Verbose + VerbosityLevel::Verbose } else { - Verbosity::Normal + VerbosityLevel::Normal }; let dest_gid = if let Some(file) = matches.value_of(options::REFERENCE) { - match fs::metadata(&file) { - Ok(meta) => Some(meta.gid()), - Err(e) => { - show_error!("failed to get attributes of '{}': {}", file, e); - return 1; - } - } + fs::metadata(&file) + .map(|meta| Some(meta.gid())) + .map_err_context(|| format!("failed to get attributes of '{}'", file))? } else { let group = matches.value_of(options::ARG_GROUP).unwrap_or_default(); if group.is_empty() { @@ -176,22 +167,24 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } else { match entries::grp2gid(group) { Ok(g) => Some(g), - _ => { - show_error!("invalid group: {}", group); - return 1; - } + _ => return Err(USimpleError::new(1, format!("invalid group: '{}'", group))), } } }; - let executor = Chgrper { + let executor = Chowner { bit_flag, dest_gid, - verbosity, + verbosity: Verbosity { + groups_only: true, + level: verbosity_level, + }, recursive, dereference: derefer != 0, preserve_root, files, + filter: IfFrom::All, + dest_uid: None, }; executor.exec() } @@ -275,172 +268,3 @@ pub fn uu_app() -> App<'static, 'static> { .help("traverse every symbolic link to a directory encountered"), ) } - -struct Chgrper { - dest_gid: Option, - bit_flag: u8, - verbosity: Verbosity, - files: Vec, - recursive: bool, - preserve_root: bool, - dereference: bool, -} - -macro_rules! unwrap { - ($m:expr, $e:ident, $err:block) => { - match $m { - Ok(meta) => meta, - Err($e) => $err, - } - }; -} - -impl Chgrper { - fn exec(&self) -> i32 { - let mut ret = 0; - for f in &self.files { - ret |= self.traverse(f); - } - ret - } - - #[cfg(windows)] - fn is_bind_root>(&self, root: P) -> bool { - // TODO: is there an equivalent on Windows? - false - } - - #[cfg(unix)] - fn is_bind_root>(&self, path: P) -> bool { - if let (Ok(given), Ok(root)) = (fs::metadata(path), fs::metadata("/")) { - given.dev() == root.dev() && given.ino() == root.ino() - } else { - // FIXME: not totally sure if it's okay to just ignore an error here - false - } - } - - fn traverse>(&self, root: P) -> i32 { - let follow_arg = self.dereference || self.bit_flag != FTS_PHYSICAL; - let path = root.as_ref(); - let meta = match self.obtain_meta(path, follow_arg) { - Some(m) => m, - _ => return 1, - }; - - // Prohibit only if: - // (--preserve-root and -R present) && - // ( - // (argument is not symlink && resolved to be '/') || - // (argument is symlink && should follow argument && resolved to be '/') - // ) - if self.recursive && self.preserve_root { - let may_exist = if follow_arg { - path.canonicalize().ok() - } else { - let real = resolve_relative_path(path); - if real.is_dir() { - Some(real.canonicalize().expect("failed to get real path")) - } else { - Some(real.into_owned()) - } - }; - - if let Some(p) = may_exist { - if p.parent().is_none() || self.is_bind_root(p) { - show_error!("it is dangerous to operate recursively on '/'"); - show_error!("use --no-preserve-root to override this failsafe"); - return 1; - } - } - } - - let ret = match wrap_chgrp( - path, - &meta, - self.dest_gid, - follow_arg, - self.verbosity.clone(), - ) { - Ok(n) => { - if !n.is_empty() { - show_error!("{}", n); - } - 0 - } - Err(e) => { - if self.verbosity != Verbosity::Silent { - show_error!("{}", e); - } - 1 - } - }; - - if !self.recursive { - ret - } else { - ret | self.dive_into(&root) - } - } - - fn dive_into>(&self, root: P) -> i32 { - let mut ret = 0; - let root = root.as_ref(); - let follow = self.dereference || self.bit_flag & FTS_LOGICAL != 0; - for entry in WalkDir::new(root).follow_links(follow).min_depth(1) { - let entry = unwrap!(entry, e, { - ret = 1; - show_error!("{}", e); - continue; - }); - let path = entry.path(); - let meta = match self.obtain_meta(path, follow) { - Some(m) => m, - _ => { - ret = 1; - continue; - } - }; - - ret = match wrap_chgrp(path, &meta, self.dest_gid, follow, self.verbosity.clone()) { - Ok(n) => { - if !n.is_empty() { - show_error!("{}", n); - } - 0 - } - Err(e) => { - if self.verbosity != Verbosity::Silent { - show_error!("{}", e); - } - 1 - } - } - } - - ret - } - - fn obtain_meta>(&self, path: P, follow: bool) -> Option { - use self::Verbosity::*; - let path = path.as_ref(); - let meta = if follow { - unwrap!(path.metadata(), e, { - match self.verbosity { - Silent => (), - _ => show_error!("cannot access '{}': {}", path.display(), e), - } - return None; - }) - } else { - unwrap!(path.symlink_metadata(), e, { - match self.verbosity { - Silent => (), - _ => show_error!("cannot dereference '{}': {}", path.display(), e), - } - return None; - }) - }; - Some(meta) - } -} diff --git a/src/uu/chown/src/chown.rs b/src/uu/chown/src/chown.rs index 8813c07e2..13d16dc2a 100644 --- a/src/uu/chown/src/chown.rs +++ b/src/uu/chown/src/chown.rs @@ -12,7 +12,7 @@ extern crate uucore; pub use uucore::entries::{self, Group, Locate, Passwd}; use uucore::fs::resolve_relative_path; use uucore::libc::{gid_t, uid_t}; -use uucore::perms::{wrap_chown, Verbosity}; +use uucore::perms::{wrap_chown, Verbosity, VerbosityLevel}; use uucore::error::{FromIo, UResult, USimpleError}; @@ -116,15 +116,15 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } let verbosity = if matches.is_present(options::verbosity::CHANGES) { - Verbosity::Changes + VerbosityLevel::Changes } else if matches.is_present(options::verbosity::SILENT) || matches.is_present(options::verbosity::QUIET) { - Verbosity::Silent + VerbosityLevel::Silent } else if matches.is_present(options::verbosity::VERBOSE) { - Verbosity::Verbose + VerbosityLevel::Verbose } else { - Verbosity::Normal + VerbosityLevel::Normal }; let filter = if let Some(spec) = matches.value_of(options::FROM) { @@ -154,7 +154,10 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { bit_flag, dest_uid, dest_gid, - verbosity, + verbosity: Verbosity { + groups_only: false, + level: verbosity, + }, recursive, dereference: derefer != 0, filter, @@ -286,23 +289,23 @@ fn parse_spec(spec: &str) -> UResult<(Option, Option)> { Ok((uid, gid)) } -enum IfFrom { +pub enum IfFrom { All, User(u32), Group(u32), UserGroup(u32, u32), } -struct Chowner { - dest_uid: Option, - dest_gid: Option, - bit_flag: u8, - verbosity: Verbosity, - filter: IfFrom, - files: Vec, - recursive: bool, - preserve_root: bool, - dereference: bool, +pub struct Chowner { + pub dest_uid: Option, + pub dest_gid: Option, + pub bit_flag: u8, + pub verbosity: Verbosity, + pub filter: IfFrom, + pub files: Vec, + pub recursive: bool, + pub preserve_root: bool, + pub dereference: bool, } macro_rules! unwrap { @@ -315,7 +318,7 @@ macro_rules! unwrap { } impl Chowner { - fn exec(&self) -> UResult<()> { + pub fn exec(&self) -> UResult<()> { let mut ret = 0; for f in &self.files { ret |= self.traverse(f); @@ -377,7 +380,7 @@ impl Chowner { 0 } Err(e) => { - if self.verbosity != Verbosity::Silent { + if self.verbosity.level != VerbosityLevel::Silent { show_error!("{}", e); } 1 @@ -432,7 +435,7 @@ impl Chowner { 0 } Err(e) => { - if self.verbosity != Verbosity::Silent { + if self.verbosity.level != VerbosityLevel::Silent { show_error!("{}", e); } 1 @@ -443,20 +446,19 @@ impl Chowner { } fn obtain_meta>(&self, path: P, follow: bool) -> Option { - use self::Verbosity::*; let path = path.as_ref(); let meta = if follow { unwrap!(path.metadata(), e, { - match self.verbosity { - Silent => (), + match self.verbosity.level { + VerbosityLevel::Silent => (), _ => show_error!("cannot access '{}': {}", path.display(), e), } return None; }) } else { unwrap!(path.symlink_metadata(), e, { - match self.verbosity { - Silent => (), + match self.verbosity.level { + VerbosityLevel::Silent => (), _ => show_error!("cannot dereference '{}': {}", path.display(), e), } return None; diff --git a/src/uu/install/src/install.rs b/src/uu/install/src/install.rs index 5c951ad5b..d5f853de7 100644 --- a/src/uu/install/src/install.rs +++ b/src/uu/install/src/install.rs @@ -18,7 +18,7 @@ use filetime::{set_file_times, FileTime}; use uucore::backup_control::{self, BackupMode}; use uucore::entries::{grp2gid, usr2uid}; use uucore::error::{FromIo, UError, UIoError, UResult, USimpleError}; -use uucore::perms::{wrap_chgrp, wrap_chown, Verbosity}; +use uucore::perms::{wrap_chown, Verbosity, VerbosityLevel}; use libc::{getegid, geteuid}; use std::error::Error; @@ -641,7 +641,10 @@ fn copy(from: &Path, to: &Path, b: &Behavior) -> UResult<()> { Some(owner_id), Some(gid), false, - Verbosity::Normal, + Verbosity { + groups_only: false, + level: VerbosityLevel::Normal, + }, ) { Ok(n) => { if !n.is_empty() { @@ -662,7 +665,17 @@ fn copy(from: &Path, to: &Path, b: &Behavior) -> UResult<()> { Ok(g) => g, _ => return Err(InstallError::NoSuchGroup(b.group.clone()).into()), }; - match wrap_chgrp(to, &meta, Some(group_id), false, Verbosity::Normal) { + match wrap_chown( + to, + &meta, + Some(group_id), + None, + false, + Verbosity { + groups_only: true, + level: VerbosityLevel::Normal, + }, + ) { Ok(n) => { if !n.is_empty() { show_error!("{}", n); diff --git a/src/uucore/src/lib/features/perms.rs b/src/uucore/src/lib/features/perms.rs index 69491c297..2542c4d35 100644 --- a/src/uucore/src/lib/features/perms.rs +++ b/src/uucore/src/lib/features/perms.rs @@ -18,86 +18,16 @@ use std::path::Path; /// The various level of verbosity #[derive(PartialEq, Clone, Debug)] -pub enum Verbosity { +pub enum VerbosityLevel { Silent, Changes, Verbose, Normal, } - -/// Actually perform the change of group on a path -fn chgrp>(path: P, gid: gid_t, follow: bool) -> IOResult<()> { - let path = path.as_ref(); - let s = CString::new(path.as_os_str().as_bytes()).unwrap(); - let ret = unsafe { - if follow { - libc::chown(s.as_ptr(), 0_u32.wrapping_sub(1), gid) - } else { - lchown(s.as_ptr(), 0_u32.wrapping_sub(1), gid) - } - }; - if ret == 0 { - Ok(()) - } else { - Err(IOError::last_os_error()) - } -} - -/// Perform the change of group on a path -/// with the various options -/// and error messages management -pub fn wrap_chgrp>( - path: P, - meta: &Metadata, - dest_gid: Option, - follow: bool, - verbosity: Verbosity, -) -> Result { - use self::Verbosity::*; - let path = path.as_ref(); - let mut out: String = String::new(); - let dest_gid = dest_gid.unwrap_or_else(|| meta.gid()); - - if let Err(e) = chgrp(path, dest_gid, follow) { - match verbosity { - Silent => (), - _ => { - out = format!("changing group of '{}': {}", path.display(), e); - if verbosity == Verbose { - out = format!( - "{}\nfailed to change group of '{}' from {} to {}", - out, - path.display(), - entries::gid2grp(meta.gid()).unwrap(), - entries::gid2grp(dest_gid).unwrap() - ); - }; - } - } - return Err(out); - } else { - let changed = dest_gid != meta.gid(); - if changed { - match verbosity { - Changes | Verbose => { - out = format!( - "changed group of '{}' from {} to {}", - path.display(), - entries::gid2grp(meta.gid()).unwrap(), - entries::gid2grp(dest_gid).unwrap() - ); - } - _ => (), - }; - } else if verbosity == Verbose { - out = format!( - "group of '{}' retained as {}", - path.display(), - entries::gid2grp(dest_gid).unwrap_or_default() - ); - } - } - Ok(out) +#[derive(PartialEq, Clone, Debug)] +pub struct Verbosity { + pub groups_only: bool, + pub level: VerbosityLevel, } /// Actually perform the change of owner on a path @@ -129,27 +59,45 @@ pub fn wrap_chown>( follow: bool, verbosity: Verbosity, ) -> Result { - use self::Verbosity::*; let dest_uid = dest_uid.unwrap_or_else(|| meta.uid()); let dest_gid = dest_gid.unwrap_or_else(|| meta.gid()); let path = path.as_ref(); let mut out: String = String::new(); if let Err(e) = chown(path, dest_uid, dest_gid, follow) { - match verbosity { - Silent => (), - _ => { - out = format!("changing ownership of '{}': {}", path.display(), e); - if verbosity == Verbose { - out = format!( - "{}\nfailed to change ownership of '{}' from {}:{} to {}:{}", - out, - path.display(), - entries::uid2usr(meta.uid()).unwrap(), - entries::gid2grp(meta.gid()).unwrap(), - entries::uid2usr(dest_uid).unwrap(), - entries::gid2grp(dest_gid).unwrap() - ); + match verbosity.level { + VerbosityLevel::Silent => (), + level => { + out = format!( + "changing {} of '{}': {}", + if verbosity.groups_only { + "group" + } else { + "ownership" + }, + path.display(), + e + ); + if level == VerbosityLevel::Verbose { + out = if verbosity.groups_only { + format!( + "{}\nfailed to change group of '{}' from {} to {}", + out, + path.display(), + entries::gid2grp(meta.gid()).unwrap(), + entries::gid2grp(dest_gid).unwrap() + ) + } else { + format!( + "{}\nfailed to change ownership of '{}' from {}:{} to {}:{}", + out, + path.display(), + entries::uid2usr(meta.uid()).unwrap(), + entries::gid2grp(meta.gid()).unwrap(), + entries::uid2usr(dest_uid).unwrap(), + entries::gid2grp(dest_gid).unwrap() + ) + }; }; } } @@ -157,26 +105,43 @@ pub fn wrap_chown>( } else { let changed = dest_uid != meta.uid() || dest_gid != meta.gid(); if changed { - match verbosity { - Changes | Verbose => { - out = format!( - "changed ownership of '{}' from {}:{} to {}:{}", - path.display(), - entries::uid2usr(meta.uid()).unwrap(), - entries::gid2grp(meta.gid()).unwrap(), - entries::uid2usr(dest_uid).unwrap(), - entries::gid2grp(dest_gid).unwrap() - ); + match verbosity.level { + VerbosityLevel::Changes | VerbosityLevel::Verbose => { + out = if verbosity.groups_only { + format!( + "changed group of '{}' from {} to {}", + path.display(), + entries::gid2grp(meta.gid()).unwrap(), + entries::gid2grp(dest_gid).unwrap() + ) + } else { + format!( + "changed ownership of '{}' from {}:{} to {}:{}", + path.display(), + entries::uid2usr(meta.uid()).unwrap(), + entries::gid2grp(meta.gid()).unwrap(), + entries::uid2usr(dest_uid).unwrap(), + entries::gid2grp(dest_gid).unwrap() + ) + }; } _ => (), }; - } else if verbosity == Verbose { - out = format!( - "ownership of '{}' retained as {}:{}", - path.display(), - entries::uid2usr(dest_uid).unwrap(), - entries::gid2grp(dest_gid).unwrap() - ); + } else if verbosity.level == VerbosityLevel::Verbose { + out = if verbosity.groups_only { + format!( + "group of '{}' retained as {}", + path.display(), + entries::gid2grp(dest_gid).unwrap_or_default() + ) + } else { + format!( + "ownership of '{}' retained as {}:{}", + path.display(), + entries::uid2usr(dest_uid).unwrap(), + entries::gid2grp(dest_gid).unwrap() + ) + }; } } Ok(out) diff --git a/tests/by-util/test_chgrp.rs b/tests/by-util/test_chgrp.rs index 1b8057e47..0741838a4 100644 --- a/tests/by-util/test_chgrp.rs +++ b/tests/by-util/test_chgrp.rs @@ -43,7 +43,7 @@ fn test_invalid_group() { .arg("__nosuchgroup__") .arg("/") .fails() - .stderr_is("chgrp: invalid group: __nosuchgroup__"); + .stderr_is("chgrp: invalid group: '__nosuchgroup__'"); } #[test] From 4e251706be95b6a1664655b97afca4b68e1ee617 Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Tue, 17 Aug 2021 22:35:04 +0200 Subject: [PATCH 050/206] refactor: move shared chown logic to uucore --- Cargo.lock | 4 +- src/uu/chgrp/Cargo.toml | 1 - src/uu/chgrp/src/chgrp.rs | 15 +- src/uu/chown/Cargo.toml | 2 - src/uu/chown/src/chown.rs | 213 +-------------------------- src/uucore/Cargo.toml | 3 +- src/uucore/src/lib/features/perms.rs | 197 +++++++++++++++++++++++++ 7 files changed, 214 insertions(+), 221 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 477895e15..1066c02ef 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2126,7 +2126,6 @@ name = "uu_chgrp" version = "0.0.7" dependencies = [ "clap", - "uu_chown", "uucore", "uucore_procs", ] @@ -2147,10 +2146,8 @@ name = "uu_chown" version = "0.0.7" dependencies = [ "clap", - "glob", "uucore", "uucore_procs", - "walkdir", ] [[package]] @@ -3165,6 +3162,7 @@ dependencies = [ "termion", "thiserror", "time", + "walkdir", "wild", "winapi 0.3.9", "z85", diff --git a/src/uu/chgrp/Cargo.toml b/src/uu/chgrp/Cargo.toml index cc160626c..0d1b7e5aa 100644 --- a/src/uu/chgrp/Cargo.toml +++ b/src/uu/chgrp/Cargo.toml @@ -18,7 +18,6 @@ path = "src/chgrp.rs" clap = { version = "2.33", features = ["wrap_help"] } uucore = { version=">=0.0.9", package="uucore", path="../../uucore", features=["entries", "fs", "perms"] } uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_procs" } -uu_chown = { version=">=0.0.6", package="uu_chown", path="../chown"} [[bin]] name = "chgrp" diff --git a/src/uu/chgrp/src/chgrp.rs b/src/uu/chgrp/src/chgrp.rs index 5ae97955d..d37da578e 100644 --- a/src/uu/chgrp/src/chgrp.rs +++ b/src/uu/chgrp/src/chgrp.rs @@ -9,10 +9,11 @@ #[macro_use] extern crate uucore; -use uu_chown::{Chowner, IfFrom}; pub use uucore::entries; use uucore::error::{FromIo, UResult, USimpleError}; -use uucore::perms::{Verbosity, VerbosityLevel}; +use uucore::perms::{ + ChownExecutor, IfFrom, Verbosity, VerbosityLevel, FTS_COMFOLLOW, FTS_LOGICAL, FTS_PHYSICAL, +}; use clap::{App, Arg}; @@ -50,11 +51,7 @@ pub mod options { pub static ARG_FILES: &str = "FILE"; } -const FTS_COMFOLLOW: u8 = 1; -const FTS_PHYSICAL: u8 = 1 << 1; -const FTS_LOGICAL: u8 = 1 << 2; - -fn usage() -> String { +fn get_usage() -> String { format!( "{0} [OPTION]... GROUP FILE...\n {0} [OPTION]... --reference=RFILE FILE...", uucore::execution_phrase() @@ -67,7 +64,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { .collect_str(InvalidEncodingHandling::ConvertLossy) .accept_any(); - let usage = usage(); + let usage = get_usage(); let mut app = uu_app().usage(&usage[..]); @@ -172,7 +169,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } }; - let executor = Chowner { + let executor = ChownExecutor { bit_flag, dest_gid, verbosity: Verbosity { diff --git a/src/uu/chown/Cargo.toml b/src/uu/chown/Cargo.toml index 828c214be..e6dc7d4fe 100644 --- a/src/uu/chown/Cargo.toml +++ b/src/uu/chown/Cargo.toml @@ -16,10 +16,8 @@ path = "src/chown.rs" [dependencies] clap = { version = "2.33", features = ["wrap_help"] } -glob = "0.3.0" uucore = { version=">=0.0.9", package="uucore", path="../../uucore", features=["entries", "fs", "perms"] } uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_procs" } -walkdir = "2.2" [[bin]] name = "chown" diff --git a/src/uu/chown/src/chown.rs b/src/uu/chown/src/chown.rs index 13d16dc2a..06f0c6a32 100644 --- a/src/uu/chown/src/chown.rs +++ b/src/uu/chown/src/chown.rs @@ -5,26 +5,22 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -// spell-checker:ignore (ToDO) COMFOLLOW Chowner Passwd RFILE RFILE's derefer dgid duid +// spell-checker:ignore (ToDO) COMFOLLOW Passwd RFILE RFILE's derefer dgid duid #[macro_use] extern crate uucore; pub use uucore::entries::{self, Group, Locate, Passwd}; -use uucore::fs::resolve_relative_path; -use uucore::libc::{gid_t, uid_t}; -use uucore::perms::{wrap_chown, Verbosity, VerbosityLevel}; +use uucore::perms::{ + ChownExecutor, IfFrom, Verbosity, VerbosityLevel, FTS_COMFOLLOW, FTS_LOGICAL, FTS_PHYSICAL, +}; use uucore::error::{FromIo, UResult, USimpleError}; use clap::{crate_version, App, Arg}; -use walkdir::WalkDir; - -use std::fs::{self, Metadata}; +use std::fs; use std::os::unix::fs::MetadataExt; -use std::convert::AsRef; -use std::path::Path; use uucore::InvalidEncodingHandling; static ABOUT: &str = "change file owner and group"; @@ -57,11 +53,7 @@ pub mod options { static ARG_OWNER: &str = "owner"; static ARG_FILES: &str = "files"; -const FTS_COMFOLLOW: u8 = 1; -const FTS_PHYSICAL: u8 = 1 << 1; -const FTS_LOGICAL: u8 = 1 << 2; - -fn usage() -> String { +fn get_usage() -> String { format!( "{0} [OPTION]... [OWNER][:[GROUP]] FILE...\n{0} [OPTION]... --reference=RFILE FILE...", uucore::execution_phrase() @@ -74,7 +66,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { .collect_str(InvalidEncodingHandling::Ignore) .accept_any(); - let usage = usage(); + let usage = get_usage(); let matches = uu_app().usage(&usage[..]).get_matches_from(args); @@ -150,7 +142,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { dest_uid = u; dest_gid = g; } - let executor = Chowner { + let executor = ChownExecutor { bit_flag, dest_uid, dest_gid, @@ -289,195 +281,6 @@ fn parse_spec(spec: &str) -> UResult<(Option, Option)> { Ok((uid, gid)) } -pub enum IfFrom { - All, - User(u32), - Group(u32), - UserGroup(u32, u32), -} - -pub struct Chowner { - pub dest_uid: Option, - pub dest_gid: Option, - pub bit_flag: u8, - pub verbosity: Verbosity, - pub filter: IfFrom, - pub files: Vec, - pub recursive: bool, - pub preserve_root: bool, - pub dereference: bool, -} - -macro_rules! unwrap { - ($m:expr, $e:ident, $err:block) => { - match $m { - Ok(meta) => meta, - Err($e) => $err, - } - }; -} - -impl Chowner { - pub fn exec(&self) -> UResult<()> { - let mut ret = 0; - for f in &self.files { - ret |= self.traverse(f); - } - if ret != 0 { - return Err(ret.into()); - } - Ok(()) - } - - fn traverse>(&self, root: P) -> i32 { - let follow_arg = self.dereference || self.bit_flag != FTS_PHYSICAL; - let path = root.as_ref(); - let meta = match self.obtain_meta(path, follow_arg) { - Some(m) => m, - _ => return 1, - }; - - // Prohibit only if: - // (--preserve-root and -R present) && - // ( - // (argument is not symlink && resolved to be '/') || - // (argument is symlink && should follow argument && resolved to be '/') - // ) - if self.recursive && self.preserve_root { - let may_exist = if follow_arg { - path.canonicalize().ok() - } else { - let real = resolve_relative_path(path); - if real.is_dir() { - Some(real.canonicalize().expect("failed to get real path")) - } else { - Some(real.into_owned()) - } - }; - - if let Some(p) = may_exist { - if p.parent().is_none() { - show_error!("it is dangerous to operate recursively on '/'"); - show_error!("use --no-preserve-root to override this failsafe"); - return 1; - } - } - } - - let ret = if self.matched(meta.uid(), meta.gid()) { - match wrap_chown( - path, - &meta, - self.dest_uid, - self.dest_gid, - follow_arg, - self.verbosity.clone(), - ) { - Ok(n) => { - if !n.is_empty() { - show_error!("{}", n); - } - 0 - } - Err(e) => { - if self.verbosity.level != VerbosityLevel::Silent { - show_error!("{}", e); - } - 1 - } - } - } else { - 0 - }; - - if !self.recursive { - ret - } else { - ret | self.dive_into(&root) - } - } - - fn dive_into>(&self, root: P) -> i32 { - let mut ret = 0; - let root = root.as_ref(); - let follow = self.dereference || self.bit_flag & FTS_LOGICAL != 0; - for entry in WalkDir::new(root).follow_links(follow).min_depth(1) { - let entry = unwrap!(entry, e, { - ret = 1; - show_error!("{}", e); - continue; - }); - let path = entry.path(); - let meta = match self.obtain_meta(path, follow) { - Some(m) => m, - _ => { - ret = 1; - continue; - } - }; - - if !self.matched(meta.uid(), meta.gid()) { - continue; - } - - ret = match wrap_chown( - path, - &meta, - self.dest_uid, - self.dest_gid, - follow, - self.verbosity.clone(), - ) { - Ok(n) => { - if !n.is_empty() { - show_error!("{}", n); - } - 0 - } - Err(e) => { - if self.verbosity.level != VerbosityLevel::Silent { - show_error!("{}", e); - } - 1 - } - } - } - ret - } - - fn obtain_meta>(&self, path: P, follow: bool) -> Option { - let path = path.as_ref(); - let meta = if follow { - unwrap!(path.metadata(), e, { - match self.verbosity.level { - VerbosityLevel::Silent => (), - _ => show_error!("cannot access '{}': {}", path.display(), e), - } - return None; - }) - } else { - unwrap!(path.symlink_metadata(), e, { - match self.verbosity.level { - VerbosityLevel::Silent => (), - _ => show_error!("cannot dereference '{}': {}", path.display(), e), - } - return None; - }) - }; - Some(meta) - } - - #[inline] - fn matched(&self, uid: uid_t, gid: gid_t) -> bool { - match self.filter { - IfFrom::All => true, - IfFrom::User(u) => u == uid, - IfFrom::Group(g) => g == gid, - IfFrom::UserGroup(u, g) => u == uid && g == gid, - } - } -} - #[cfg(test)] mod test { use super::*; diff --git a/src/uucore/Cargo.toml b/src/uucore/Cargo.toml index 28cba3a61..c49e0a0f3 100644 --- a/src/uucore/Cargo.toml +++ b/src/uucore/Cargo.toml @@ -26,6 +26,7 @@ lazy_static = { version="1.3", optional=true } nix = { version="<= 0.19", optional=true } platform-info = { version="<= 0.1", optional=true } time = { version="<= 0.1.43", optional=true } +walkdir = { version="2.3.2", optional=true } # * "problem" dependencies (pinned) data-encoding = { version="2.1", optional=true } data-encoding-macro = { version="0.1.12", optional=true } @@ -50,7 +51,7 @@ entries = ["libc"] fs = ["libc"] fsext = ["libc", "time"] mode = ["libc"] -perms = ["libc"] +perms = ["libc", "walkdir"] process = ["libc"] ringbuffer = [] signals = [] diff --git a/src/uucore/src/lib/features/perms.rs b/src/uucore/src/lib/features/perms.rs index 2542c4d35..4d2a2afad 100644 --- a/src/uucore/src/lib/features/perms.rs +++ b/src/uucore/src/lib/features/perms.rs @@ -3,8 +3,12 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. +use crate::error::UResult; pub use crate::features::entries; +use crate::fs::resolve_relative_path; +use crate::show_error; use libc::{self, gid_t, lchown, uid_t}; +use walkdir::WalkDir; use std::io::Error as IOError; use std::io::Result as IOResult; @@ -146,3 +150,196 @@ pub fn wrap_chown>( } Ok(out) } + +pub enum IfFrom { + All, + User(u32), + Group(u32), + UserGroup(u32, u32), +} + +pub struct ChownExecutor { + pub dest_uid: Option, + pub dest_gid: Option, + pub bit_flag: u8, + pub verbosity: Verbosity, + pub filter: IfFrom, + pub files: Vec, + pub recursive: bool, + pub preserve_root: bool, + pub dereference: bool, +} + +macro_rules! unwrap { + ($m:expr, $e:ident, $err:block) => { + match $m { + Ok(meta) => meta, + Err($e) => $err, + } + }; +} + +pub const FTS_COMFOLLOW: u8 = 1; +pub const FTS_PHYSICAL: u8 = 1 << 1; +pub const FTS_LOGICAL: u8 = 1 << 2; + +impl ChownExecutor { + pub fn exec(&self) -> UResult<()> { + let mut ret = 0; + for f in &self.files { + ret |= self.traverse(f); + } + if ret != 0 { + return Err(ret.into()); + } + Ok(()) + } + + fn traverse>(&self, root: P) -> i32 { + let follow_arg = self.dereference || self.bit_flag != FTS_PHYSICAL; + let path = root.as_ref(); + let meta = match self.obtain_meta(path, follow_arg) { + Some(m) => m, + _ => return 1, + }; + + // Prohibit only if: + // (--preserve-root and -R present) && + // ( + // (argument is not symlink && resolved to be '/') || + // (argument is symlink && should follow argument && resolved to be '/') + // ) + if self.recursive && self.preserve_root { + let may_exist = if follow_arg { + path.canonicalize().ok() + } else { + let real = resolve_relative_path(path); + if real.is_dir() { + Some(real.canonicalize().expect("failed to get real path")) + } else { + Some(real.into_owned()) + } + }; + + if let Some(p) = may_exist { + if p.parent().is_none() { + show_error!("it is dangerous to operate recursively on '/'"); + show_error!("use --no-preserve-root to override this failsafe"); + return 1; + } + } + } + + let ret = if self.matched(meta.uid(), meta.gid()) { + match wrap_chown( + path, + &meta, + self.dest_uid, + self.dest_gid, + follow_arg, + self.verbosity.clone(), + ) { + Ok(n) => { + if !n.is_empty() { + show_error!("{}", n); + } + 0 + } + Err(e) => { + if self.verbosity.level != VerbosityLevel::Silent { + show_error!("{}", e); + } + 1 + } + } + } else { + 0 + }; + + if !self.recursive { + ret + } else { + ret | self.dive_into(&root) + } + } + + fn dive_into>(&self, root: P) -> i32 { + let mut ret = 0; + let root = root.as_ref(); + let follow = self.dereference || self.bit_flag & FTS_LOGICAL != 0; + for entry in WalkDir::new(root).follow_links(follow).min_depth(1) { + let entry = unwrap!(entry, e, { + ret = 1; + show_error!("{}", e); + continue; + }); + let path = entry.path(); + let meta = match self.obtain_meta(path, follow) { + Some(m) => m, + _ => { + ret = 1; + continue; + } + }; + + if !self.matched(meta.uid(), meta.gid()) { + continue; + } + + ret = match wrap_chown( + path, + &meta, + self.dest_uid, + self.dest_gid, + follow, + self.verbosity.clone(), + ) { + Ok(n) => { + if !n.is_empty() { + show_error!("{}", n); + } + 0 + } + Err(e) => { + if self.verbosity.level != VerbosityLevel::Silent { + show_error!("{}", e); + } + 1 + } + } + } + ret + } + + fn obtain_meta>(&self, path: P, follow: bool) -> Option { + let path = path.as_ref(); + let meta = if follow { + unwrap!(path.metadata(), e, { + match self.verbosity.level { + VerbosityLevel::Silent => (), + _ => show_error!("cannot access '{}': {}", path.display(), e), + } + return None; + }) + } else { + unwrap!(path.symlink_metadata(), e, { + match self.verbosity.level { + VerbosityLevel::Silent => (), + _ => show_error!("cannot dereference '{}': {}", path.display(), e), + } + return None; + }) + }; + Some(meta) + } + + #[inline] + fn matched(&self, uid: uid_t, gid: gid_t) -> bool { + match self.filter { + IfFrom::All => true, + IfFrom::User(u) => u == uid, + IfFrom::Group(g) => g == gid, + IfFrom::UserGroup(u, g) => u == uid && g == gid, + } + } +} From d06c07482909f0d81ab007babe3a6f2ed423b20b Mon Sep 17 00:00:00 2001 From: Jan Verbeek Date: Tue, 24 Aug 2021 17:10:16 +0200 Subject: [PATCH 051/206] Run clippy on the full workspace These lints were cluttering up the "problems" tab in my VS Code. `--workspace` fixes the disparity. --- .github/workflows/CICD.yml | 2 +- .pre-commit-config.yaml | 2 +- src/uu/csplit/src/csplit.rs | 28 ++++---- src/uu/dd/src/parseargs/unit_tests.rs | 26 ++++---- src/uu/factor/src/table.rs | 2 +- src/uu/head/src/head.rs | 5 +- src/uu/od/src/inputdecoder.rs | 1 + src/uu/od/src/mockstream.rs | 8 +-- src/uu/od/src/multifilereader.rs | 58 ++++++++-------- src/uu/od/src/parse_formats.rs | 80 +++++++++++------------ src/uu/od/src/prn_float.rs | 1 + src/uucore/src/lib/features/ringbuffer.rs | 3 +- 12 files changed, 109 insertions(+), 107 deletions(-) diff --git a/.github/workflows/CICD.yml b/.github/workflows/CICD.yml index d2a164f2d..d816fb053 100644 --- a/.github/workflows/CICD.yml +++ b/.github/workflows/CICD.yml @@ -130,7 +130,7 @@ jobs: run: | ## `clippy` lint testing # * convert any warnings to GHA UI annotations; ref: - S=$(cargo +nightly clippy --all-targets ${{ matrix.job.cargo-options }} ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }} -- -D warnings 2>&1) && printf "%s\n" "$S" || { printf "%s\n" "$S" ; printf "%s" "$S" | sed -E -n -e '/^error:/{' -e "N; s/^error:[[:space:]]+(.*)\\n[[:space:]]+-->[[:space:]]+${PWD//\//\\/}\/(.*):([0-9]+):([0-9]+).*$/::error file=\2,line=\3,col=\4::ERROR: \`cargo clippy\`: \1 (file:'\2', line:\3)/p;" -e '}' ; exit 1 ; } + S=$(cargo +nightly clippy --workspace --all-targets ${{ matrix.job.cargo-options }} ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }} -- -D warnings 2>&1) && printf "%s\n" "$S" || { printf "%s\n" "$S" ; printf "%s" "$S" | sed -E -n -e '/^error:/{' -e "N; s/^error:[[:space:]]+(.*)\\n[[:space:]]+-->[[:space:]]+${PWD//\//\\/}\/(.*):([0-9]+):([0-9]+).*$/::error file=\2,line=\3,col=\4::ERROR: \`cargo clippy\`: \1 (file:'\2', line:\3)/p;" -e '}' ; exit 1 ; } code_spellcheck: name: Style/spelling diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e3ad98ee3..f90466bed 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -11,7 +11,7 @@ repos: - id: rust-clippy name: Rust clippy description: Run cargo clippy on files included in the commit. - entry: cargo +nightly clippy --all-targets --all-features -- + entry: cargo +nightly clippy --workspace --all-targets --all-features -- pass_filenames: false types: [file, rust] language: system diff --git a/src/uu/csplit/src/csplit.rs b/src/uu/csplit/src/csplit.rs index 9e93bbe42..603d25265 100644 --- a/src/uu/csplit/src/csplit.rs +++ b/src/uu/csplit/src/csplit.rs @@ -568,7 +568,7 @@ mod tests { assert_eq!(input_splitter.add_line_to_buffer(0, line), None); assert_eq!(input_splitter.buffer_len(), 1); } - item @ _ => panic!("wrong item: {:?}", item), + item => panic!("wrong item: {:?}", item), }; match input_splitter.next() { @@ -577,7 +577,7 @@ mod tests { assert_eq!(input_splitter.add_line_to_buffer(1, line), None); assert_eq!(input_splitter.buffer_len(), 2); } - item @ _ => panic!("wrong item: {:?}", item), + item => panic!("wrong item: {:?}", item), }; match input_splitter.next() { @@ -589,7 +589,7 @@ mod tests { ); assert_eq!(input_splitter.buffer_len(), 2); } - item @ _ => panic!("wrong item: {:?}", item), + item => panic!("wrong item: {:?}", item), }; input_splitter.rewind_buffer(); @@ -599,7 +599,7 @@ mod tests { assert_eq!(line, String::from("bbb")); assert_eq!(input_splitter.buffer_len(), 1); } - item @ _ => panic!("wrong item: {:?}", item), + item => panic!("wrong item: {:?}", item), }; match input_splitter.next() { @@ -607,7 +607,7 @@ mod tests { assert_eq!(line, String::from("ccc")); assert_eq!(input_splitter.buffer_len(), 0); } - item @ _ => panic!("wrong item: {:?}", item), + item => panic!("wrong item: {:?}", item), }; match input_splitter.next() { @@ -615,7 +615,7 @@ mod tests { assert_eq!(line, String::from("ddd")); assert_eq!(input_splitter.buffer_len(), 0); } - item @ _ => panic!("wrong item: {:?}", item), + item => panic!("wrong item: {:?}", item), }; assert!(input_splitter.next().is_none()); @@ -640,7 +640,7 @@ mod tests { assert_eq!(input_splitter.add_line_to_buffer(0, line), None); assert_eq!(input_splitter.buffer_len(), 1); } - item @ _ => panic!("wrong item: {:?}", item), + item => panic!("wrong item: {:?}", item), }; match input_splitter.next() { @@ -649,7 +649,7 @@ mod tests { assert_eq!(input_splitter.add_line_to_buffer(1, line), None); assert_eq!(input_splitter.buffer_len(), 2); } - item @ _ => panic!("wrong item: {:?}", item), + item => panic!("wrong item: {:?}", item), }; match input_splitter.next() { @@ -658,7 +658,7 @@ mod tests { assert_eq!(input_splitter.add_line_to_buffer(2, line), None); assert_eq!(input_splitter.buffer_len(), 3); } - item @ _ => panic!("wrong item: {:?}", item), + item => panic!("wrong item: {:?}", item), }; input_splitter.rewind_buffer(); @@ -669,7 +669,7 @@ mod tests { assert_eq!(input_splitter.add_line_to_buffer(0, line), None); assert_eq!(input_splitter.buffer_len(), 3); } - item @ _ => panic!("wrong item: {:?}", item), + item => panic!("wrong item: {:?}", item), }; match input_splitter.next() { @@ -677,7 +677,7 @@ mod tests { assert_eq!(line, String::from("aaa")); assert_eq!(input_splitter.buffer_len(), 2); } - item @ _ => panic!("wrong item: {:?}", item), + item => panic!("wrong item: {:?}", item), }; match input_splitter.next() { @@ -685,7 +685,7 @@ mod tests { assert_eq!(line, String::from("bbb")); assert_eq!(input_splitter.buffer_len(), 1); } - item @ _ => panic!("wrong item: {:?}", item), + item => panic!("wrong item: {:?}", item), }; match input_splitter.next() { @@ -693,7 +693,7 @@ mod tests { assert_eq!(line, String::from("ccc")); assert_eq!(input_splitter.buffer_len(), 0); } - item @ _ => panic!("wrong item: {:?}", item), + item => panic!("wrong item: {:?}", item), }; match input_splitter.next() { @@ -701,7 +701,7 @@ mod tests { assert_eq!(line, String::from("ddd")); assert_eq!(input_splitter.buffer_len(), 0); } - item @ _ => panic!("wrong item: {:?}", item), + item => panic!("wrong item: {:?}", item), }; assert!(input_splitter.next().is_none()); diff --git a/src/uu/dd/src/parseargs/unit_tests.rs b/src/uu/dd/src/parseargs/unit_tests.rs index d4d74f58f..78de7f847 100644 --- a/src/uu/dd/src/parseargs/unit_tests.rs +++ b/src/uu/dd/src/parseargs/unit_tests.rs @@ -50,7 +50,7 @@ fn unimplemented_flags_should_error() { let mut succeeded = Vec::new(); // The following flags are not implemented - for flag in vec!["cio", "nocache", "nolinks", "text", "binary"] { + for &flag in &["cio", "nocache", "nolinks", "text", "binary"] { let args = vec![ String::from("dd"), format!("--iflag={}", flag), @@ -58,13 +58,11 @@ fn unimplemented_flags_should_error() { ]; let matches = uu_app().get_matches_from_safe(args).unwrap(); - match parse_iflags(&matches) { - Ok(_) => succeeded.push(format!("iflag={}", flag)), - Err(_) => { /* expected behaviour :-) */ } + if parse_iflags(&matches).is_ok() { + succeeded.push(format!("iflag={}", flag)) } - match parse_oflags(&matches) { - Ok(_) => succeeded.push(format!("oflag={}", flag)), - Err(_) => { /* expected behaviour :-) */ } + if parse_oflags(&matches).is_ok() { + succeeded.push(format!("oflag={}", flag)) } } @@ -356,7 +354,7 @@ fn parse_icf_token_ibm() { assert_eq!(exp.len(), act.len()); for cf in &exp { - assert!(exp.contains(&cf)); + assert!(exp.contains(cf)); } } @@ -373,7 +371,7 @@ fn parse_icf_tokens_elu() { assert_eq!(exp.len(), act.len()); for cf in &exp { - assert!(exp.contains(&cf)); + assert!(exp.contains(cf)); } } @@ -405,7 +403,7 @@ fn parse_icf_tokens_remaining() { assert_eq!(exp.len(), act.len()); for cf in &exp { - assert!(exp.contains(&cf)); + assert!(exp.contains(cf)); } } @@ -429,7 +427,7 @@ fn parse_iflag_tokens() { assert_eq!(exp.len(), act.len()); for cf in &exp { - assert!(exp.contains(&cf)); + assert!(exp.contains(cf)); } } @@ -453,7 +451,7 @@ fn parse_oflag_tokens() { assert_eq!(exp.len(), act.len()); for cf in &exp { - assert!(exp.contains(&cf)); + assert!(exp.contains(cf)); } } @@ -481,7 +479,7 @@ fn parse_iflag_tokens_linux() { assert_eq!(exp.len(), act.len()); for cf in &exp { - assert!(exp.contains(&cf)); + assert!(exp.contains(cf)); } } @@ -509,7 +507,7 @@ fn parse_oflag_tokens_linux() { assert_eq!(exp.len(), act.len()); for cf in &exp { - assert!(exp.contains(&cf)); + assert!(exp.contains(cf)); } } diff --git a/src/uu/factor/src/table.rs b/src/uu/factor/src/table.rs index 6d4047e49..0fb338d9d 100644 --- a/src/uu/factor/src/table.rs +++ b/src/uu/factor/src/table.rs @@ -86,7 +86,7 @@ mod tests { let mut n_c: [u64; CHUNK_SIZE] = rng.gen(); let mut f_c: [Factors; CHUNK_SIZE] = rng.gen(); - let mut n_i = n_c.clone(); + let mut n_i = n_c; let mut f_i = f_c.clone(); for (n, f) in n_i.iter_mut().zip(f_i.iter_mut()) { factor(n, f); diff --git a/src/uu/head/src/head.rs b/src/uu/head/src/head.rs index e47488ac4..760cb44d5 100644 --- a/src/uu/head/src/head.rs +++ b/src/uu/head/src/head.rs @@ -483,7 +483,7 @@ mod tests { fn options(args: &str) -> Result { let combined = "head ".to_owned() + args; let args = combined.split_whitespace(); - HeadOptions::get_from(args.map(|s| OsString::from(s))) + HeadOptions::get_from(args.map(OsString::from)) } #[test] fn test_args_modes() { @@ -522,6 +522,7 @@ 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(); @@ -552,7 +553,7 @@ mod tests { assert!(parse_mode("1T", Modes::Bytes).is_err()); } fn arg_outputs(src: &str) -> Result { - let split = src.split_whitespace().map(|x| OsString::from(x)); + let split = src.split_whitespace().map(OsString::from); match arg_iterate(split) { Ok(args) => { let vec = args diff --git a/src/uu/od/src/inputdecoder.rs b/src/uu/od/src/inputdecoder.rs index 606495461..c347ccc10 100644 --- a/src/uu/od/src/inputdecoder.rs +++ b/src/uu/od/src/inputdecoder.rs @@ -161,6 +161,7 @@ mod tests { use std::io::Cursor; #[test] + #[allow(clippy::float_cmp)] fn smoke_test() { let data = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0xff, 0xff]; let mut input = PeekReader::new(Cursor::new(&data)); diff --git a/src/uu/od/src/mockstream.rs b/src/uu/od/src/mockstream.rs index 810e1d35b..5beecbf9c 100644 --- a/src/uu/od/src/mockstream.rs +++ b/src/uu/od/src/mockstream.rs @@ -56,15 +56,15 @@ impl FailingMockStream { /// `kind` and `message` can be specified to define the exact error. pub fn new(kind: ErrorKind, message: &'static str, repeat_count: i32) -> FailingMockStream { FailingMockStream { - kind: kind, - message: message, - repeat_count: repeat_count, + kind, + message, + repeat_count, } } fn error(&mut self) -> Result { if self.repeat_count == 0 { - return Ok(0); + Ok(0) } else { if self.repeat_count > 0 { self.repeat_count -= 1; diff --git a/src/uu/od/src/multifilereader.rs b/src/uu/od/src/multifilereader.rs index 2f44da803..0f8616467 100644 --- a/src/uu/od/src/multifilereader.rs +++ b/src/uu/od/src/multifilereader.rs @@ -122,9 +122,10 @@ mod tests { #[test] fn test_multi_file_reader_one_read() { - let mut inputs = Vec::new(); - inputs.push(InputSource::Stream(Box::new(Cursor::new(&b"abcd"[..])))); - inputs.push(InputSource::Stream(Box::new(Cursor::new(&b"ABCD"[..])))); + let inputs = vec![ + InputSource::Stream(Box::new(Cursor::new(&b"abcd"[..]))), + InputSource::Stream(Box::new(Cursor::new(&b"ABCD"[..]))), + ]; let mut v = [0; 10]; let mut sut = MultifileReader::new(inputs); @@ -136,9 +137,10 @@ mod tests { #[test] fn test_multi_file_reader_two_reads() { - let mut inputs = Vec::new(); - inputs.push(InputSource::Stream(Box::new(Cursor::new(&b"abcd"[..])))); - inputs.push(InputSource::Stream(Box::new(Cursor::new(&b"ABCD"[..])))); + let inputs = vec![ + InputSource::Stream(Box::new(Cursor::new(&b"abcd"[..]))), + InputSource::Stream(Box::new(Cursor::new(&b"ABCD"[..]))), + ]; let mut v = [0; 5]; let mut sut = MultifileReader::new(inputs); @@ -154,9 +156,10 @@ mod tests { let c = Cursor::new(&b"1234"[..]) .chain(FailingMockStream::new(ErrorKind::Other, "Failing", 1)) .chain(Cursor::new(&b"5678"[..])); - let mut inputs = Vec::new(); - inputs.push(InputSource::Stream(Box::new(c))); - inputs.push(InputSource::Stream(Box::new(Cursor::new(&b"ABCD"[..])))); + let inputs = vec![ + InputSource::Stream(Box::new(c)), + InputSource::Stream(Box::new(Cursor::new(&b"ABCD"[..]))), + ]; let mut v = [0; 5]; let mut sut = MultifileReader::new(inputs); @@ -171,24 +174,25 @@ mod tests { #[test] fn test_multi_file_reader_read_error_at_start() { - let mut inputs = Vec::new(); - inputs.push(InputSource::Stream(Box::new(FailingMockStream::new( - ErrorKind::Other, - "Failing", - 1, - )))); - inputs.push(InputSource::Stream(Box::new(Cursor::new(&b"abcd"[..])))); - inputs.push(InputSource::Stream(Box::new(FailingMockStream::new( - ErrorKind::Other, - "Failing", - 1, - )))); - inputs.push(InputSource::Stream(Box::new(Cursor::new(&b"ABCD"[..])))); - inputs.push(InputSource::Stream(Box::new(FailingMockStream::new( - ErrorKind::Other, - "Failing", - 1, - )))); + let inputs = vec![ + InputSource::Stream(Box::new(FailingMockStream::new( + ErrorKind::Other, + "Failing", + 1, + ))), + InputSource::Stream(Box::new(Cursor::new(&b"abcd"[..]))), + InputSource::Stream(Box::new(FailingMockStream::new( + ErrorKind::Other, + "Failing", + 1, + ))), + InputSource::Stream(Box::new(Cursor::new(&b"ABCD"[..]))), + InputSource::Stream(Box::new(FailingMockStream::new( + ErrorKind::Other, + "Failing", + 1, + ))), + ]; let mut v = [0; 5]; let mut sut = MultifileReader::new(inputs); diff --git a/src/uu/od/src/parse_formats.rs b/src/uu/od/src/parse_formats.rs index f5b150d61..80d477b27 100644 --- a/src/uu/od/src/parse_formats.rs +++ b/src/uu/od/src/parse_formats.rs @@ -316,9 +316,7 @@ fn parse_type_string(params: &str) -> Result, Strin } #[cfg(test)] -pub fn parse_format_flags_str( - args_str: &Vec<&'static str>, -) -> Result, String> { +pub fn parse_format_flags_str(args_str: &[&'static str]) -> Result, String> { let args: Vec = args_str.iter().map(|s| s.to_string()).collect(); parse_format_flags(&args).map(|v| { // tests using this function assume add_ascii_dump is not set @@ -332,7 +330,7 @@ pub fn parse_format_flags_str( #[test] fn test_no_options() { assert_eq!( - parse_format_flags_str(&vec!["od"]).unwrap(), + parse_format_flags_str(&["od"]).unwrap(), vec![FORMAT_ITEM_OCT16] ); } @@ -340,7 +338,7 @@ fn test_no_options() { #[test] fn test_one_option() { assert_eq!( - parse_format_flags_str(&vec!["od", "-F"]).unwrap(), + parse_format_flags_str(&["od", "-F"]).unwrap(), vec![FORMAT_ITEM_F64] ); } @@ -348,7 +346,7 @@ fn test_one_option() { #[test] fn test_two_separate_options() { assert_eq!( - parse_format_flags_str(&vec!["od", "-F", "-x"]).unwrap(), + parse_format_flags_str(&["od", "-F", "-x"]).unwrap(), vec![FORMAT_ITEM_F64, FORMAT_ITEM_HEX16] ); } @@ -356,7 +354,7 @@ fn test_two_separate_options() { #[test] fn test_two_combined_options() { assert_eq!( - parse_format_flags_str(&vec!["od", "-Fx"]).unwrap(), + parse_format_flags_str(&["od", "-Fx"]).unwrap(), vec![FORMAT_ITEM_F64, FORMAT_ITEM_HEX16] ); } @@ -364,7 +362,7 @@ fn test_two_combined_options() { #[test] fn test_ignore_non_format_parameters() { assert_eq!( - parse_format_flags_str(&vec!["od", "-d", "-Ax"]).unwrap(), + parse_format_flags_str(&["od", "-d", "-Ax"]).unwrap(), vec![FORMAT_ITEM_DEC16U] ); } @@ -372,7 +370,7 @@ fn test_ignore_non_format_parameters() { #[test] fn test_ignore_separate_parameters() { assert_eq!( - parse_format_flags_str(&vec!["od", "-I", "-A", "x"]).unwrap(), + parse_format_flags_str(&["od", "-I", "-A", "x"]).unwrap(), vec![FORMAT_ITEM_DEC64S] ); } @@ -380,36 +378,36 @@ fn test_ignore_separate_parameters() { #[test] fn test_ignore_trailing_vals() { assert_eq!( - parse_format_flags_str(&vec!["od", "-D", "--", "-x"]).unwrap(), + parse_format_flags_str(&["od", "-D", "--", "-x"]).unwrap(), vec![FORMAT_ITEM_DEC32U] ); } #[test] fn test_invalid_long_format() { - parse_format_flags_str(&vec!["od", "--format=X"]).unwrap_err(); - parse_format_flags_str(&vec!["od", "--format=xX"]).unwrap_err(); - parse_format_flags_str(&vec!["od", "--format=aC"]).unwrap_err(); - parse_format_flags_str(&vec!["od", "--format=fI"]).unwrap_err(); - parse_format_flags_str(&vec!["od", "--format=xD"]).unwrap_err(); + parse_format_flags_str(&["od", "--format=X"]).unwrap_err(); + parse_format_flags_str(&["od", "--format=xX"]).unwrap_err(); + parse_format_flags_str(&["od", "--format=aC"]).unwrap_err(); + parse_format_flags_str(&["od", "--format=fI"]).unwrap_err(); + parse_format_flags_str(&["od", "--format=xD"]).unwrap_err(); - parse_format_flags_str(&vec!["od", "--format=xC1"]).unwrap_err(); - parse_format_flags_str(&vec!["od", "--format=x1C"]).unwrap_err(); - parse_format_flags_str(&vec!["od", "--format=xz1"]).unwrap_err(); - parse_format_flags_str(&vec!["od", "--format=xzC"]).unwrap_err(); - parse_format_flags_str(&vec!["od", "--format=xzz"]).unwrap_err(); - parse_format_flags_str(&vec!["od", "--format=xCC"]).unwrap_err(); + parse_format_flags_str(&["od", "--format=xC1"]).unwrap_err(); + parse_format_flags_str(&["od", "--format=x1C"]).unwrap_err(); + parse_format_flags_str(&["od", "--format=xz1"]).unwrap_err(); + parse_format_flags_str(&["od", "--format=xzC"]).unwrap_err(); + parse_format_flags_str(&["od", "--format=xzz"]).unwrap_err(); + parse_format_flags_str(&["od", "--format=xCC"]).unwrap_err(); - parse_format_flags_str(&vec!["od", "--format=c1"]).unwrap_err(); - parse_format_flags_str(&vec!["od", "--format=x256"]).unwrap_err(); - parse_format_flags_str(&vec!["od", "--format=d5"]).unwrap_err(); - parse_format_flags_str(&vec!["od", "--format=f1"]).unwrap_err(); + parse_format_flags_str(&["od", "--format=c1"]).unwrap_err(); + parse_format_flags_str(&["od", "--format=x256"]).unwrap_err(); + parse_format_flags_str(&["od", "--format=d5"]).unwrap_err(); + parse_format_flags_str(&["od", "--format=f1"]).unwrap_err(); } #[test] fn test_long_format_a() { assert_eq!( - parse_format_flags_str(&vec!["od", "--format=a"]).unwrap(), + parse_format_flags_str(&["od", "--format=a"]).unwrap(), vec![FORMAT_ITEM_A] ); } @@ -417,7 +415,7 @@ fn test_long_format_a() { #[test] fn test_long_format_cz() { assert_eq!( - parse_format_flags(&vec!["od".to_string(), "--format=cz".to_string()]).unwrap(), + parse_format_flags(&["od".to_string(), "--format=cz".to_string()]).unwrap(), vec![ParsedFormatterItemInfo::new(FORMAT_ITEM_C, true)] ); } @@ -425,7 +423,7 @@ fn test_long_format_cz() { #[test] fn test_long_format_d() { assert_eq!( - parse_format_flags_str(&vec!["od", "--format=d8"]).unwrap(), + parse_format_flags_str(&["od", "--format=d8"]).unwrap(), vec![FORMAT_ITEM_DEC64S] ); } @@ -433,7 +431,7 @@ fn test_long_format_d() { #[test] fn test_long_format_d_default() { assert_eq!( - parse_format_flags_str(&vec!["od", "--format=d"]).unwrap(), + parse_format_flags_str(&["od", "--format=d"]).unwrap(), vec![FORMAT_ITEM_DEC32S] ); } @@ -441,7 +439,7 @@ fn test_long_format_d_default() { #[test] fn test_long_format_o_default() { assert_eq!( - parse_format_flags_str(&vec!["od", "--format=o"]).unwrap(), + parse_format_flags_str(&["od", "--format=o"]).unwrap(), vec![FORMAT_ITEM_OCT32] ); } @@ -449,7 +447,7 @@ fn test_long_format_o_default() { #[test] fn test_long_format_u_default() { assert_eq!( - parse_format_flags_str(&vec!["od", "--format=u"]).unwrap(), + parse_format_flags_str(&["od", "--format=u"]).unwrap(), vec![FORMAT_ITEM_DEC32U] ); } @@ -457,7 +455,7 @@ fn test_long_format_u_default() { #[test] fn test_long_format_x_default() { assert_eq!( - parse_format_flags_str(&vec!["od", "--format=x"]).unwrap(), + parse_format_flags_str(&["od", "--format=x"]).unwrap(), vec![FORMAT_ITEM_HEX32] ); } @@ -465,7 +463,7 @@ fn test_long_format_x_default() { #[test] fn test_long_format_f_default() { assert_eq!( - parse_format_flags_str(&vec!["od", "--format=f"]).unwrap(), + parse_format_flags_str(&["od", "--format=f"]).unwrap(), vec![FORMAT_ITEM_F32] ); } @@ -473,7 +471,7 @@ fn test_long_format_f_default() { #[test] fn test_long_format_next_arg() { assert_eq!( - parse_format_flags_str(&vec!["od", "--format", "f8"]).unwrap(), + parse_format_flags_str(&["od", "--format", "f8"]).unwrap(), vec![FORMAT_ITEM_F64] ); } @@ -481,7 +479,7 @@ fn test_long_format_next_arg() { #[test] fn test_short_format_next_arg() { assert_eq!( - parse_format_flags_str(&vec!["od", "-t", "x8"]).unwrap(), + parse_format_flags_str(&["od", "-t", "x8"]).unwrap(), vec![FORMAT_ITEM_HEX64] ); } @@ -489,23 +487,23 @@ fn test_short_format_next_arg() { #[test] fn test_short_format_combined_arg() { assert_eq!( - parse_format_flags_str(&vec!["od", "-tu8"]).unwrap(), + parse_format_flags_str(&["od", "-tu8"]).unwrap(), vec![FORMAT_ITEM_DEC64U] ); } #[test] fn test_format_next_arg_invalid() { - parse_format_flags_str(&vec!["od", "--format", "-v"]).unwrap_err(); - parse_format_flags_str(&vec!["od", "--format"]).unwrap_err(); - parse_format_flags_str(&vec!["od", "-t", "-v"]).unwrap_err(); - parse_format_flags_str(&vec!["od", "-t"]).unwrap_err(); + parse_format_flags_str(&["od", "--format", "-v"]).unwrap_err(); + parse_format_flags_str(&["od", "--format"]).unwrap_err(); + parse_format_flags_str(&["od", "-t", "-v"]).unwrap_err(); + parse_format_flags_str(&["od", "-t"]).unwrap_err(); } #[test] fn test_mixed_formats() { assert_eq!( - parse_format_flags(&vec![ + parse_format_flags(&[ "od".to_string(), "--skip-bytes=2".to_string(), "-vItu1z".to_string(), diff --git a/src/uu/od/src/prn_float.rs b/src/uu/od/src/prn_float.rs index 411bc9c10..a9bf1279e 100644 --- a/src/uu/od/src/prn_float.rs +++ b/src/uu/od/src/prn_float.rs @@ -91,6 +91,7 @@ fn format_float(f: f64, width: usize, precision: usize) -> String { } #[test] +#[allow(clippy::excessive_precision)] fn test_format_flo32() { assert_eq!(format_flo32(1.0), " 1.0000000"); assert_eq!(format_flo32(9.9999990), " 9.9999990"); diff --git a/src/uucore/src/lib/features/ringbuffer.rs b/src/uucore/src/lib/features/ringbuffer.rs index 1cb0d2b0d..9a6176f92 100644 --- a/src/uucore/src/lib/features/ringbuffer.rs +++ b/src/uucore/src/lib/features/ringbuffer.rs @@ -106,7 +106,6 @@ mod tests { use crate::ringbuffer::RingBuffer; use std::collections::VecDeque; - use std::iter::FromIterator; #[test] fn test_size_limit_zero() { @@ -128,7 +127,7 @@ mod tests { fn test_from_iter() { let iter = [0, 1, 2].iter(); let actual = RingBuffer::from_iter(iter, 2).data; - let expected = VecDeque::from_iter([1, 2].iter()); + let expected: VecDeque<&i32> = [1, 2].iter().collect(); assert_eq!(expected, actual); } } From acfd1ebe57ceb99b6585a12a127e2fb4f41e7632 Mon Sep 17 00:00:00 2001 From: Jan Verbeek Date: Tue, 24 Aug 2021 17:28:10 +0200 Subject: [PATCH 052/206] fixup! Run clippy on the full workspace --- src/uu/dd/src/parseargs/unit_tests.rs | 12 +++++------- src/uu/split/src/split.rs | 2 +- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/uu/dd/src/parseargs/unit_tests.rs b/src/uu/dd/src/parseargs/unit_tests.rs index 78de7f847..b898f1e5d 100644 --- a/src/uu/dd/src/parseargs/unit_tests.rs +++ b/src/uu/dd/src/parseargs/unit_tests.rs @@ -10,7 +10,7 @@ fn unimplemented_flags_should_error_non_linux() { let mut succeeded = Vec::new(); // The following flags are only implemented in linux - for flag in vec![ + for &flag in &[ "direct", "directory", "dsync", @@ -27,13 +27,11 @@ fn unimplemented_flags_should_error_non_linux() { ]; let matches = uu_app().get_matches_from_safe(args).unwrap(); - match parse_iflags(&matches) { - Ok(_) => succeeded.push(format!("iflag={}", flag)), - Err(_) => { /* expected behaviour :-) */ } + if parse_iflags(&matches).is_ok() { + succeeded.push(format!("iflag={}", flag)); } - match parse_oflags(&matches) { - Ok(_) => succeeded.push(format!("oflag={}", flag)), - Err(_) => { /* expected behaviour :-) */ } + if parse_oflags(&matches).is_ok() { + succeeded.push(format!("oflag={}", flag)); } } diff --git a/src/uu/split/src/split.rs b/src/uu/split/src/split.rs index 070df81f6..e405c757a 100644 --- a/src/uu/split/src/split.rs +++ b/src/uu/split/src/split.rs @@ -104,7 +104,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { settings.strategy = String::from(OPT_LINES); settings.strategy_param = matches.value_of(OPT_LINES).unwrap().to_owned(); // take any (other) defined strategy - for strategy in vec![OPT_LINE_BYTES, OPT_BYTES].into_iter() { + for &strategy in &[OPT_LINE_BYTES, OPT_BYTES] { if matches.occurrences_of(strategy) > 0 { settings.strategy = String::from(strategy); settings.strategy_param = matches.value_of(strategy).unwrap().to_owned(); From ea41cc0ff6c703ff1266fac571441536e910b099 Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Tue, 24 Aug 2021 19:11:47 +0200 Subject: [PATCH 053/206] uucore/fs: use the latest resolution that did not fail When we ignore failures resolving symbolic links we should keep the latest resolution that did not fail, not the original path. --- src/uucore/src/lib/features/fs.rs | 50 ++++++++++++++++++++----------- tests/by-util/test_realpath.rs | 20 +++++++++++++ 2 files changed, 52 insertions(+), 18 deletions(-) diff --git a/src/uucore/src/lib/features/fs.rs b/src/uucore/src/lib/features/fs.rs index ea77a3506..d81d63a73 100644 --- a/src/uucore/src/lib/features/fs.rs +++ b/src/uucore/src/lib/features/fs.rs @@ -15,6 +15,7 @@ use libc::{ use std::borrow::Cow; use std::env; use std::fs; +use std::io::Error as IOError; use std::io::Result as IOResult; use std::io::{Error, ErrorKind}; #[cfg(any(unix, target_os = "redox"))] @@ -109,26 +110,42 @@ pub fn normalize_path(path: &Path) -> PathBuf { ret } -fn resolve>(original: P) -> IOResult { +fn resolve>(original: P) -> Result { const MAX_LINKS_FOLLOWED: u32 = 255; let mut followed = 0; let mut result = original.as_ref().to_path_buf(); + + let mut first_resolution = None; loop { if followed == MAX_LINKS_FOLLOWED { - return Err(Error::new( - ErrorKind::InvalidInput, - "maximum links followed", + return Err(( + // When we hit MAX_LINKS_FOLLOWED we should return the first resolution (that's what GNU does - for whatever reason) + first_resolution.unwrap(), + Error::new(ErrorKind::InvalidInput, "maximum links followed"), )); } - if !fs::symlink_metadata(&result)?.file_type().is_symlink() { - break; + match fs::symlink_metadata(&result) { + Ok(meta) => { + if !meta.file_type().is_symlink() { + break; + } + } + Err(e) => return Err((result, e)), } followed += 1; - let path = fs::read_link(&result)?; - result.pop(); - result.push(path); + match fs::read_link(&result) { + Ok(path) => { + result.pop(); + result.push(path); + } + Err(e) => return Err((result, e)), + } + + if first_resolution.is_none() { + first_resolution = Some(result.clone()); + } } Ok(result) } @@ -214,11 +231,10 @@ pub fn canonicalize>( } match resolve(&result) { - Err(_) if miss_mode == MissingHandling::Missing => continue, - Err(e) => return Err(e), + Err((path, _)) if miss_mode == MissingHandling::Missing => result = path, + Err((_, e)) => return Err(e), Ok(path) => { - result.pop(); - result.push(path); + result = path; } } } @@ -230,14 +246,12 @@ pub fn canonicalize>( } match resolve(&result) { - Err(e) if miss_mode == MissingHandling::Existing => { + Err((_, e)) if miss_mode == MissingHandling::Existing => { return Err(e); } - Ok(path) => { - result.pop(); - result.push(path); + Ok(path) | Err((path, _)) => { + result = path; } - Err(_) => (), } if res_mode == ResolveMode::Physical { result = normalize_path(&result); diff --git a/tests/by-util/test_realpath.rs b/tests/by-util/test_realpath.rs index 8cb1551f0..7b6da5d36 100644 --- a/tests/by-util/test_realpath.rs +++ b/tests/by-util/test_realpath.rs @@ -139,3 +139,23 @@ fn test_realpath_logical_mode() { .succeeds() .stdout_contains("dir1\n"); } + +#[test] +fn test_realpath_dangling() { + let (at, mut ucmd) = at_and_ucmd!(); + at.symlink_file("nonexistent-file", "link"); + ucmd.arg("link") + .succeeds() + .stdout_only(at.plus_as_string("nonexistent-file\n")); +} + +#[test] +fn test_realpath_loop() { + let (at, mut ucmd) = at_and_ucmd!(); + at.symlink_file("2", "1"); + at.symlink_file("3", "2"); + at.symlink_file("1", "3"); + ucmd.arg("1") + .succeeds() + .stdout_only(at.plus_as_string("2\n")); +} From f3a47428f89e866e871c4c501553f90021b59d94 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Tue, 24 Aug 2021 22:35:41 +0200 Subject: [PATCH 054/206] Document where is the source doc --- DEVELOPER_INSTRUCTIONS.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/DEVELOPER_INSTRUCTIONS.md b/DEVELOPER_INSTRUCTIONS.md index e0a5cf001..b81903edd 100644 --- a/DEVELOPER_INSTRUCTIONS.md +++ b/DEVELOPER_INSTRUCTIONS.md @@ -1,3 +1,14 @@ +Documentation +------------- + +The source of the documentation is available on: + +https://uutils.github.io/coreutils-docs/coreutils/ + +The documentation is updated everyday on this repository: + +https://github.com/uutils/coreutils-docs + Code Coverage Report Generation --------------------------------- From eb6ab9f88340b7b668cbdb25fd91cc6f0749e7ff Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Tue, 24 Aug 2021 23:00:00 +0200 Subject: [PATCH 055/206] Document some modules --- src/uucore/src/lib/features/fs.rs | 2 ++ src/uucore/src/lib/features/fsext.rs | 2 ++ src/uucore/src/lib/features/mode.rs | 2 ++ src/uucore/src/lib/features/perms.rs | 2 ++ src/uucore/src/lib/features/process.rs | 2 ++ 5 files changed, 10 insertions(+) diff --git a/src/uucore/src/lib/features/fs.rs b/src/uucore/src/lib/features/fs.rs index ea77a3506..2c0217885 100644 --- a/src/uucore/src/lib/features/fs.rs +++ b/src/uucore/src/lib/features/fs.rs @@ -6,6 +6,8 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. +//! Set of functions to manage files and symlinks + #[cfg(unix)] use libc::{ mode_t, S_IFBLK, S_IFCHR, S_IFDIR, S_IFIFO, S_IFLNK, S_IFMT, S_IFREG, S_IFSOCK, S_IRGRP, diff --git a/src/uucore/src/lib/features/fsext.rs b/src/uucore/src/lib/features/fsext.rs index 75375c7e2..72471ff9e 100644 --- a/src/uucore/src/lib/features/fsext.rs +++ b/src/uucore/src/lib/features/fsext.rs @@ -7,6 +7,8 @@ // For the full copyright and license information, please view the LICENSE file // that was distributed with this source code. +//! Set of functions to manage file systems + // spell-checker:ignore (arch) bitrig ; (fs) cifs smbfs extern crate time; diff --git a/src/uucore/src/lib/features/mode.rs b/src/uucore/src/lib/features/mode.rs index 8e9f063ff..4cf3cf6f3 100644 --- a/src/uucore/src/lib/features/mode.rs +++ b/src/uucore/src/lib/features/mode.rs @@ -5,6 +5,8 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. +//! Set of functions to parse modes + // spell-checker:ignore (vars) fperm srwx use libc::{mode_t, S_IRGRP, S_IROTH, S_IRUSR, S_IWGRP, S_IWOTH, S_IWUSR}; diff --git a/src/uucore/src/lib/features/perms.rs b/src/uucore/src/lib/features/perms.rs index 4d2a2afad..a5b2522b9 100644 --- a/src/uucore/src/lib/features/perms.rs +++ b/src/uucore/src/lib/features/perms.rs @@ -3,6 +3,8 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. +//! Common functions to manage permissions + use crate::error::UResult; pub use crate::features::entries; use crate::fs::resolve_relative_path; diff --git a/src/uucore/src/lib/features/process.rs b/src/uucore/src/lib/features/process.rs index 21bfa992c..fc4b7ed48 100644 --- a/src/uucore/src/lib/features/process.rs +++ b/src/uucore/src/lib/features/process.rs @@ -9,6 +9,8 @@ // spell-checker:ignore (vars) cvar exitstatus // spell-checker:ignore (sys/unix) WIFSIGNALED +//! Set of functions to manage IDs + use libc::{gid_t, pid_t, uid_t}; use std::fmt; use std::io; From ea16cc72c7b5b1619d808234148a12c89033743b Mon Sep 17 00:00:00 2001 From: Jan Verbeek Date: Wed, 25 Aug 2021 01:41:25 +0200 Subject: [PATCH 056/206] Make clippy workspace selection more finegrained (#2598) * Make clippy workspace selection more finegrained * fixup! Make clippy workspace selection more finegrained * fixup! Make clippy workspace selection more finegrained * fixup! Make clippy workspace selection more finegrained * fixup! Make clippy workspace selection more finegrained * fixup! Make clippy workspace selection more finegrained --- .github/workflows/CICD.yml | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/.github/workflows/CICD.yml b/.github/workflows/CICD.yml index d816fb053..c3d98fa36 100644 --- a/.github/workflows/CICD.yml +++ b/.github/workflows/CICD.yml @@ -102,11 +102,17 @@ jobs: fail-fast: false matrix: job: - - { os: ubuntu-latest , features: feat_os_unix } + - { os: ubuntu-latest } - { os: macos-latest , features: feat_os_macos } - { os: windows-latest , features: feat_os_windows } steps: - uses: actions/checkout@v2 + - name: Install/setup prerequisites + shell: bash + run: | + case '${{ matrix.job.os }}' in + macos-latest) brew install coreutils ;; # needed for show-utils.sh + esac - name: Initialize workflow variables id: vars shell: bash @@ -115,9 +121,14 @@ jobs: outputs() { step_id="vars"; for var in "$@" ; do echo steps.${step_id}.outputs.${var}="${!var}"; echo ::set-output name=${var}::${!var}; done; } # target-specific options # * CARGO_FEATURES_OPTION - CARGO_FEATURES_OPTION='' ; - if [ -n "${{ matrix.job.features }}" ]; then CARGO_FEATURES_OPTION='--features "${{ matrix.job.features }}"' ; fi + CARGO_FEATURES_OPTION='--all-features' ; + if [ -n "${{ matrix.job.features }}" ]; then CARGO_FEATURES_OPTION='--features ${{ matrix.job.features }}' ; fi outputs CARGO_FEATURES_OPTION + # * determine sub-crate utility list + UTILITY_LIST="$(./util/show-utils.sh ${CARGO_FEATURES_OPTION})" + echo UTILITY_LIST=${UTILITY_LIST} + CARGO_UTILITY_LIST_OPTIONS="$(for u in ${UTILITY_LIST}; do echo "-puu_${u}"; done;)" + outputs CARGO_UTILITY_LIST_OPTIONS - name: Install `rust` toolchain uses: actions-rs/toolchain@v1 with: @@ -130,7 +141,7 @@ jobs: run: | ## `clippy` lint testing # * convert any warnings to GHA UI annotations; ref: - S=$(cargo +nightly clippy --workspace --all-targets ${{ matrix.job.cargo-options }} ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }} -- -D warnings 2>&1) && printf "%s\n" "$S" || { printf "%s\n" "$S" ; printf "%s" "$S" | sed -E -n -e '/^error:/{' -e "N; s/^error:[[:space:]]+(.*)\\n[[:space:]]+-->[[:space:]]+${PWD//\//\\/}\/(.*):([0-9]+):([0-9]+).*$/::error file=\2,line=\3,col=\4::ERROR: \`cargo clippy\`: \1 (file:'\2', line:\3)/p;" -e '}' ; exit 1 ; } + S=$(cargo +nightly clippy --all-targets ${{ steps.vars.outputs.CARGO_UTILITY_LIST_OPTIONS }} ${{ matrix.job.cargo-options }} ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }} -- -D warnings 2>&1) && printf "%s\n" "$S" || { printf "%s\n" "$S" ; printf "%s" "$S" | sed -E -n -e '/^error:/{' -e "N; s/^error:[[:space:]]+(.*)\\n[[:space:]]+-->[[:space:]]+${PWD//\//\\/}\/(.*):([0-9]+):([0-9]+).*$/::error file=\2,line=\3,col=\4::ERROR: \`cargo clippy\`: \1 (file:'\2', line:\3)/p;" -e '}' ; exit 1 ; } code_spellcheck: name: Style/spelling From e5d6c6970b2f7c7b40a65190ed5de5f8dac0af70 Mon Sep 17 00:00:00 2001 From: Jan Verbeek Date: Tue, 24 Aug 2021 15:41:24 +0200 Subject: [PATCH 057/206] yes: Cleanup Report errors properly instead of panicking. Replace zero_copy by a simpler specialized private module. Do not assume splices move all data at once. Use the modern uutils machinery. Remove the "latency" feature. The time it takes to prepare the buffer is drowned out by the startup time anyway. yes: Add tests yes: Fix long input test on Windows --- .../workspace.wordlist.txt | 4 +- Cargo.lock | 3 +- src/uu/yes/Cargo.toml | 8 +- src/uu/yes/src/splice.rs | 110 +++++++++++++ src/uu/yes/src/yes.rs | 61 ++++---- src/uucore/Cargo.toml | 4 - src/uucore/src/lib/features.rs | 2 - src/uucore/src/lib/features/zero_copy.rs | 148 ------------------ .../src/lib/features/zero_copy/platform.rs | 21 --- .../features/zero_copy/platform/default.rs | 21 --- .../lib/features/zero_copy/platform/linux.rs | 113 ------------- .../lib/features/zero_copy/platform/unix.rs | 18 --- .../features/zero_copy/platform/windows.rs | 19 --- src/uucore/src/lib/lib.rs | 10 -- tests/by-util/test_yes.rs | 73 ++++++++- 15 files changed, 217 insertions(+), 398 deletions(-) create mode 100644 src/uu/yes/src/splice.rs delete mode 100644 src/uucore/src/lib/features/zero_copy.rs delete mode 100644 src/uucore/src/lib/features/zero_copy/platform.rs delete mode 100644 src/uucore/src/lib/features/zero_copy/platform/default.rs delete mode 100644 src/uucore/src/lib/features/zero_copy/platform/linux.rs delete mode 100644 src/uucore/src/lib/features/zero_copy/platform/unix.rs delete mode 100644 src/uucore/src/lib/features/zero_copy/platform/windows.rs diff --git a/.vscode/cspell.dictionaries/workspace.wordlist.txt b/.vscode/cspell.dictionaries/workspace.wordlist.txt index 9c4b1c82f..bee22c2d0 100644 --- a/.vscode/cspell.dictionaries/workspace.wordlist.txt +++ b/.vscode/cspell.dictionaries/workspace.wordlist.txt @@ -106,12 +106,14 @@ whoami # * vars/errno errno +EBADF EEXIST +EINVAL ENODATA ENOENT ENOSYS -EPERM EOPNOTSUPP +EPERM # * vars/fcntl F_GETFL diff --git a/Cargo.lock b/Cargo.lock index 1066c02ef..c0d3ceee6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3141,6 +3141,7 @@ name = "uu_yes" version = "0.0.7" dependencies = [ "clap", + "nix 0.20.0", "uucore", "uucore_procs", ] @@ -3157,8 +3158,6 @@ dependencies = [ "getopts", "lazy_static", "libc", - "nix 0.19.1", - "platform-info", "termion", "thiserror", "time", diff --git a/src/uu/yes/Cargo.toml b/src/uu/yes/Cargo.toml index ff8465557..b963d4974 100644 --- a/src/uu/yes/Cargo.toml +++ b/src/uu/yes/Cargo.toml @@ -16,13 +16,11 @@ path = "src/yes.rs" [dependencies] clap = { version = "2.33", features = ["wrap_help"] } -uucore = { version=">=0.0.9", package="uucore", path="../../uucore", features=["zero-copy"] } +uucore = { version=">=0.0.9", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_procs" } -[features] -default = [] -# -latency = [] +[target.'cfg(any(target_os = "linux", target_os = "android"))'.dependencies] +nix = "0.20.0" [[bin]] name = "yes" diff --git a/src/uu/yes/src/splice.rs b/src/uu/yes/src/splice.rs new file mode 100644 index 000000000..b0573bc9e --- /dev/null +++ b/src/uu/yes/src/splice.rs @@ -0,0 +1,110 @@ +//! On Linux we can use vmsplice() to write data more efficiently. +//! +//! This does not always work. We're not allowed to splice to some targets, +//! and on some systems (notably WSL 1) it isn't supported at all. +//! +//! If we get an error code that suggests splicing isn't supported then we +//! tell that to the caller so it can fall back to a robust naïve method. If +//! we get another kind of error we bubble it up as normal. +//! +//! vmsplice() can only splice into a pipe, so if the output is not a pipe +//! we make our own and use splice() to bridge the gap from the pipe to the +//! output. +//! +//! We assume that an "unsupported" error will only ever happen before any +//! data was successfully written to the output. That way we don't have to +//! make any effort to rescue data from the pipe if splice() fails, we can +//! just fall back and start over from the beginning. + +use std::{ + fs::File, + io, + os::unix::io::{AsRawFd, FromRawFd}, +}; + +use nix::{ + errno::Errno, + fcntl::SpliceFFlags, + libc::S_IFIFO, + sys::{stat::fstat, uio::IoVec}, +}; + +pub(crate) fn splice_data(bytes: &[u8], out: &impl AsRawFd) -> Result<()> { + let is_pipe = fstat(out.as_raw_fd())?.st_mode & S_IFIFO != 0; + + if is_pipe { + loop { + let mut bytes = bytes; + while !bytes.is_empty() { + let len = vmsplice(out, bytes)?; + bytes = &bytes[len..]; + } + } + } else { + let (read, write) = pipe()?; + loop { + let mut bytes = bytes; + while !bytes.is_empty() { + let len = vmsplice(&write, bytes)?; + let mut remaining = len; + while remaining > 0 { + match splice(&read, out, remaining)? { + 0 => panic!("Unexpected end of pipe"), + n => remaining -= n, + }; + } + bytes = &bytes[len..]; + } + } + } +} + +pub(crate) enum Error { + Unsupported, + Io(io::Error), +} + +type Result = std::result::Result; + +impl From for Error { + fn from(error: nix::Error) -> Self { + match error { + nix::Error::Sys(errno) => Error::Io(io::Error::from_raw_os_error(errno as i32)), + _ => Error::Io(io::Error::last_os_error()), + } + } +} + +fn maybe_unsupported(error: nix::Error) -> Error { + match error.as_errno() { + Some(Errno::EINVAL) | Some(Errno::ENOSYS) | Some(Errno::EBADF) => Error::Unsupported, + _ => error.into(), + } +} + +fn splice(source: &impl AsRawFd, target: &impl AsRawFd, len: usize) -> Result { + nix::fcntl::splice( + source.as_raw_fd(), + None, + target.as_raw_fd(), + None, + len, + SpliceFFlags::empty(), + ) + .map_err(maybe_unsupported) +} + +fn vmsplice(target: &impl AsRawFd, bytes: &[u8]) -> Result { + nix::fcntl::vmsplice( + target.as_raw_fd(), + &[IoVec::from_slice(bytes)], + SpliceFFlags::empty(), + ) + .map_err(maybe_unsupported) +} + +fn pipe() -> nix::Result<(File, File)> { + let (read, write) = nix::unistd::pipe()?; + // SAFETY: The file descriptors do not have other owners. + unsafe { Ok((File::from_raw_fd(read), File::from_raw_fd(write))) } +} diff --git a/src/uu/yes/src/yes.rs b/src/uu/yes/src/yes.rs index 2c0d43000..03ae4e415 100644 --- a/src/uu/yes/src/yes.rs +++ b/src/uu/yes/src/yes.rs @@ -7,37 +7,27 @@ /* last synced with: yes (GNU coreutils) 8.13 */ +use std::borrow::Cow; +use std::io::{self, Write}; + #[macro_use] extern crate clap; #[macro_use] extern crate uucore; use clap::{App, Arg}; -use std::borrow::Cow; -use std::io::{self, Write}; -use uucore::zero_copy::ZeroCopyWriter; +use uucore::error::{UResult, USimpleError}; + +#[cfg(any(target_os = "linux", target_os = "android"))] +mod splice; // it's possible that using a smaller or larger buffer might provide better performance on some // systems, but honestly this is good enough const BUF_SIZE: usize = 16 * 1024; -pub fn uumain(args: impl uucore::Args) -> i32 { - let app = uu_app(); - - let matches = match app.get_matches_from_safe(args) { - Ok(m) => m, - Err(ref e) - if e.kind == clap::ErrorKind::HelpDisplayed - || e.kind == clap::ErrorKind::VersionDisplayed => - { - println!("{}", e); - return 0; - } - Err(f) => { - show_error!("{}", f); - return 1; - } - }; +#[uucore_procs::gen_uumain] +pub fn uumain(args: impl uucore::Args) -> UResult<()> { + let matches = uu_app().get_matches_from(args); let string = if let Some(values) = matches.values_of("STRING") { let mut result = values.fold(String::new(), |res, s| res + s + " "); @@ -51,16 +41,17 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let mut buffer = [0; BUF_SIZE]; let bytes = prepare_buffer(&string, &mut buffer); - exec(bytes); - - 0 + match exec(bytes) { + Ok(()) => Ok(()), + Err(err) if err.kind() == io::ErrorKind::BrokenPipe => Ok(()), + Err(err) => Err(USimpleError::new(1, format!("standard output: {}", err))), + } } pub fn uu_app() -> App<'static, 'static> { app_from_crate!().arg(Arg::with_name("STRING").index(1).multiple(true)) } -#[cfg(not(feature = "latency"))] fn prepare_buffer<'a>(input: &'a str, buffer: &'a mut [u8; BUF_SIZE]) -> &'a [u8] { if input.len() < BUF_SIZE / 2 { let mut size = 0; @@ -75,16 +66,20 @@ fn prepare_buffer<'a>(input: &'a str, buffer: &'a mut [u8; BUF_SIZE]) -> &'a [u8 } } -#[cfg(feature = "latency")] -fn prepare_buffer<'a>(input: &'a str, _buffer: &'a mut [u8; BUF_SIZE]) -> &'a [u8] { - input.as_bytes() -} +pub fn exec(bytes: &[u8]) -> io::Result<()> { + let stdout = io::stdout(); + let mut stdout = stdout.lock(); + + #[cfg(any(target_os = "linux", target_os = "android"))] + { + match splice::splice_data(bytes, &stdout) { + Ok(_) => return Ok(()), + Err(splice::Error::Io(err)) => return Err(err), + Err(splice::Error::Unsupported) => (), + } + } -pub fn exec(bytes: &[u8]) { - let mut stdout_raw = io::stdout(); - let mut writer = ZeroCopyWriter::with_default(&mut stdout_raw, |stdout| stdout.lock()); loop { - // TODO: needs to check if pipe fails - writer.write_all(bytes).unwrap(); + stdout.write_all(bytes)?; } } diff --git a/src/uucore/Cargo.toml b/src/uucore/Cargo.toml index c49e0a0f3..6d27ecad4 100644 --- a/src/uucore/Cargo.toml +++ b/src/uucore/Cargo.toml @@ -22,9 +22,6 @@ getopts = "<= 0.2.21" wild = "2.0" # * optional thiserror = { version="1.0", optional=true } -lazy_static = { version="1.3", optional=true } -nix = { version="<= 0.19", optional=true } -platform-info = { version="<= 0.1", optional=true } time = { version="<= 0.1.43", optional=true } walkdir = { version="2.3.2", optional=true } # * "problem" dependencies (pinned) @@ -58,4 +55,3 @@ signals = [] utf8 = [] utmpx = ["time", "libc", "dns-lookup"] wide = [] -zero-copy = ["nix", "libc", "lazy_static", "platform-info"] diff --git a/src/uucore/src/lib/features.rs b/src/uucore/src/lib/features.rs index f90fc7b3d..60be88664 100644 --- a/src/uucore/src/lib/features.rs +++ b/src/uucore/src/lib/features.rs @@ -8,8 +8,6 @@ pub mod fs; pub mod fsext; #[cfg(feature = "ringbuffer")] pub mod ringbuffer; -#[cfg(feature = "zero-copy")] -pub mod zero_copy; // * (platform-specific) feature-gated modules // ** non-windows diff --git a/src/uucore/src/lib/features/zero_copy.rs b/src/uucore/src/lib/features/zero_copy.rs deleted file mode 100644 index 1eb2c1547..000000000 --- a/src/uucore/src/lib/features/zero_copy.rs +++ /dev/null @@ -1,148 +0,0 @@ -use self::platform::*; - -use std::io::{self, Write}; - -mod platform; - -pub trait AsRawObject { - fn as_raw_object(&self) -> RawObject; -} - -pub trait FromRawObject: Sized { - /// # Safety - /// ToDO ... - unsafe fn from_raw_object(obj: RawObject) -> Option; -} - -// TODO: also make a SpliceWriter that takes an input fd and and output fd and uses splice() to -// transfer data -// TODO: make a TeeWriter or something that takes an input fd and two output fds and uses tee() to -// transfer to both output fds - -enum InnerZeroCopyWriter { - Platform(PlatformZeroCopyWriter), - Standard(T), -} - -impl Write for InnerZeroCopyWriter { - fn write(&mut self, buf: &[u8]) -> io::Result { - match self { - InnerZeroCopyWriter::Platform(ref mut writer) => writer.write(buf), - InnerZeroCopyWriter::Standard(ref mut writer) => writer.write(buf), - } - } - - fn flush(&mut self) -> io::Result<()> { - match self { - InnerZeroCopyWriter::Platform(ref mut writer) => writer.flush(), - InnerZeroCopyWriter::Standard(ref mut writer) => writer.flush(), - } - } -} - -pub struct ZeroCopyWriter { - /// This field is never used, but we need it to drop file descriptors - #[allow(dead_code)] - raw_obj_owner: Option, - - inner: InnerZeroCopyWriter, -} - -struct TransformContainer<'a, A: Write + AsRawObject + Sized, B: Write + Sized> { - /// This field is never used and probably could be converted into PhantomData, but might be - /// useful for restructuring later (at the moment it's basically left over from an earlier - /// design) - #[allow(dead_code)] - original: Option<&'a mut A>, - - transformed: Option, -} - -impl<'a, A: Write + AsRawObject + Sized, B: Write + Sized> Write for TransformContainer<'a, A, B> { - fn write(&mut self, bytes: &[u8]) -> io::Result { - self.transformed.as_mut().unwrap().write(bytes) - } - - fn flush(&mut self) -> io::Result<()> { - self.transformed.as_mut().unwrap().flush() - } -} - -impl<'a, A: Write + AsRawObject + Sized, B: Write + Sized> AsRawObject - for TransformContainer<'a, A, B> -{ - fn as_raw_object(&self) -> RawObject { - panic!("Test should never be used") - } -} - -impl ZeroCopyWriter { - pub fn new(writer: T) -> Self { - let raw_obj = writer.as_raw_object(); - match unsafe { PlatformZeroCopyWriter::new(raw_obj) } { - Ok(inner) => ZeroCopyWriter { - raw_obj_owner: Some(writer), - inner: InnerZeroCopyWriter::Platform(inner), - }, - _ => { - // creating the splice writer failed for whatever reason, so just make a default - // writer - ZeroCopyWriter { - raw_obj_owner: None, - inner: InnerZeroCopyWriter::Standard(writer), - } - } - } - } - - pub fn with_default<'a: 'b, 'b, F, W>( - writer: &'a mut T, - func: F, - ) -> ZeroCopyWriter - where - F: Fn(&'a mut T) -> W, - W: Write + Sized + 'b, - { - let raw_obj = writer.as_raw_object(); - match unsafe { PlatformZeroCopyWriter::new(raw_obj) } { - Ok(inner) => ZeroCopyWriter { - raw_obj_owner: Some(TransformContainer { - original: Some(writer), - transformed: None, - }), - inner: InnerZeroCopyWriter::Platform(inner), - }, - _ => { - // XXX: should func actually consume writer and leave it up to the user to save the value? - // maybe provide a default stdin method then? in some cases it would make more sense for the - // value to be consumed - let real_writer = func(writer); - ZeroCopyWriter { - raw_obj_owner: None, - inner: InnerZeroCopyWriter::Standard(TransformContainer { - original: None, - transformed: Some(real_writer), - }), - } - } - } - } - - // XXX: unsure how to get something like this working without allocating, so not providing it - /*pub fn stdout() -> ZeroCopyWriter { - let mut stdout = io::stdout(); - ZeroCopyWriter::with_default(&mut stdout, |stdout| { - stdout.lock() - }) - }*/ -} - -impl Write for ZeroCopyWriter { - fn write(&mut self, buf: &[u8]) -> io::Result { - self.inner.write(buf) - } - - fn flush(&mut self) -> io::Result<()> { - self.inner.flush() - } -} diff --git a/src/uucore/src/lib/features/zero_copy/platform.rs b/src/uucore/src/lib/features/zero_copy/platform.rs deleted file mode 100644 index 67e4354c5..000000000 --- a/src/uucore/src/lib/features/zero_copy/platform.rs +++ /dev/null @@ -1,21 +0,0 @@ -#[cfg(any(target_os = "linux", target_os = "android"))] -pub use self::linux::*; -#[cfg(unix)] -pub use self::unix::*; -#[cfg(windows)] -pub use self::windows::*; - -// Add any operating systems we support here -#[cfg(not(any(target_os = "linux", target_os = "android")))] -pub use self::default::*; - -#[cfg(any(target_os = "linux", target_os = "android"))] -mod linux; -#[cfg(unix)] -mod unix; -#[cfg(windows)] -mod windows; - -// Add any operating systems we support here -#[cfg(not(any(target_os = "linux", target_os = "android")))] -mod default; diff --git a/src/uucore/src/lib/features/zero_copy/platform/default.rs b/src/uucore/src/lib/features/zero_copy/platform/default.rs deleted file mode 100644 index 47239a361..000000000 --- a/src/uucore/src/lib/features/zero_copy/platform/default.rs +++ /dev/null @@ -1,21 +0,0 @@ -use crate::features::zero_copy::RawObject; - -use std::io::{self, Write}; - -pub struct PlatformZeroCopyWriter; - -impl PlatformZeroCopyWriter { - pub unsafe fn new(_obj: RawObject) -> Result { - Err(()) - } -} - -impl Write for PlatformZeroCopyWriter { - fn write(&mut self, _bytes: &[u8]) -> io::Result { - panic!("should never occur") - } - - fn flush(&mut self) -> io::Result<()> { - panic!("should never occur") - } -} diff --git a/src/uucore/src/lib/features/zero_copy/platform/linux.rs b/src/uucore/src/lib/features/zero_copy/platform/linux.rs deleted file mode 100644 index e2bed3061..000000000 --- a/src/uucore/src/lib/features/zero_copy/platform/linux.rs +++ /dev/null @@ -1,113 +0,0 @@ -use std::io::{self, Write}; -use std::os::unix::io::RawFd; - -use libc::{O_APPEND, S_IFIFO, S_IFREG}; -use nix::errno::Errno; -use nix::fcntl::{fcntl, splice, vmsplice, FcntlArg, SpliceFFlags}; -use nix::sys::stat::{fstat, FileStat}; -use nix::sys::uio::IoVec; -use nix::unistd::pipe; -use platform_info::{PlatformInfo, Uname}; - -use crate::features::zero_copy::{FromRawObject, RawObject}; - -lazy_static::lazy_static! { - static ref IN_WSL: bool = { - let info = PlatformInfo::new().unwrap(); - info.release().contains("Microsoft") - }; -} - -pub struct PlatformZeroCopyWriter { - raw_obj: RawObject, - read_pipe: RawFd, - write_pipe: RawFd, - #[allow(clippy::type_complexity)] - write_fn: fn(&mut PlatformZeroCopyWriter, &[IoVec<&[u8]>], usize) -> io::Result, -} - -impl PlatformZeroCopyWriter { - pub unsafe fn new(raw_obj: RawObject) -> nix::Result { - if *IN_WSL { - // apparently WSL hasn't implemented vmsplice(), causing writes to fail - // thus, we will just say zero-copy doesn't work there rather than working - // around it - return Err(nix::Error::from(Errno::EOPNOTSUPP)); - } - - let stat_info: FileStat = fstat(raw_obj)?; - let access_mode: libc::c_int = fcntl(raw_obj, FcntlArg::F_GETFL)?; - - let is_regular = (stat_info.st_mode & S_IFREG) != 0; - let is_append = (access_mode & O_APPEND) != 0; - let is_fifo = (stat_info.st_mode & S_IFIFO) != 0; - - if is_regular && !is_append { - let (read_pipe, write_pipe) = pipe()?; - - Ok(PlatformZeroCopyWriter { - raw_obj, - read_pipe, - write_pipe, - write_fn: write_regular, - }) - } else if is_fifo { - Ok(PlatformZeroCopyWriter { - raw_obj, - read_pipe: Default::default(), - write_pipe: Default::default(), - write_fn: write_fifo, - }) - } else { - // FIXME: how to error? - Err(nix::Error::from(Errno::UnknownErrno)) - } - } -} - -impl FromRawObject for PlatformZeroCopyWriter { - unsafe fn from_raw_object(obj: RawObject) -> Option { - PlatformZeroCopyWriter::new(obj).ok() - } -} - -impl Write for PlatformZeroCopyWriter { - fn write(&mut self, buf: &[u8]) -> io::Result { - let iovec = &[IoVec::from_slice(buf)]; - - let func = self.write_fn; - func(self, iovec, buf.len()) - } - - fn flush(&mut self) -> io::Result<()> { - // XXX: not sure if we need anything else - Ok(()) - } -} - -fn write_regular( - writer: &mut PlatformZeroCopyWriter, - iovec: &[IoVec<&[u8]>], - len: usize, -) -> io::Result { - vmsplice(writer.write_pipe, iovec, SpliceFFlags::empty()) - .and_then(|_| { - splice( - writer.read_pipe, - None, - writer.raw_obj, - None, - len, - SpliceFFlags::empty(), - ) - }) - .map_err(|_| io::Error::last_os_error()) -} - -fn write_fifo( - writer: &mut PlatformZeroCopyWriter, - iovec: &[IoVec<&[u8]>], - _len: usize, -) -> io::Result { - vmsplice(writer.raw_obj, iovec, SpliceFFlags::empty()).map_err(|_| io::Error::last_os_error()) -} diff --git a/src/uucore/src/lib/features/zero_copy/platform/unix.rs b/src/uucore/src/lib/features/zero_copy/platform/unix.rs deleted file mode 100644 index 553549c9b..000000000 --- a/src/uucore/src/lib/features/zero_copy/platform/unix.rs +++ /dev/null @@ -1,18 +0,0 @@ -use std::os::unix::io::{AsRawFd, FromRawFd, RawFd}; - -use crate::features::zero_copy::{AsRawObject, FromRawObject}; - -pub type RawObject = RawFd; - -impl AsRawObject for T { - fn as_raw_object(&self) -> RawObject { - self.as_raw_fd() - } -} - -// FIXME: check if this works right -impl FromRawObject for T { - unsafe fn from_raw_object(obj: RawObject) -> Option { - Some(T::from_raw_fd(obj)) - } -} diff --git a/src/uucore/src/lib/features/zero_copy/platform/windows.rs b/src/uucore/src/lib/features/zero_copy/platform/windows.rs deleted file mode 100644 index 8134bfda3..000000000 --- a/src/uucore/src/lib/features/zero_copy/platform/windows.rs +++ /dev/null @@ -1,19 +0,0 @@ -use std::os::windows::io::{AsRawHandle, FromRawHandle, RawHandle}; - -use crate::features::zero_copy::{AsRawObject, FromRawObject}; - -pub type RawObject = RawHandle; - -impl AsRawObject for T { - fn as_raw_object(&self) -> RawObject { - self.as_raw_handle() - } -} - -impl FromRawObject for T { - unsafe fn from_raw_object(obj: RawObject) -> Option { - Some(T::from_raw_handle(obj)) - } -} - -// TODO: see if there's some zero-copy stuff in Windows diff --git a/src/uucore/src/lib/lib.rs b/src/uucore/src/lib/lib.rs index 6acd4e017..a39834ec1 100644 --- a/src/uucore/src/lib/lib.rs +++ b/src/uucore/src/lib/lib.rs @@ -3,14 +3,6 @@ // Copyright (C) ~ Alex Lyon // Copyright (C) ~ Roy Ivy III ; MIT license -// * feature-gated external crates -#[cfg(all(feature = "lazy_static", target_os = "linux"))] -extern crate lazy_static; -#[cfg(feature = "nix")] -extern crate nix; -#[cfg(feature = "platform-info")] -extern crate platform_info; - // * feature-gated external crates (re-shared as public internal modules) #[cfg(feature = "libc")] pub extern crate libc; @@ -46,8 +38,6 @@ pub use crate::features::fs; pub use crate::features::fsext; #[cfg(feature = "ringbuffer")] pub use crate::features::ringbuffer; -#[cfg(feature = "zero-copy")] -pub use crate::features::zero_copy; // * (platform-specific) feature-gated modules // ** non-windows diff --git a/tests/by-util/test_yes.rs b/tests/by-util/test_yes.rs index 651491045..7e950e1ea 100644 --- a/tests/by-util/test_yes.rs +++ b/tests/by-util/test_yes.rs @@ -1 +1,72 @@ -// ToDO: add tests +use std::io::Read; + +use crate::common::util::*; + +/// Run `yes`, capture some of the output, close the pipe, and verify it. +fn run(args: &[&str], expected: &[u8]) { + let mut cmd = new_ucmd!(); + let mut child = cmd.args(args).run_no_wait(); + let mut stdout = child.stdout.take().unwrap(); + let mut buf = vec![0; expected.len()]; + stdout.read_exact(&mut buf).unwrap(); + drop(stdout); + assert!(child.wait().unwrap().success()); + assert_eq!(buf.as_slice(), expected); +} + +#[test] +fn test_simple() { + run(&[], b"y\ny\ny\ny\n"); +} + +#[test] +fn test_args() { + run(&["a", "bar", "c"], b"a bar c\na bar c\na ba"); +} + +#[test] +fn test_long_output() { + run(&[], "y\n".repeat(512 * 1024).as_bytes()); +} + +/// Test with an output that seems likely to get mangled in case of incomplete writes. +#[test] +fn test_long_odd_output() { + run(&["abcdef"], "abcdef\n".repeat(1024 * 1024).as_bytes()); +} + +/// Test with an input that doesn't fit in the standard buffer. +#[test] +fn test_long_input() { + #[cfg(not(windows))] + const TIMES: usize = 14000; + // On Windows the command line is limited to 8191 bytes. + // This is not actually enough to fill the buffer, but it's still nice to + // try something long. + #[cfg(windows)] + const TIMES: usize = 500; + let arg = "abcdefg".repeat(TIMES) + "\n"; + let expected_out = arg.repeat(30); + run(&[&arg[..arg.len() - 1]], expected_out.as_bytes()); +} + +#[test] +#[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "netbsd"))] +fn test_piped_to_dev_full() { + use std::fs::OpenOptions; + + for &append in &[true, false] { + { + let dev_full = OpenOptions::new() + .write(true) + .append(append) + .open("/dev/full") + .unwrap(); + + new_ucmd!() + .set_stdout(dev_full) + .fails() + .stderr_contains("No space left on device"); + } + } +} From 52cfd4c6cb184473448ca3137f29d1d3986b6901 Mon Sep 17 00:00:00 2001 From: Jeffrey Finkelstein Date: Mon, 23 Aug 2021 22:41:53 -0400 Subject: [PATCH 058/206] hashsum: don't copy input buffer on Windows Remove a copy operation of the input buffer being read for digest when reading in text mode on Windows. Previously, the code was copying the buffer to a completely new `Vec`, replacing "\r\n" with "\n". Instead, the code now scans for the indices at which each "\r\n" occurs in the input buffer and inputs into the digest only the characters before the "\r" and after it. --- Cargo.lock | 1 + src/uu/hashsum/Cargo.toml | 1 + src/uu/hashsum/src/hashsum.rs | 42 +++++++++++++++-------------------- 3 files changed, 20 insertions(+), 24 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c0d3ceee6..adc373f85 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2392,6 +2392,7 @@ dependencies = [ "hex", "libc", "md5", + "memchr 2.4.0", "regex", "regex-syntax", "sha1", diff --git a/src/uu/hashsum/Cargo.toml b/src/uu/hashsum/Cargo.toml index 43d78119b..b4da17b71 100644 --- a/src/uu/hashsum/Cargo.toml +++ b/src/uu/hashsum/Cargo.toml @@ -19,6 +19,7 @@ digest = "0.6.2" clap = { version = "2.33", features = ["wrap_help"] } hex = "0.2.0" libc = "0.2.42" +memchr = "2" md5 = "0.3.5" regex = "1.0.1" regex-syntax = "0.6.7" diff --git a/src/uu/hashsum/src/hashsum.rs b/src/uu/hashsum/src/hashsum.rs index 1e677358e..384ef1388 100644 --- a/src/uu/hashsum/src/hashsum.rs +++ b/src/uu/hashsum/src/hashsum.rs @@ -7,7 +7,7 @@ // * For the full copyright and license information, please view the LICENSE // * file that was distributed with this source code. -// spell-checker:ignore (ToDO) algo, algoname, regexes, nread +// spell-checker:ignore (ToDO) algo, algoname, regexes, nread memmem #[macro_use] extern crate clap; @@ -22,6 +22,7 @@ use self::digest::Digest; use clap::{App, Arg, ArgMatches}; use hex::ToHex; use md5::Context as Md5; +use memchr::memmem; use regex::Regex; use sha1::Sha1; use sha2::{Sha224, Sha256, Sha384, Sha512}; @@ -586,8 +587,6 @@ fn digest_reader<'a, T: Read>( // Digest file, do not hold too much in memory at any given moment let windows = cfg!(windows); let mut buffer = Vec::with_capacity(524_288); - let mut vec = Vec::with_capacity(524_288); - let mut looking_for_newline = false; loop { match reader.read_to_end(&mut buffer) { Ok(0) => { @@ -595,24 +594,23 @@ fn digest_reader<'a, T: Read>( } Ok(nread) => { if windows && !binary { - // Windows text mode returns '\n' when reading '\r\n' - for &b in buffer.iter().take(nread) { - if looking_for_newline { - if b != b'\n' { - vec.push(b'\r'); - } - if b != b'\r' { - vec.push(b); - looking_for_newline = false; - } - } else if b != b'\r' { - vec.push(b); - } else { - looking_for_newline = true; - } + // In Windows text mode, replace each occurrence of + // "\r\n" with "\n". + // + // Find all occurrences of "\r\n", inputting the + // slice just before the "\n" in the previous + // instance of "\r\n" and the beginning of this + // "\r\n". + // + // FIXME This fails if one call to `read()` ends + // with the "\r" and the next call to `read()` + // begins with the "\n". + let mut i_prev = 0; + for i in memmem::find_iter(&buffer[0..nread], b"\r\n") { + digest.input(&buffer[i_prev..i]); + i_prev = i + 1; } - digest.input(&vec); - vec.clear(); + digest.input(&buffer[i_prev..nread]); } else { digest.input(&buffer[..nread]); } @@ -620,10 +618,6 @@ fn digest_reader<'a, T: Read>( Err(e) => return Err(e), } } - if windows && looking_for_newline { - vec.push(b'\r'); - digest.input(&vec); - } if digest.output_bits() > 0 { Ok(digest.result_str()) From 080998b6ef82dbb09a8fc470f9f28141756042d2 Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Mon, 16 Aug 2021 00:14:50 +0200 Subject: [PATCH 059/206] chmod: pad all file modes to 4 digits --- src/uu/chmod/src/chmod.rs | 4 ++-- tests/by-util/test_chmod.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/uu/chmod/src/chmod.rs b/src/uu/chmod/src/chmod.rs index 5108ec924..bdcafc435 100644 --- a/src/uu/chmod/src/chmod.rs +++ b/src/uu/chmod/src/chmod.rs @@ -323,7 +323,7 @@ impl Chmoder { } if self.verbose { show_error!( - "failed to change mode of file '{}' from {:o} ({}) to {:o} ({})", + "failed to change mode of file '{}' from {:04o} ({}) to {:04o} ({})", file.display(), fperm, display_permissions_unix(fperm as mode_t, false), @@ -335,7 +335,7 @@ impl Chmoder { } else { if self.verbose || self.changes { show_error!( - "mode of '{}' changed from {:o} ({}) to {:o} ({})", + "mode of '{}' changed from {:04o} ({}) to {:04o} ({})", file.display(), fperm, display_permissions_unix(fperm as mode_t, false), diff --git a/tests/by-util/test_chmod.rs b/tests/by-util/test_chmod.rs index 186c645e5..5031105f9 100644 --- a/tests/by-util/test_chmod.rs +++ b/tests/by-util/test_chmod.rs @@ -330,8 +330,8 @@ fn test_chmod_recursive() { .arg("a") .arg("z") .succeeds() - .stderr_contains(&"to 333 (-wx-wx-wx)") - .stderr_contains(&"to 222 (-w--w--w-)"); + .stderr_contains(&"to 0333 (-wx-wx-wx)") + .stderr_contains(&"to 0222 (-w--w--w-)"); assert_eq!(at.metadata("z/y").permissions().mode(), 0o100222); assert_eq!(at.metadata("a/a").permissions().mode(), 0o100222); From 74958794c6619f6cc1cf00f5c0834cea5a253017 Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Mon, 16 Aug 2021 00:18:45 +0200 Subject: [PATCH 060/206] chmod: print change message to stdout, not stderr --- src/uu/chmod/src/chmod.rs | 4 ++-- tests/by-util/test_chmod.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/uu/chmod/src/chmod.rs b/src/uu/chmod/src/chmod.rs index bdcafc435..874c78dea 100644 --- a/src/uu/chmod/src/chmod.rs +++ b/src/uu/chmod/src/chmod.rs @@ -322,7 +322,7 @@ impl Chmoder { show_error!("{}", err); } if self.verbose { - show_error!( + println!( "failed to change mode of file '{}' from {:04o} ({}) to {:04o} ({})", file.display(), fperm, @@ -334,7 +334,7 @@ impl Chmoder { Err(1) } else { if self.verbose || self.changes { - show_error!( + println!( "mode of '{}' changed from {:04o} ({}) to {:04o} ({})", file.display(), fperm, diff --git a/tests/by-util/test_chmod.rs b/tests/by-util/test_chmod.rs index 5031105f9..f0973c712 100644 --- a/tests/by-util/test_chmod.rs +++ b/tests/by-util/test_chmod.rs @@ -330,8 +330,8 @@ fn test_chmod_recursive() { .arg("a") .arg("z") .succeeds() - .stderr_contains(&"to 0333 (-wx-wx-wx)") - .stderr_contains(&"to 0222 (-w--w--w-)"); + .stdout_contains(&"to 0333 (-wx-wx-wx)") + .stdout_contains(&"to 0222 (-w--w--w-)"); assert_eq!(at.metadata("z/y").permissions().mode(), 0o100222); assert_eq!(at.metadata("a/a").permissions().mode(), 0o100222); From 9697c89d174e896f5454a233e570916cbfa9b2ff Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Mon, 16 Aug 2021 00:30:25 +0200 Subject: [PATCH 061/206] uucore/mode: handle mode 0 Trimming all '0' characters results in an invalid string if the string contained only '0's. --- src/uucore/src/lib/features/mode.rs | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/src/uucore/src/lib/features/mode.rs b/src/uucore/src/lib/features/mode.rs index 8e9f063ff..eb5bfc580 100644 --- a/src/uucore/src/lib/features/mode.rs +++ b/src/uucore/src/lib/features/mode.rs @@ -11,19 +11,21 @@ use libc::{mode_t, S_IRGRP, S_IROTH, S_IRUSR, S_IWGRP, S_IWOTH, S_IWUSR}; pub fn parse_numeric(fperm: u32, mut mode: &str) -> Result { let (op, pos) = parse_op(mode, Some('='))?; - mode = mode[pos..].trim().trim_start_matches('0'); - if mode.len() > 4 { - Err(format!("mode is too large ({} > 7777)", mode)) - } else { - match u32::from_str_radix(mode, 8) { - Ok(change) => Ok(match op { - '+' => fperm | change, - '-' => fperm & !change, - '=' => change, - _ => unreachable!(), - }), - Err(err) => Err(err.to_string()), + mode = mode[pos..].trim(); + match u32::from_str_radix(mode, 8) { + Ok(change) => { + if change > 0o7777 { + Err(format!("mode is too large ({} > 7777", mode)) + } else { + Ok(match op { + '+' => fperm | change, + '-' => fperm & !change, + '=' => change, + _ => unreachable!(), + }) + } } + Err(err) => Err(err.to_string()), } } From 15b40f6aa2d325e1362309f0d82115c18c7dbd83 Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Mon, 16 Aug 2021 21:56:09 +0200 Subject: [PATCH 062/206] chmod: implement special handling of directory setuid/setgid --- src/uu/chmod/src/chmod.rs | 2 +- src/uu/install/src/mode.rs | 2 +- src/uucore/src/lib/features/mode.rs | 73 ++++++++++++++++------------- tests/by-util/test_chmod.rs | 25 +++++++++- 4 files changed, 66 insertions(+), 36 deletions(-) diff --git a/src/uu/chmod/src/chmod.rs b/src/uu/chmod/src/chmod.rs index 874c78dea..65357a576 100644 --- a/src/uu/chmod/src/chmod.rs +++ b/src/uu/chmod/src/chmod.rs @@ -282,7 +282,7 @@ impl Chmoder { // cmode is guaranteed to be Some in this case let arr: &[char] = &['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']; let result = if mode.contains(arr) { - mode::parse_numeric(fperm, mode) + mode::parse_numeric(fperm, mode, file.is_dir()) } else { mode::parse_symbolic(fperm, mode, file.is_dir()) }; diff --git a/src/uu/install/src/mode.rs b/src/uu/install/src/mode.rs index b8d5cd839..5c3f8f28f 100644 --- a/src/uu/install/src/mode.rs +++ b/src/uu/install/src/mode.rs @@ -9,7 +9,7 @@ pub fn parse(mode_string: &str, considering_dir: bool) -> Result { // Passing 000 as the existing permissions seems to mirror GNU behavior. if mode_string.contains(numbers) { - mode::parse_numeric(0, mode_string) + mode::parse_numeric(0, mode_string, considering_dir) } else { mode::parse_symbolic(0, mode_string, considering_dir) } diff --git a/src/uucore/src/lib/features/mode.rs b/src/uucore/src/lib/features/mode.rs index eb5bfc580..9ddb8540f 100644 --- a/src/uucore/src/lib/features/mode.rs +++ b/src/uucore/src/lib/features/mode.rs @@ -9,23 +9,26 @@ use libc::{mode_t, S_IRGRP, S_IROTH, S_IRUSR, S_IWGRP, S_IWOTH, S_IWUSR}; -pub fn parse_numeric(fperm: u32, mut mode: &str) -> Result { - let (op, pos) = parse_op(mode, Some('='))?; +pub fn parse_numeric(fperm: u32, mut mode: &str, considering_dir: bool) -> Result { + let (op, pos) = parse_op(mode).map_or_else(|_| (None, 0), |(op, pos)| (Some(op), pos)); mode = mode[pos..].trim(); - match u32::from_str_radix(mode, 8) { - Ok(change) => { - if change > 0o7777 { - Err(format!("mode is too large ({} > 7777", mode)) - } else { - Ok(match op { - '+' => fperm | change, - '-' => fperm & !change, - '=' => change, - _ => unreachable!(), - }) - } - } - Err(err) => Err(err.to_string()), + let change = if mode.is_empty() { + 0 + } else { + u32::from_str_radix(mode, 8).map_err(|e| e.to_string())? + }; + if change > 0o7777 { + Err(format!("mode is too large ({} > 7777", change)) + } else { + Ok(match op { + Some('+') => fperm | change, + Some('-') => fperm & !change, + // If this is a directory, we keep the setgid and setuid bits, + // unless the mode contains 5 or more octal digits or the mode is "=" + None if considering_dir && mode.len() < 5 => change | (fperm & (0o4000 | 0o2000)), + None | Some('=') => change, + Some(_) => unreachable!(), + }) } } @@ -45,7 +48,7 @@ pub fn parse_symbolic( let last_umask = unsafe { umask(0) }; mode = &mode[pos..]; while !mode.is_empty() { - let (op, pos) = parse_op(mode, None)?; + let (op, pos) = parse_op(mode)?; mode = &mode[pos..]; let (mut srwx, pos) = parse_change(mode, fperm, considering_dir); if respect_umask { @@ -55,7 +58,13 @@ pub fn parse_symbolic( match op { '+' => fperm |= srwx & mask, '-' => fperm &= !(srwx & mask), - '=' => fperm = (fperm & !mask) | (srwx & mask), + '=' => { + if considering_dir { + // keep the setgid and setuid bits for directories + srwx |= fperm & (0o4000 | 0o2000); + } + fperm = (fperm & !mask) | (srwx & mask) + } _ => unreachable!(), } } @@ -70,9 +79,9 @@ fn parse_levels(mode: &str) -> (u32, usize) { let mut pos = 0; for ch in mode.chars() { mask |= match ch { - 'u' => 0o7700, - 'g' => 0o7070, - 'o' => 0o7007, + 'u' => 0o4700, + 'g' => 0o2070, + 'o' => 0o1007, 'a' => 0o7777, _ => break, }; @@ -84,24 +93,22 @@ fn parse_levels(mode: &str) -> (u32, usize) { (mask, pos) } -fn parse_op(mode: &str, default: Option) -> Result<(char, usize), String> { +fn parse_op(mode: &str) -> Result<(char, usize), String> { let ch = mode .chars() .next() .ok_or_else(|| "unexpected end of mode".to_owned())?; - Ok(match ch { - '+' | '-' | '=' => (ch, 1), - _ => { - let ch = default.ok_or_else(|| { - format!("invalid operator (expected +, -, or =, but found {})", ch) - })?; - (ch, 0) - } - }) + match ch { + '+' | '-' | '=' => Ok((ch, 1)), + _ => Err(format!( + "invalid operator (expected +, -, or =, but found {})", + ch + )), + } } fn parse_change(mode: &str, fperm: u32, considering_dir: bool) -> (u32, usize) { - let mut srwx = fperm & 0o7000; + let mut srwx = 0; let mut pos = 0; for ch in mode.chars() { match ch { @@ -132,7 +139,7 @@ pub fn parse_mode(mode: &str) -> Result { let fperm = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH; let arr: &[char] = &['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']; let result = if mode.contains(arr) { - parse_numeric(fperm as u32, mode) + parse_numeric(fperm as u32, mode, true) } else { parse_symbolic(fperm as u32, mode, true) }; diff --git a/tests/by-util/test_chmod.rs b/tests/by-util/test_chmod.rs index f0973c712..667f7b476 100644 --- a/tests/by-util/test_chmod.rs +++ b/tests/by-util/test_chmod.rs @@ -1,5 +1,5 @@ use crate::common::util::*; -use std::fs::{metadata, set_permissions, OpenOptions}; +use std::fs::{metadata, set_permissions, OpenOptions, Permissions}; use std::os::unix::fs::{OpenOptionsExt, PermissionsExt}; use std::sync::Mutex; @@ -490,3 +490,26 @@ fn test_chmod_strip_minus_from_mode() { assert_eq!(test.1, args.join(" ")); } } + +#[test] +fn test_chmod_keep_setgid() { + for &(from, arg, to) in &[ + (0o7777, "777", 0o46777), + (0o7777, "=777", 0o40777), + (0o7777, "0777", 0o46777), + (0o7777, "=0777", 0o40777), + (0o7777, "00777", 0o40777), + (0o2444, "a+wx", 0o42777), + (0o2444, "a=wx", 0o42333), + (0o1444, "g+s", 0o43444), + (0o4444, "u-s", 0o40444), + (0o7444, "a-s", 0o41444), + ] { + let (at, mut ucmd) = at_and_ucmd!(); + at.mkdir("dir"); + set_permissions(at.plus("dir"), Permissions::from_mode(from)).unwrap(); + let r = ucmd.arg(arg).arg("dir").succeeds(); + println!("{}", r.stderr_str()); + assert_eq!(at.metadata("dir").permissions().mode(), to); + } +} From 945e57ea22642b33ba88b85f9819a8a85d8a9fdd Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Tue, 17 Aug 2021 00:20:44 +0200 Subject: [PATCH 063/206] chmod: show an error if a permission wasn't removed due to umask --- src/uu/chmod/src/chmod.rs | 37 ++++++++++++++++++++++++----- src/uu/install/src/install.rs | 3 ++- src/uu/install/src/mode.rs | 4 ++-- src/uucore/src/lib/features/mode.rs | 20 ++++++++-------- tests/by-util/test_chmod.rs | 20 ++++++++++++---- 5 files changed, 60 insertions(+), 24 deletions(-) diff --git a/src/uu/chmod/src/chmod.rs b/src/uu/chmod/src/chmod.rs index 65357a576..d307b1c73 100644 --- a/src/uu/chmod/src/chmod.rs +++ b/src/uu/chmod/src/chmod.rs @@ -255,7 +255,9 @@ impl Chmoder { } #[cfg(any(unix, target_os = "redox"))] fn chmod_file(&self, file: &Path) -> Result<(), i32> { - let mut fperm = match fs::metadata(file) { + use uucore::mode::get_umask; + + let fperm = match fs::metadata(file) { Ok(meta) => meta.mode() & 0o7777, Err(err) => { if is_symlink(file) { @@ -278,18 +280,30 @@ impl Chmoder { Some(mode) => self.change_file(fperm, mode, file)?, None => { let cmode_unwrapped = self.cmode.clone().unwrap(); + let mut new_mode = fperm; + let mut naively_expected_new_mode = new_mode; for mode in cmode_unwrapped.split(',') { // cmode is guaranteed to be Some in this case let arr: &[char] = &['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']; let result = if mode.contains(arr) { - mode::parse_numeric(fperm, mode, file.is_dir()) + mode::parse_numeric(new_mode, mode, file.is_dir()).map(|v| (v, v)) } else { - mode::parse_symbolic(fperm, mode, file.is_dir()) + mode::parse_symbolic(new_mode, mode, get_umask(), file.is_dir()).map(|m| { + // calculate the new mode as if umask was 0 + let naive_mode = mode::parse_symbolic( + naively_expected_new_mode, + mode, + 0, + file.is_dir(), + ) + .unwrap(); // we know that mode must be valid, so this cannot fail + (m, naive_mode) + }) }; match result { - Ok(mode) => { - self.change_file(fperm, mode, file)?; - fperm = mode; + Ok((mode, naive_mode)) => { + new_mode = mode; + naively_expected_new_mode = naive_mode; } Err(f) => { if !self.quiet { @@ -299,6 +313,17 @@ impl Chmoder { } } } + 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.display(), + display_permissions_unix(new_mode, false), + display_permissions_unix(naively_expected_new_mode, false) + ); + return Err(1); + } } } diff --git a/src/uu/install/src/install.rs b/src/uu/install/src/install.rs index d5f853de7..72b7ddea8 100644 --- a/src/uu/install/src/install.rs +++ b/src/uu/install/src/install.rs @@ -18,6 +18,7 @@ use filetime::{set_file_times, FileTime}; use uucore::backup_control::{self, BackupMode}; use uucore::entries::{grp2gid, usr2uid}; use uucore::error::{FromIo, UError, UIoError, UResult, USimpleError}; +use uucore::mode::get_umask; use uucore::perms::{wrap_chown, Verbosity, VerbosityLevel}; use libc::{getegid, geteuid}; @@ -378,7 +379,7 @@ fn behavior(matches: &ArgMatches) -> UResult { let specified_mode: Option = if matches.is_present(OPT_MODE) { let x = matches.value_of(OPT_MODE).ok_or(1)?; - Some(mode::parse(x, considering_dir).map_err(|err| { + Some(mode::parse(x, considering_dir, get_umask()).map_err(|err| { show_error!("Invalid mode string: {}", err); 1 })?) diff --git a/src/uu/install/src/mode.rs b/src/uu/install/src/mode.rs index 5c3f8f28f..fd4cee50e 100644 --- a/src/uu/install/src/mode.rs +++ b/src/uu/install/src/mode.rs @@ -4,14 +4,14 @@ use std::path::Path; use uucore::mode; /// Takes a user-supplied string and tries to parse to u16 mode bitmask. -pub fn parse(mode_string: &str, considering_dir: bool) -> Result { +pub fn parse(mode_string: &str, considering_dir: bool, umask: u32) -> Result { let numbers: &[char] = &['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']; // Passing 000 as the existing permissions seems to mirror GNU behavior. if mode_string.contains(numbers) { mode::parse_numeric(0, mode_string, considering_dir) } else { - mode::parse_symbolic(0, mode_string, considering_dir) + mode::parse_symbolic(0, mode_string, umask, considering_dir) } } diff --git a/src/uucore/src/lib/features/mode.rs b/src/uucore/src/lib/features/mode.rs index 9ddb8540f..4c150ff27 100644 --- a/src/uucore/src/lib/features/mode.rs +++ b/src/uucore/src/lib/features/mode.rs @@ -7,7 +7,7 @@ // spell-checker:ignore (vars) fperm srwx -use libc::{mode_t, S_IRGRP, S_IROTH, S_IRUSR, S_IWGRP, S_IWOTH, S_IWUSR}; +use libc::{mode_t, umask, S_IRGRP, S_IROTH, S_IRUSR, S_IWGRP, S_IWOTH, S_IWUSR}; pub fn parse_numeric(fperm: u32, mut mode: &str, considering_dir: bool) -> Result { let (op, pos) = parse_op(mode).map_or_else(|_| (None, 0), |(op, pos)| (Some(op), pos)); @@ -35,24 +35,21 @@ pub fn parse_numeric(fperm: u32, mut mode: &str, considering_dir: bool) -> Resul pub fn parse_symbolic( mut fperm: u32, mut mode: &str, + umask: u32, considering_dir: bool, ) -> Result { - #[cfg(unix)] - use libc::umask; - let (mask, pos) = parse_levels(mode); if pos == mode.len() { return Err(format!("invalid mode ({})", mode)); } let respect_umask = pos == 0; - let last_umask = unsafe { umask(0) }; mode = &mode[pos..]; while !mode.is_empty() { let (op, pos) = parse_op(mode)?; mode = &mode[pos..]; let (mut srwx, pos) = parse_change(mode, fperm, considering_dir); if respect_umask { - srwx &= !(last_umask as u32); + srwx &= !(umask as u32); } mode = &mode[pos..]; match op { @@ -68,9 +65,6 @@ pub fn parse_symbolic( _ => unreachable!(), } } - unsafe { - umask(last_umask); - } Ok(fperm) } @@ -141,11 +135,17 @@ pub fn parse_mode(mode: &str) -> Result { let result = if mode.contains(arr) { parse_numeric(fperm as u32, mode, true) } else { - parse_symbolic(fperm as u32, mode, true) + parse_symbolic(fperm as u32, mode, get_umask(), true) }; result.map(|mode| mode as mode_t) } +pub fn get_umask() -> u32 { + let mask = unsafe { umask(0) }; + unsafe { umask(mask) }; + mask +} + #[cfg(test)] mod test { diff --git a/tests/by-util/test_chmod.rs b/tests/by-util/test_chmod.rs index 667f7b476..6ffc86325 100644 --- a/tests/by-util/test_chmod.rs +++ b/tests/by-util/test_chmod.rs @@ -201,11 +201,6 @@ fn test_chmod_ugoa() { before: 0o100000, after: 0o100755, }, - TestCase { - args: vec!["-w", TEST_FILE], - before: 0o100777, - after: 0o100577, - }, TestCase { args: vec!["-x", TEST_FILE], before: 0o100777, @@ -213,6 +208,21 @@ fn test_chmod_ugoa() { }, ]; run_tests(tests); + + // check that we print an error if umask prevents us from removing a permission + let (at, mut ucmd) = at_and_ucmd!(); + at.touch("file"); + set_permissions(at.plus("file"), Permissions::from_mode(0o777)).unwrap(); + ucmd.args(&["-w", "file"]) + .fails() + .code_is(1) + // spell-checker:disable-next-line + .stderr_is("chmod: file: new permissions are r-xrwxrwx, not r-xr-xr-x"); + assert_eq!( + metadata(at.plus("file")).unwrap().permissions().mode(), + 0o100577 + ); + unsafe { umask(last); } From b841a114211cea27c8f8026695182c885da421c9 Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Tue, 17 Aug 2021 15:27:34 +0200 Subject: [PATCH 064/206] chmod: fail if the operand list is empty --- src/uu/chmod/src/chmod.rs | 4 ++++ tests/by-util/test_chmod.rs | 9 +++++++++ 2 files changed, 13 insertions(+) diff --git a/src/uu/chmod/src/chmod.rs b/src/uu/chmod/src/chmod.rs index d307b1c73..6b165d198 100644 --- a/src/uu/chmod/src/chmod.rs +++ b/src/uu/chmod/src/chmod.rs @@ -98,6 +98,10 @@ pub fn uumain(args: impl uucore::Args) -> i32 { Some(cmode) }; + if files.is_empty() { + crash!(1, "missing operand"); + } + let chmoder = Chmoder { changes, quiet, diff --git a/tests/by-util/test_chmod.rs b/tests/by-util/test_chmod.rs index 6ffc86325..5106b4fe6 100644 --- a/tests/by-util/test_chmod.rs +++ b/tests/by-util/test_chmod.rs @@ -523,3 +523,12 @@ fn test_chmod_keep_setgid() { assert_eq!(at.metadata("dir").permissions().mode(), to); } } + +#[test] +fn test_no_operands() { + new_ucmd!() + .arg("777") + .fails() + .code_is(1) + .stderr_is("chmod: missing operand"); +} From 5825889931f29f3583af9c1ff7e8796ae4497b72 Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Tue, 17 Aug 2021 15:35:14 +0200 Subject: [PATCH 065/206] chmod: correctly handle modes after -- --- src/uu/chmod/src/chmod.rs | 3 +++ tests/by-util/test_chmod.rs | 14 ++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/src/uu/chmod/src/chmod.rs b/src/uu/chmod/src/chmod.rs index 6b165d198..60705526b 100644 --- a/src/uu/chmod/src/chmod.rs +++ b/src/uu/chmod/src/chmod.rs @@ -184,6 +184,9 @@ pub fn uu_app() -> App<'static, 'static> { // e.g. "chmod -v -xw -R FILE" -> "chmod -v xw -R FILE" pub fn strip_minus_from_mode(args: &mut Vec) -> bool { for arg in args { + if arg == "--" { + break; + } if arg.starts_with('-') { if let Some(second) = arg.chars().nth(1) { match second { diff --git a/tests/by-util/test_chmod.rs b/tests/by-util/test_chmod.rs index 5106b4fe6..9478c141b 100644 --- a/tests/by-util/test_chmod.rs +++ b/tests/by-util/test_chmod.rs @@ -532,3 +532,17 @@ fn test_no_operands() { .code_is(1) .stderr_is("chmod: missing operand"); } + +#[test] +fn test_mode_after_dash_dash() { + let (at, ucmd) = at_and_ucmd!(); + run_single_test( + &TestCase { + args: vec!["--", "-r", TEST_FILE], + before: 0o100777, + after: 0o100333, + }, + at, + ucmd, + ); +} From 38afdd6ab4ee30a61e5ebcddab9c6b39b6a4b085 Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Tue, 17 Aug 2021 15:42:50 +0200 Subject: [PATCH 066/206] uucore/mode: add cast for some platforms --- src/uu/chmod/src/chmod.rs | 4 ++-- src/uucore/src/lib/features/mode.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/uu/chmod/src/chmod.rs b/src/uu/chmod/src/chmod.rs index 60705526b..8502148bb 100644 --- a/src/uu/chmod/src/chmod.rs +++ b/src/uu/chmod/src/chmod.rs @@ -326,8 +326,8 @@ impl Chmoder { show_error!( "{}: new permissions are {}, not {}", file.display(), - display_permissions_unix(new_mode, false), - display_permissions_unix(naively_expected_new_mode, false) + display_permissions_unix(new_mode as mode_t, false), + display_permissions_unix(naively_expected_new_mode as mode_t, false) ); return Err(1); } diff --git a/src/uucore/src/lib/features/mode.rs b/src/uucore/src/lib/features/mode.rs index 4c150ff27..794cda418 100644 --- a/src/uucore/src/lib/features/mode.rs +++ b/src/uucore/src/lib/features/mode.rs @@ -143,7 +143,7 @@ pub fn parse_mode(mode: &str) -> Result { pub fn get_umask() -> u32 { let mask = unsafe { umask(0) }; unsafe { umask(mask) }; - mask + mask as u32 } #[cfg(test)] From 5121e2eec1ddbb6e640252ab41fa0011eba11c92 Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Tue, 17 Aug 2021 17:02:15 +0200 Subject: [PATCH 067/206] chmod: add check for quiet --- src/uu/chmod/src/chmod.rs | 2 +- tests/by-util/test_chmod.rs | 13 ++++++++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/uu/chmod/src/chmod.rs b/src/uu/chmod/src/chmod.rs index 8502148bb..bf6e81305 100644 --- a/src/uu/chmod/src/chmod.rs +++ b/src/uu/chmod/src/chmod.rs @@ -229,7 +229,7 @@ impl Chmoder { if !self.quiet { show_error!("cannot operate on dangling symlink '{}'", filename); } - } else { + } else if !self.quiet { show_error!("cannot access '{}': No such file or directory", filename); } return Err(1); diff --git a/tests/by-util/test_chmod.rs b/tests/by-util/test_chmod.rs index 9478c141b..1b8983bc3 100644 --- a/tests/by-util/test_chmod.rs +++ b/tests/by-util/test_chmod.rs @@ -360,13 +360,24 @@ fn test_chmod_recursive() { fn test_chmod_non_existing_file() { new_ucmd!() .arg("-R") - .arg("--verbose") .arg("-r,a+w") .arg("does-not-exist") .fails() .stderr_contains(&"cannot access 'does-not-exist': No such file or directory"); } +#[test] +fn test_chmod_non_existing_file_silent() { + new_ucmd!() + .arg("-R") + .arg("--quiet") + .arg("-r,a+w") + .arg("does-not-exist") + .fails() + .no_stderr() + .code_is(1); +} + #[test] fn test_chmod_preserve_root() { new_ucmd!() From 5ab05a92193fc8cc6f44a8b7944cef889543c5ff Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Tue, 17 Aug 2021 17:03:11 +0200 Subject: [PATCH 068/206] chmod: remove redundant cfg redox is unix. --- src/uu/chmod/src/chmod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uu/chmod/src/chmod.rs b/src/uu/chmod/src/chmod.rs index bf6e81305..26b3fd85b 100644 --- a/src/uu/chmod/src/chmod.rs +++ b/src/uu/chmod/src/chmod.rs @@ -260,7 +260,7 @@ impl Chmoder { // instead it just sets the readonly attribute on the file Err(0) } - #[cfg(any(unix, target_os = "redox"))] + #[cfg(unix)] fn chmod_file(&self, file: &Path) -> Result<(), i32> { use uucore::mode::get_umask; From 229948ae45110faca23b293e293dab0aab836f62 Mon Sep 17 00:00:00 2001 From: Andreas Hartmann Date: Mon, 23 Aug 2021 11:58:38 +0200 Subject: [PATCH 069/206] uu: Replace `safe_unwrap` with `crash_if_err` Unify the usage of macros `safe_unwrap` and `crash_if_err` that are identical to each other except for the assumption of a default error code. Use the more generic `crash_if_err` so that `safe_unwrap` is now obsolete and can be removed. --- src/uu/base32/src/base_common.rs | 2 +- src/uu/expand/src/expand.rs | 13 ++++--- src/uu/fold/src/fold.rs | 2 +- src/uu/hashsum/src/hashsum.rs | 60 +++++++++++++++++++------------- src/uu/ls/src/ls.rs | 4 +-- src/uu/pinky/src/pinky.rs | 2 +- src/uu/unexpand/src/unexpand.rs | 12 +++---- src/uu/who/src/who.rs | 2 +- 8 files changed, 56 insertions(+), 41 deletions(-) diff --git a/src/uu/base32/src/base_common.rs b/src/uu/base32/src/base_common.rs index 615093311..78962d9db 100644 --- a/src/uu/base32/src/base_common.rs +++ b/src/uu/base32/src/base_common.rs @@ -122,7 +122,7 @@ pub fn base_app<'a>(name: &str, version: &'a str, about: &'a str) -> App<'static pub fn get_input<'a>(config: &Config, stdin_ref: &'a Stdin) -> Box { match &config.to_read { Some(name) => { - let file_buf = safe_unwrap!(File::open(Path::new(name))); + let file_buf = crash_if_err!(1, File::open(Path::new(name))); Box::new(BufReader::new(file_buf)) // as Box } None => { diff --git a/src/uu/expand/src/expand.rs b/src/uu/expand/src/expand.rs index 04cb42649..f5a5686f6 100644 --- a/src/uu/expand/src/expand.rs +++ b/src/uu/expand/src/expand.rs @@ -329,12 +329,15 @@ fn expand(options: Options) { // now dump out either spaces if we're expanding, or a literal tab if we're not if init || !options.iflag { if nts <= options.tspaces.len() { - safe_unwrap!(output.write_all(options.tspaces[..nts].as_bytes())); + crash_if_err!( + 1, + output.write_all(options.tspaces[..nts].as_bytes()) + ); } else { - safe_unwrap!(output.write_all(" ".repeat(nts).as_bytes())); + crash_if_err!(1, output.write_all(" ".repeat(nts).as_bytes())); }; } else { - safe_unwrap!(output.write_all(&buf[byte..byte + nbytes])); + crash_if_err!(1, output.write_all(&buf[byte..byte + nbytes])); } } _ => { @@ -352,14 +355,14 @@ fn expand(options: Options) { init = false; } - safe_unwrap!(output.write_all(&buf[byte..byte + nbytes])); + crash_if_err!(1, output.write_all(&buf[byte..byte + nbytes])); } } byte += nbytes; // advance the pointer } - safe_unwrap!(output.flush()); + crash_if_err!(1, output.flush()); buf.truncate(0); // clear the buffer } } diff --git a/src/uu/fold/src/fold.rs b/src/uu/fold/src/fold.rs index c5628125d..c4cc16469 100644 --- a/src/uu/fold/src/fold.rs +++ b/src/uu/fold/src/fold.rs @@ -119,7 +119,7 @@ fn fold(filenames: Vec, bytes: bool, spaces: bool, width: usize) { stdin_buf = stdin(); &mut stdin_buf as &mut dyn Read } else { - file_buf = safe_unwrap!(File::open(Path::new(filename))); + file_buf = crash_if_err!(1, File::open(Path::new(filename))); &mut file_buf as &mut dyn Read }); diff --git a/src/uu/hashsum/src/hashsum.rs b/src/uu/hashsum/src/hashsum.rs index 384ef1388..f308da300 100644 --- a/src/uu/hashsum/src/hashsum.rs +++ b/src/uu/hashsum/src/hashsum.rs @@ -469,7 +469,7 @@ where stdin_buf = stdin(); Box::new(stdin_buf) as Box } else { - file_buf = safe_unwrap!(File::open(filename)); + file_buf = crash_if_err!(1, File::open(filename)); Box::new(file_buf) as Box }); if options.check { @@ -486,19 +486,25 @@ where } else { "+".to_string() }; - let gnu_re = safe_unwrap!(Regex::new(&format!( - r"^(?P[a-fA-F0-9]{}) (?P[ \*])(?P.*)", - modifier, - ))); - let bsd_re = safe_unwrap!(Regex::new(&format!( - r"^{algorithm} \((?P.*)\) = (?P[a-fA-F0-9]{digest_size})", - algorithm = options.algoname, - digest_size = modifier, - ))); + let gnu_re = crash_if_err!( + 1, + Regex::new(&format!( + r"^(?P[a-fA-F0-9]{}) (?P[ \*])(?P.*)", + modifier, + )) + ); + let bsd_re = crash_if_err!( + 1, + Regex::new(&format!( + r"^{algorithm} \((?P.*)\) = (?P[a-fA-F0-9]{digest_size})", + algorithm = options.algoname, + digest_size = modifier, + )) + ); let buffer = file; for (i, line) in buffer.lines().enumerate() { - let line = safe_unwrap!(line); + let line = crash_if_err!(1, line); let (ck_filename, sum, binary_check) = match gnu_re.captures(&line) { Some(caps) => ( caps.name("fileName").unwrap().as_str(), @@ -528,14 +534,17 @@ where } }, }; - let f = safe_unwrap!(File::open(ck_filename)); + let f = crash_if_err!(1, File::open(ck_filename)); let mut ckf = BufReader::new(Box::new(f) as Box); - let real_sum = safe_unwrap!(digest_reader( - &mut *options.digest, - &mut ckf, - binary_check, - options.output_bits - )) + let real_sum = crash_if_err!( + 1, + digest_reader( + &mut *options.digest, + &mut ckf, + binary_check, + options.output_bits + ) + ) .to_ascii_lowercase(); if sum == real_sum { if !options.quiet { @@ -549,12 +558,15 @@ where } } } else { - let sum = safe_unwrap!(digest_reader( - &mut *options.digest, - &mut file, - options.binary, - options.output_bits - )); + let sum = crash_if_err!( + 1, + digest_reader( + &mut *options.digest, + &mut file, + options.binary, + options.output_bits + ) + ); if options.tag { println!("{} ({}) = {}", options.algoname, filename.display(), sum); } else { diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index da8955152..3d957474c 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -1362,8 +1362,8 @@ fn enter_directory(dir: &PathData, config: &Config, out: &mut BufWriter) vec![] }; - let mut temp: Vec<_> = safe_unwrap!(fs::read_dir(&dir.p_buf)) - .map(|res| safe_unwrap!(res)) + let mut temp: Vec<_> = crash_if_err!(1, fs::read_dir(&dir.p_buf)) + .map(|res| crash_if_err!(1, res)) .filter(|e| should_display(e, config)) .map(|e| PathData::new(DirEntry::path(&e), Some(e.file_type()), None, config, false)) .collect(); diff --git a/src/uu/pinky/src/pinky.rs b/src/uu/pinky/src/pinky.rs index 02573c994..4aa27affa 100644 --- a/src/uu/pinky/src/pinky.rs +++ b/src/uu/pinky/src/pinky.rs @@ -291,7 +291,7 @@ impl Pinky { let mut s = ut.host(); if self.include_where && !s.is_empty() { - s = safe_unwrap!(ut.canon_host()); + s = crash_if_err!(1, ut.canon_host()); print!(" {}", s); } diff --git a/src/uu/unexpand/src/unexpand.rs b/src/uu/unexpand/src/unexpand.rs index cb8541048..7fb9b2590 100644 --- a/src/uu/unexpand/src/unexpand.rs +++ b/src/uu/unexpand/src/unexpand.rs @@ -178,13 +178,13 @@ fn write_tabs( break; } - safe_unwrap!(output.write_all(b"\t")); + crash_if_err!(1, output.write_all(b"\t")); scol += nts; } } while col > scol { - safe_unwrap!(output.write_all(b" ")); + crash_if_err!(1, output.write_all(b" ")); scol += 1; } } @@ -272,7 +272,7 @@ fn unexpand(options: Options) { init, true, ); - safe_unwrap!(output.write_all(&buf[byte..])); + crash_if_err!(1, output.write_all(&buf[byte..])); scol = col; break; } @@ -292,7 +292,7 @@ fn unexpand(options: Options) { }; if !tabs_buffered { - safe_unwrap!(output.write_all(&buf[byte..byte + nbytes])); + crash_if_err!(1, output.write_all(&buf[byte..byte + nbytes])); scol = col; // now printed up to this column } } @@ -317,7 +317,7 @@ fn unexpand(options: Options) { } else { 0 }; - safe_unwrap!(output.write_all(&buf[byte..byte + nbytes])); + crash_if_err!(1, output.write_all(&buf[byte..byte + nbytes])); scol = col; // we've now printed up to this column } } @@ -336,7 +336,7 @@ fn unexpand(options: Options) { init, true, ); - safe_unwrap!(output.flush()); + crash_if_err!(1, output.flush()); buf.truncate(0); // clear out the buffer } } diff --git a/src/uu/who/src/who.rs b/src/uu/who/src/who.rs index b0ef7b3fa..d61747127 100644 --- a/src/uu/who/src/who.rs +++ b/src/uu/who/src/who.rs @@ -492,7 +492,7 @@ impl Who { }; let s = if self.do_lookup { - safe_unwrap!(ut.canon_host()) + crash_if_err!(1, ut.canon_host()) } else { ut.host() }; From 17551952515ba46a1400d72a30486fcc04654d49 Mon Sep 17 00:00:00 2001 From: Andreas Hartmann Date: Mon, 23 Aug 2021 12:00:34 +0200 Subject: [PATCH 070/206] uu: Replace `return_if_err` with `crash_if_err` Unify the usage of macros `return_if_err` and `crash_if_err`. As `return_if_err` is used only in `uumain` routines of utilities, it achieves the same thing as `crash_if_err`, which calls the `crash!` macro to terminate function execution immediately. --- src/uu/csplit/src/csplit.rs | 6 +++--- src/uu/seq/src/seq.rs | 6 +++--- src/uu/stdbuf/src/stdbuf.rs | 2 +- src/uu/timeout/src/timeout.rs | 8 ++++---- src/uu/uname/src/uname.rs | 2 +- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/uu/csplit/src/csplit.rs b/src/uu/csplit/src/csplit.rs index 603d25265..977583a2f 100644 --- a/src/uu/csplit/src/csplit.rs +++ b/src/uu/csplit/src/csplit.rs @@ -725,14 +725,14 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .unwrap() .map(str::to_string) .collect(); - let patterns = return_if_err!(1, patterns::get_patterns(&patterns[..])); + let patterns = crash_if_err!(1, patterns::get_patterns(&patterns[..])); let options = CsplitOptions::new(&matches); if file_name == "-" { let stdin = io::stdin(); crash_if_err!(1, csplit(&options, patterns, stdin.lock())); } else { - let file = return_if_err!(1, File::open(file_name)); - let file_metadata = return_if_err!(1, file.metadata()); + let file = crash_if_err!(1, File::open(file_name)); + let file_metadata = crash_if_err!(1, file.metadata()); if !file_metadata.is_file() { crash!(1, "'{}' is not a regular file", file_name); } diff --git a/src/uu/seq/src/seq.rs b/src/uu/seq/src/seq.rs index 05388e7a1..2e700f952 100644 --- a/src/uu/seq/src/seq.rs +++ b/src/uu/seq/src/seq.rs @@ -105,7 +105,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let dec = slice.find('.').unwrap_or(len); largest_dec = len - dec; padding = dec; - return_if_err!(1, slice.parse()) + crash_if_err!(1, slice.parse()) } else { Number::BigInt(BigInt::one()) }; @@ -115,7 +115,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let dec = slice.find('.').unwrap_or(len); largest_dec = cmp::max(largest_dec, len - dec); padding = cmp::max(padding, dec); - return_if_err!(1, slice.parse()) + crash_if_err!(1, slice.parse()) } else { Number::BigInt(BigInt::one()) }; @@ -130,7 +130,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let last = { let slice = numbers[numbers.len() - 1]; padding = cmp::max(padding, slice.find('.').unwrap_or_else(|| slice.len())); - return_if_err!(1, slice.parse()) + crash_if_err!(1, slice.parse()) }; if largest_dec > 0 { largest_dec -= 1; diff --git a/src/uu/stdbuf/src/stdbuf.rs b/src/uu/stdbuf/src/stdbuf.rs index e87b3a225..77d80f777 100644 --- a/src/uu/stdbuf/src/stdbuf.rs +++ b/src/uu/stdbuf/src/stdbuf.rs @@ -170,7 +170,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let command_params: Vec<&str> = command_values.collect(); let mut tmp_dir = tempdir().unwrap(); - let (preload_env, libstdbuf) = return_if_err!(1, get_preload_env(&mut tmp_dir)); + let (preload_env, libstdbuf) = crash_if_err!(1, get_preload_env(&mut tmp_dir)); command.env(preload_env, libstdbuf); set_command_env(&mut command, "_STDBUF_I", options.stdin); set_command_env(&mut command, "_STDBUF_O", options.stdout); diff --git a/src/uu/timeout/src/timeout.rs b/src/uu/timeout/src/timeout.rs index 89fde82ca..21b0a0c37 100644 --- a/src/uu/timeout/src/timeout.rs +++ b/src/uu/timeout/src/timeout.rs @@ -221,7 +221,7 @@ fn timeout( cmd[0] ); } - return_if_err!(ERR_EXIT_STATUS, process.send_signal(signal)); + crash_if_err!(ERR_EXIT_STATUS, process.send_signal(signal)); if let Some(kill_after) = kill_after { match process.wait_or_timeout(kill_after) { Ok(Some(status)) => { @@ -235,13 +235,13 @@ fn timeout( if verbose { show_error!("sending signal KILL to command '{}'", cmd[0]); } - return_if_err!( + crash_if_err!( ERR_EXIT_STATUS, process.send_signal( uucore::signals::signal_by_name_or_value("KILL").unwrap() ) ); - return_if_err!(ERR_EXIT_STATUS, process.wait()); + crash_if_err!(ERR_EXIT_STATUS, process.wait()); 137 } Err(_) => 124, @@ -251,7 +251,7 @@ fn timeout( } } Err(_) => { - return_if_err!(ERR_EXIT_STATUS, process.send_signal(signal)); + crash_if_err!(ERR_EXIT_STATUS, process.send_signal(signal)); ERR_EXIT_STATUS } } diff --git a/src/uu/uname/src/uname.rs b/src/uu/uname/src/uname.rs index 5f8dfd8a8..2c396081e 100644 --- a/src/uu/uname/src/uname.rs +++ b/src/uu/uname/src/uname.rs @@ -53,7 +53,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let usage = format!("{} [OPTION]...", uucore::execution_phrase()); let matches = uu_app().usage(&usage[..]).get_matches_from(args); - let uname = return_if_err!(1, PlatformInfo::new()); + let uname = crash_if_err!(1, PlatformInfo::new()); let mut output = String::new(); let all = matches.is_present(options::ALL); From 4fbb74131427ff9d1f162f6eeb37ee4b84b43a9b Mon Sep 17 00:00:00 2001 From: Andreas Hartmann Date: Mon, 23 Aug 2021 12:02:11 +0200 Subject: [PATCH 071/206] macros: Remove obsolete macros Removes the `return_if_err!` and `safe_unwrap!` macros, which have now been replaces by `crash_if_err!` throughout the whole code and thus aren't used any longer. --- src/uucore/src/lib/macros.rs | 30 ------------------------------ 1 file changed, 30 deletions(-) diff --git a/src/uucore/src/lib/macros.rs b/src/uucore/src/lib/macros.rs index d16f04504..c7b15dba3 100644 --- a/src/uucore/src/lib/macros.rs +++ b/src/uucore/src/lib/macros.rs @@ -99,24 +99,6 @@ macro_rules! crash_if_err( //==== -/// 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. -#[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; - } - } - ) -); - -//==== - #[macro_export] macro_rules! safe_write( ($fd:expr, $($args:tt)+) => ( @@ -137,18 +119,6 @@ macro_rules! safe_writeln( ) ); -/// Unwraps the Result. Instead of panicking, it exists the program with exit -/// code 1. -#[macro_export] -macro_rules! safe_unwrap( - ($exp:expr) => ( - match $exp { - Ok(m) => m, - Err(f) => $crate::crash!(1, "{}", f.to_string()) - } - ) -); - //-- message templates //-- message templates : (join utility sub-macros) From 2c6410f4d898a8091b59864321b26b23620c2530 Mon Sep 17 00:00:00 2001 From: Andreas Hartmann Date: Tue, 20 Jul 2021 15:07:53 +0200 Subject: [PATCH 072/206] backup_control: Add arguments module Contains functions that create the CLI arguments associated with the backup functionality. --- src/uucore/src/lib/mods/backup_control.rs | 33 +++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/src/uucore/src/lib/mods/backup_control.rs b/src/uucore/src/lib/mods/backup_control.rs index 6fa48d308..5ef400813 100644 --- a/src/uucore/src/lib/mods/backup_control.rs +++ b/src/uucore/src/lib/mods/backup_control.rs @@ -25,6 +25,39 @@ pub enum BackupMode { ExistingBackup, } +pub mod arguments { + extern crate clap; + + pub static OPT_BACKUP: &str = "backupopt_backup"; + pub static OPT_BACKUP_NO_ARG: &str = "backupopt_b"; + pub static OPT_SUFFIX: &str = "backupopt_suffix"; + + pub fn backup() -> clap::Arg<'static, 'static> { + clap::Arg::with_name(OPT_BACKUP) + .long("backup") + .help("make a backup of each existing destination file") + .takes_value(true) + .require_equals(true) + .min_values(0) + .value_name("CONTROL") + } + + pub fn backup_no_args() -> clap::Arg<'static, 'static> { + clap::Arg::with_name(OPT_BACKUP_NO_ARG) + .short("b") + .help("like --backup but does not accept an argument") + } + + pub fn suffix() -> clap::Arg<'static, 'static> { + clap::Arg::with_name(OPT_SUFFIX) + .short("S") + .long("suffix") + .help("override the usual backup suffix") + .takes_value(true) + .value_name("SUFFIX") + } +} + pub fn determine_backup_suffix(supplied_suffix: Option<&str>) -> String { if let Some(suffix) = supplied_suffix { String::from(suffix) From 54086ef4c535babe18582ca8ad2a8f090311f4e3 Mon Sep 17 00:00:00 2001 From: Andreas Hartmann Date: Wed, 21 Jul 2021 09:17:57 +0200 Subject: [PATCH 073/206] backup_control: Implement custom error type Implements an error type based on `UError` that replaces the previously used error strings. The errors are currently returned when determining the backup mode, but extensions for future uses are already in place as comments. --- src/uucore/Cargo.toml | 1 + src/uucore/src/lib/mods/backup_control.rs | 142 ++++++++++++++++++++++ 2 files changed, 143 insertions(+) diff --git a/src/uucore/Cargo.toml b/src/uucore/Cargo.toml index 6d27ecad4..e45c9e5b5 100644 --- a/src/uucore/Cargo.toml +++ b/src/uucore/Cargo.toml @@ -16,6 +16,7 @@ edition = "2018" path="src/lib/lib.rs" [dependencies] +clap = "2.33.3" dns-lookup = { version="1.0.5", optional=true } dunce = "1.0.0" getopts = "<= 0.2.21" diff --git a/src/uucore/src/lib/mods/backup_control.rs b/src/uucore/src/lib/mods/backup_control.rs index 5ef400813..a26cef073 100644 --- a/src/uucore/src/lib/mods/backup_control.rs +++ b/src/uucore/src/lib/mods/backup_control.rs @@ -1,5 +1,89 @@ +//! Implement GNU-style backup functionality. +//! +//! This module implements the backup functionality as described in the [GNU +//! manual][1]. It provides +//! +//! - pre-defined [`clap`-Arguments][2] for inclusion in utilities that +//! implement backups +//! - determination of the [backup mode][3] +//! - determination of the [backup suffix][4] +//! - [backup target path construction][5] +//! - [Error types][6] for backup-related errors +//! - GNU-compliant [help texts][7] for backup-related errors +//! +//! Backup-functionality is implemented by the following utilities: +//! +//! - `cp` +//! - `install` +//! - `ln` +//! - `mv` +//! +//! +//! [1]: https://www.gnu.org/software/coreutils/manual/html_node/Backup-options.html +//! [2]: arguments +//! [3]: `determine_backup_mode()` +//! [4]: `determine_backup_suffix()` +//! [5]: `get_backup_path()` +//! [6]: `BackupError` +//! [7]: `BACKUP_CONTROL_LONG_HELP` +//! +//! +//! # Usage example +//! +//! ``` +//! #[macro_use] +//! extern crate uucore; +//! +//! use clap::{App, Arg, ArgMatches}; +//! use std::path::{Path, PathBuf}; +//! use uucore::backup_control::{self, BackupMode}; +//! use uucore::error::{UError, UResult}; +//! +//! fn main() { +//! let usage = String::from("app [OPTION]... ARG"); +//! let long_usage = String::from("And here's a detailed explanation"); +//! +//! let matches = App::new("app") +//! .arg(backup_control::arguments::backup()) +//! .arg(backup_control::arguments::backup_no_args()) +//! .arg(backup_control::arguments::suffix()) +//! .usage(&usage[..]) +//! .after_help(&*format!( +//! "{}\n{}", +//! long_usage, +//! backup_control::BACKUP_CONTROL_LONG_HELP +//! )) +//! .get_matches_from(vec![ +//! "app", "--backup=t", "--suffix=bak~" +//! ]); +//! +//! let backup_mode = match backup_control::determine_backup_mode(&matches) { +//! Err(e) => { +//! show!(e); +//! return; +//! }, +//! Ok(mode) => mode, +//! }; +//! let backup_suffix = backup_control::determine_backup_suffix(&matches); +//! let target_path = Path::new("/tmp/example"); +//! +//! let backup_path = backup_control::get_backup_path( +//! backup_mode, target_path, &backup_suffix +//! ); +//! +//! // Perform your backups here. +//! +//! } +//! ``` + +// spell-checker:ignore backupopt + +use crate::error::{UError, UResult}; +use clap::ArgMatches; use std::{ env, + error::Error, + fmt::{Debug, Display}, path::{Path, PathBuf}, }; @@ -17,6 +101,12 @@ the VERSION_CONTROL environment variable. Here are the values: existing, nil numbered if numbered backups exist, simple otherwise simple, never always make simple backups"; +static VALID_ARGS_HELP: &str = "Valid arguments are: + - ‘none’, ‘off’ + - ‘simple’, ‘never’ + - ‘existing’, ‘nil’ + - ‘numbered’, ‘t’"; + #[derive(Debug, Clone, Copy, Eq, PartialEq)] pub enum BackupMode { NoBackup, @@ -25,6 +115,58 @@ pub enum BackupMode { ExistingBackup, } +#[derive(Debug, Eq, PartialEq)] +pub enum BackupError { + InvalidArgument(String, String), + AmbiguousArgument(String, String), + BackupImpossible(), + // BackupFailed(PathBuf, PathBuf, std::io::Error), +} + +impl UError for BackupError { + fn code(&self) -> i32 { + match self { + BackupError::BackupImpossible() => 2, + _ => 1, + } + } + + fn usage(&self) -> bool { + // Suggested by clippy. + matches!( + self, + BackupError::InvalidArgument(_, _) | BackupError::AmbiguousArgument(_, _) + ) + } +} + +impl Error for BackupError {} + +impl Display for BackupError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + use BackupError as BE; + match self { + BE::InvalidArgument(arg, origin) => write!( + f, + "invalid argument ‘{}’ for ‘{}’\n{}", + arg, origin, VALID_ARGS_HELP + ), + BE::AmbiguousArgument(arg, origin) => write!( + f, + "ambiguous argument ‘{}’ for ‘{}’\n{}", + arg, origin, VALID_ARGS_HELP + ), + BE::BackupImpossible() => write!(f, "cannot create backup"), + // Placeholder for later + // BE::BackupFailed(from, to, e) => Display::fmt( + // &uio_error!(e, "failed to backup '{}' to '{}'", from.display(), to.display()), + // f + // ), + } + } +} + +/// Arguments for backup-related functionality pub mod arguments { extern crate clap; From e132fd49d925c543be95b423ba1a3c31910216a8 Mon Sep 17 00:00:00 2001 From: Andreas Hartmann Date: Wed, 21 Jul 2021 09:21:18 +0200 Subject: [PATCH 074/206] backup_control: Rework function interfaces Change all relevant functions to return `UResult`s from `BackupError` instead of error strings. Make `determine_backup_mode/suffix` accept `clap::ArgMatches` as input argument to parse for the arguments themselves, using the arguments with are defined in the `arguments` submodule. This way the user only needs to include the pre-defined arguments from the `arguments` module and passes a reference to the applications `ArgMatches` into the respective functions here. The functions then take care of handling the arguments. It is recommended to use the arguments provided in the `arguments` module over custom-defined ones. --- src/uucore/src/lib/mods/backup_control.rs | 43 +++++++++-------------- 1 file changed, 16 insertions(+), 27 deletions(-) diff --git a/src/uucore/src/lib/mods/backup_control.rs b/src/uucore/src/lib/mods/backup_control.rs index a26cef073..88f0c161a 100644 --- a/src/uucore/src/lib/mods/backup_control.rs +++ b/src/uucore/src/lib/mods/backup_control.rs @@ -200,7 +200,8 @@ pub mod arguments { } } -pub fn determine_backup_suffix(supplied_suffix: Option<&str>) -> String { +pub fn determine_backup_suffix(matches: &ArgMatches) -> String { + let supplied_suffix = matches.value_of(arguments::OPT_SUFFIX); if let Some(suffix) = supplied_suffix { String::from(suffix) } else { @@ -305,17 +306,13 @@ pub fn determine_backup_suffix(supplied_suffix: Option<&str>) -> String { /// }; /// } /// ``` -pub fn determine_backup_mode( - short_opt_present: bool, - long_opt_present: bool, - long_opt_value: Option<&str>, -) -> Result { - if long_opt_present { +pub fn determine_backup_mode(matches: &ArgMatches) -> UResult { + if matches.is_present(arguments::OPT_BACKUP) { // Use method to determine the type of backups to make. When this option // is used but method is not specified, then the value of the // VERSION_CONTROL environment variable is used. And if VERSION_CONTROL // is not set, the default backup type is ‘existing’. - if let Some(method) = long_opt_value { + if let Some(method) = matches.value_of(arguments::OPT_BACKUP) { // Second argument is for the error string that is returned. match_method(method, "backup type") } else if let Ok(method) = env::var("VERSION_CONTROL") { @@ -324,7 +321,7 @@ pub fn determine_backup_mode( } else { Ok(BackupMode::ExistingBackup) } - } else if short_opt_present { + } else if matches.is_present(arguments::OPT_BACKUP_NO_ARG) { // the short form of this option, -b does not accept any argument. // Using -b is equivalent to using --backup=existing. Ok(BackupMode::ExistingBackup) @@ -347,10 +344,13 @@ pub fn determine_backup_mode( /// /// # Errors /// -/// If `method` is ambiguous (i.e. may resolve to multiple backup modes) or -/// invalid, an error is returned. The error contains the formatted error string -/// which may then be passed to the [`show_usage_error`] macro. -fn match_method(method: &str, origin: &str) -> Result { +/// If `method` is invalid or ambiguous (i.e. may resolve to multiple backup +/// modes), an [`InvalidArgument`][10] or [`AmbiguousArgument`][11] error is +/// returned, respectively. +/// +/// [10]: BackupError::InvalidArgument +/// [11]: BackupError::AmbiguousArgument +fn match_method(method: &str, origin: &str) -> UResult { let matches: Vec<&&str> = BACKUP_CONTROL_VALUES .iter() .filter(|val| val.starts_with(method)) @@ -364,21 +364,10 @@ fn match_method(method: &str, origin: &str) -> Result { _ => unreachable!(), // cannot happen as we must have exactly one match // from the list above. } + } else if matches.is_empty() { + Err(BackupError::InvalidArgument(method.to_string(), origin.to_string()).into()) } else { - let error_type = if matches.is_empty() { - "invalid" - } else { - "ambiguous" - }; - Err(format!( - "{0} argument ‘{1}’ for ‘{2}’ -Valid arguments are: - - ‘none’, ‘off’ - - ‘simple’, ‘never’ - - ‘existing’, ‘nil’ - - ‘numbered’, ‘t’", - error_type, method, origin - )) + Err(BackupError::AmbiguousArgument(method.to_string(), origin.to_string()).into()) } } From 6f4e43e7c6cb251c6449ccf60e4dc0a41ad22250 Mon Sep 17 00:00:00 2001 From: Andreas Hartmann Date: Wed, 21 Jul 2021 09:24:36 +0200 Subject: [PATCH 075/206] backup_control: Update docs Add documentation to the module itself and update existing documentations for functions whose interfaces changed. Add more doctests. --- src/uucore/src/lib/mods/backup_control.rs | 117 +++++++++++++--------- 1 file changed, 68 insertions(+), 49 deletions(-) diff --git a/src/uucore/src/lib/mods/backup_control.rs b/src/uucore/src/lib/mods/backup_control.rs index 88f0c161a..24b39c634 100644 --- a/src/uucore/src/lib/mods/backup_control.rs +++ b/src/uucore/src/lib/mods/backup_control.rs @@ -107,18 +107,37 @@ static VALID_ARGS_HELP: &str = "Valid arguments are: - ‘existing’, ‘nil’ - ‘numbered’, ‘t’"; +/// Available backup modes. +/// +/// The mapping of the backup modes to the CLI arguments is annotated on the +/// enum variants. #[derive(Debug, Clone, Copy, Eq, PartialEq)] pub enum BackupMode { + /// Argument 'none', 'off' NoBackup, + /// Argument 'simple', 'never' SimpleBackup, + /// Argument 'numbered', 't' NumberedBackup, + /// Argument 'existing', 'nil' ExistingBackup, } +/// Backup error types. +/// +/// Errors are currently raised by [`determine_backup_mode`] only. All errors +/// are implemented as [`UCustomError`] for uniform handling across utilities. #[derive(Debug, Eq, PartialEq)] pub enum BackupError { + /// An invalid argument (e.g. 'foo') was given as backup type. First + /// parameter is the argument, second is the arguments origin (CLI or + /// ENV-var) InvalidArgument(String, String), + /// An ambiguous argument (e.g. 'n') was given as backup type. First + /// parameter is the argument, second is the arguments origin (CLI or + /// ENV-var) AmbiguousArgument(String, String), + /// Currently unused BackupImpossible(), // BackupFailed(PathBuf, PathBuf, std::io::Error), } @@ -166,7 +185,12 @@ impl Display for BackupError { } } -/// Arguments for backup-related functionality +/// Arguments for backup-related functionality. +/// +/// Rather than implementing the `clap`-Arguments for every utility, it is +/// recommended to include the `clap` arguments via the functions provided here. +/// This way the backup-specific arguments are handled uniformly across +/// utilities and can be maintained in one central place. pub mod arguments { extern crate clap; @@ -174,6 +198,7 @@ pub mod arguments { pub static OPT_BACKUP_NO_ARG: &str = "backupopt_b"; pub static OPT_SUFFIX: &str = "backupopt_suffix"; + /// '--backup' argument pub fn backup() -> clap::Arg<'static, 'static> { clap::Arg::with_name(OPT_BACKUP) .long("backup") @@ -184,12 +209,14 @@ pub mod arguments { .value_name("CONTROL") } + /// '-b' argument pub fn backup_no_args() -> clap::Arg<'static, 'static> { clap::Arg::with_name(OPT_BACKUP_NO_ARG) .short("b") .help("like --backup but does not accept an argument") } + /// '-S, --suffix' argument pub fn suffix() -> clap::Arg<'static, 'static> { clap::Arg::with_name(OPT_SUFFIX) .short("S") @@ -200,6 +227,16 @@ pub mod arguments { } } +/// Obtain the suffix to use for a backup. +/// +/// In order of precedence, this function obtains the backup suffix +/// +/// 1. From the '-S' or '--suffix' CLI argument, if present +/// 2. From the "SIMPLE_BACKUP_SUFFIX" environment variable, if present +/// 3. By using the default '~' if none of the others apply +/// +/// This function directly takes [`clap::ArgMatches`] as argument and looks for +/// the '-S' and '--suffix' arguments itself. pub fn determine_backup_suffix(matches: &ArgMatches) -> String { let supplied_suffix = matches.value_of(arguments::OPT_SUFFIX); if let Some(suffix) = supplied_suffix { @@ -214,7 +251,13 @@ pub fn determine_backup_suffix(matches: &ArgMatches) -> String { /// Parses the backup options according to the [GNU manual][1], and converts /// them to an instance of `BackupMode` for further processing. /// -/// For an explanation of what the arguments mean, refer to the examples below. +/// Takes [`clap::ArgMatches`] as argument which **must** contain the options +/// from [`arguments::backup()`] and [`arguments::backup_no_args()`]. Otherwise +/// the `NoBackup` mode is returned unconditionally. +/// +/// It is recommended for anyone who would like to implement the +/// backup-functionality to use the arguments prepared in the `arguments` +/// submodule (see examples) /// /// [1]: https://www.gnu.org/software/coreutils/manual/html_node/Backup-options.html /// @@ -223,9 +266,11 @@ pub fn determine_backup_suffix(matches: &ArgMatches) -> String { /// /// If an argument supplied directly to the long `backup` option, or read in /// through the `VERSION CONTROL` env var is ambiguous (i.e. may resolve to -/// multiple backup modes) or invalid, an error is returned. The error contains -/// the formatted error string which may then be passed to the -/// [`show_usage_error`] macro. +/// multiple backup modes) or invalid, an [`InvalidArgument`][10] or +/// [`AmbiguousArgument`][11] error is returned, respectively. +/// +/// [10]: BackupError::InvalidArgument +/// [11]: BackupError::AmbiguousArgument /// /// /// # Examples @@ -237,34 +282,18 @@ pub fn determine_backup_suffix(matches: &ArgMatches) -> String { /// #[macro_use] /// extern crate uucore; /// use uucore::backup_control::{self, BackupMode}; -/// use clap::{App, Arg}; +/// use clap::{App, Arg, ArgMatches}; /// /// fn main() { -/// let OPT_BACKUP: &str = "backup"; -/// let OPT_BACKUP_NO_ARG: &str = "b"; /// let matches = App::new("app") -/// .arg(Arg::with_name(OPT_BACKUP_NO_ARG) -/// .short(OPT_BACKUP_NO_ARG)) -/// .arg(Arg::with_name(OPT_BACKUP) -/// .long(OPT_BACKUP) -/// .takes_value(true) -/// .require_equals(true) -/// .min_values(0)) +/// .arg(backup_control::arguments::backup()) +/// .arg(backup_control::arguments::backup_no_args()) /// .get_matches_from(vec![ /// "app", "-b", "--backup=t" /// ]); -/// -/// let backup_mode = backup_control::determine_backup_mode( -/// matches.is_present(OPT_BACKUP_NO_ARG), matches.is_present(OPT_BACKUP), -/// matches.value_of(OPT_BACKUP) -/// ); -/// let backup_mode = match backup_mode { -/// Err(err) => { -/// show_usage_error!("{}", err); -/// return; -/// }, -/// Ok(mode) => mode, -/// }; +/// +/// let backup_mode = backup_control::determine_backup_mode(&matches).unwrap(); +/// assert_eq!(backup_mode, BackupMode::NumberedBackup) /// } /// ``` /// @@ -275,35 +304,24 @@ pub fn determine_backup_suffix(matches: &ArgMatches) -> String { /// ``` /// #[macro_use] /// extern crate uucore; -/// use uucore::backup_control::{self, BackupMode}; -/// use clap::{crate_version, App, Arg, ArgMatches}; +/// use uucore::backup_control::{self, BackupMode, BackupError}; +/// use clap::{App, Arg, ArgMatches}; /// /// fn main() { -/// let OPT_BACKUP: &str = "backup"; -/// let OPT_BACKUP_NO_ARG: &str = "b"; /// let matches = App::new("app") -/// .arg(Arg::with_name(OPT_BACKUP_NO_ARG) -/// .short(OPT_BACKUP_NO_ARG)) -/// .arg(Arg::with_name(OPT_BACKUP) -/// .long(OPT_BACKUP) -/// .takes_value(true) -/// .require_equals(true) -/// .min_values(0)) +/// .arg(backup_control::arguments::backup()) +/// .arg(backup_control::arguments::backup_no_args()) /// .get_matches_from(vec![ /// "app", "-b", "--backup=n" /// ]); /// -/// let backup_mode = backup_control::determine_backup_mode( -/// matches.is_present(OPT_BACKUP_NO_ARG), matches.is_present(OPT_BACKUP), -/// matches.value_of(OPT_BACKUP) -/// ); -/// let backup_mode = match backup_mode { -/// Err(err) => { -/// show_usage_error!("{}", err); -/// return; -/// }, -/// Ok(mode) => mode, -/// }; +/// let backup_mode = backup_control::determine_backup_mode(&matches); +/// +/// assert!(backup_mode.is_err()); +/// let err = backup_mode.unwrap_err(); +/// // assert_eq!(err, BackupError::AmbiguousArgument); +/// // Use uucore functionality to show the error to the user +/// show!(err); /// } /// ``` pub fn determine_backup_mode(matches: &ArgMatches) -> UResult { @@ -319,6 +337,7 @@ pub fn determine_backup_mode(matches: &ArgMatches) -> UResult { // Second argument is for the error string that is returned. match_method(&method, "$VERSION_CONTROL") } else { + // Default if no argument is provided to '--backup' Ok(BackupMode::ExistingBackup) } } else if matches.is_present(arguments::OPT_BACKUP_NO_ARG) { From 6c86957a244e882f969fe623c9048ecb18bd470e Mon Sep 17 00:00:00 2001 From: Andreas Hartmann Date: Wed, 21 Jul 2021 09:26:32 +0200 Subject: [PATCH 076/206] backup_control: Fix internal tests Adapt the tests to work with the changed function interfaces. Added a convenience function to construct a `clap` application that's used to test the functions from a "user"-perspective. --- src/uucore/src/lib/mods/backup_control.rs | 96 +++++++++-------------- 1 file changed, 36 insertions(+), 60 deletions(-) diff --git a/src/uucore/src/lib/mods/backup_control.rs b/src/uucore/src/lib/mods/backup_control.rs index 24b39c634..a899c81a0 100644 --- a/src/uucore/src/lib/mods/backup_control.rs +++ b/src/uucore/src/lib/mods/backup_control.rs @@ -438,6 +438,7 @@ mod tests { use super::*; use std::env; // Required to instantiate mutex in shared context + use clap::App; use lazy_static::lazy_static; use std::sync::Mutex; @@ -454,16 +455,20 @@ mod tests { // Environment variable for "VERSION_CONTROL" static ENV_VERSION_CONTROL: &str = "VERSION_CONTROL"; + fn make_app() -> clap::App<'static, 'static> { + App::new("app") + .arg(arguments::backup()) + .arg(arguments::backup_no_args()) + .arg(arguments::suffix()) + } + // Defaults to --backup=existing #[test] fn test_backup_mode_short_only() { - let short_opt_present = true; - let long_opt_present = false; - let long_opt_value = None; let _dummy = TEST_MUTEX.lock().unwrap(); + let matches = make_app().get_matches_from(vec!["app", "-b"]); - let result = - determine_backup_mode(short_opt_present, long_opt_present, long_opt_value).unwrap(); + let result = determine_backup_mode(&matches).unwrap(); assert_eq!(result, BackupMode::ExistingBackup); } @@ -471,13 +476,10 @@ mod tests { // --backup takes precedence over -b #[test] fn test_backup_mode_long_preferred_over_short() { - let short_opt_present = true; - let long_opt_present = true; - let long_opt_value = Some("none"); let _dummy = TEST_MUTEX.lock().unwrap(); + let matches = make_app().get_matches_from(vec!["app", "-b", "--backup=none"]); - let result = - determine_backup_mode(short_opt_present, long_opt_present, long_opt_value).unwrap(); + let result = determine_backup_mode(&matches).unwrap(); assert_eq!(result, BackupMode::NoBackup); } @@ -485,13 +487,10 @@ mod tests { // --backup can be passed without an argument #[test] fn test_backup_mode_long_without_args_no_env() { - let short_opt_present = false; - let long_opt_present = true; - let long_opt_value = None; let _dummy = TEST_MUTEX.lock().unwrap(); + let matches = make_app().get_matches_from(vec!["app", "--backup"]); - let result = - determine_backup_mode(short_opt_present, long_opt_present, long_opt_value).unwrap(); + let result = determine_backup_mode(&matches).unwrap(); assert_eq!(result, BackupMode::ExistingBackup); } @@ -499,13 +498,10 @@ mod tests { // --backup can be passed with an argument only #[test] fn test_backup_mode_long_with_args() { - let short_opt_present = false; - let long_opt_present = true; - let long_opt_value = Some("simple"); let _dummy = TEST_MUTEX.lock().unwrap(); + let matches = make_app().get_matches_from(vec!["app", "--backup=simple"]); - let result = - determine_backup_mode(short_opt_present, long_opt_present, long_opt_value).unwrap(); + let result = determine_backup_mode(&matches).unwrap(); assert_eq!(result, BackupMode::SimpleBackup); } @@ -513,43 +509,36 @@ mod tests { // --backup errors on invalid argument #[test] fn test_backup_mode_long_with_args_invalid() { - let short_opt_present = false; - let long_opt_present = true; - let long_opt_value = Some("foobar"); let _dummy = TEST_MUTEX.lock().unwrap(); + let matches = make_app().get_matches_from(vec!["app", "--backup=foobar"]); - let result = determine_backup_mode(short_opt_present, long_opt_present, long_opt_value); + let result = determine_backup_mode(&matches); assert!(result.is_err()); - let text = result.unwrap_err(); + let text = format!("{}", result.unwrap_err()); assert!(text.contains("invalid argument ‘foobar’ for ‘backup type’")); } // --backup errors on ambiguous argument #[test] fn test_backup_mode_long_with_args_ambiguous() { - let short_opt_present = false; - let long_opt_present = true; - let long_opt_value = Some("n"); let _dummy = TEST_MUTEX.lock().unwrap(); + let matches = make_app().get_matches_from(vec!["app", "--backup=n"]); - let result = determine_backup_mode(short_opt_present, long_opt_present, long_opt_value); + let result = determine_backup_mode(&matches); assert!(result.is_err()); - let text = result.unwrap_err(); + let text = format!("{}", result.unwrap_err()); assert!(text.contains("ambiguous argument ‘n’ for ‘backup type’")); } // --backup accepts shortened arguments (si for simple) #[test] fn test_backup_mode_long_with_arg_shortened() { - let short_opt_present = false; - let long_opt_present = true; - let long_opt_value = Some("si"); let _dummy = TEST_MUTEX.lock().unwrap(); + let matches = make_app().get_matches_from(vec!["app", "--backup=si"]); - let result = - determine_backup_mode(short_opt_present, long_opt_present, long_opt_value).unwrap(); + let result = determine_backup_mode(&matches).unwrap(); assert_eq!(result, BackupMode::SimpleBackup); } @@ -557,14 +546,11 @@ mod tests { // -b ignores the "VERSION_CONTROL" environment variable #[test] fn test_backup_mode_short_only_ignore_env() { - let short_opt_present = true; - let long_opt_present = false; - let long_opt_value = None; let _dummy = TEST_MUTEX.lock().unwrap(); env::set_var(ENV_VERSION_CONTROL, "none"); + let matches = make_app().get_matches_from(vec!["app", "-b"]); - let result = - determine_backup_mode(short_opt_present, long_opt_present, long_opt_value).unwrap(); + let result = determine_backup_mode(&matches).unwrap(); assert_eq!(result, BackupMode::ExistingBackup); env::remove_var(ENV_VERSION_CONTROL); @@ -573,14 +559,11 @@ mod tests { // --backup can be passed without an argument, but reads env var if existent #[test] fn test_backup_mode_long_without_args_with_env() { - let short_opt_present = false; - let long_opt_present = true; - let long_opt_value = None; let _dummy = TEST_MUTEX.lock().unwrap(); env::set_var(ENV_VERSION_CONTROL, "none"); + let matches = make_app().get_matches_from(vec!["app", "--backup"]); - let result = - determine_backup_mode(short_opt_present, long_opt_present, long_opt_value).unwrap(); + let result = determine_backup_mode(&matches).unwrap(); assert_eq!(result, BackupMode::NoBackup); env::remove_var(ENV_VERSION_CONTROL); @@ -589,16 +572,14 @@ mod tests { // --backup errors on invalid VERSION_CONTROL env var #[test] fn test_backup_mode_long_with_env_var_invalid() { - let short_opt_present = false; - let long_opt_present = true; - let long_opt_value = None; let _dummy = TEST_MUTEX.lock().unwrap(); env::set_var(ENV_VERSION_CONTROL, "foobar"); + let matches = make_app().get_matches_from(vec!["app", "--backup"]); - let result = determine_backup_mode(short_opt_present, long_opt_present, long_opt_value); + let result = determine_backup_mode(&matches); assert!(result.is_err()); - let text = result.unwrap_err(); + let text = format!("{}", result.unwrap_err()); assert!(text.contains("invalid argument ‘foobar’ for ‘$VERSION_CONTROL’")); env::remove_var(ENV_VERSION_CONTROL); } @@ -606,16 +587,14 @@ mod tests { // --backup errors on ambiguous VERSION_CONTROL env var #[test] fn test_backup_mode_long_with_env_var_ambiguous() { - let short_opt_present = false; - let long_opt_present = true; - let long_opt_value = None; let _dummy = TEST_MUTEX.lock().unwrap(); env::set_var(ENV_VERSION_CONTROL, "n"); + let matches = make_app().get_matches_from(vec!["app", "--backup"]); - let result = determine_backup_mode(short_opt_present, long_opt_present, long_opt_value); + let result = determine_backup_mode(&matches); assert!(result.is_err()); - let text = result.unwrap_err(); + let text = format!("{}", result.unwrap_err()); assert!(text.contains("ambiguous argument ‘n’ for ‘$VERSION_CONTROL’")); env::remove_var(ENV_VERSION_CONTROL); } @@ -623,14 +602,11 @@ mod tests { // --backup accepts shortened env vars (si for simple) #[test] fn test_backup_mode_long_with_env_var_shortened() { - let short_opt_present = false; - let long_opt_present = true; - let long_opt_value = None; let _dummy = TEST_MUTEX.lock().unwrap(); env::set_var(ENV_VERSION_CONTROL, "si"); + let matches = make_app().get_matches_from(vec!["app", "--backup"]); - let result = - determine_backup_mode(short_opt_present, long_opt_present, long_opt_value).unwrap(); + let result = determine_backup_mode(&matches).unwrap(); assert_eq!(result, BackupMode::SimpleBackup); env::remove_var(ENV_VERSION_CONTROL); From f2311f87f43761e5e40097c49d3e967a6920368c Mon Sep 17 00:00:00 2001 From: Andreas Hartmann Date: Wed, 21 Jul 2021 09:28:58 +0200 Subject: [PATCH 077/206] cp: Adapt to modified `backup_control` interface --- src/uu/cp/src/cp.rs | 38 ++++++-------------------------------- 1 file changed, 6 insertions(+), 32 deletions(-) diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index 9a80b18ab..df70ccea8 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -231,8 +231,6 @@ fn usage() -> String { mod options { pub const ARCHIVE: &str = "archive"; pub const ATTRIBUTES_ONLY: &str = "attributes-only"; - pub const BACKUP: &str = "backup"; - pub const BACKUP_NO_ARG: &str = "b"; pub const CLI_SYMBOLIC_LINKS: &str = "cli-symbolic-links"; pub const CONTEXT: &str = "context"; pub const COPY_CONTENTS: &str = "copy-contents"; @@ -257,7 +255,6 @@ mod options { pub const REMOVE_DESTINATION: &str = "remove-destination"; pub const SPARSE: &str = "sparse"; pub const STRIP_TRAILING_SLASHES: &str = "strip-trailing-slashes"; - pub const SUFFIX: &str = "suffix"; pub const SYMBOLIC_LINK: &str = "symbolic-link"; pub const TARGET_DIRECTORY: &str = "target-directory"; pub const UPDATE: &str = "update"; @@ -355,24 +352,9 @@ pub fn uu_app() -> App<'static, 'static> { .conflicts_with(options::FORCE) .help("remove each existing destination file before attempting to open it \ (contrast with --force). On Windows, current only works for writeable files.")) - .arg(Arg::with_name(options::BACKUP) - .long(options::BACKUP) - .help("make a backup of each existing destination file") - .takes_value(true) - .require_equals(true) - .min_values(0) - .value_name("CONTROL") - ) - .arg(Arg::with_name(options::BACKUP_NO_ARG) - .short(options::BACKUP_NO_ARG) - .help("like --backup but does not accept an argument") - ) - .arg(Arg::with_name(options::SUFFIX) - .short("S") - .long(options::SUFFIX) - .takes_value(true) - .value_name("SUFFIX") - .help("override the usual backup suffix")) + .arg(backup_control::arguments::backup()) + .arg(backup_control::arguments::backup_no_args()) + .arg(backup_control::arguments::suffix()) .arg(Arg::with_name(options::UPDATE) .short("u") .long(options::UPDATE) @@ -604,20 +586,12 @@ impl Options { || matches.is_present(options::RECURSIVE_ALIAS) || matches.is_present(options::ARCHIVE); - let backup_mode = backup_control::determine_backup_mode( - matches.is_present(options::BACKUP_NO_ARG), - matches.is_present(options::BACKUP), - matches.value_of(options::BACKUP), - ); - let backup_mode = match backup_mode { - Err(err) => { - return Err(Error::Backup(err)); - } + let backup_mode = match backup_control::determine_backup_mode(matches) { + Err(e) => return Err(Error::Backup(format!("{}", e))), Ok(mode) => mode, }; - let backup_suffix = - backup_control::determine_backup_suffix(matches.value_of(options::SUFFIX)); + let backup_suffix = backup_control::determine_backup_suffix(matches); let overwrite = OverwriteMode::from_matches(matches); From ce0d9bce2849e91bc47caf754e4310029ad09f16 Mon Sep 17 00:00:00 2001 From: Andreas Hartmann Date: Wed, 21 Jul 2021 09:30:00 +0200 Subject: [PATCH 078/206] install: Adapt to modified `backup_control` interface --- src/uu/install/src/install.rs | 46 +++++++---------------------------- 1 file changed, 9 insertions(+), 37 deletions(-) diff --git a/src/uu/install/src/install.rs b/src/uu/install/src/install.rs index d5f853de7..88912e8a9 100644 --- a/src/uu/install/src/install.rs +++ b/src/uu/install/src/install.rs @@ -17,7 +17,7 @@ use file_diff::diff; use filetime::{set_file_times, FileTime}; use uucore::backup_control::{self, BackupMode}; use uucore::entries::{grp2gid, usr2uid}; -use uucore::error::{FromIo, UError, UIoError, UResult, USimpleError}; +use uucore::error::{FromIo, UError, UIoError, UResult}; use uucore::perms::{wrap_chown, Verbosity, VerbosityLevel}; use libc::{getegid, geteuid}; @@ -154,8 +154,6 @@ static ABOUT: &str = "Copy SOURCE to DEST or multiple SOURCE(s) to the existing DIRECTORY, while setting permission modes and owner/group"; static OPT_COMPARE: &str = "compare"; -static OPT_BACKUP: &str = "backup"; -static OPT_BACKUP_NO_ARG: &str = "backup2"; static OPT_DIRECTORY: &str = "directory"; static OPT_IGNORED: &str = "ignored"; static OPT_CREATE_LEADING: &str = "create-leading"; @@ -165,7 +163,6 @@ static OPT_OWNER: &str = "owner"; static OPT_PRESERVE_TIMESTAMPS: &str = "preserve-timestamps"; static OPT_STRIP: &str = "strip"; static OPT_STRIP_PROGRAM: &str = "strip-program"; -static OPT_SUFFIX: &str = "suffix"; static OPT_TARGET_DIRECTORY: &str = "target-directory"; static OPT_NO_TARGET_DIRECTORY: &str = "no-target-directory"; static OPT_VERBOSE: &str = "verbose"; @@ -208,19 +205,10 @@ pub fn uu_app() -> App<'static, 'static> { .version(crate_version!()) .about(ABOUT) .arg( - Arg::with_name(OPT_BACKUP) - .long(OPT_BACKUP) - .help("make a backup of each existing destination file") - .takes_value(true) - .require_equals(true) - .min_values(0) - .value_name("CONTROL") + backup_control::arguments::backup() ) .arg( - // TODO implement flag - Arg::with_name(OPT_BACKUP_NO_ARG) - .short("b") - .help("like --backup but does not accept an argument") + backup_control::arguments::backup_no_args() ) .arg( Arg::with_name(OPT_IGNORED) @@ -278,9 +266,9 @@ pub fn uu_app() -> App<'static, 'static> { ) .arg( Arg::with_name(OPT_STRIP) - .short("s") - .long(OPT_STRIP) - .help("strip symbol tables (no action Windows)") + .short("s") + .long(OPT_STRIP) + .help("strip symbol tables (no action Windows)") ) .arg( Arg::with_name(OPT_STRIP_PROGRAM) @@ -289,14 +277,7 @@ pub fn uu_app() -> App<'static, 'static> { .value_name("PROGRAM") ) .arg( - // TODO implement flag - Arg::with_name(OPT_SUFFIX) - .short("S") - .long(OPT_SUFFIX) - .help("override the usual backup suffix") - .value_name("SUFFIX") - .takes_value(true) - .min_values(1) + backup_control::arguments::suffix() ) .arg( // TODO implement flag @@ -386,23 +367,14 @@ fn behavior(matches: &ArgMatches) -> UResult { None }; - let backup_mode = backup_control::determine_backup_mode( - matches.is_present(OPT_BACKUP_NO_ARG), - matches.is_present(OPT_BACKUP), - matches.value_of(OPT_BACKUP), - ); - let backup_mode = match backup_mode { - Err(err) => return Err(USimpleError::new(1, err)), - Ok(mode) => mode, - }; - + let backup_mode = backup_control::determine_backup_mode(matches)?; let target_dir = matches.value_of(OPT_TARGET_DIRECTORY).map(|d| d.to_owned()); Ok(Behavior { main_function, specified_mode, backup_mode, - suffix: backup_control::determine_backup_suffix(matches.value_of(OPT_SUFFIX)), + suffix: backup_control::determine_backup_suffix(matches), owner: matches.value_of(OPT_OWNER).unwrap_or("").to_string(), group: matches.value_of(OPT_GROUP).unwrap_or("").to_string(), verbose: matches.is_present(OPT_VERBOSE), From 2a1a923accfb1e895085c5c1edac767e879abcbd Mon Sep 17 00:00:00 2001 From: Andreas Hartmann Date: Wed, 21 Jul 2021 09:30:15 +0200 Subject: [PATCH 079/206] ln: Adapt to modified `backup_control` interface --- src/uu/ln/src/ln.rs | 46 +++++---------------------------------------- 1 file changed, 5 insertions(+), 41 deletions(-) diff --git a/src/uu/ln/src/ln.rs b/src/uu/ln/src/ln.rs index f1689a44b..4eeb637e7 100644 --- a/src/uu/ln/src/ln.rs +++ b/src/uu/ln/src/ln.rs @@ -54,7 +54,6 @@ enum LnError { FailedToLink(String), MissingDestination(String), ExtraOperand(String), - InvalidBackupMode(String), } impl Display for LnError { @@ -72,7 +71,6 @@ impl Display for LnError { s, uucore::execution_phrase() ), - Self::InvalidBackupMode(s) => write!(f, "{}", s), } } } @@ -87,7 +85,6 @@ impl UError for LnError { Self::FailedToLink(_) => 1, Self::MissingDestination(_) => 1, Self::ExtraOperand(_) => 1, - Self::InvalidBackupMode(_) => 1, } } } @@ -119,13 +116,10 @@ fn long_usage() -> String { static ABOUT: &str = "change file owner and group"; mod options { - pub const BACKUP_NO_ARG: &str = "b"; - pub const BACKUP: &str = "backup"; pub const FORCE: &str = "force"; pub const INTERACTIVE: &str = "interactive"; pub const NO_DEREFERENCE: &str = "no-dereference"; pub const SYMBOLIC: &str = "symbolic"; - pub const SUFFIX: &str = "suffix"; pub const TARGET_DIRECTORY: &str = "target-directory"; pub const NO_TARGET_DIRECTORY: &str = "no-target-directory"; pub const RELATIVE: &str = "relative"; @@ -164,19 +158,8 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { OverwriteMode::NoClobber }; - let backup_mode = backup_control::determine_backup_mode( - matches.is_present(options::BACKUP_NO_ARG), - matches.is_present(options::BACKUP), - matches.value_of(options::BACKUP), - ); - let backup_mode = match backup_mode { - Err(err) => { - return Err(LnError::InvalidBackupMode(err).into()); - } - Ok(mode) => mode, - }; - - let backup_suffix = backup_control::determine_backup_suffix(matches.value_of(options::SUFFIX)); + let backup_mode = backup_control::determine_backup_mode(&matches)?; + let backup_suffix = backup_control::determine_backup_suffix(&matches); let settings = Settings { overwrite: overwrite_mode, @@ -199,20 +182,8 @@ pub fn uu_app() -> App<'static, 'static> { App::new(uucore::util_name()) .version(crate_version!()) .about(ABOUT) - .arg( - Arg::with_name(options::BACKUP) - .long(options::BACKUP) - .help("make a backup of each existing destination file") - .takes_value(true) - .require_equals(true) - .min_values(0) - .value_name("CONTROL"), - ) - .arg( - Arg::with_name(options::BACKUP_NO_ARG) - .short(options::BACKUP_NO_ARG) - .help("like --backup but does not accept an argument"), - ) + .arg(backup_control::arguments::backup()) + .arg(backup_control::arguments::backup_no_args()) // TODO: opts.arg( // Arg::with_name(("d", "directory", "allow users with appropriate privileges to attempt \ // to make hard links to directories"); @@ -250,14 +221,7 @@ pub fn uu_app() -> App<'static, 'static> { // override added for https://github.com/uutils/coreutils/issues/2359 .overrides_with(options::SYMBOLIC), ) - .arg( - Arg::with_name(options::SUFFIX) - .short("S") - .long(options::SUFFIX) - .help("override the usual backup suffix") - .value_name("SUFFIX") - .takes_value(true), - ) + .arg(backup_control::arguments::suffix()) .arg( Arg::with_name(options::TARGET_DIRECTORY) .short("t") From 8ecef029f623aa84838bf3f2d81bb5bdc9d8f9a7 Mon Sep 17 00:00:00 2001 From: Andreas Hartmann Date: Wed, 21 Jul 2021 09:30:27 +0200 Subject: [PATCH 080/206] mv: Adapt to modified `backup_control` interface --- src/uu/mv/src/mv.rs | 35 +++++++---------------------------- 1 file changed, 7 insertions(+), 28 deletions(-) diff --git a/src/uu/mv/src/mv.rs b/src/uu/mv/src/mv.rs index 5f825113b..e2e2352a0 100644 --- a/src/uu/mv/src/mv.rs +++ b/src/uu/mv/src/mv.rs @@ -44,13 +44,10 @@ pub enum OverwriteMode { static ABOUT: &str = "Move SOURCE to DEST, or multiple SOURCE(s) to DIRECTORY."; static LONG_HELP: &str = ""; -static OPT_BACKUP: &str = "backup"; -static OPT_BACKUP_NO_ARG: &str = "b"; static OPT_FORCE: &str = "force"; static OPT_INTERACTIVE: &str = "interactive"; static OPT_NO_CLOBBER: &str = "no-clobber"; static OPT_STRIP_TRAILING_SLASHES: &str = "strip-trailing-slashes"; -static OPT_SUFFIX: &str = "suffix"; static OPT_TARGET_DIRECTORY: &str = "target-directory"; static OPT_NO_TARGET_DIRECTORY: &str = "no-target-directory"; static OPT_UPDATE: &str = "update"; @@ -85,14 +82,9 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .unwrap_or_default(); let overwrite_mode = determine_overwrite_mode(&matches); - let backup_mode = backup_control::determine_backup_mode( - matches.is_present(OPT_BACKUP_NO_ARG), - matches.is_present(OPT_BACKUP), - matches.value_of(OPT_BACKUP), - ); - let backup_mode = match backup_mode { - Err(err) => { - show_usage_error!("{}", err); + let backup_mode = match backup_control::determine_backup_mode(&matches) { + Err(e) => { + show!(e); return 1; } Ok(mode) => mode, @@ -103,7 +95,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { return 1; } - let backup_suffix = backup_control::determine_backup_suffix(matches.value_of(OPT_SUFFIX)); + let backup_suffix = backup_control::determine_backup_suffix(&matches); let behavior = Behavior { overwrite: overwrite_mode, @@ -137,18 +129,10 @@ pub fn uu_app() -> App<'static, 'static> { .version(crate_version!()) .about(ABOUT) .arg( - Arg::with_name(OPT_BACKUP) - .long(OPT_BACKUP) - .help("make a backup of each existing destination file") - .takes_value(true) - .require_equals(true) - .min_values(0) - .value_name("CONTROL") + backup_control::arguments::backup() ) .arg( - Arg::with_name(OPT_BACKUP_NO_ARG) - .short(OPT_BACKUP_NO_ARG) - .help("like --backup but does not accept an argument") + backup_control::arguments::backup_no_args() ) .arg( Arg::with_name(OPT_FORCE) @@ -173,12 +157,7 @@ pub fn uu_app() -> App<'static, 'static> { .help("remove any trailing slashes from each SOURCE argument") ) .arg( - Arg::with_name(OPT_SUFFIX) - .short("S") - .long(OPT_SUFFIX) - .help("override the usual backup suffix") - .takes_value(true) - .value_name("SUFFIX") + backup_control::arguments::suffix() ) .arg( Arg::with_name(OPT_TARGET_DIRECTORY) From c756878b20b97062f2c833eef4e431753a5880c2 Mon Sep 17 00:00:00 2001 From: Andreas Hartmann Date: Mon, 23 Aug 2021 09:13:47 +0200 Subject: [PATCH 081/206] backup_control: Use C-locale quotes in outputs --- src/uucore/src/lib/mods/backup_control.rs | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/uucore/src/lib/mods/backup_control.rs b/src/uucore/src/lib/mods/backup_control.rs index a899c81a0..83617f8ed 100644 --- a/src/uucore/src/lib/mods/backup_control.rs +++ b/src/uucore/src/lib/mods/backup_control.rs @@ -102,10 +102,10 @@ the VERSION_CONTROL environment variable. Here are the values: simple, never always make simple backups"; static VALID_ARGS_HELP: &str = "Valid arguments are: - - ‘none’, ‘off’ - - ‘simple’, ‘never’ - - ‘existing’, ‘nil’ - - ‘numbered’, ‘t’"; + - 'none', 'off' + - 'simple', 'never' + - 'existing', 'nil' + - 'numbered', 't'"; /// Available backup modes. /// @@ -167,12 +167,12 @@ impl Display for BackupError { match self { BE::InvalidArgument(arg, origin) => write!( f, - "invalid argument ‘{}’ for ‘{}’\n{}", + "invalid argument '{}' for '{}'\n{}", arg, origin, VALID_ARGS_HELP ), BE::AmbiguousArgument(arg, origin) => write!( f, - "ambiguous argument ‘{}’ for ‘{}’\n{}", + "ambiguous argument '{}' for '{}'\n{}", arg, origin, VALID_ARGS_HELP ), BE::BackupImpossible() => write!(f, "cannot create backup"), @@ -329,7 +329,7 @@ pub fn determine_backup_mode(matches: &ArgMatches) -> UResult { // Use method to determine the type of backups to make. When this option // is used but method is not specified, then the value of the // VERSION_CONTROL environment variable is used. And if VERSION_CONTROL - // is not set, the default backup type is ‘existing’. + // is not set, the default backup type is 'existing'. if let Some(method) = matches.value_of(arguments::OPT_BACKUP) { // Second argument is for the error string that is returned. match_method(method, "backup type") @@ -516,7 +516,7 @@ mod tests { assert!(result.is_err()); let text = format!("{}", result.unwrap_err()); - assert!(text.contains("invalid argument ‘foobar’ for ‘backup type’")); + assert!(text.contains("invalid argument 'foobar' for 'backup type'")); } // --backup errors on ambiguous argument @@ -529,7 +529,7 @@ mod tests { assert!(result.is_err()); let text = format!("{}", result.unwrap_err()); - assert!(text.contains("ambiguous argument ‘n’ for ‘backup type’")); + assert!(text.contains("ambiguous argument 'n' for 'backup type'")); } // --backup accepts shortened arguments (si for simple) @@ -580,7 +580,7 @@ mod tests { assert!(result.is_err()); let text = format!("{}", result.unwrap_err()); - assert!(text.contains("invalid argument ‘foobar’ for ‘$VERSION_CONTROL’")); + assert!(text.contains("invalid argument 'foobar' for '$VERSION_CONTROL'")); env::remove_var(ENV_VERSION_CONTROL); } @@ -595,7 +595,7 @@ mod tests { assert!(result.is_err()); let text = format!("{}", result.unwrap_err()); - assert!(text.contains("ambiguous argument ‘n’ for ‘$VERSION_CONTROL’")); + assert!(text.contains("ambiguous argument 'n' for '$VERSION_CONTROL'")); env::remove_var(ENV_VERSION_CONTROL); } From d4697d98835834e2ea76bd2443c2bbcb5c56e734 Mon Sep 17 00:00:00 2001 From: 353fc443 <85840256+353fc443@users.noreply.github.com> Date: Wed, 25 Aug 2021 22:02:35 +0530 Subject: [PATCH 082/206] env: added UResult --- src/uu/env/src/env.rs | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/src/uu/env/src/env.rs b/src/uu/env/src/env.rs index 9fcdf84ea..423fc676b 100644 --- a/src/uu/env/src/env.rs +++ b/src/uu/env/src/env.rs @@ -12,6 +12,9 @@ #[macro_use] extern crate clap; +#[macro_use] +extern crate uucore; + use clap::{App, AppSettings, Arg}; use ini::Ini; use std::borrow::Cow; @@ -19,6 +22,7 @@ use std::env; use std::io::{self, Write}; use std::iter::Iterator; use std::process::Command; +use uucore::error::{UResult, USimpleError}; const USAGE: &str = "env [OPTION]... [-] [NAME=VALUE]... [COMMAND [ARG]...]"; const AFTER_HELP: &str = "\ @@ -70,7 +74,7 @@ fn parse_program_opt<'a>(opts: &mut Options<'a>, opt: &'a str) -> Result<(), i32 } } -fn load_config_file(opts: &mut Options) -> Result<(), i32> { +fn load_config_file(opts: &mut Options) -> UResult<()> { // NOTE: config files are parsed using an INI parser b/c it's available and compatible with ".env"-style files // ... * but support for actual INI files, although working, is not intended, nor claimed for &file in &opts.files { @@ -157,7 +161,7 @@ pub fn uu_app() -> App<'static, 'static> { .help("remove variable from the environment")) } -fn run_env(args: impl uucore::Args) -> Result<(), i32> { +fn run_env(args: impl uucore::Args) -> UResult<()> { let app = uu_app(); let matches = app.get_matches_from(args); @@ -188,8 +192,10 @@ fn run_env(args: impl uucore::Args) -> Result<(), i32> { match env::set_current_dir(d) { Ok(()) => d, Err(error) => { - eprintln!("env: cannot change directory to \"{}\": {}", d, error); - return Err(125); + return Err(USimpleError::new( + 125, + format!("env: cannot change directory to \"{}\": {}", d, error), + )); } }; } @@ -253,9 +259,9 @@ fn run_env(args: impl uucore::Args) -> Result<(), i32> { // FIXME: this should just use execvp() (no fork()) on Unix-like systems match Command::new(&*prog).args(args).status() { - Ok(exit) if !exit.success() => return Err(exit.code().unwrap()), - Err(ref err) if err.kind() == io::ErrorKind::NotFound => return Err(127), - Err(_) => return Err(126), + Ok(exit) if !exit.success() => return Err(exit.code().unwrap().into()), + Err(ref err) if err.kind() == io::ErrorKind::NotFound => return Err(127.into()), + Err(_) => return Err(126.into()), Ok(_) => (), } } else { @@ -266,9 +272,7 @@ fn run_env(args: impl uucore::Args) -> Result<(), i32> { Ok(()) } -pub fn uumain(args: impl uucore::Args) -> i32 { - match run_env(args) { - Ok(()) => 0, - Err(code) => code, - } +#[uucore_procs::gen_uumain] +pub fn uumain(args: impl uucore::Args) -> UResult<()> { + run_env(args) } From 48437fc49dd15f8be9b2f282ee50dcaa9f499b34 Mon Sep 17 00:00:00 2001 From: Jan Verbeek Date: Wed, 25 Aug 2021 11:24:00 +0200 Subject: [PATCH 083/206] wc: Optimize, improve correctness - Reuse allocations for read lines - Increase splice size - Check if /dev/null was opened correctly - Do not discard read bytes after I/O error - Add fast line counting with bytecount --- Cargo.lock | 7 ++ src/uu/wc/Cargo.toml | 1 + .../wc/src/{count_bytes.rs => count_fast.rs} | 66 ++++++++++++++----- src/uu/wc/src/countable.rs | 24 ++++--- src/uu/wc/src/wc.rs | 27 ++++---- src/uu/wc/src/word_count.rs | 8 +-- 6 files changed, 88 insertions(+), 45 deletions(-) rename src/uu/wc/src/{count_bytes.rs => count_fast.rs} (63%) diff --git a/Cargo.lock b/Cargo.lock index adc373f85..8a9d0bdff 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -188,6 +188,12 @@ dependencies = [ "utf8-width", ] +[[package]] +name = "bytecount" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72feb31ffc86498dacdbd0fcebb56138e7177a8cc5cea4516031d15ae85a742e" + [[package]] name = "byteorder" version = "1.4.3" @@ -3110,6 +3116,7 @@ dependencies = [ name = "uu_wc" version = "0.0.7" dependencies = [ + "bytecount", "clap", "libc", "nix 0.20.0", diff --git a/src/uu/wc/Cargo.toml b/src/uu/wc/Cargo.toml index 31a7ac7af..49735adf7 100644 --- a/src/uu/wc/Cargo.toml +++ b/src/uu/wc/Cargo.toml @@ -19,6 +19,7 @@ clap = { version = "2.33", features = ["wrap_help"] } uucore = { version=">=0.0.9", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_procs" } thiserror = "1.0" +bytecount = "0.6.2" [target.'cfg(unix)'.dependencies] nix = "0.20" diff --git a/src/uu/wc/src/count_bytes.rs b/src/uu/wc/src/count_fast.rs similarity index 63% rename from src/uu/wc/src/count_bytes.rs rename to src/uu/wc/src/count_fast.rs index 83cc71ac4..b23e9ed8f 100644 --- a/src/uu/wc/src/count_bytes.rs +++ b/src/uu/wc/src/count_fast.rs @@ -1,13 +1,15 @@ +use crate::word_count::WordCount; + use super::{WcResult, WordCountable}; #[cfg(any(target_os = "linux", target_os = "android"))] use std::fs::{File, OpenOptions}; -use std::io::ErrorKind; +use std::io::{ErrorKind, Read}; #[cfg(unix)] use libc::S_IFREG; #[cfg(unix)] -use nix::sys::stat::fstat; +use nix::sys::stat; #[cfg(any(target_os = "linux", target_os = "android"))] use std::os::unix::io::{AsRawFd, FromRawFd, RawFd}; @@ -18,7 +20,8 @@ use nix::fcntl::{splice, SpliceFFlags}; #[cfg(any(target_os = "linux", target_os = "android"))] use nix::unistd::pipe; -const BUF_SIZE: usize = 16384; +const BUF_SIZE: usize = 16 * 1024; +const SPLICE_SIZE: usize = 128 * 1024; /// Splice wrapper which handles short writes #[cfg(any(target_os = "linux", target_os = "android"))] @@ -37,15 +40,24 @@ fn splice_exact(read_fd: RawFd, write_fd: RawFd, num_bytes: usize) -> nix::Resul /// This is a Linux-specific function to count the number of bytes using the /// `splice` system call, which is faster than using `read`. +/// +/// On error it returns the number of bytes it did manage to read, since the +/// caller will fall back to a simpler method. #[inline] #[cfg(any(target_os = "linux", target_os = "android"))] -fn count_bytes_using_splice(fd: RawFd) -> nix::Result { +fn count_bytes_using_splice(fd: RawFd) -> Result { let null_file = OpenOptions::new() .write(true) .open("/dev/null") - .map_err(|_| nix::Error::last())?; + .map_err(|_| 0_usize)?; let null = null_file.as_raw_fd(); - let (pipe_rd, pipe_wr) = pipe()?; + let null_rdev = stat::fstat(null).map_err(|_| 0_usize)?.st_rdev; + if (stat::major(null_rdev), stat::minor(null_rdev)) != (1, 3) { + // This is not a proper /dev/null, writing to it is probably bad + // Bit of an edge case, but it has been known to happen + return Err(0); + } + let (pipe_rd, pipe_wr) = pipe().map_err(|_| 0_usize)?; // Ensure the pipe is closed when the function returns. // SAFETY: The file descriptors do not have other owners. @@ -53,12 +65,16 @@ fn count_bytes_using_splice(fd: RawFd) -> nix::Result { let mut byte_count = 0; loop { - let res = splice(fd, None, pipe_wr, None, BUF_SIZE, SpliceFFlags::empty())?; - if res == 0 { - break; - } - byte_count += res; - splice_exact(pipe_rd, null, res)?; + match splice(fd, None, pipe_wr, None, SPLICE_SIZE, SpliceFFlags::empty()) { + Ok(0) => break, + Ok(res) => { + byte_count += res; + if splice_exact(pipe_rd, null, res).is_err() { + return Err(byte_count); + } + } + Err(_) => return Err(byte_count), + }; } Ok(byte_count) @@ -73,10 +89,12 @@ fn count_bytes_using_splice(fd: RawFd) -> nix::Result { /// other things such as lines and words. #[inline] pub(crate) fn count_bytes_fast(handle: &mut T) -> WcResult { + let mut byte_count = 0; + #[cfg(unix)] { let fd = handle.as_raw_fd(); - if let Ok(stat) = fstat(fd) { + if let Ok(stat) = stat::fstat(fd) { // If the file is regular, then the `st_size` should hold // the file's size in bytes. if (stat.st_mode & S_IFREG) != 0 { @@ -87,8 +105,9 @@ pub(crate) fn count_bytes_fast(handle: &mut T) -> WcResult return Ok(n), + Err(n) => byte_count = n, } } } @@ -97,7 +116,6 @@ pub(crate) fn count_bytes_fast(handle: &mut T) -> WcResult return Ok(byte_count), @@ -109,3 +127,19 @@ pub(crate) fn count_bytes_fast(handle: &mut T) -> WcResult(handle: &mut R) -> WcResult { + let mut total = WordCount::default(); + let mut buf = [0; BUF_SIZE]; + loop { + match handle.read(&mut buf) { + Ok(0) => return Ok(total), + Ok(n) => { + total.bytes += n; + total.lines += bytecount::count(&buf[..n], b'\n'); + } + Err(ref e) if e.kind() == ErrorKind::Interrupted => continue, + Err(e) => return Err(e.into()), + } + } +} diff --git a/src/uu/wc/src/countable.rs b/src/uu/wc/src/countable.rs index 3da910a03..098c451c7 100644 --- a/src/uu/wc/src/countable.rs +++ b/src/uu/wc/src/countable.rs @@ -28,7 +28,7 @@ impl WordCountable for StdinLock<'_> { where Self: Sized, { - Lines { buf: self } + Lines::new(self) } } impl WordCountable for File { @@ -38,9 +38,7 @@ impl WordCountable for File { where Self: Sized, { - Lines { - buf: BufReader::new(self), - } + Lines::new(BufReader::new(self)) } } @@ -53,19 +51,25 @@ impl WordCountable for File { /// [`io::Lines`]:: io::Lines pub struct Lines { buf: B, + line: Vec, } -impl Iterator for Lines { - type Item = io::Result>; +impl Lines { + fn new(reader: B) -> Self { + Lines { + buf: reader, + line: Vec::new(), + } + } - fn next(&mut self) -> Option { - let mut line = Vec::new(); + pub fn next(&mut self) -> Option> { + self.line.clear(); // reading from a TTY seems to raise a condition on, rather than return Some(0) like a file. // hence the option wrapped in a result here - match self.buf.read_until(b'\n', &mut line) { + match self.buf.read_until(b'\n', &mut self.line) { Ok(0) => None, - Ok(_n) => Some(Ok(line)), + Ok(_n) => Some(Ok(&self.line)), Err(e) => Some(Err(e)), } } diff --git a/src/uu/wc/src/wc.rs b/src/uu/wc/src/wc.rs index d77cd6b4b..68fd23fb4 100644 --- a/src/uu/wc/src/wc.rs +++ b/src/uu/wc/src/wc.rs @@ -8,10 +8,10 @@ #[macro_use] extern crate uucore; -mod count_bytes; +mod count_fast; mod countable; mod word_count; -use count_bytes::count_bytes_fast; +use count_fast::{count_bytes_and_lines_fast, count_bytes_fast}; use countable::WordCountable; use word_count::{TitledWordCount, WordCount}; @@ -220,19 +220,20 @@ fn word_count_from_reader( // we do not need to decode the byte stream if we're only counting bytes/newlines let decode_chars = settings.show_chars || settings.show_words || settings.show_max_line_length; + if !decode_chars { + return count_bytes_and_lines_fast(&mut reader); + } + // Sum the WordCount for each line. Show a warning for each line // that results in an IO error when trying to read it. - let total = reader - .lines() - .filter_map(|res| match res { - Ok(line) => Some(line), - Err(e) => { - show_warning!("Error while reading {}: {}", path, e); - None - } - }) - .map(|line| WordCount::from_line(&line, decode_chars)) - .sum(); + let mut lines = reader.lines(); + let mut total = WordCount::default(); + while let Some(res) = lines.next() { + match res { + Ok(line) => total += WordCount::from_line(line), + Err(e) => show_warning!("Error while reading {}: {}", path, e), + } + } Ok(total) } diff --git a/src/uu/wc/src/word_count.rs b/src/uu/wc/src/word_count.rs index bdb510f58..848e64b98 100644 --- a/src/uu/wc/src/word_count.rs +++ b/src/uu/wc/src/word_count.rs @@ -74,15 +74,11 @@ impl WordCount { /// fields will be set to 0. If it is `true`, this function will /// attempt to decode the bytes first as UTF-8, and failing that, /// as ASCII. - pub fn from_line(line: &[u8], decode_chars: bool) -> WordCount { + pub fn from_line(line: &[u8]) -> WordCount { // GNU 'wc' only counts lines that end in LF as lines let lines = (*line.last().unwrap() == LF) as usize; let bytes = line.len(); - let (words, chars) = if decode_chars { - WordCount::word_and_char_count(line) - } else { - (0, 0) - }; + let (words, chars) = WordCount::word_and_char_count(line); // -L is a GNU 'wc' extension so same behavior on LF let max_line_length = if chars > 0 { chars - lines } else { 0 }; WordCount { From 6f7d740592111d5f558a841f6c5d1fdb6c0af03f Mon Sep 17 00:00:00 2001 From: Jan Verbeek Date: Wed, 25 Aug 2021 13:26:44 +0200 Subject: [PATCH 084/206] wc: Do a chunked read with proper UTF-8 handling This brings the results mostly in line with GNU wc and solves nasty behavior with long lines. --- Cargo.lock | 8 +++ src/uu/wc/Cargo.toml | 2 + src/uu/wc/src/countable.rs | 53 +++------------- src/uu/wc/src/wc.rs | 60 ++++++++++++++++--- src/uu/wc/src/word_count.rs | 80 ------------------------- tests/by-util/test_wc.rs | 15 +++-- tests/fixtures/wc/UTF_8_test.txt | Bin 22781 -> 23025 bytes tests/fixtures/wc/UTF_8_weirdchars.txt | 25 ++++++++ 8 files changed, 105 insertions(+), 138 deletions(-) create mode 100644 tests/fixtures/wc/UTF_8_weirdchars.txt diff --git a/Cargo.lock b/Cargo.lock index 8a9d0bdff..4d51b124d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2046,6 +2046,12 @@ dependencies = [ "log", ] +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + [[package]] name = "utf8-width" version = "0.1.5" @@ -3121,6 +3127,8 @@ dependencies = [ "libc", "nix 0.20.0", "thiserror", + "unicode-width", + "utf-8", "uucore", "uucore_procs", ] diff --git a/src/uu/wc/Cargo.toml b/src/uu/wc/Cargo.toml index 49735adf7..1fdaa02f7 100644 --- a/src/uu/wc/Cargo.toml +++ b/src/uu/wc/Cargo.toml @@ -20,6 +20,8 @@ uucore = { version=">=0.0.9", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_procs" } thiserror = "1.0" bytecount = "0.6.2" +utf-8 = "0.7.6" +unicode-width = "0.1.8" [target.'cfg(unix)'.dependencies] nix = "0.20" diff --git a/src/uu/wc/src/countable.rs b/src/uu/wc/src/countable.rs index 098c451c7..a14623559 100644 --- a/src/uu/wc/src/countable.rs +++ b/src/uu/wc/src/countable.rs @@ -4,7 +4,7 @@ //! for some common file-like objects. Use the [`WordCountable::lines`] //! method to get an iterator over lines of a file-like object. use std::fs::File; -use std::io::{self, BufRead, BufReader, Read, StdinLock}; +use std::io::{BufRead, BufReader, Read, StdinLock}; #[cfg(unix)] use std::os::unix::io::AsRawFd; @@ -12,65 +12,26 @@ use std::os::unix::io::AsRawFd; #[cfg(unix)] pub trait WordCountable: AsRawFd + Read { type Buffered: BufRead; - fn lines(self) -> Lines; + fn buffered(self) -> Self::Buffered; } #[cfg(not(unix))] pub trait WordCountable: Read { type Buffered: BufRead; - fn lines(self) -> Lines; + fn buffered(self) -> Self::Buffered; } impl WordCountable for StdinLock<'_> { type Buffered = Self; - fn lines(self) -> Lines - where - Self: Sized, - { - Lines::new(self) + fn buffered(self) -> Self::Buffered { + self } } impl WordCountable for File { type Buffered = BufReader; - fn lines(self) -> Lines - where - Self: Sized, - { - Lines::new(BufReader::new(self)) - } -} - -/// An iterator over the lines of an instance of `BufRead`. -/// -/// Similar to [`io::Lines`] but yields each line as a `Vec` and -/// includes the newline character (`\n`, the `0xA` byte) that -/// terminates the line. -/// -/// [`io::Lines`]:: io::Lines -pub struct Lines { - buf: B, - line: Vec, -} - -impl Lines { - fn new(reader: B) -> Self { - Lines { - buf: reader, - line: Vec::new(), - } - } - - pub fn next(&mut self) -> Option> { - self.line.clear(); - - // reading from a TTY seems to raise a condition on, rather than return Some(0) like a file. - // hence the option wrapped in a result here - match self.buf.read_until(b'\n', &mut self.line) { - Ok(0) => None, - Ok(_n) => Some(Ok(&self.line)), - Err(e) => Some(Err(e)), - } + fn buffered(self) -> Self::Buffered { + BufReader::new(self) } } diff --git a/src/uu/wc/src/wc.rs b/src/uu/wc/src/wc.rs index 68fd23fb4..5b0ae0a0a 100644 --- a/src/uu/wc/src/wc.rs +++ b/src/uu/wc/src/wc.rs @@ -13,11 +13,14 @@ mod countable; mod word_count; use count_fast::{count_bytes_and_lines_fast, count_bytes_fast}; use countable::WordCountable; +use unicode_width::UnicodeWidthChar; +use utf8::{BufReadDecoder, BufReadDecoderError}; use word_count::{TitledWordCount, WordCount}; use clap::{crate_version, App, Arg, ArgMatches}; use thiserror::Error; +use std::cmp::max; use std::fs::{self, File}; use std::io::{self, ErrorKind, Write}; use std::path::Path; @@ -224,16 +227,59 @@ fn word_count_from_reader( return count_bytes_and_lines_fast(&mut reader); } - // Sum the WordCount for each line. Show a warning for each line - // that results in an IO error when trying to read it. - let mut lines = reader.lines(); let mut total = WordCount::default(); - while let Some(res) = lines.next() { - match res { - Ok(line) => total += WordCount::from_line(line), - Err(e) => show_warning!("Error while reading {}: {}", path, e), + let mut reader = BufReadDecoder::new(reader.buffered()); + let mut in_word = false; + let mut current_len = 0; + + while let Some(chunk) = reader.next_strict() { + match chunk { + Ok(text) => { + for ch in text.chars() { + if ch.is_whitespace() { + in_word = false; + } else if ch.is_ascii_control() { + // These count as characters but do not affect the word state + } else if !in_word { + in_word = true; + total.words += 1; + } + match ch { + '\n' => { + total.max_line_length = max(current_len, total.max_line_length); + current_len = 0; + total.lines += 1; + } + // '\x0c' = '\f' + '\r' | '\x0c' => { + total.max_line_length = max(current_len, total.max_line_length); + current_len = 0; + } + '\t' => { + current_len -= current_len % 8; + current_len += 8; + } + _ => { + current_len += ch.width().unwrap_or(0); + } + } + total.chars += 1; + } + total.bytes += text.len(); + } + Err(BufReadDecoderError::InvalidByteSequence(bytes)) => { + // GNU wc treats invalid data as neither word nor char nor whitespace, + // so no other counters are affected + total.bytes += bytes.len(); + } + Err(BufReadDecoderError::Io(e)) => { + show_warning!("Error while reading {}: {}", path, e); + } } } + + total.max_line_length = max(current_len, total.max_line_length); + Ok(total) } diff --git a/src/uu/wc/src/word_count.rs b/src/uu/wc/src/word_count.rs index 848e64b98..ac9de390c 100644 --- a/src/uu/wc/src/word_count.rs +++ b/src/uu/wc/src/word_count.rs @@ -1,19 +1,5 @@ use std::cmp::max; -use std::iter::Sum; use std::ops::{Add, AddAssign}; -use std::str::from_utf8; - -const CR: u8 = b'\r'; -const LF: u8 = b'\n'; -const SPACE: u8 = b' '; -const TAB: u8 = b'\t'; -const SYN: u8 = 0x16_u8; -const FF: u8 = 0x0C_u8; - -#[inline(always)] -fn is_word_separator(byte: u8) -> bool { - byte == SPACE || byte == TAB || byte == CR || byte == SYN || byte == FF -} #[derive(Debug, Default, Copy, Clone)] pub struct WordCount { @@ -44,76 +30,10 @@ impl AddAssign for WordCount { } } -impl Sum for WordCount { - fn sum(iter: I) -> WordCount - where - I: Iterator, - { - iter.fold(WordCount::default(), |acc, x| acc + x) - } -} - impl WordCount { - /// Count the characters and whitespace-separated words in the given bytes. - /// - /// `line` is a slice of bytes that will be decoded as ASCII characters. - fn ascii_word_and_char_count(line: &[u8]) -> (usize, usize) { - let word_count = line.split(|&x| is_word_separator(x)).count(); - let char_count = line.iter().filter(|c| c.is_ascii()).count(); - (word_count, char_count) - } - - /// Create a [`WordCount`] from a sequence of bytes representing a line. - /// - /// If the last byte of `line` encodes a newline character (`\n`), - /// then the [`lines`] field will be set to 1. Otherwise, it will - /// be set to 0. The [`bytes`] field is simply the length of - /// `line`. - /// - /// If `decode_chars` is `false`, the [`chars`] and [`words`] - /// fields will be set to 0. If it is `true`, this function will - /// attempt to decode the bytes first as UTF-8, and failing that, - /// as ASCII. - pub fn from_line(line: &[u8]) -> WordCount { - // GNU 'wc' only counts lines that end in LF as lines - let lines = (*line.last().unwrap() == LF) as usize; - let bytes = line.len(); - let (words, chars) = WordCount::word_and_char_count(line); - // -L is a GNU 'wc' extension so same behavior on LF - let max_line_length = if chars > 0 { chars - lines } else { 0 }; - WordCount { - bytes, - chars, - lines, - words, - max_line_length, - } - } - - /// Count the UTF-8 characters and words in the given string slice. - /// - /// `s` is a string slice that is assumed to be a UTF-8 string. - fn utf8_word_and_char_count(s: &str) -> (usize, usize) { - let word_count = s.split_whitespace().count(); - let char_count = s.chars().count(); - (word_count, char_count) - } - pub fn with_title(self, title: Option<&str>) -> TitledWordCount { TitledWordCount { title, count: self } } - - /// Count the characters and words in the given slice of bytes. - /// - /// `line` is a slice of bytes that will be decoded as UTF-8 - /// characters, or if that fails, as ASCII characters. - fn word_and_char_count(line: &[u8]) -> (usize, usize) { - // try and convert the bytes to UTF-8 first - match from_utf8(line) { - Ok(s) => WordCount::utf8_word_and_char_count(s), - Err(..) => WordCount::ascii_word_and_char_count(line), - } - } } /// This struct supplements the actual word count with an optional title that is diff --git a/tests/by-util/test_wc.rs b/tests/by-util/test_wc.rs index 88c65c997..1c6625ab4 100644 --- a/tests/by-util/test_wc.rs +++ b/tests/by-util/test_wc.rs @@ -53,11 +53,16 @@ fn test_utf8() { .args(&["-lwmcL"]) .pipe_in_fixture("UTF_8_test.txt") .run() - .stdout_is(" 300 4969 22781 22213 79\n"); - // GNU returns " 300 2086 22219 22781 79" - // - // TODO: we should fix the word, character, and byte count to - // match the behavior of GNU wc + .stdout_is(" 303 2119 23025 22457 79\n"); +} + +#[test] +fn test_utf8_extra() { + new_ucmd!() + .arg("-lwmcL") + .pipe_in_fixture("UTF_8_weirdchars.txt") + .run() + .stdout_is(" 25 87 513 442 48\n"); } #[test] diff --git a/tests/fixtures/wc/UTF_8_test.txt b/tests/fixtures/wc/UTF_8_test.txt index a5b5d50e6b61eb9a3b751b3954f83e61bb59db9b..cd0474c82c04fd18c01edf17d80374e9d483709b 100644 GIT binary patch delta 307 zcmeynk@4eZ#tj96j1`j$1Vb1rCI@N@Pu{>QQD4IqU0Pa_nNzHgmtT@k*tdNpfoLrPzkeHWTsgPN$keHmDT2PXhl#{BXP@J!zUZI{^tdNmd z=AWvNl$x5SkeOGUT2zvnqEM2rP+XFklcSKEn4PMi05-5BBUPa&wYW5=q*x&{B{i=k zGdVE_q%yy>NFg(~ASX39HLoPGBr`v6a$>L&W9(!>{@~5dLOU55D<(7QYff$uW}B=n uqQY1)`JlbpDF-d@J@WiaX*|_Agac+AHoHx6cyuPnp`NTHMw3mWb*=vKt}+@F(E7f diff --git a/tests/fixtures/wc/UTF_8_weirdchars.txt b/tests/fixtures/wc/UTF_8_weirdchars.txt new file mode 100644 index 000000000..0c7670f5e --- /dev/null +++ b/tests/fixtures/wc/UTF_8_weirdchars.txt @@ -0,0 +1,25 @@ +zero-width space inbetween these: x​x +and inbetween two spaces: [ ​ ] +and at the end of the line: ​ + +non-breaking space: x x [   ]   + +simple unicode: xµx [ µ ] µ + +wide: xwx [ w ] w + +simple emoji: x👩x [ 👩 ] 👩 + +complex emoji: x👩‍🔬x [ 👩‍🔬 ] 👩‍🔬 + +Hello, world! + +line feed: x x [ ] + +vertical tab: x x [ ] + +horizontal tab: x x [ ] +this should be the longest line: +1234567 12345678 123456781234567812345678 + +Control character: xx [  ]  From 35793fc2607e504d21235cf78d903e29d831bc0c Mon Sep 17 00:00:00 2001 From: Jan Verbeek Date: Wed, 25 Aug 2021 14:09:24 +0200 Subject: [PATCH 085/206] wc: Accept badly-encoded filenames --- src/uu/wc/src/wc.rs | 33 ++++++++++++++++----------------- src/uu/wc/src/word_count.rs | 5 +++-- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/uu/wc/src/wc.rs b/src/uu/wc/src/wc.rs index 5b0ae0a0a..5832db25f 100644 --- a/src/uu/wc/src/wc.rs +++ b/src/uu/wc/src/wc.rs @@ -23,7 +23,7 @@ use thiserror::Error; use std::cmp::max; use std::fs::{self, File}; use std::io::{self, ErrorKind, Write}; -use std::path::Path; +use std::path::{Path, PathBuf}; /// The minimum character width for formatting counts when reading from stdin. const MINIMUM_WIDTH: usize = 7; @@ -33,7 +33,7 @@ pub enum WcError { #[error("{0}")] Io(#[from] io::Error), #[error("Expected a file, found directory {0}")] - IsDirectory(String), + IsDirectory(PathBuf), } type WcResult = Result; @@ -117,7 +117,7 @@ enum StdinKind { /// Supported inputs. enum Input { /// A regular file. - Path(String), + Path(PathBuf), /// Standard input. Stdin(StdinKind), @@ -125,10 +125,10 @@ enum Input { impl Input { /// Converts input to title that appears in stats. - fn to_title(&self) -> Option<&str> { + fn to_title(&self) -> Option<&Path> { match self { Input::Path(path) => Some(path), - Input::Stdin(StdinKind::Explicit) => Some("-"), + Input::Stdin(StdinKind::Explicit) => Some("-".as_ref()), Input::Stdin(StdinKind::Implicit) => None, } } @@ -140,13 +140,13 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let matches = uu_app().usage(&usage[..]).get_matches_from(args); let mut inputs: Vec = matches - .values_of(ARG_FILES) + .values_of_os(ARG_FILES) .map(|v| { v.map(|i| { if i == "-" { Input::Stdin(StdinKind::Explicit) } else { - Input::Path(ToString::to_string(i)) + Input::Path(i.into()) } }) .collect() @@ -206,7 +206,7 @@ pub fn uu_app() -> App<'static, 'static> { fn word_count_from_reader( mut reader: T, settings: &Settings, - path: &str, + path: &Path, ) -> WcResult { let only_count_bytes = settings.show_bytes && (!(settings.show_chars @@ -273,7 +273,7 @@ fn word_count_from_reader( total.bytes += bytes.len(); } Err(BufReadDecoderError::Io(e)) => { - show_warning!("Error while reading {}: {}", path, e); + show_warning!("Error while reading {}: {}", path.display(), e); } } } @@ -288,11 +288,10 @@ fn word_count_from_input(input: &Input, settings: &Settings) -> WcResult { let stdin = io::stdin(); let stdin_lock = stdin.lock(); - word_count_from_reader(stdin_lock, settings, "-") + word_count_from_reader(stdin_lock, settings, "-".as_ref()) } Input::Path(path) => { - let path_obj = Path::new(path); - if path_obj.is_dir() { + if path.is_dir() { Err(WcError::IsDirectory(path.to_owned())) } else { let file = File::open(path)?; @@ -314,10 +313,10 @@ fn word_count_from_input(input: &Input, settings: &Settings) -> WcResult { - show_error_custom_description!(path, "Is a directory"); + show_error_custom_description!(path.display(), "Is a directory"); } (Input::Path(path), WcError::Io(e)) if e.kind() == ErrorKind::NotFound => { - show_error_custom_description!(path, "No such file or directory"); + show_error_custom_description!(path.display(), "No such file or directory"); } (_, e) => { show_error!("{}", e); @@ -428,7 +427,7 @@ fn wc(inputs: Vec, settings: &Settings) -> Result<(), u32> { if let Err(err) = print_stats(settings, &result, max_width) { show_warning!( "failed to print result for {}: {}", - result.title.unwrap_or(""), + result.title.unwrap_or_else(|| "".as_ref()).display(), err ); error_count += 1; @@ -436,7 +435,7 @@ fn wc(inputs: Vec, settings: &Settings) -> Result<(), u32> { } if num_inputs > 1 { - let total_result = total_word_count.with_title(Some("total")); + let total_result = total_word_count.with_title(Some("total".as_ref())); if let Err(err) = print_stats(settings, &total_result, max_width) { show_warning!("failed to print total: {}", err); error_count += 1; @@ -506,7 +505,7 @@ fn print_stats( } if let Some(title) = result.title { - writeln!(stdout_lock, " {}", title)?; + writeln!(stdout_lock, " {}", title.display())?; } else { writeln!(stdout_lock)?; } diff --git a/src/uu/wc/src/word_count.rs b/src/uu/wc/src/word_count.rs index ac9de390c..617b811fc 100644 --- a/src/uu/wc/src/word_count.rs +++ b/src/uu/wc/src/word_count.rs @@ -1,5 +1,6 @@ use std::cmp::max; use std::ops::{Add, AddAssign}; +use std::path::Path; #[derive(Debug, Default, Copy, Clone)] pub struct WordCount { @@ -31,7 +32,7 @@ impl AddAssign for WordCount { } impl WordCount { - pub fn with_title(self, title: Option<&str>) -> TitledWordCount { + pub fn with_title(self, title: Option<&Path>) -> TitledWordCount { TitledWordCount { title, count: self } } } @@ -42,6 +43,6 @@ impl WordCount { /// it would result in unnecessary copying of `String`. #[derive(Debug, Default, Clone)] pub struct TitledWordCount<'a> { - pub title: Option<&'a str>, + pub title: Option<&'a Path>, pub count: WordCount, } From 657a04f706e7d58beec9af1dda8b0f859c241bdc Mon Sep 17 00:00:00 2001 From: Jan Verbeek Date: Wed, 25 Aug 2021 14:26:03 +0200 Subject: [PATCH 086/206] wc: Stricter simpler error handling Errors are now always shown with the corresponding filename. Errors are no longer converted into warnings. Previously `wc < .` would cause a loop. Checking whether something is a directory is no longer done in advance. This removes race conditions and the edge case where stdin is a directory. The custom error type is removed because io::Error is now enough. --- Cargo.lock | 1 - src/uu/wc/Cargo.toml | 1 - src/uu/wc/src/count_fast.rs | 12 ++++---- src/uu/wc/src/wc.rs | 55 ++++++++++++------------------------- tests/by-util/test_wc.rs | 4 +-- 5 files changed, 26 insertions(+), 47 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4d51b124d..a9c095ccb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3126,7 +3126,6 @@ dependencies = [ "clap", "libc", "nix 0.20.0", - "thiserror", "unicode-width", "utf-8", "uucore", diff --git a/src/uu/wc/Cargo.toml b/src/uu/wc/Cargo.toml index 1fdaa02f7..80d6014da 100644 --- a/src/uu/wc/Cargo.toml +++ b/src/uu/wc/Cargo.toml @@ -18,7 +18,6 @@ path = "src/wc.rs" clap = { version = "2.33", features = ["wrap_help"] } uucore = { version=">=0.0.9", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_procs" } -thiserror = "1.0" bytecount = "0.6.2" utf-8 = "0.7.6" unicode-width = "0.1.8" diff --git a/src/uu/wc/src/count_fast.rs b/src/uu/wc/src/count_fast.rs index b23e9ed8f..f2081be8c 100644 --- a/src/uu/wc/src/count_fast.rs +++ b/src/uu/wc/src/count_fast.rs @@ -1,10 +1,10 @@ use crate::word_count::WordCount; -use super::{WcResult, WordCountable}; +use super::WordCountable; #[cfg(any(target_os = "linux", target_os = "android"))] use std::fs::{File, OpenOptions}; -use std::io::{ErrorKind, Read}; +use std::io::{self, ErrorKind, Read}; #[cfg(unix)] use libc::S_IFREG; @@ -88,7 +88,7 @@ fn count_bytes_using_splice(fd: RawFd) -> Result { /// 3. Otherwise, we just read normally, but without the overhead of counting /// other things such as lines and words. #[inline] -pub(crate) fn count_bytes_fast(handle: &mut T) -> WcResult { +pub(crate) fn count_bytes_fast(handle: &mut T) -> io::Result { let mut byte_count = 0; #[cfg(unix)] @@ -123,12 +123,12 @@ pub(crate) fn count_bytes_fast(handle: &mut T) -> WcResult continue, - Err(e) => return Err(e.into()), + Err(e) => return Err(e), } } } -pub(crate) fn count_bytes_and_lines_fast(handle: &mut R) -> WcResult { +pub(crate) fn count_bytes_and_lines_fast(handle: &mut R) -> io::Result { let mut total = WordCount::default(); let mut buf = [0; BUF_SIZE]; loop { @@ -139,7 +139,7 @@ pub(crate) fn count_bytes_and_lines_fast(handle: &mut R) -> WcResult continue, - Err(e) => return Err(e.into()), + Err(e) => return Err(e), } } } diff --git a/src/uu/wc/src/wc.rs b/src/uu/wc/src/wc.rs index 5832db25f..396f6eeaf 100644 --- a/src/uu/wc/src/wc.rs +++ b/src/uu/wc/src/wc.rs @@ -18,26 +18,15 @@ use utf8::{BufReadDecoder, BufReadDecoderError}; use word_count::{TitledWordCount, WordCount}; use clap::{crate_version, App, Arg, ArgMatches}; -use thiserror::Error; use std::cmp::max; use std::fs::{self, File}; -use std::io::{self, ErrorKind, Write}; +use std::io::{self, Write}; use std::path::{Path, PathBuf}; /// The minimum character width for formatting counts when reading from stdin. const MINIMUM_WIDTH: usize = 7; -#[derive(Error, Debug)] -pub enum WcError { - #[error("{0}")] - Io(#[from] io::Error), - #[error("Expected a file, found directory {0}")] - IsDirectory(PathBuf), -} - -type WcResult = Result; - struct Settings { show_bytes: bool, show_chars: bool, @@ -132,6 +121,13 @@ impl Input { Input::Stdin(StdinKind::Implicit) => None, } } + + fn path_display(&self) -> std::path::Display<'_> { + match self { + Input::Path(path) => path.display(), + Input::Stdin(_) => Path::display("'standard input'".as_ref()), + } + } } pub fn uumain(args: impl uucore::Args) -> i32 { @@ -206,8 +202,7 @@ pub fn uu_app() -> App<'static, 'static> { fn word_count_from_reader( mut reader: T, settings: &Settings, - path: &Path, -) -> WcResult { +) -> io::Result { let only_count_bytes = settings.show_bytes && (!(settings.show_chars || settings.show_lines @@ -273,7 +268,7 @@ fn word_count_from_reader( total.bytes += bytes.len(); } Err(BufReadDecoderError::Io(e)) => { - show_warning!("Error while reading {}: {}", path.display(), e); + return Err(e); } } } @@ -283,20 +278,16 @@ fn word_count_from_reader( Ok(total) } -fn word_count_from_input(input: &Input, settings: &Settings) -> WcResult { +fn word_count_from_input(input: &Input, settings: &Settings) -> io::Result { match input { Input::Stdin(_) => { let stdin = io::stdin(); let stdin_lock = stdin.lock(); - word_count_from_reader(stdin_lock, settings, "-".as_ref()) + word_count_from_reader(stdin_lock, settings) } Input::Path(path) => { - if path.is_dir() { - Err(WcError::IsDirectory(path.to_owned())) - } else { - let file = File::open(path)?; - word_count_from_reader(file, settings, path) - } + let file = File::open(path)?; + word_count_from_reader(file, settings) } } } @@ -310,18 +301,8 @@ fn word_count_from_input(input: &Input, settings: &Settings) -> WcResult { - show_error_custom_description!(path.display(), "Is a directory"); - } - (Input::Path(path), WcError::Io(e)) if e.kind() == ErrorKind::NotFound => { - show_error_custom_description!(path.display(), "No such file or directory"); - } - (_, e) => { - show_error!("{}", e); - } - }; +fn show_error(input: &Input, err: io::Error) { + show_error!("{}: {}", input.path_display(), err); } /// Compute the number of digits needed to represent any count for this input. @@ -343,7 +324,7 @@ fn show_error(input: &Input, err: WcError) { /// let input = Input::Stdin(StdinKind::Explicit); /// assert_eq!(7, digit_width(input)); /// ``` -fn digit_width(input: &Input) -> WcResult> { +fn digit_width(input: &Input) -> io::Result> { match input { Input::Stdin(_) => Ok(Some(MINIMUM_WIDTH)), Input::Path(filename) => { @@ -453,7 +434,7 @@ fn print_stats( settings: &Settings, result: &TitledWordCount, mut min_width: usize, -) -> WcResult<()> { +) -> io::Result<()> { let stdout = io::stdout(); let mut stdout_lock = stdout.lock(); diff --git a/tests/by-util/test_wc.rs b/tests/by-util/test_wc.rs index 1c6625ab4..8cf10656a 100644 --- a/tests/by-util/test_wc.rs +++ b/tests/by-util/test_wc.rs @@ -212,7 +212,7 @@ fn test_read_from_directory_error() { new_ucmd!() .args(&["."]) .fails() - .stderr_contains(".: Is a directory\n") + .stderr_contains(".: Is a directory") .stdout_is("0 0 0 .\n"); } @@ -222,5 +222,5 @@ fn test_read_from_nonexistent_file() { new_ucmd!() .args(&["bogusfile"]) .fails() - .stderr_contains("bogusfile: No such file or directory\n"); + .stderr_contains("bogusfile: No such file or directory"); } From d0c0564947ad763760b0f07b2d3d337c08e9a2e8 Mon Sep 17 00:00:00 2001 From: Jan Verbeek Date: Wed, 25 Aug 2021 14:40:17 +0200 Subject: [PATCH 087/206] wc: Swap order of characters and bytes in output This way it matches GNU and busybox. --- src/uu/wc/src/wc.rs | 14 +++++++------- tests/by-util/test_wc.rs | 4 ++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/uu/wc/src/wc.rs b/src/uu/wc/src/wc.rs index 396f6eeaf..cc55032a7 100644 --- a/src/uu/wc/src/wc.rs +++ b/src/uu/wc/src/wc.rs @@ -460,13 +460,6 @@ fn print_stats( write!(stdout_lock, "{:1$}", result.count.words, min_width)?; is_first = false; } - if settings.show_bytes { - if !is_first { - write!(stdout_lock, " ")?; - } - write!(stdout_lock, "{:1$}", result.count.bytes, min_width)?; - is_first = false; - } if settings.show_chars { if !is_first { write!(stdout_lock, " ")?; @@ -474,6 +467,13 @@ fn print_stats( write!(stdout_lock, "{:1$}", result.count.chars, min_width)?; is_first = false; } + if settings.show_bytes { + if !is_first { + write!(stdout_lock, " ")?; + } + write!(stdout_lock, "{:1$}", result.count.bytes, min_width)?; + is_first = false; + } if settings.show_max_line_length { if !is_first { write!(stdout_lock, " ")?; diff --git a/tests/by-util/test_wc.rs b/tests/by-util/test_wc.rs index 8cf10656a..2f586fe29 100644 --- a/tests/by-util/test_wc.rs +++ b/tests/by-util/test_wc.rs @@ -53,7 +53,7 @@ fn test_utf8() { .args(&["-lwmcL"]) .pipe_in_fixture("UTF_8_test.txt") .run() - .stdout_is(" 303 2119 23025 22457 79\n"); + .stdout_is(" 303 2119 22457 23025 79\n"); } #[test] @@ -62,7 +62,7 @@ fn test_utf8_extra() { .arg("-lwmcL") .pipe_in_fixture("UTF_8_weirdchars.txt") .run() - .stdout_is(" 25 87 513 442 48\n"); + .stdout_is(" 25 87 442 513 48\n"); } #[test] From c16e492cd0cd9790306978d012ea6db2eab968cc Mon Sep 17 00:00:00 2001 From: Jan Verbeek Date: Wed, 25 Aug 2021 15:29:08 +0200 Subject: [PATCH 088/206] wc: Make output width more consistent with GNU --- src/uu/wc/src/wc.rs | 10 +++++----- tests/by-util/test_wc.rs | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/uu/wc/src/wc.rs b/src/uu/wc/src/wc.rs index cc55032a7..b4649ab91 100644 --- a/src/uu/wc/src/wc.rs +++ b/src/uu/wc/src/wc.rs @@ -324,9 +324,9 @@ fn show_error(input: &Input, err: io::Error) { /// let input = Input::Stdin(StdinKind::Explicit); /// assert_eq!(7, digit_width(input)); /// ``` -fn digit_width(input: &Input) -> io::Result> { +fn digit_width(input: &Input) -> io::Result { match input { - Input::Stdin(_) => Ok(Some(MINIMUM_WIDTH)), + Input::Stdin(_) => Ok(MINIMUM_WIDTH), Input::Path(filename) => { let path = Path::new(filename); let metadata = fs::metadata(path)?; @@ -337,9 +337,9 @@ fn digit_width(input: &Input) -> io::Result> { // instead). See GitHub issue #2201. let num_bytes = metadata.len(); let num_digits = num_bytes.to_string().len(); - Ok(Some(num_digits)) + Ok(num_digits) } else { - Ok(None) + Ok(MINIMUM_WIDTH) } } } @@ -377,7 +377,7 @@ fn digit_width(input: &Input) -> io::Result> { fn max_width(inputs: &[Input]) -> usize { let mut result = 1; for input in inputs { - if let Ok(Some(n)) = digit_width(input) { + if let Ok(n) = digit_width(input) { result = result.max(n); } } diff --git a/tests/by-util/test_wc.rs b/tests/by-util/test_wc.rs index 2f586fe29..7274c5888 100644 --- a/tests/by-util/test_wc.rs +++ b/tests/by-util/test_wc.rs @@ -213,7 +213,7 @@ fn test_read_from_directory_error() { .args(&["."]) .fails() .stderr_contains(".: Is a directory") - .stdout_is("0 0 0 .\n"); + .stdout_is(" 0 0 0 .\n"); } /// Test that getting counts from nonexistent file is an error. From 9972cd1327f1af41c7f17f4211a49f9f1e7cc463 Mon Sep 17 00:00:00 2001 From: Jan Verbeek Date: Wed, 25 Aug 2021 15:57:33 +0200 Subject: [PATCH 089/206] wc: Report counts and failures correctly If reading fails midway through then a count should be reported for what could be read. If opening a file fails then no count should be reported. The exit code shouldn't report the number of failures, that's fragile in case of many failures. --- src/uu/wc/src/count_fast.rs | 18 +++++---- src/uu/wc/src/wc.rs | 80 ++++++++++++++++++++++++++----------- 2 files changed, 66 insertions(+), 32 deletions(-) diff --git a/src/uu/wc/src/count_fast.rs b/src/uu/wc/src/count_fast.rs index f2081be8c..deaa890fd 100644 --- a/src/uu/wc/src/count_fast.rs +++ b/src/uu/wc/src/count_fast.rs @@ -88,7 +88,7 @@ fn count_bytes_using_splice(fd: RawFd) -> Result { /// 3. Otherwise, we just read normally, but without the overhead of counting /// other things such as lines and words. #[inline] -pub(crate) fn count_bytes_fast(handle: &mut T) -> io::Result { +pub(crate) fn count_bytes_fast(handle: &mut T) -> (usize, Option) { let mut byte_count = 0; #[cfg(unix)] @@ -98,7 +98,7 @@ pub(crate) fn count_bytes_fast(handle: &mut T) -> io::Result(handle: &mut T) -> io::Result return Ok(n), + Ok(n) => return (n, None), Err(n) => byte_count = n, } } @@ -118,28 +118,30 @@ pub(crate) fn count_bytes_fast(handle: &mut T) -> io::Result return Ok(byte_count), + Ok(0) => return (byte_count, None), Ok(n) => { byte_count += n; } Err(ref e) if e.kind() == ErrorKind::Interrupted => continue, - Err(e) => return Err(e), + Err(e) => return (byte_count, Some(e)), } } } -pub(crate) fn count_bytes_and_lines_fast(handle: &mut R) -> io::Result { +pub(crate) fn count_bytes_and_lines_fast( + handle: &mut R, +) -> (WordCount, Option) { let mut total = WordCount::default(); let mut buf = [0; BUF_SIZE]; loop { match handle.read(&mut buf) { - Ok(0) => return Ok(total), + Ok(0) => return (total, None), Ok(n) => { total.bytes += n; total.lines += bytecount::count(&buf[..n], b'\n'); } Err(ref e) if e.kind() == ErrorKind::Interrupted => continue, - Err(e) => return Err(e), + Err(e) => return (total, Some(e)), } } } diff --git a/src/uu/wc/src/wc.rs b/src/uu/wc/src/wc.rs index b4649ab91..8f219a1e9 100644 --- a/src/uu/wc/src/wc.rs +++ b/src/uu/wc/src/wc.rs @@ -202,17 +202,21 @@ pub fn uu_app() -> App<'static, 'static> { fn word_count_from_reader( mut reader: T, settings: &Settings, -) -> io::Result { +) -> (WordCount, Option) { let only_count_bytes = settings.show_bytes && (!(settings.show_chars || settings.show_lines || settings.show_max_line_length || settings.show_words)); if only_count_bytes { - return Ok(WordCount { - bytes: count_bytes_fast(&mut reader)?, - ..WordCount::default() - }); + let (bytes, error) = count_bytes_fast(&mut reader); + return ( + WordCount { + bytes, + ..WordCount::default() + }, + error, + ); } // we do not need to decode the byte stream if we're only counting bytes/newlines @@ -268,27 +272,47 @@ fn word_count_from_reader( total.bytes += bytes.len(); } Err(BufReadDecoderError::Io(e)) => { - return Err(e); + return (total, Some(e)); } } } total.max_line_length = max(current_len, total.max_line_length); - Ok(total) + (total, None) } -fn word_count_from_input(input: &Input, settings: &Settings) -> io::Result { +enum CountResult { + /// Nothing went wrong. + Success(WordCount), + /// Managed to open but failed to read. + Interrupted(WordCount, io::Error), + /// Didn't even manage to open. + Failure(io::Error), +} + +/// If we fail opening a file we only show the error. If we fail reading it +/// we show a count for what we managed to read. +/// +/// Therefore the reading implementations always return a total and sometimes +/// return an error: (WordCount, Option). +fn word_count_from_input(input: &Input, settings: &Settings) -> CountResult { match input { Input::Stdin(_) => { let stdin = io::stdin(); let stdin_lock = stdin.lock(); - word_count_from_reader(stdin_lock, settings) - } - Input::Path(path) => { - let file = File::open(path)?; - word_count_from_reader(file, settings) + match word_count_from_reader(stdin_lock, settings) { + (total, Some(error)) => CountResult::Interrupted(total, error), + (total, None) => CountResult::Success(total), + } } + Input::Path(path) => match File::open(path) { + Err(error) => CountResult::Failure(error), + Ok(file) => match word_count_from_reader(file, settings) { + (total, Some(error)) => CountResult::Interrupted(total, error), + (total, None) => CountResult::Success(total), + }, + }, } } @@ -390,7 +414,7 @@ fn wc(inputs: Vec, settings: &Settings) -> Result<(), u32> { // The width is the number of digits needed to print the number of // bytes in the largest file. This is true regardless of whether // the `settings` indicate that the bytes will be displayed. - let mut error_count = 0; + let mut failure = false; let max_width = max_width(&inputs); let mut total_word_count = WordCount::default(); @@ -398,11 +422,19 @@ fn wc(inputs: Vec, settings: &Settings) -> Result<(), u32> { let num_inputs = inputs.len(); for input in &inputs { - let word_count = word_count_from_input(input, settings).unwrap_or_else(|err| { - show_error(input, err); - error_count += 1; - WordCount::default() - }); + let word_count = match word_count_from_input(input, settings) { + CountResult::Success(word_count) => word_count, + CountResult::Interrupted(word_count, error) => { + show_error(input, error); + failure = true; + word_count + } + CountResult::Failure(error) => { + show_error(input, error); + failure = true; + continue; + } + }; total_word_count += word_count; let result = word_count.with_title(input.to_title()); if let Err(err) = print_stats(settings, &result, max_width) { @@ -411,7 +443,7 @@ fn wc(inputs: Vec, settings: &Settings) -> Result<(), u32> { result.title.unwrap_or_else(|| "".as_ref()).display(), err ); - error_count += 1; + failure = true; } } @@ -419,14 +451,14 @@ fn wc(inputs: Vec, settings: &Settings) -> Result<(), u32> { let total_result = total_word_count.with_title(Some("total".as_ref())); if let Err(err) = print_stats(settings, &total_result, max_width) { show_warning!("failed to print total: {}", err); - error_count += 1; + failure = true; } } - if error_count == 0 { - Ok(()) + if failure { + Err(1) } else { - Err(error_count) + Ok(()) } } From 0f1e79fe29c6d15c8c6ae8583eaed9cbd60abfed Mon Sep 17 00:00:00 2001 From: Jan Verbeek Date: Wed, 25 Aug 2021 16:39:00 +0200 Subject: [PATCH 090/206] wc: Fix linters --- .vscode/cspell.dictionaries/workspace.wordlist.txt | 1 + src/uu/wc/src/count_fast.rs | 1 + tests/by-util/test_wc.rs | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.vscode/cspell.dictionaries/workspace.wordlist.txt b/.vscode/cspell.dictionaries/workspace.wordlist.txt index bee22c2d0..049025d43 100644 --- a/.vscode/cspell.dictionaries/workspace.wordlist.txt +++ b/.vscode/cspell.dictionaries/workspace.wordlist.txt @@ -9,6 +9,7 @@ aho-corasick backtrace blake2b_simd bstr +bytecount byteorder chacha chrono diff --git a/src/uu/wc/src/count_fast.rs b/src/uu/wc/src/count_fast.rs index deaa890fd..b4078d6be 100644 --- a/src/uu/wc/src/count_fast.rs +++ b/src/uu/wc/src/count_fast.rs @@ -21,6 +21,7 @@ use nix::fcntl::{splice, SpliceFFlags}; use nix::unistd::pipe; const BUF_SIZE: usize = 16 * 1024; +#[cfg(any(target_os = "linux", target_os = "android"))] const SPLICE_SIZE: usize = 128 * 1024; /// Splice wrapper which handles short writes diff --git a/tests/by-util/test_wc.rs b/tests/by-util/test_wc.rs index 7274c5888..c567c0dd5 100644 --- a/tests/by-util/test_wc.rs +++ b/tests/by-util/test_wc.rs @@ -1,6 +1,6 @@ use crate::common::util::*; -// spell-checker:ignore (flags) lwmcL clmwL ; (path) bogusfile emptyfile manyemptylines moby notrailingnewline onelongemptyline onelongword +// spell-checker:ignore (flags) lwmcL clmwL ; (path) bogusfile emptyfile manyemptylines moby notrailingnewline onelongemptyline onelongword weirdchars #[test] fn test_count_bytes_large_stdin() { From 4943517327f8cf003c35de034bc6c8a270f8e575 Mon Sep 17 00:00:00 2001 From: Jan Verbeek Date: Wed, 25 Aug 2021 17:25:40 +0200 Subject: [PATCH 091/206] wc: Adjust expected error messages for Windows --- tests/by-util/test_wc.rs | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/tests/by-util/test_wc.rs b/tests/by-util/test_wc.rs index c567c0dd5..f74cd7b9d 100644 --- a/tests/by-util/test_wc.rs +++ b/tests/by-util/test_wc.rs @@ -205,22 +205,27 @@ fn test_file_bytes_dictate_width() { /// Test that getting counts from a directory is an error. #[test] fn test_read_from_directory_error() { - // TODO To match GNU `wc`, the `stdout` should be: - // - // " 0 0 0 .\n" - // + #[cfg(not(windows))] + const MSG: &str = ".: Is a directory"; + #[cfg(windows)] + const MSG: &str = ".: Access is denied"; new_ucmd!() .args(&["."]) .fails() - .stderr_contains(".: Is a directory") + .stderr_contains(MSG) .stdout_is(" 0 0 0 .\n"); } /// Test that getting counts from nonexistent file is an error. #[test] fn test_read_from_nonexistent_file() { + #[cfg(not(windows))] + const MSG: &str = "bogusfile: No such file or directory"; + #[cfg(windows)] + const MSG: &str = "bogusfile: The system cannot find the file specified"; new_ucmd!() .args(&["bogusfile"]) .fails() - .stderr_contains("bogusfile: No such file or directory"); + .stderr_contains(MSG) + .stdout_is(""); } From f50b0048606ed943344a7058efb49806253b945f Mon Sep 17 00:00:00 2001 From: Jan Verbeek Date: Wed, 25 Aug 2021 17:54:55 +0200 Subject: [PATCH 092/206] wc: Adjust expected report for directories for Windows --- tests/by-util/test_wc.rs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/tests/by-util/test_wc.rs b/tests/by-util/test_wc.rs index f74cd7b9d..eabaf58eb 100644 --- a/tests/by-util/test_wc.rs +++ b/tests/by-util/test_wc.rs @@ -206,14 +206,20 @@ fn test_file_bytes_dictate_width() { #[test] fn test_read_from_directory_error() { #[cfg(not(windows))] - const MSG: &str = ".: Is a directory"; + const STDERR: &str = ".: Is a directory"; #[cfg(windows)] - const MSG: &str = ".: Access is denied"; + const STDERR: &str = ".: Access is denied"; + + #[cfg(not(windows))] + const STDOUT: &str = " 0 0 0 .\n"; + #[cfg(windows)] + const STDOUT: &str = ""; + new_ucmd!() .args(&["."]) .fails() - .stderr_contains(MSG) - .stdout_is(" 0 0 0 .\n"); + .stderr_contains(STDERR) + .stdout_is(STDOUT); } /// Test that getting counts from nonexistent file is an error. From 1358aeecdd79fb2b5a554d2f733288a9a3c72455 Mon Sep 17 00:00:00 2001 From: Jan Verbeek Date: Wed, 25 Aug 2021 18:11:54 +0200 Subject: [PATCH 093/206] wc: Avoid unnecessary work in general loop This gives a big speedup if e.g. only characters are being counted. --- src/uu/wc/src/wc.rs | 60 +++++++++++++++++++++++++-------------------- 1 file changed, 34 insertions(+), 26 deletions(-) diff --git a/src/uu/wc/src/wc.rs b/src/uu/wc/src/wc.rs index 8f219a1e9..01c3d8fdc 100644 --- a/src/uu/wc/src/wc.rs +++ b/src/uu/wc/src/wc.rs @@ -235,34 +235,42 @@ fn word_count_from_reader( match chunk { Ok(text) => { for ch in text.chars() { - if ch.is_whitespace() { - in_word = false; - } else if ch.is_ascii_control() { - // These count as characters but do not affect the word state - } else if !in_word { - in_word = true; - total.words += 1; - } - match ch { - '\n' => { - total.max_line_length = max(current_len, total.max_line_length); - current_len = 0; - total.lines += 1; - } - // '\x0c' = '\f' - '\r' | '\x0c' => { - total.max_line_length = max(current_len, total.max_line_length); - current_len = 0; - } - '\t' => { - current_len -= current_len % 8; - current_len += 8; - } - _ => { - current_len += ch.width().unwrap_or(0); + if settings.show_words { + if ch.is_whitespace() { + in_word = false; + } else if ch.is_ascii_control() { + // These count as characters but do not affect the word state + } else if !in_word { + in_word = true; + total.words += 1; } } - total.chars += 1; + if settings.show_max_line_length { + match ch { + '\n' => { + total.max_line_length = max(current_len, total.max_line_length); + current_len = 0; + } + // '\x0c' = '\f' + '\r' | '\x0c' => { + total.max_line_length = max(current_len, total.max_line_length); + current_len = 0; + } + '\t' => { + current_len -= current_len % 8; + current_len += 8; + } + _ => { + current_len += ch.width().unwrap_or(0); + } + } + } + if settings.show_lines && ch == '\n' { + total.lines += 1; + } + if settings.show_chars { + total.chars += 1; + } } total.bytes += text.len(); } From 94e33c97f363097242e98f537f3aa1acbb61baf3 Mon Sep 17 00:00:00 2001 From: Jan Verbeek Date: Wed, 25 Aug 2021 20:40:30 +0200 Subject: [PATCH 094/206] wc: Add benchmarking documentation --- src/uu/wc/BENCHMARKING.md | 124 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 124 insertions(+) create mode 100644 src/uu/wc/BENCHMARKING.md diff --git a/src/uu/wc/BENCHMARKING.md b/src/uu/wc/BENCHMARKING.md new file mode 100644 index 000000000..b9d8cc22d --- /dev/null +++ b/src/uu/wc/BENCHMARKING.md @@ -0,0 +1,124 @@ +# Benchmarking wc + + + +Much of what makes wc fast is avoiding unnecessary work. It has multiple strategies, depending on which data is requested. + +## Strategies + +### Counting bytes + +In the case of `wc -c` the content of the input doesn't have to be inspected at all, only the size has to be known. That enables a few optimizations. + +#### File size + +If it can, wc reads the file size directly. This is not interesting to benchmark, except to see if it still works. Try `wc -c largefile`. + +#### `splice()` + +On Linux `splice()` is used to get the input's length while discarding it directly. + +The best way I've found to generate a fast input to test `splice()` is to pipe the output of uutils `cat` into it. Note that GNU `cat` is slower and therefore less suitable, and that if a file is given as its input directly (as in `wc -c < largefile`) the first strategy kicks in. Try `uucat somefile | wc -c`. + +### Counting lines + +In the case of `wc -l` or `wc -cl` the input doesn't have to be decoded. It's read in chunks and the `bytecount` crate is used to count the newlines. + +It's useful to vary the line length in the input. GNU wc seems particularly bad at short lines. + +### Processing unicode + +This is the most general strategy, and it's necessary for counting words, characters, and line lengths. Individual steps are still switched on and off depending on what must be reported. + +Try varying which of the `-w`, `-m`, `-l` and `-L` flags are used. (The `-c` flag is unlikely to make a difference.) + +Passing no flags is equivalent to passing `-wcl`. That case should perhaps be given special attention as it's the default. + +## Generating files + +To generate a file with many very short lines, run `yes | head -c50000000 > 25Mshortlines`. + +To get a file with less artificial contents, download a book from Project Gutenberg and concatenate it a lot of times: + +``` +wget https://www.gutenberg.org/files/2701/2701-0.txt -O moby.txt +cat moby.txt moby.txt moby.txt moby.txt > moby4.txt +cat moby4.txt moby4.txt moby4.txt moby4.txt > moby16.txt +cat moby16.txt moby16.txt moby16.txt moby16.txt > moby64.txt +``` + +And get one with lots of unicode too: + +``` +wget https://www.gutenberg.org/files/30613/30613-0.txt -O odyssey.txt +cat odyssey.txt odyssey.txt odyssey.txt odyssey.txt > odyssey4.txt +cat odyssey4.txt odyssey4.txt odyssey4.txt odyssey4.txt > odyssey16.txt +cat odyssey16.txt odyssey16.txt odyssey16.txt odyssey16.txt > odyssey64.txt +cat odyssey64.txt odyssey64.txt odyssey64.txt odyssey64.txt > odyssey256.txt +``` + +Finally, it's interesting to try a binary file. Look for one with `du -sh /usr/bin/* | sort -h`. On my system `/usr/bin/docker` is a good candidate as it's fairly large. + +## Running benchmarks + +Use [`hyperfine`](https://github.com/sharkdp/hyperfine) to compare the performance. For example, `hyperfine 'wc somefile' 'uuwc somefile'`. + +If you want to get fancy and exhaustive, generate a table: + +| | moby64.txt | odyssey256.txt | 25Mshortlines | /usr/bin/docker | +|------------------------|--------------|------------------|-----------------|-------------------| +| `wc ` | 1.3965 | 1.6182 | 5.2967 | 2.2294 | +| `wc -c ` | 0.8134 | 1.2774 | 0.7732 | 0.9106 | +| `uucat | wc -c` | 2.7760 | 2.5565 | 2.3769 | 2.3982 | +| `wc -l ` | 1.1441 | 1.2854 | 2.9681 | 1.1493 | +| `wc -L ` | 2.1087 | 1.2551 | 5.4577 | 2.1490 | +| `wc -m ` | 2.7272 | 2.1704 | 7.3371 | 3.4347 | +| `wc -w ` | 1.9007 | 1.5206 | 4.7851 | 2.8529 | +| `wc -lwcmL ` | 1.1687 | 0.9169 | 4.4092 | 2.0663 | + +Beware that: +- Results are fuzzy and change from run to run +- You'll often want to check versions of uutils wc against each other instead of against GNU +- This takes a lot of time to generate +- This only shows the relative speedup, not the absolute time, which may be misleading if the time is very short + +Created by the following Python script: +```python +import json +import subprocess + +from tabulate import tabulate + +bins = ["wc", "uuwc"] +files = ["moby64.txt", "odyssey256.txt", "25Mshortlines", "/usr/bin/docker"] +cmds = [ + "{cmd} {file}", + "{cmd} -c {file}", + "uucat {file} | {cmd} -c", + "{cmd} -l {file}", + "{cmd} -L {file}", + "{cmd} -m {file}", + "{cmd} -w {file}", + "{cmd} -lwcmL {file}", +] + +table = [] +for cmd in cmds: + row = ["`" + cmd.format(cmd="wc", file="") + "`"] + for file in files: + subprocess.run( + [ + "hyperfine", + cmd.format(cmd=bins[0], file=file), + cmd.format(cmd=bins[1], file=file), + "--export-json=out.json", + ], + check=True, + ) + with open("out.json") as f: + res = json.load(f)["results"] + row.append(round(res[0]["mean"] / res[1]["mean"], 4)) + table.append(row) +print(tabulate(table, [""] + files, tablefmt="github")) +``` +(You may have to adjust the `bins` and `files` variables depending on your setup, and please do add other interesting cases to `cmds`.) From d7ff2ce5add4e30e51d8f1858874bd8241252d0e Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Thu, 26 Aug 2021 11:49:45 +0200 Subject: [PATCH 095/206] doc: Add instructions to run GNU tests --- DEVELOPER_INSTRUCTIONS.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/DEVELOPER_INSTRUCTIONS.md b/DEVELOPER_INSTRUCTIONS.md index b81903edd..6d96c7737 100644 --- a/DEVELOPER_INSTRUCTIONS.md +++ b/DEVELOPER_INSTRUCTIONS.md @@ -9,6 +9,19 @@ The documentation is updated everyday on this repository: https://github.com/uutils/coreutils-docs +Running GNU tests +----------------- + +- Check out https://github.com/coreutils/coreutils next to your fork as gnu +- Check out https://github.com/coreutils/gnulib next to your fork as gnulib +- Rename the checkout of your fork to uutils + +At the end you should have uutils, gnu and gnulib checked out next to each other. + +- Run `cd uutils && ./util/build-gnu.sh && cd ..` to get everything ready (this may take a while) +- Finally, you can run `tests with bash uutils/util/run-gnu-test.sh `. Instead of `` insert the test you want to run, e.g. `tests/misc/wc-proc`. + + Code Coverage Report Generation --------------------------------- From 1ab128fb8d2838d4243cf86dc99f2d22307866ef Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Thu, 26 Aug 2021 12:00:27 +0200 Subject: [PATCH 096/206] add spell checker exception --- DEVELOPER_INSTRUCTIONS.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/DEVELOPER_INSTRUCTIONS.md b/DEVELOPER_INSTRUCTIONS.md index 6d96c7737..027d4dca1 100644 --- a/DEVELOPER_INSTRUCTIONS.md +++ b/DEVELOPER_INSTRUCTIONS.md @@ -12,6 +12,8 @@ https://github.com/uutils/coreutils-docs Running GNU tests ----------------- + + - Check out https://github.com/coreutils/coreutils next to your fork as gnu - Check out https://github.com/coreutils/gnulib next to your fork as gnulib - Rename the checkout of your fork to uutils From 406e60eef2dee2c07a7db4195fbb1271eb4fccae Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Fri, 27 Aug 2021 09:28:43 +0200 Subject: [PATCH 097/206] make build-gnu.sh executable --- util/build-gnu.sh | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 util/build-gnu.sh diff --git a/util/build-gnu.sh b/util/build-gnu.sh old mode 100644 new mode 100755 From d5dd4f6cff943f987f8a2765e6d65033035de9a9 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Fri, 27 Aug 2021 10:57:41 +0200 Subject: [PATCH 098/206] ls: only quote ~ and # when they appear at the start of the name For example, we quote '~a' and '~' but not a~ --- src/uu/ls/src/quoting_style.rs | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/src/uu/ls/src/quoting_style.rs b/src/uu/ls/src/quoting_style.rs index f9ba55f7b..5f421b2ee 100644 --- a/src/uu/ls/src/quoting_style.rs +++ b/src/uu/ls/src/quoting_style.rs @@ -1,6 +1,9 @@ use std::char::from_digit; -const SPECIAL_SHELL_CHARS: &str = "~`#$&*()|[]{};\\'\"<>?! "; +// These are characters with special meaning in the shell (e.g. bash). +// The first const contains characters that only have a special meaning when they appear at the beginning of a name. +const SPECIAL_SHELL_CHARS_START: &[char] = &['~', '#']; +const SPECIAL_SHELL_CHARS: &str = "`$&*()|[]{};\\'\"<>?! "; pub(super) enum QuotingStyle { Shell { @@ -198,6 +201,8 @@ fn shell_without_escape(name: &str, quotes: Quotes, show_control_chars: bool) -> } } } + + must_quote = must_quote || name.starts_with(SPECIAL_SHELL_CHARS_START); (escaped_str, must_quote) } @@ -246,6 +251,7 @@ fn shell_with_escape(name: &str, quotes: Quotes) -> (String, bool) { } } } + must_quote = must_quote || name.starts_with(SPECIAL_SHELL_CHARS_START); (escaped_str, must_quote) } @@ -659,4 +665,29 @@ mod tests { ], ); } + + #[test] + fn test_tilde_and_hash() { + check_names("~", vec![("'~'", "shell"), ("'~'", "shell-escape")]); + check_names( + "~name", + vec![("'~name'", "shell"), ("'~name'", "shell-escape")], + ); + check_names( + "some~name", + vec![("some~name", "shell"), ("some~name", "shell-escape")], + ); + check_names("name~", vec![("name~", "shell"), ("name~", "shell-escape")]); + + check_names("#", vec![("'#'", "shell"), ("'#'", "shell-escape")]); + check_names( + "#name", + vec![("'#name'", "shell"), ("'#name'", "shell-escape")], + ); + check_names( + "some#name", + vec![("some#name", "shell"), ("some#name", "shell-escape")], + ); + check_names("name#", vec![("name#", "shell"), ("name#", "shell-escape")]); + } } From 58ce3d1e825685fefedbc1b754f57bd6b28d5c8d Mon Sep 17 00:00:00 2001 From: Jan Verbeek Date: Thu, 26 Aug 2021 21:04:42 +0200 Subject: [PATCH 099/206] Make scripts executable --- util/GHA-delete-GNU-workflow-logs.sh | 0 util/build-code_coverage.sh | 0 util/compare_gnu_result.py | 0 util/publish.sh | 0 util/run-gnu-test.sh | 0 util/show-code_coverage.sh | 0 util/update-version.sh | 0 7 files changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 util/GHA-delete-GNU-workflow-logs.sh mode change 100644 => 100755 util/build-code_coverage.sh mode change 100644 => 100755 util/compare_gnu_result.py mode change 100644 => 100755 util/publish.sh mode change 100644 => 100755 util/run-gnu-test.sh mode change 100644 => 100755 util/show-code_coverage.sh mode change 100644 => 100755 util/update-version.sh diff --git a/util/GHA-delete-GNU-workflow-logs.sh b/util/GHA-delete-GNU-workflow-logs.sh old mode 100644 new mode 100755 diff --git a/util/build-code_coverage.sh b/util/build-code_coverage.sh old mode 100644 new mode 100755 diff --git a/util/compare_gnu_result.py b/util/compare_gnu_result.py old mode 100644 new mode 100755 diff --git a/util/publish.sh b/util/publish.sh old mode 100644 new mode 100755 diff --git a/util/run-gnu-test.sh b/util/run-gnu-test.sh old mode 100644 new mode 100755 diff --git a/util/show-code_coverage.sh b/util/show-code_coverage.sh old mode 100644 new mode 100755 diff --git a/util/update-version.sh b/util/update-version.sh old mode 100644 new mode 100755 From d285472210f640f7461845fc9a5a4e4fc2f674ae Mon Sep 17 00:00:00 2001 From: Jan Verbeek Date: Thu, 26 Aug 2021 21:18:13 +0200 Subject: [PATCH 100/206] GNU tests: make script runnable from any directory --- util/run-gnu-test.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/util/run-gnu-test.sh b/util/run-gnu-test.sh index 9d51a983e..483fc1be9 100755 --- a/util/run-gnu-test.sh +++ b/util/run-gnu-test.sh @@ -1,5 +1,6 @@ #!/bin/bash # spell-checker:ignore (env/vars) BUILDDIR GNULIB SUBDIRS +cd "$(dirname "${BASH_SOURCE[0]}")/../.." set -e BUILDDIR="${PWD}/uutils/target/release" GNULIB_DIR="${PWD}/gnulib" From 1c0518308301fcf0c2122c3d5441a75e91971e5f Mon Sep 17 00:00:00 2001 From: Jan Verbeek Date: Thu, 26 Aug 2021 22:42:07 +0200 Subject: [PATCH 101/206] Move strip_errno to libcore --- src/uu/sort/src/sort.rs | 31 ++++++++----------------------- src/uucore/src/lib/mods/error.rs | 9 +++++++++ 2 files changed, 17 insertions(+), 23 deletions(-) diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index 9aae23bb8..abba1e63b 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -45,7 +45,7 @@ use std::path::Path; use std::path::PathBuf; use std::str::Utf8Error; use unicode_width::UnicodeWidthStr; -use uucore::error::{set_exit_code, UError, UResult, USimpleError, UUsageError}; +use uucore::error::{set_exit_code, strip_errno, UError, UResult, USimpleError, UUsageError}; use uucore::parse_size::{parse_size, ParseSizeError}; use uucore::version_cmp::version_cmp; use uucore::InvalidEncodingHandling; @@ -197,27 +197,17 @@ impl Display for SortError { Ok(()) } } - SortError::OpenFailed { path, error } => write!( - f, - "open failed: {}: {}", - path, - strip_errno(&error.to_string()) - ), + SortError::OpenFailed { path, error } => { + write!(f, "open failed: {}: {}", path, strip_errno(error)) + } SortError::ParseKeyError { key, msg } => { write!(f, "failed to parse key `{}`: {}", key, msg) } - SortError::ReadFailed { path, error } => write!( - f, - "cannot read: {}: {}", - path, - strip_errno(&error.to_string()) - ), + SortError::ReadFailed { path, error } => { + write!(f, "cannot read: {}: {}", path, strip_errno(error)) + } SortError::OpenTmpFileFailed { error } => { - write!( - f, - "failed to open temporary file: {}", - strip_errno(&error.to_string()) - ) + write!(f, "failed to open temporary file: {}", strip_errno(error)) } SortError::CompressProgExecutionFailed { code } => { write!(f, "couldn't execute compress program: errno {}", code) @@ -1814,11 +1804,6 @@ fn print_sorted<'a, T: Iterator>>( } } -/// Strips the trailing " (os error XX)" from io error strings. -fn strip_errno(err: &str) -> &str { - &err[..err.find(" (os error ").unwrap_or(err.len())] -} - fn open(path: impl AsRef) -> UResult> { let path = path.as_ref(); if path == "-" { diff --git a/src/uucore/src/lib/mods/error.rs b/src/uucore/src/lib/mods/error.rs index c2b3069c8..6af934d3e 100644 --- a/src/uucore/src/lib/mods/error.rs +++ b/src/uucore/src/lib/mods/error.rs @@ -409,6 +409,15 @@ impl Display for UIoError { } } +/// Strip the trailing " (os error XX)" from io error strings. +pub fn strip_errno(err: &std::io::Error) -> String { + let mut msg = err.to_string(); + if let Some(pos) = msg.find(" (os error ") { + msg.drain(pos..); + } + msg +} + /// Enables the conversion from [`std::io::Error`] to [`UError`] and from [`std::io::Result`] to /// [`UResult`]. pub trait FromIo { From afb460f4ca2da65a4308ac4ce9f432e080181908 Mon Sep 17 00:00:00 2001 From: Jan Verbeek Date: Fri, 27 Aug 2021 00:33:04 +0200 Subject: [PATCH 102/206] rmdir: match GNU - Implement all of GNU's fiddly little details - Don't assume Linux for error codes - Accept badly-encoded filenames - Report errors after the fact instead of checking ahead of time - General cleanup rmdir now passes GNU's tests. --- .../workspace.wordlist.txt | 4 + Cargo.lock | 1 + src/uu/rmdir/Cargo.toml | 1 + src/uu/rmdir/src/rmdir.rs | 216 ++++++++++------ tests/by-util/test_rmdir.rs | 238 +++++++++++++----- 5 files changed, 325 insertions(+), 135 deletions(-) diff --git a/.vscode/cspell.dictionaries/workspace.wordlist.txt b/.vscode/cspell.dictionaries/workspace.wordlist.txt index 049025d43..aec4b4a42 100644 --- a/.vscode/cspell.dictionaries/workspace.wordlist.txt +++ b/.vscode/cspell.dictionaries/workspace.wordlist.txt @@ -107,14 +107,18 @@ whoami # * vars/errno errno +EACCES EBADF +EBUSY EEXIST EINVAL ENODATA ENOENT ENOSYS +ENOTEMPTY EOPNOTSUPP EPERM +EROFS # * vars/fcntl F_GETFL diff --git a/Cargo.lock b/Cargo.lock index a9c095ccb..c632db295 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2802,6 +2802,7 @@ name = "uu_rmdir" version = "0.0.7" dependencies = [ "clap", + "libc", "uucore", "uucore_procs", ] diff --git a/src/uu/rmdir/Cargo.toml b/src/uu/rmdir/Cargo.toml index 27d94ec1d..cdb08f908 100644 --- a/src/uu/rmdir/Cargo.toml +++ b/src/uu/rmdir/Cargo.toml @@ -18,6 +18,7 @@ path = "src/rmdir.rs" clap = { version = "2.33", features = ["wrap_help"] } uucore = { version=">=0.0.9", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_procs" } +libc = "0.2.42" [[bin]] name = "rmdir" diff --git a/src/uu/rmdir/src/rmdir.rs b/src/uu/rmdir/src/rmdir.rs index cafd8e982..d8cad0421 100644 --- a/src/uu/rmdir/src/rmdir.rs +++ b/src/uu/rmdir/src/rmdir.rs @@ -11,8 +11,11 @@ extern crate uucore; use clap::{crate_version, App, Arg}; -use std::fs; +use std::fs::{read_dir, remove_dir}; +use std::io; use std::path::Path; +use uucore::error::{set_exit_code, strip_errno, UResult}; +use uucore::util_name; static ABOUT: &str = "Remove the DIRECTORY(ies), if they are empty."; static OPT_IGNORE_FAIL_NON_EMPTY: &str = "ignore-fail-on-non-empty"; @@ -21,35 +24,158 @@ static OPT_VERBOSE: &str = "verbose"; static ARG_DIRS: &str = "dirs"; -#[cfg(unix)] -static ENOTDIR: i32 = 20; -#[cfg(windows)] -static ENOTDIR: i32 = 267; - fn usage() -> String { format!("{0} [OPTION]... DIRECTORY...", 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); - let dirs: Vec = matches - .values_of(ARG_DIRS) - .map(|v| v.map(ToString::to_string).collect()) - .unwrap_or_default(); + let opts = Opts { + ignore: matches.is_present(OPT_IGNORE_FAIL_NON_EMPTY), + parents: matches.is_present(OPT_PARENTS), + verbose: matches.is_present(OPT_VERBOSE), + }; - let ignore = matches.is_present(OPT_IGNORE_FAIL_NON_EMPTY); - let parents = matches.is_present(OPT_PARENTS); - let verbose = matches.is_present(OPT_VERBOSE); + for path in matches + .values_of_os(ARG_DIRS) + .unwrap_or_default() + .map(Path::new) + { + if let Err(error) = remove(path, opts) { + let Error { error, path } = error; - match remove(dirs, ignore, parents, verbose) { - Ok(()) => ( /* pass */ ), - Err(e) => return e, + if opts.ignore && dir_not_empty(&error, path) { + continue; + } + + set_exit_code(1); + + // If `foo` is a symlink to a directory then `rmdir foo/` may give + // a "not a directory" error. This is confusing as `rm foo/` says + // "is a directory". + // This differs from system to system. Some don't give an error. + // Windows simply allows calling RemoveDirectory on symlinks so we + // don't need to worry about it here. + // GNU rmdir seems to print "Symbolic link not followed" if: + // - It has a trailing slash + // - It's a symlink + // - It either points to a directory or dangles + #[cfg(unix)] + { + use std::ffi::OsStr; + use std::os::unix::ffi::OsStrExt; + + fn is_symlink(path: &Path) -> io::Result { + Ok(path.symlink_metadata()?.file_type().is_symlink()) + } + + fn points_to_directory(path: &Path) -> io::Result { + Ok(path.metadata()?.file_type().is_dir()) + } + + let path = path.as_os_str().as_bytes(); + if error.raw_os_error() == Some(libc::ENOTDIR) && path.ends_with(b"/") { + // Strip the trailing slash or .symlink_metadata() will follow the symlink + let path: &Path = OsStr::from_bytes(&path[..path.len() - 1]).as_ref(); + if is_symlink(path).unwrap_or(false) + && points_to_directory(path).unwrap_or(true) + { + show_error!( + "failed to remove '{}/': Symbolic link not followed", + path.display() + ); + continue; + } + } + } + + show_error!( + "failed to remove '{}': {}", + path.display(), + strip_errno(&error) + ); + } } - 0 + Ok(()) +} + +struct Error<'a> { + error: io::Error, + path: &'a Path, +} + +fn remove(mut path: &Path, opts: Opts) -> Result<(), Error<'_>> { + remove_single(path, opts)?; + if opts.parents { + while let Some(new) = path.parent() { + path = new; + if path.as_os_str() == "" { + break; + } + remove_single(path, opts)?; + } + } + Ok(()) +} + +fn remove_single(path: &Path, opts: Opts) -> Result<(), Error<'_>> { + if opts.verbose { + println!("{}: removing directory, '{}'", util_name(), path.display()); + } + remove_dir(path).map_err(|error| Error { error, path }) +} + +// POSIX: https://pubs.opengroup.org/onlinepubs/009696799/functions/rmdir.html +#[cfg(not(windows))] +const NOT_EMPTY_CODES: &[i32] = &[libc::ENOTEMPTY, libc::EEXIST]; + +// 145 is ERROR_DIR_NOT_EMPTY, determined experimentally. +#[cfg(windows)] +const NOT_EMPTY_CODES: &[i32] = &[145]; + +// Other error codes you might get for directories that could be found and are +// not empty. +// This is a subset of the error codes listed in rmdir(2) from the Linux man-pages +// project. Maybe other systems have additional codes that apply? +#[cfg(not(windows))] +const PERHAPS_EMPTY_CODES: &[i32] = &[libc::EACCES, libc::EBUSY, libc::EPERM, libc::EROFS]; + +// Probably incomplete, I can't find a list of possible errors for +// RemoveDirectory anywhere. +#[cfg(windows)] +const PERHAPS_EMPTY_CODES: &[i32] = &[ + 5, // ERROR_ACCESS_DENIED, found experimentally. +]; + +fn dir_not_empty(error: &io::Error, path: &Path) -> bool { + if let Some(code) = error.raw_os_error() { + if NOT_EMPTY_CODES.contains(&code) { + return true; + } + // If --ignore-fail-on-non-empty is used then we want to ignore all errors + // for non-empty directories, even if the error was e.g. because there's + // no permission. So we do an additional check. + if PERHAPS_EMPTY_CODES.contains(&code) { + if let Ok(mut iterator) = read_dir(path) { + if iterator.next().is_some() { + return true; + } + } + } + } + false +} + +#[derive(Clone, Copy, Debug)] +struct Opts { + ignore: bool, + parents: bool, + verbose: bool, } pub fn uu_app() -> App<'static, 'static> { @@ -84,57 +210,3 @@ pub fn uu_app() -> App<'static, 'static> { .required(true), ) } - -fn remove(dirs: Vec, ignore: bool, parents: bool, verbose: bool) -> Result<(), i32> { - let mut r = Ok(()); - - for dir in &dirs { - let path = Path::new(&dir[..]); - r = remove_dir(path, ignore, verbose).and(r); - if parents { - let mut p = path; - while let Some(new_p) = p.parent() { - p = new_p; - match p.as_os_str().to_str() { - None => break, - Some(s) => match s { - "" | "." | "/" => break, - _ => (), - }, - }; - r = remove_dir(p, ignore, verbose).and(r); - } - } - } - - r -} - -fn remove_dir(path: &Path, ignore: bool, verbose: bool) -> Result<(), i32> { - let mut read_dir = fs::read_dir(path).map_err(|e| { - if e.raw_os_error() == Some(ENOTDIR) { - show_error!("failed to remove '{}': Not a directory", path.display()); - } else { - show_error!("reading directory '{}': {}", path.display(), e); - } - 1 - })?; - - let mut r = Ok(()); - - if read_dir.next().is_none() { - match fs::remove_dir(path) { - Err(e) => { - show_error!("removing directory '{}': {}", path.display(), e); - r = Err(1); - } - Ok(_) if verbose => println!("removing directory, '{}'", path.display()), - _ => (), - } - } else if !ignore { - show_error!("failed to remove '{}': Directory not empty", path.display()); - r = Err(1); - } - - r -} diff --git a/tests/by-util/test_rmdir.rs b/tests/by-util/test_rmdir.rs index 4b74b2522..c8f22aa6c 100644 --- a/tests/by-util/test_rmdir.rs +++ b/tests/by-util/test_rmdir.rs @@ -1,126 +1,238 @@ use crate::common::util::*; +const DIR: &str = "dir"; +const DIR_FILE: &str = "dir/file"; +const NESTED_DIR: &str = "dir/ect/ory"; +const NESTED_DIR_FILE: &str = "dir/ect/ory/file"; + +#[cfg(windows)] +const NOT_FOUND: &str = "The system cannot find the file specified."; +#[cfg(not(windows))] +const NOT_FOUND: &str = "No such file or directory"; + +#[cfg(windows)] +const NOT_EMPTY: &str = "The directory is not empty."; +#[cfg(not(windows))] +const NOT_EMPTY: &str = "Directory not empty"; + +#[cfg(windows)] +const NOT_A_DIRECTORY: &str = "The directory name is invalid."; +#[cfg(not(windows))] +const NOT_A_DIRECTORY: &str = "Not a directory"; + #[test] fn test_rmdir_empty_directory_no_parents() { let (at, mut ucmd) = at_and_ucmd!(); - let dir = "test_rmdir_empty_no_parents"; - at.mkdir(dir); - assert!(at.dir_exists(dir)); + at.mkdir(DIR); - ucmd.arg(dir).succeeds().no_stderr(); + ucmd.arg(DIR).succeeds().no_stderr(); - assert!(!at.dir_exists(dir)); + assert!(!at.dir_exists(DIR)); } #[test] fn test_rmdir_empty_directory_with_parents() { let (at, mut ucmd) = at_and_ucmd!(); - let dir = "test_rmdir_empty/with/parents"; - at.mkdir_all(dir); - assert!(at.dir_exists(dir)); + at.mkdir_all(NESTED_DIR); - ucmd.arg("-p").arg(dir).succeeds().no_stderr(); + ucmd.arg("-p").arg(NESTED_DIR).succeeds().no_stderr(); - assert!(!at.dir_exists(dir)); + assert!(!at.dir_exists(NESTED_DIR)); + assert!(!at.dir_exists(DIR)); } #[test] fn test_rmdir_nonempty_directory_no_parents() { let (at, mut ucmd) = at_and_ucmd!(); - let dir = "test_rmdir_nonempty_no_parents"; - let file = "test_rmdir_nonempty_no_parents/foo"; - at.mkdir(dir); - assert!(at.dir_exists(dir)); + at.mkdir(DIR); + at.touch(DIR_FILE); - at.touch(file); - assert!(at.file_exists(file)); + ucmd.arg(DIR) + .fails() + .stderr_is(format!("rmdir: failed to remove 'dir': {}", NOT_EMPTY)); - ucmd.arg(dir).fails().stderr_is( - "rmdir: failed to remove 'test_rmdir_nonempty_no_parents': Directory not \ - empty\n", - ); - - assert!(at.dir_exists(dir)); + assert!(at.dir_exists(DIR)); } #[test] fn test_rmdir_nonempty_directory_with_parents() { let (at, mut ucmd) = at_and_ucmd!(); - let dir = "test_rmdir_nonempty/with/parents"; - let file = "test_rmdir_nonempty/with/parents/foo"; - at.mkdir_all(dir); - assert!(at.dir_exists(dir)); + at.mkdir_all(NESTED_DIR); + at.touch(NESTED_DIR_FILE); - at.touch(file); - assert!(at.file_exists(file)); + ucmd.arg("-p").arg(NESTED_DIR).fails().stderr_is(format!( + "rmdir: failed to remove 'dir/ect/ory': {}", + NOT_EMPTY + )); - ucmd.arg("-p").arg(dir).fails().stderr_is( - "rmdir: failed to remove 'test_rmdir_nonempty/with/parents': Directory not \ - empty\nrmdir: failed to remove 'test_rmdir_nonempty/with': Directory not \ - empty\nrmdir: failed to remove 'test_rmdir_nonempty': Directory not \ - empty\n", - ); - - assert!(at.dir_exists(dir)); + assert!(at.dir_exists(NESTED_DIR)); } #[test] fn test_rmdir_ignore_nonempty_directory_no_parents() { let (at, mut ucmd) = at_and_ucmd!(); - let dir = "test_rmdir_ignore_nonempty_no_parents"; - let file = "test_rmdir_ignore_nonempty_no_parents/foo"; - at.mkdir(dir); - assert!(at.dir_exists(dir)); - - at.touch(file); - assert!(at.file_exists(file)); + at.mkdir(DIR); + at.touch(DIR_FILE); ucmd.arg("--ignore-fail-on-non-empty") - .arg(dir) + .arg(DIR) .succeeds() .no_stderr(); - assert!(at.dir_exists(dir)); + assert!(at.dir_exists(DIR)); } #[test] fn test_rmdir_ignore_nonempty_directory_with_parents() { let (at, mut ucmd) = at_and_ucmd!(); - let dir = "test_rmdir_ignore_nonempty/with/parents"; - let file = "test_rmdir_ignore_nonempty/with/parents/foo"; - at.mkdir_all(dir); - assert!(at.dir_exists(dir)); - - at.touch(file); - assert!(at.file_exists(file)); + at.mkdir_all(NESTED_DIR); + at.touch(NESTED_DIR_FILE); ucmd.arg("--ignore-fail-on-non-empty") .arg("-p") - .arg(dir) + .arg(NESTED_DIR) .succeeds() .no_stderr(); - assert!(at.dir_exists(dir)); + assert!(at.dir_exists(NESTED_DIR)); } #[test] -fn test_rmdir_remove_symlink_match_gnu_error() { +fn test_rmdir_not_a_directory() { let (at, mut ucmd) = at_and_ucmd!(); - let file = "file"; - let fl = "fl"; - at.touch(file); - assert!(at.file_exists(file)); - at.symlink_file(file, fl); - assert!(at.file_exists(fl)); + at.touch("file"); - ucmd.arg("fl/") + ucmd.arg("--ignore-fail-on-non-empty") + .arg("file") .fails() - .stderr_is("rmdir: failed to remove 'fl/': Not a directory"); + .no_stdout() + .stderr_is(format!( + "rmdir: failed to remove 'file': {}", + NOT_A_DIRECTORY + )); +} + +#[test] +fn test_verbose_single() { + let (at, mut ucmd) = at_and_ucmd!(); + + at.mkdir(DIR); + + ucmd.arg("-v") + .arg(DIR) + .succeeds() + .no_stderr() + .stdout_is("rmdir: removing directory, 'dir'\n"); +} + +#[test] +fn test_verbose_multi() { + let (at, mut ucmd) = at_and_ucmd!(); + + at.mkdir(DIR); + + ucmd.arg("-v") + .arg("does_not_exist") + .arg(DIR) + .fails() + .stdout_is( + "rmdir: removing directory, 'does_not_exist'\n\ + rmdir: removing directory, 'dir'\n", + ) + .stderr_is(format!( + "rmdir: failed to remove 'does_not_exist': {}", + NOT_FOUND + )); +} + +#[test] +fn test_verbose_nested_failure() { + let (at, mut ucmd) = at_and_ucmd!(); + + at.mkdir_all(NESTED_DIR); + at.touch("dir/ect/file"); + + ucmd.arg("-pv") + .arg(NESTED_DIR) + .fails() + .stdout_is( + "rmdir: removing directory, 'dir/ect/ory'\n\ + rmdir: removing directory, 'dir/ect'\n", + ) + .stderr_is(format!("rmdir: failed to remove 'dir/ect': {}", NOT_EMPTY)); +} + +#[cfg(unix)] +#[test] +fn test_rmdir_ignore_nonempty_no_permissions() { + use std::fs; + + let (at, mut ucmd) = at_and_ucmd!(); + + // We make the *parent* dir read-only to prevent deleting the dir in it. + at.mkdir_all("dir/ect/ory"); + at.touch("dir/ect/ory/file"); + let dir_ect = at.plus("dir/ect"); + let mut perms = fs::metadata(&dir_ect).unwrap().permissions(); + perms.set_readonly(true); + fs::set_permissions(&dir_ect, perms.clone()).unwrap(); + + // rmdir should now get a permissions error that it interprets as + // a non-empty error. + ucmd.arg("--ignore-fail-on-non-empty") + .arg("dir/ect/ory") + .succeeds() + .no_stderr(); + + assert!(at.dir_exists("dir/ect/ory")); + + // Politely restore permissions for cleanup + perms.set_readonly(false); + fs::set_permissions(&dir_ect, perms).unwrap(); +} + +#[test] +fn test_rmdir_remove_symlink_file() { + let (at, mut ucmd) = at_and_ucmd!(); + + at.touch("file"); + at.symlink_file("file", "fl"); + + ucmd.arg("fl/").fails().stderr_is(format!( + "rmdir: failed to remove 'fl/': {}", + NOT_A_DIRECTORY + )); +} + +// This behavior is known to happen on Linux but not all Unixes +#[cfg(any(target_os = "linux", target_os = "android"))] +#[test] +fn test_rmdir_remove_symlink_dir() { + let (at, mut ucmd) = at_and_ucmd!(); + + at.mkdir("dir"); + at.symlink_dir("dir", "dl"); + + ucmd.arg("dl/") + .fails() + .stderr_is("rmdir: failed to remove 'dl/': Symbolic link not followed"); +} + +#[cfg(any(target_os = "linux", target_os = "android"))] +#[test] +fn test_rmdir_remove_symlink_dangling() { + let (at, mut ucmd) = at_and_ucmd!(); + + at.symlink_dir("dir", "dl"); + + ucmd.arg("dl/") + .fails() + .stderr_is("rmdir: failed to remove 'dl/': Symbolic link not followed"); } From 1df9a1691c8a274f8cae890a2aeb273ba1d49181 Mon Sep 17 00:00:00 2001 From: Jeffrey Finkelstein Date: Fri, 27 Aug 2021 21:25:03 -0400 Subject: [PATCH 103/206] seq: combine first, inc, last parameters into type Combine the `first`, `increment`, and `last` parameters of the `print_seq()` and `print_seq_integers()` functions into a `RangeF64` or `RangeInt` type, respectively. --- src/uu/seq/src/seq.rs | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/src/uu/seq/src/seq.rs b/src/uu/seq/src/seq.rs index 2e700f952..50e30721c 100644 --- a/src/uu/seq/src/seq.rs +++ b/src/uu/seq/src/seq.rs @@ -85,6 +85,16 @@ impl FromStr for Number { } } +/// A range of integers. +/// +/// The elements are (first, increment, last). +type RangeInt = (BigInt, BigInt, BigInt); + +/// A range of f64. +/// +/// The elements are (first, increment, last). +type RangeF64 = (f64, f64, f64); + pub fn uumain(args: impl uucore::Args) -> i32 { let usage = usage(); let matches = uu_app().usage(&usage[..]).get_matches_from(args); @@ -139,9 +149,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { match (first, last, increment) { (Number::BigInt(first), Number::BigInt(last), Number::BigInt(increment)) => { print_seq_integers( - first, - increment, - last, + (first, increment, last), options.separator, options.terminator, options.widths, @@ -149,9 +157,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { ) } (first, last, increment) => print_seq( - first.into_f64(), - increment.into_f64(), - last.into_f64(), + (first.into_f64(), increment.into_f64(), last.into_f64()), largest_dec, options.separator, options.terminator, @@ -208,17 +214,15 @@ fn done_printing(next: &T, increment: &T, last: &T) -> bool } /// Floating point based code path -#[allow(clippy::too_many_arguments)] fn print_seq( - first: f64, - increment: f64, - last: f64, + range: RangeF64, largest_dec: usize, separator: String, terminator: String, pad: bool, padding: usize, ) { + let (first, increment, last) = range; let mut i = 0isize; let mut value = first + i as f64 * increment; while !done_printing(&value, &increment, &last) { @@ -245,14 +249,13 @@ fn print_seq( /// BigInt based code path fn print_seq_integers( - first: BigInt, - increment: BigInt, - last: BigInt, + range: RangeInt, separator: String, terminator: String, pad: bool, padding: usize, ) { + let (first, increment, last) = range; let mut value = first; let mut is_first_iteration = true; while !done_printing(&value, &increment, &last) { From 2c66d9e0a7874b3500325651c1e00c18f9e97aa4 Mon Sep 17 00:00:00 2001 From: Jeffrey Finkelstein Date: Fri, 27 Aug 2021 18:55:02 -0400 Subject: [PATCH 104/206] seq: print negative zero at start of integer seq. --- src/uu/seq/src/seq.rs | 43 +++++++++++++++++++++++++++++++++++++-- tests/by-util/test_seq.rs | 18 ++++++++++++++++ 2 files changed, 59 insertions(+), 2 deletions(-) diff --git a/src/uu/seq/src/seq.rs b/src/uu/seq/src/seq.rs index 50e30721c..f24b8be68 100644 --- a/src/uu/seq/src/seq.rs +++ b/src/uu/seq/src/seq.rs @@ -38,6 +38,8 @@ struct SeqOptions { } enum Number { + /// Negative zero, as if it were an integer. + MinusZero, BigInt(BigInt), F64(f64), } @@ -45,6 +47,7 @@ enum Number { impl Number { fn is_zero(&self) -> bool { match self { + Number::MinusZero => true, Number::BigInt(n) => n.is_zero(), Number::F64(n) => n.is_zero(), } @@ -52,6 +55,7 @@ impl Number { fn into_f64(self) -> f64 { match self { + Number::MinusZero => -0., // BigInt::to_f64() can not return None. Number::BigInt(n) => n.to_f64().unwrap(), Number::F64(n) => n, @@ -67,7 +71,16 @@ impl FromStr for Number { } match s.parse::() { - Ok(n) => Ok(Number::BigInt(n)), + 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)) + } + } Err(_) => match s.parse::() { Ok(value) if value.is_nan() => Err(format!( "invalid 'not-a-number' argument: '{}'\nTry '{} --help' for more information.", @@ -147,6 +160,14 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } match (first, last, increment) { + (Number::MinusZero, Number::BigInt(last), Number::BigInt(increment)) => print_seq_integers( + (BigInt::zero(), increment, last), + options.separator, + options.terminator, + options.widths, + padding, + true, + ), (Number::BigInt(first), Number::BigInt(last), Number::BigInt(increment)) => { print_seq_integers( (first, increment, last), @@ -154,6 +175,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { options.terminator, options.widths, padding, + false, ) } (first, last, increment) => print_seq( @@ -247,13 +269,27 @@ fn print_seq( crash_if_err!(1, stdout().flush()); } -/// BigInt based code path +/// Print an integer sequence. +/// +/// This function prints a sequence of integers defined by `range`, +/// which defines the first integer, last integer, and increment of the +/// range. The `separator` is inserted between each integer and +/// `terminator` is inserted at the end. +/// +/// The `pad` parameter indicates whether to pad numbers to the width +/// given in `padding`. +/// +/// If `is_first_minus_zero` is `true`, then the `first` parameter is +/// printed as if it were negative zero, even though no such number +/// exists as an integer (negative zero only exists for floating point +/// numbers). Only set this to `true` if `first` is actually zero. fn print_seq_integers( range: RangeInt, separator: String, terminator: String, pad: bool, padding: usize, + is_first_minus_zero: bool, ) { let (first, increment, last) = range; let mut value = first; @@ -262,6 +298,9 @@ fn print_seq_integers( if !is_first_iteration { print!("{}", separator); } + if is_first_iteration && is_first_minus_zero { + print!("-"); + } is_first_iteration = false; if pad { print!("{number:>0width$}", number = value, width = padding); diff --git a/tests/by-util/test_seq.rs b/tests/by-util/test_seq.rs index eab7e16ce..48221eaf2 100644 --- a/tests/by-util/test_seq.rs +++ b/tests/by-util/test_seq.rs @@ -140,3 +140,21 @@ fn test_seq_wrong_arg_floats() { fn test_zero_step_floats() { new_ucmd!().args(&["10.0", "0", "32"]).fails(); } + +#[test] +fn test_preserve_negative_zero_start() { + new_ucmd!() + .args(&["-0", "1"]) + .succeeds() + .stdout_is("-0\n1\n") + .no_stderr(); +} + +#[test] +fn test_drop_negative_zero_end() { + new_ucmd!() + .args(&["1", "-1", "-0"]) + .succeeds() + .stdout_is("1\n0\n") + .no_stderr(); +} From 1493027f27b1a89d769ac1eaccee208beced9d10 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sat, 28 Aug 2021 09:55:15 +0200 Subject: [PATCH 105/206] remove the env: --- src/uu/env/src/env.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uu/env/src/env.rs b/src/uu/env/src/env.rs index 423fc676b..baa3ca86c 100644 --- a/src/uu/env/src/env.rs +++ b/src/uu/env/src/env.rs @@ -194,7 +194,7 @@ fn run_env(args: impl uucore::Args) -> UResult<()> { Err(error) => { return Err(USimpleError::new( 125, - format!("env: cannot change directory to \"{}\": {}", d, error), + format!("cannot change directory to \"{}\": {}", d, error), )); } }; From 0525d9bf6ae10a6df95a9ef57b8427ea3113c401 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sat, 28 Aug 2021 11:44:34 +0200 Subject: [PATCH 106/206] Remove useless import of USimpleError --- src/uu/install/src/install.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uu/install/src/install.rs b/src/uu/install/src/install.rs index 548ffec59..b72721d20 100644 --- a/src/uu/install/src/install.rs +++ b/src/uu/install/src/install.rs @@ -17,7 +17,7 @@ use file_diff::diff; use filetime::{set_file_times, FileTime}; use uucore::backup_control::{self, BackupMode}; use uucore::entries::{grp2gid, usr2uid}; -use uucore::error::{FromIo, UError, UIoError, UResult, USimpleError}; +use uucore::error::{FromIo, UError, UIoError, UResult}; use uucore::mode::get_umask; use uucore::perms::{wrap_chown, Verbosity, VerbosityLevel}; From 2c3e401b0b7f156142e0114dba28c7c756d8fc30 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sat, 28 Aug 2021 15:06:44 +0200 Subject: [PATCH 107/206] Remove some warnings on freebsd --- tests/by-util/test_install.rs | 4 +++- tests/by-util/test_touch.rs | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/by-util/test_install.rs b/tests/by-util/test_install.rs index 846dc5836..339a40454 100644 --- a/tests/by-util/test_install.rs +++ b/tests/by-util/test_install.rs @@ -4,7 +4,7 @@ use crate::common::util::*; use filetime::FileTime; use rust_users::*; use std::os::unix::fs::PermissionsExt; -#[cfg(not(windows))] +#[cfg(not(any(windows, target_os = "freebsd")))] use std::process::Command; #[cfg(target_os = "linux")] use std::thread::sleep; @@ -551,7 +551,9 @@ fn test_install_copy_then_compare_file_with_extra_mode() { } const STRIP_TARGET_FILE: &str = "helloworld_installed"; +#[cfg(not(any(windows, target_os = "freebsd")))] const SYMBOL_DUMP_PROGRAM: &str = "objdump"; +#[cfg(not(any(windows, target_os = "freebsd")))] const STRIP_SOURCE_FILE_SYMBOL: &str = "main"; fn strip_source_file() -> &'static str { diff --git a/tests/by-util/test_touch.rs b/tests/by-util/test_touch.rs index c7261fad3..e8a17bbca 100644 --- a/tests/by-util/test_touch.rs +++ b/tests/by-util/test_touch.rs @@ -16,6 +16,7 @@ fn get_file_times(at: &AtPath, path: &str) -> (FileTime, FileTime) { ) } +#[cfg(not(target_os = "freebsd"))] fn get_symlink_times(at: &AtPath, path: &str) -> (FileTime, FileTime) { let m = at.symlink_metadata(path); ( From b4c95d49d83243827f601af6828e8bfad76a7e12 Mon Sep 17 00:00:00 2001 From: Jan Verbeek Date: Sat, 28 Aug 2021 22:31:20 +0200 Subject: [PATCH 108/206] pwd: Properly resolve logical working directory (#2604) * pwd: Properly resolve logical working directory * fixup! pwd: Properly resolve logical working directory * fixup! pwd: Properly resolve logical working directory --- .../cspell.dictionaries/jargon.wordlist.txt | 1 + .../workspace.wordlist.txt | 3 + src/uu/pwd/src/pwd.rs | 170 +++++++++++++++--- tests/by-util/test_pwd.rs | 121 ++++++++++++- tests/common/macros.rs | 6 +- 5 files changed, 270 insertions(+), 31 deletions(-) diff --git a/.vscode/cspell.dictionaries/jargon.wordlist.txt b/.vscode/cspell.dictionaries/jargon.wordlist.txt index 232266427..34abfc511 100644 --- a/.vscode/cspell.dictionaries/jargon.wordlist.txt +++ b/.vscode/cspell.dictionaries/jargon.wordlist.txt @@ -50,6 +50,7 @@ iflags kibi kibibytes lcase +lossily mebi mebibytes mergeable diff --git a/.vscode/cspell.dictionaries/workspace.wordlist.txt b/.vscode/cspell.dictionaries/workspace.wordlist.txt index aec4b4a42..82cbbe15f 100644 --- a/.vscode/cspell.dictionaries/workspace.wordlist.txt +++ b/.vscode/cspell.dictionaries/workspace.wordlist.txt @@ -321,3 +321,6 @@ uucore_procs uumain uutil uutils + +# * function names +getcwd diff --git a/src/uu/pwd/src/pwd.rs b/src/uu/pwd/src/pwd.rs index 75dc637e6..e8f0c0013 100644 --- a/src/uu/pwd/src/pwd.rs +++ b/src/uu/pwd/src/pwd.rs @@ -10,28 +10,115 @@ extern crate uucore; use clap::{crate_version, App, Arg}; use std::env; -use std::io; +use std::io::{self, Write}; use std::path::{Path, PathBuf}; -use uucore::error::{FromIo, UResult, USimpleError}; +use uucore::error::{FromIo, UResult}; static ABOUT: &str = "Display the full filename of the current working directory."; static OPT_LOGICAL: &str = "logical"; static OPT_PHYSICAL: &str = "physical"; -pub fn absolute_path(path: &Path) -> io::Result { - let path_buf = path.canonicalize()?; +fn physical_path() -> io::Result { + // std::env::current_dir() is a thin wrapper around libc's getcwd(). + // On Unix, getcwd() must return the physical path: + // https://pubs.opengroup.org/onlinepubs/9699919799/functions/getcwd.html + #[cfg(unix)] + { + env::current_dir() + } + + // On Windows we have to resolve it. + // On other systems we also resolve it, just in case. + #[cfg(not(unix))] + { + env::current_dir().and_then(|path| path.canonicalize()) + } +} + +fn logical_path() -> io::Result { + // getcwd() on Windows seems to include symlinks, so this is easy. #[cfg(windows)] - let path_buf = Path::new( - path_buf - .as_path() - .to_string_lossy() - .trim_start_matches(r"\\?\"), - ) - .to_path_buf(); + { + env::current_dir() + } - Ok(path_buf) + // If we're not on Windows we do things Unix-style. + // + // Typical Unix-like kernels don't actually keep track of the logical working + // directory. They know the precise directory a process is in, and the getcwd() + // syscall reconstructs a path from that. + // + // The logical working directory is maintained by the shell, in the $PWD + // environment variable. So we check carefully if that variable looks + // reasonable, and if not then we fall back to the physical path. + // + // POSIX: https://pubs.opengroup.org/onlinepubs/9699919799/utilities/pwd.html + #[cfg(not(windows))] + { + fn looks_reasonable(path: &Path) -> bool { + // First, check if it's an absolute path. + if !path.has_root() { + return false; + } + + // Then, make sure there are no . or .. components. + // Path::components() isn't useful here, it normalizes those out. + + // to_string_lossy() may allocate, but that's fine, we call this + // only once per run. It may also lose information, but not any + // information that we need for this check. + if path + .to_string_lossy() + .split(std::path::is_separator) + .any(|piece| piece == "." || piece == "..") + { + return false; + } + + // Finally, check if it matches the directory we're in. + #[cfg(unix)] + { + use std::fs::metadata; + use std::os::unix::fs::MetadataExt; + let path_info = match metadata(path) { + Ok(info) => info, + Err(_) => return false, + }; + let real_info = match metadata(".") { + Ok(info) => info, + Err(_) => return false, + }; + if path_info.dev() != real_info.dev() || path_info.ino() != real_info.ino() { + return false; + } + } + + #[cfg(not(unix))] + { + use std::fs::canonicalize; + let canon_path = match canonicalize(path) { + Ok(path) => path, + Err(_) => return false, + }; + let real_path = match canonicalize(".") { + Ok(path) => path, + Err(_) => return false, + }; + if canon_path != real_path { + return false; + } + } + + true + } + + match env::var_os("PWD").map(PathBuf::from) { + Some(value) if looks_reasonable(&value) => Ok(value), + _ => env::current_dir(), + } + } } fn usage() -> String { @@ -43,24 +130,48 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let usage = usage(); let matches = uu_app().usage(&usage[..]).get_matches_from(args); + let cwd = if matches.is_present(OPT_LOGICAL) { + logical_path() + } else { + physical_path() + } + .map_err_context(|| "failed to get current directory".to_owned())?; - match env::current_dir() { - Ok(logical_path) => { - if matches.is_present(OPT_LOGICAL) { - println!("{}", logical_path.display()); - } else { - let physical_path = absolute_path(&logical_path) - .map_err_context(|| "failed to get absolute path".to_string())?; - println!("{}", physical_path.display()); - } - } - Err(e) => { - return Err(USimpleError::new( - 1, - format!("failed to get current directory {}", e), - )) - } - }; + // \\?\ is a prefix Windows gives to paths under certain circumstances, + // including when canonicalizing them. + // With the right extension trait we can remove it non-lossily, but + // we print it lossily anyway, so no reason to bother. + #[cfg(windows)] + let cwd = cwd + .to_string_lossy() + .strip_prefix(r"\\?\") + .map(Into::into) + .unwrap_or(cwd); + + print_path(&cwd).map_err_context(|| "failed to print current directory".to_owned())?; + + Ok(()) +} + +fn print_path(path: &Path) -> io::Result<()> { + let stdout = io::stdout(); + let mut stdout = stdout.lock(); + + // On Unix we print non-lossily. + #[cfg(unix)] + { + use std::os::unix::ffi::OsStrExt; + stdout.write_all(path.as_os_str().as_bytes())?; + stdout.write_all(b"\n")?; + } + + // On other platforms we potentially mangle it. + // There might be some clever way to do it correctly on Windows, but + // invalid unicode in filenames is rare there. + #[cfg(not(unix))] + { + writeln!(stdout, "{}", path.display())?; + } Ok(()) } @@ -79,6 +190,7 @@ pub fn uu_app() -> App<'static, 'static> { Arg::with_name(OPT_PHYSICAL) .short("P") .long(OPT_PHYSICAL) + .overrides_with(OPT_LOGICAL) .help("avoid all symlinks"), ) } diff --git a/tests/by-util/test_pwd.rs b/tests/by-util/test_pwd.rs index 2779b9e62..bc08ddbb0 100644 --- a/tests/by-util/test_pwd.rs +++ b/tests/by-util/test_pwd.rs @@ -1,9 +1,13 @@ +// spell-checker:ignore (words) symdir somefakedir + +use std::path::PathBuf; + use crate::common::util::*; #[test] fn test_default() { let (at, mut ucmd) = at_and_ucmd!(); - ucmd.run().stdout_is(at.root_dir_resolved() + "\n"); + ucmd.succeeds().stdout_is(at.root_dir_resolved() + "\n"); } #[test] @@ -11,3 +15,118 @@ fn test_failed() { let (_at, mut ucmd) = at_and_ucmd!(); ucmd.arg("will-fail").fails(); } + +#[cfg(unix)] +#[test] +fn test_deleted_dir() { + use std::process::Command; + + let ts = TestScenario::new(util_name!()); + let at = ts.fixtures.clone(); + let output = Command::new("sh") + .arg("-c") + .arg(format!( + "cd '{}'; mkdir foo; cd foo; rmdir ../foo; exec {} {}", + at.root_dir_resolved(), + ts.bin_path.to_str().unwrap(), + ts.util_name, + )) + .output() + .unwrap(); + assert!(!output.status.success()); + assert!(output.stdout.is_empty()); + assert_eq!( + output.stderr, + b"pwd: failed to get current directory: No such file or directory\n" + ); +} + +struct Env { + ucmd: UCommand, + #[cfg(not(windows))] + root: String, + subdir: String, + symdir: String, +} + +fn symlinked_env() -> Env { + let (at, mut ucmd) = at_and_ucmd!(); + at.mkdir("subdir"); + // Note: on Windows this requires admin permissions + at.symlink_dir("subdir", "symdir"); + let root = PathBuf::from(at.root_dir_resolved()); + ucmd.raw.current_dir(root.join("symdir")); + #[cfg(not(windows))] + ucmd.env("PWD", root.join("symdir")); + Env { + ucmd, + #[cfg(not(windows))] + root: root.to_string_lossy().into_owned(), + subdir: root.join("subdir").to_string_lossy().into_owned(), + symdir: root.join("symdir").to_string_lossy().into_owned(), + } +} + +#[test] +fn test_symlinked_logical() { + let mut env = symlinked_env(); + env.ucmd.arg("-L").succeeds().stdout_is(env.symdir + "\n"); +} + +#[test] +fn test_symlinked_physical() { + let mut env = symlinked_env(); + env.ucmd.arg("-P").succeeds().stdout_is(env.subdir + "\n"); +} + +#[test] +fn test_symlinked_default() { + let mut env = symlinked_env(); + env.ucmd.succeeds().stdout_is(env.subdir + "\n"); +} + +#[cfg(not(windows))] +pub mod untrustworthy_pwd_var { + use std::path::Path; + + use super::*; + + #[test] + fn test_nonexistent_logical() { + let (at, mut ucmd) = at_and_ucmd!(); + ucmd.arg("-L") + .env("PWD", "/somefakedir") + .succeeds() + .stdout_is(at.root_dir_resolved() + "\n"); + } + + #[test] + fn test_wrong_logical() { + let mut env = symlinked_env(); + env.ucmd + .arg("-L") + .env("PWD", env.root) + .succeeds() + .stdout_is(env.subdir + "\n"); + } + + #[test] + fn test_redundant_logical() { + let mut env = symlinked_env(); + env.ucmd + .arg("-L") + .env("PWD", Path::new(&env.symdir).join(".")) + .succeeds() + .stdout_is(env.subdir + "\n"); + } + + #[test] + fn test_relative_logical() { + let mut env = symlinked_env(); + env.ucmd + .arg("-L") + .env("PWD", ".") + .succeeds() + .stdout_is(env.subdir + "\n"); + } +} diff --git a/tests/common/macros.rs b/tests/common/macros.rs index 62b8c4824..108bc0fb7 100644 --- a/tests/common/macros.rs +++ b/tests/common/macros.rs @@ -31,7 +31,11 @@ macro_rules! path_concat { #[macro_export] macro_rules! util_name { () => { - module_path!().split("_").nth(1).expect("no test name") + module_path!() + .split("_") + .nth(1) + .and_then(|s| s.split("::").next()) + .expect("no test name") }; } From 5cd55391ec1a69fb07d9a0566361543be698dcf6 Mon Sep 17 00:00:00 2001 From: Jeffrey Finkelstein Date: Sat, 28 Aug 2021 16:28:38 -0400 Subject: [PATCH 109/206] seq: compute width of numbers after parsing Fix a bug in `seq` where the number of characters needed to print the number was computed incorrectly in some cases. This commit changes the computation of the width to be after parsing the number instead of before, in order to accommodate inputs like `1e3`, which requires four digits when printing the number, not three. --- src/uu/seq/src/seq.rs | 66 +++++++++++++++++++++++++++++++++++---- tests/by-util/test_seq.rs | 18 +++++++++++ 2 files changed, 78 insertions(+), 6 deletions(-) diff --git a/src/uu/seq/src/seq.rs b/src/uu/seq/src/seq.rs index f24b8be68..730c5efc7 100644 --- a/src/uu/seq/src/seq.rs +++ b/src/uu/seq/src/seq.rs @@ -61,6 +61,38 @@ impl Number { Number::F64(n) => n, } } + + /// Number of characters needed to print the integral part of the number. + /// + /// The number of characters includes one character to represent the + /// minus sign ("-") if this number is negative. + /// + /// # Examples + /// + /// ```rust,ignore + /// use num_bigint::{BigInt, Sign}; + /// + /// assert_eq!( + /// Number::BigInt(BigInt::new(Sign::Plus, vec![123])).num_digits(), + /// 3 + /// ); + /// assert_eq!( + /// Number::BigInt(BigInt::new(Sign::Minus, vec![123])).num_digits(), + /// 4 + /// ); + /// assert_eq!(Number::F64(123.45).num_digits(), 3); + /// assert_eq!(Number::MinusZero.num_digits(), 2); + /// ``` + fn num_digits(&self) -> usize { + match self { + Number::MinusZero => 2, + Number::BigInt(n) => n.to_string().len(), + Number::F64(n) => { + let s = n.to_string(); + s.find('.').unwrap_or_else(|| s.len()) + } + } + } } impl FromStr for Number { @@ -121,13 +153,11 @@ pub fn uumain(args: impl uucore::Args) -> i32 { }; let mut largest_dec = 0; - let mut padding = 0; let first = if numbers.len() > 1 { let slice = numbers[0]; let len = slice.len(); let dec = slice.find('.').unwrap_or(len); largest_dec = len - dec; - padding = dec; crash_if_err!(1, slice.parse()) } else { Number::BigInt(BigInt::one()) @@ -137,7 +167,6 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let len = slice.len(); let dec = slice.find('.').unwrap_or(len); largest_dec = cmp::max(largest_dec, len - dec); - padding = cmp::max(padding, dec); crash_if_err!(1, slice.parse()) } else { Number::BigInt(BigInt::one()) @@ -150,15 +179,18 @@ pub fn uumain(args: impl uucore::Args) -> i32 { ); return 1; } - let last = { + let last: Number = { let slice = numbers[numbers.len() - 1]; - padding = cmp::max(padding, slice.find('.').unwrap_or_else(|| slice.len())); crash_if_err!(1, slice.parse()) }; if largest_dec > 0 { largest_dec -= 1; } + let padding = first + .num_digits() + .max(increment.num_digits()) + .max(last.num_digits()); match (first, last, increment) { (Number::MinusZero, Number::BigInt(last), Number::BigInt(increment)) => print_seq_integers( (BigInt::zero(), increment, last), @@ -298,12 +330,14 @@ fn print_seq_integers( if !is_first_iteration { print!("{}", separator); } + let mut width = padding; if is_first_iteration && is_first_minus_zero { print!("-"); + width -= 1; } is_first_iteration = false; if pad { - print!("{number:>0width$}", number = value, width = padding); + print!("{number:>0width$}", number = value, width = width); } else { print!("{}", value); } @@ -314,3 +348,23 @@ fn print_seq_integers( print!("{}", terminator); } } + +#[cfg(test)] +mod tests { + use crate::Number; + use num_bigint::{BigInt, Sign}; + + #[test] + fn test_number_num_digits() { + assert_eq!( + Number::BigInt(BigInt::new(Sign::Plus, vec![123])).num_digits(), + 3 + ); + assert_eq!( + Number::BigInt(BigInt::new(Sign::Minus, vec![123])).num_digits(), + 4 + ); + assert_eq!(Number::F64(123.45).num_digits(), 3); + assert_eq!(Number::MinusZero.num_digits(), 2); + } +} diff --git a/tests/by-util/test_seq.rs b/tests/by-util/test_seq.rs index 48221eaf2..458769839 100644 --- a/tests/by-util/test_seq.rs +++ b/tests/by-util/test_seq.rs @@ -158,3 +158,21 @@ fn test_drop_negative_zero_end() { .stdout_is("1\n0\n") .no_stderr(); } + +#[test] +fn test_width_scientific_notation() { + new_ucmd!() + .args(&["-w", "999", "1e3"]) + .succeeds() + .stdout_is("0999\n1000\n") + .no_stderr(); +} + +#[test] +fn test_width_negative_zero() { + new_ucmd!() + .args(&["-w", "-0", "1"]) + .succeeds() + .stdout_is("-0\n01\n") + .no_stderr(); +} From 3ed74df4a68de71862d8fb9e7add11624b14384b Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sun, 29 Aug 2021 18:35:08 +0200 Subject: [PATCH 110/206] Update to freebsd-vm@v0.1.5 --- .github/workflows/CICD.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/CICD.yml b/.github/workflows/CICD.yml index c3d98fa36..d7dd4ad97 100644 --- a/.github/workflows/CICD.yml +++ b/.github/workflows/CICD.yml @@ -541,7 +541,7 @@ jobs: - uses: actions/checkout@v2 - name: Prepare, build and test id: test - uses: vmactions/freebsd-vm@v0.1.4 + uses: vmactions/freebsd-vm@v0.1.5 with: usesh: true prepare: pkg install -y curl gmake sudo From 67e7fdfc2ec2a7e7ce4b204355a712ec0e1169ad Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Mon, 30 Aug 2021 02:00:06 +0200 Subject: [PATCH 111/206] ls: use single quotes when $, \ or ` are present in a filename --- src/uu/ls/src/quoting_style.rs | 35 +++++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/src/uu/ls/src/quoting_style.rs b/src/uu/ls/src/quoting_style.rs index 5f421b2ee..9f075afff 100644 --- a/src/uu/ls/src/quoting_style.rs +++ b/src/uu/ls/src/quoting_style.rs @@ -283,7 +283,7 @@ pub(super) fn escape_name(name: &str, style: &QuotingStyle) -> String { always_quote, show_control, } => { - let (quotes, must_quote) = if name.contains('"') { + let (quotes, must_quote) = if name.contains(&['"', '`', '$', '\\'][..]) { (Quotes::Single, true) } else if name.contains('\'') { (Quotes::Double, true) @@ -690,4 +690,37 @@ mod tests { ); check_names("name#", vec![("name#", "shell"), ("name#", "shell-escape")]); } + + #[test] + fn test_special_chars_in_double_quotes() { + check_names( + "can'$t", + vec![ + ("'can'\\''$t'", "shell"), + ("'can'\\''$t'", "shell-always"), + ("'can'\\''$t'", "shell-escape"), + ("'can'\\''$t'", "shell-escape-always"), + ], + ); + + check_names( + "can'`t", + vec![ + ("'can'\\''`t'", "shell"), + ("'can'\\''`t'", "shell-always"), + ("'can'\\''`t'", "shell-escape"), + ("'can'\\''`t'", "shell-escape-always"), + ], + ); + + check_names( + "can'\\t", + vec![ + ("'can'\\''\\t'", "shell"), + ("'can'\\''\\t'", "shell-always"), + ("'can'\\''\\t'", "shell-escape"), + ("'can'\\''\\t'", "shell-escape-always"), + ], + ); + } } From 97a0c06ff46b8bf05039830f6e4ff2411cfbbe44 Mon Sep 17 00:00:00 2001 From: Mahmoud Soltan Date: Mon, 30 Aug 2021 23:09:16 +0200 Subject: [PATCH 112/206] Proper columns for `ls -l` (#2623) * Used .as_path() and .as_str() when required: when the argument required is a Path and not a PathBuf, or an str and not a Path, respectively. * Changed display_items to take Vec, which is passed, instead of [PathData] * Added a pad_right function. * Implemented column-formating to mimic the behavior of GNU coreutils's ls Added returns in display_dir_entry_size that keep track of uname and group lengths. Renamed variables to make more sense. Changed display_item_long to take all the lengths it needs to render correctly. Implemented owner, group, and author padding right to mimic GNU ls. * Added a todo for future quality-of-life cache addition. * Documented display_item_long, as a first step in documenting all functions. * Revert "Used .as_path() and .as_str() when required:" This reverts commit b88db6a8170f827a7adc58de14acb59f19be2db1. * Revert "Changed display_items to take Vec, which is passed, instead of [PathData]" This reverts commit 0c690dda8d5eb1b257afb4c74d9c2dfc1f7cc97b. * Ran cargo fmt to get rid of Style/format `fmt` testing error. * Added a test for `ls -l` and `ls -lan` line formats. * Changed uname -> username for cspell. Removed extra blank line for rustfmt. --- src/uu/ls/src/ls.rs | 102 +++++++++++++++++++++++++++++++++------ tests/by-util/test_ls.rs | 55 +++++++++++++++++++++ 2 files changed, 143 insertions(+), 14 deletions(-) diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index 3d957474c..bf449d96b 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -1394,14 +1394,17 @@ fn get_metadata(entry: &Path, dereference: bool) -> std::io::Result { } } -fn display_dir_entry_size(entry: &PathData, config: &Config) -> (usize, usize) { +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. if let Some(md) = entry.md() { ( display_symlink_count(md).len(), + display_uname(md, config).len(), + display_group(md, config).len(), display_size_or_rdev(md, config).len(), ) } else { - (0, 0) + (0, 0, 0, 0) } } @@ -1409,15 +1412,28 @@ fn pad_left(string: String, count: usize) -> String { format!("{:>width$}", string, width = count) } +fn pad_right(string: String, count: usize) -> String { + format!("{:) { if config.format == Format::Long { - let (mut max_links, mut max_width) = (1, 1); + let ( + mut longest_link_count_len, + mut longest_uname_len, + mut longest_group_len, + mut longest_size_len, + ) = (1, 1, 1, 1); let mut total_size = 0; for item in items { - let (links, width) = display_dir_entry_size(item, config); - max_links = links.max(max_links); - max_width = width.max(max_width); + 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); + longest_size_len = size_len.max(longest_size_len); total_size += item.md().map_or(0, |md| get_block_size(md, config)); } @@ -1426,7 +1442,15 @@ fn display_items(items: &[PathData], config: &Config, out: &mut BufWriter, ) { @@ -1557,27 +1619,39 @@ fn display_item_long( out, "{} {}", display_permissions(md, true), - pad_left(display_symlink_count(md), max_links), + pad_left(display_symlink_count(md), longest_link_count_len), ); if config.long.owner { - let _ = write!(out, " {}", display_uname(md, config)); + let _ = write!( + out, + " {}", + pad_right(display_uname(md, config), longest_uname_len) + ); } if config.long.group { - let _ = write!(out, " {}", display_group(md, config)); + let _ = write!( + out, + " {}", + pad_right(display_group(md, config), longest_group_len) + ); } // Author is only different from owner on GNU/Hurd, so we reuse // the owner, since GNU/Hurd is not currently supported by Rust. if config.long.author { - let _ = write!(out, " {}", display_uname(md, config)); + let _ = write!( + out, + " {}", + pad_right(display_uname(md, config), longest_uname_len) + ); } let _ = writeln!( out, " {} {} {}", - pad_left(display_size_or_rdev(md, config), max_size), + pad_left(display_size_or_rdev(md, config), 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 diff --git a/tests/by-util/test_ls.rs b/tests/by-util/test_ls.rs index a3372050a..546fb86a8 100644 --- a/tests/by-util/test_ls.rs +++ b/tests/by-util/test_ls.rs @@ -333,6 +333,61 @@ fn test_ls_long() { } } +#[test] +fn test_ls_long_format() { + #[cfg(not(windows))] + let last; + #[cfg(not(windows))] + { + let _guard = UMASK_MUTEX.lock(); + last = unsafe { umask(0) }; + + unsafe { + umask(0o002); + } + } + + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + at.mkdir(&at.plus_as_string("test-long-dir")); + at.touch(&at.plus_as_string("test-long-dir/test-long-file")); + at.mkdir(&at.plus_as_string("test-long-dir/test-long-dir")); + + for arg in &["-l", "--long", "--format=long", "--format=verbose"] { + let result = scene.ucmd().arg(arg).arg("test-long-dir").succeeds(); + // Assuming sane username do not have spaces within them. + // A line of the output should be: + // One of the characters -bcCdDlMnpPsStTx? + // rwx, with - for missing permissions, thrice. + // 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. + // A month, followed by a single space. + // A day, preceded by column whitespace, and followed by a single space. + // Either a year or a time, currently [0-9:]+, preceded by column whitespace, + // and followed by a single space. + // Whatever comes after is irrelevant to this specific test. + #[cfg(not(windows))] + result.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:]+ " + ).unwrap()); + } + + let result = scene.ucmd().arg("-lan").arg("test-long-dir").succeeds(); + // This checks for the line with the .. entry. The uname and group should be digits. + #[cfg(not(windows))] + result.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:]+ \.\." + ).unwrap()); + + #[cfg(not(windows))] + { + unsafe { + umask(last); + } + } +} + #[test] fn test_ls_long_total_size() { let scene = TestScenario::new(util_name!()); From 1e78a40e20603f66e6d3da9b58bf346a3d3087f0 Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Mon, 30 Aug 2021 23:13:31 +0200 Subject: [PATCH 113/206] CICD: use nightly rust for code coverage --- .github/workflows/CICD.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/CICD.yml b/.github/workflows/CICD.yml index d7dd4ad97..4f92d7d73 100644 --- a/.github/workflows/CICD.yml +++ b/.github/workflows/CICD.yml @@ -14,7 +14,6 @@ env: PROJECT_DESC: "Core universal (cross-platform) utilities" PROJECT_AUTH: "uutils" RUST_MIN_SRV: "1.47.0" ## MSRV v1.47.0 - RUST_COV_SRV: "2021-05-06" ## (~v1.52.0) supported rust version for code coverage; (date required/used by 'coverage') ## !maint: refactor when code coverage support is included in the stable channel on: [push, pull_request] @@ -606,7 +605,7 @@ jobs: ## VARs setup outputs() { step_id="vars"; for var in "$@" ; do echo steps.${step_id}.outputs.${var}="${!var}"; echo ::set-output name=${var}::${!var}; done; } # toolchain - TOOLCHAIN="nightly-${{ env.RUST_COV_SRV }}" ## default to "nightly" toolchain (required for certain required unstable compiler flags) ## !maint: refactor when stable channel has needed support + TOOLCHAIN="nightly" ## default to "nightly" toolchain (required for certain required unstable compiler flags) ## !maint: refactor when stable channel has needed support # * specify gnu-type TOOLCHAIN for windows; `grcov` requires gnu-style code coverage data files case ${{ matrix.job.os }} in windows-*) TOOLCHAIN="$TOOLCHAIN-x86_64-pc-windows-gnu" ;; esac; # * use requested TOOLCHAIN if specified From b82401e744e0b588c8c86dad19633c0dd5c946e2 Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Tue, 31 Aug 2021 00:36:35 +0200 Subject: [PATCH 114/206] uucore: fall back to stripping the error message for unexpected io error kinds Rust recently added new error kinds, which causes tests to fail on beta/nightly. However, we can't match for these new error kinds because they are marked as unstable. As a workaround we call `.to_string()` on the original error and strip the ` (os error XX)` bit. The downside of this is that the error messages are not capitalized. --- src/uucore/src/lib/mods/error.rs | 58 +++++++++++++++++--------------- 1 file changed, 31 insertions(+), 27 deletions(-) diff --git a/src/uucore/src/lib/mods/error.rs b/src/uucore/src/lib/mods/error.rs index 6af934d3e..9a697d822 100644 --- a/src/uucore/src/lib/mods/error.rs +++ b/src/uucore/src/lib/mods/error.rs @@ -368,7 +368,7 @@ impl UIoError { pub fn new>(kind: std::io::ErrorKind, context: S) -> Box { Box::new(Self { context: context.into(), - inner: std::io::Error::new(kind, ""), + inner: kind.into(), }) } } @@ -380,32 +380,36 @@ impl Error for UIoError {} impl Display for UIoError { fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> { use std::io::ErrorKind::*; - write!( - f, - "{}: {}", - self.context, - match self.inner.kind() { - NotFound => "No such file or directory", - PermissionDenied => "Permission denied", - ConnectionRefused => "Connection refused", - ConnectionReset => "Connection reset", - ConnectionAborted => "Connection aborted", - NotConnected => "Not connected", - AddrInUse => "Address in use", - AddrNotAvailable => "Address not available", - BrokenPipe => "Broken pipe", - AlreadyExists => "Already exists", - WouldBlock => "Would block", - InvalidInput => "Invalid input", - InvalidData => "Invalid data", - TimedOut => "Timed out", - WriteZero => "Write zero", - Interrupted => "Interrupted", - Other => "Other", - UnexpectedEof => "Unexpected end of file", - _ => panic!("Unexpected io error: {}", self.inner), - }, - ) + + let message; + let message = match self.inner.kind() { + NotFound => "No such file or directory", + PermissionDenied => "Permission denied", + ConnectionRefused => "Connection refused", + ConnectionReset => "Connection reset", + ConnectionAborted => "Connection aborted", + NotConnected => "Not connected", + AddrInUse => "Address in use", + AddrNotAvailable => "Address not available", + BrokenPipe => "Broken pipe", + AlreadyExists => "Already exists", + WouldBlock => "Would block", + InvalidInput => "Invalid input", + InvalidData => "Invalid data", + TimedOut => "Timed out", + WriteZero => "Write zero", + Interrupted => "Interrupted", + Other => "Other", + UnexpectedEof => "Unexpected end of file", + _ => { + // TODO: using `strip_errno()` causes the error message + // to not be capitalized. When the new error variants (https://github.com/rust-lang/rust/issues/86442) + // are stabilized, we should add them to the match statement. + message = strip_errno(&self.inner); + &message + } + }; + write!(f, "{}: {}", self.context, message,) } } From 4f891add5a77c7a8d15cfbec13c9577b243ed88a Mon Sep 17 00:00:00 2001 From: Jan Verbeek Date: Sun, 29 Aug 2021 20:08:43 +0200 Subject: [PATCH 115/206] uucore: Add a Quotable extension trait for displaying filenames --- .../cspell.dictionaries/jargon.wordlist.txt | 1 + .../cspell.dictionaries/shell.wordlist.txt | 1 + src/uucore/src/lib/lib.rs | 1 + src/uucore/src/lib/mods.rs | 1 + src/uucore/src/lib/mods/display.rs | 357 ++++++++++++++++++ 5 files changed, 361 insertions(+) create mode 100644 src/uucore/src/lib/mods/display.rs diff --git a/.vscode/cspell.dictionaries/jargon.wordlist.txt b/.vscode/cspell.dictionaries/jargon.wordlist.txt index 34abfc511..089adffa3 100644 --- a/.vscode/cspell.dictionaries/jargon.wordlist.txt +++ b/.vscode/cspell.dictionaries/jargon.wordlist.txt @@ -1,3 +1,4 @@ +AFAICT arity autogenerate autogenerated diff --git a/.vscode/cspell.dictionaries/shell.wordlist.txt b/.vscode/cspell.dictionaries/shell.wordlist.txt index 07c2364ac..4ed281efb 100644 --- a/.vscode/cspell.dictionaries/shell.wordlist.txt +++ b/.vscode/cspell.dictionaries/shell.wordlist.txt @@ -8,6 +8,7 @@ csh globstar inotify localtime +mksh mountinfo mountpoint mtab diff --git a/src/uucore/src/lib/lib.rs b/src/uucore/src/lib/lib.rs index a39834ec1..5352a6356 100644 --- a/src/uucore/src/lib/lib.rs +++ b/src/uucore/src/lib/lib.rs @@ -19,6 +19,7 @@ mod parser; // string parsing modules // * cross-platform modules pub use crate::mods::backup_control; pub use crate::mods::coreopts; +pub use crate::mods::display; pub use crate::mods::error; pub use crate::mods::os; pub use crate::mods::panic; diff --git a/src/uucore/src/lib/mods.rs b/src/uucore/src/lib/mods.rs index b0235832b..8f6d14976 100644 --- a/src/uucore/src/lib/mods.rs +++ b/src/uucore/src/lib/mods.rs @@ -2,6 +2,7 @@ pub mod backup_control; pub mod coreopts; +pub mod display; pub mod error; pub mod os; pub mod panic; diff --git a/src/uucore/src/lib/mods/display.rs b/src/uucore/src/lib/mods/display.rs new file mode 100644 index 000000000..42e9fceba --- /dev/null +++ b/src/uucore/src/lib/mods/display.rs @@ -0,0 +1,357 @@ +/// Utilities for printing paths, with special attention paid to special +/// characters and invalid unicode. +/// +/// For displaying paths in informational messages use `Quotable::quote`. This +/// will wrap quotes around the filename and add the necessary escapes to make +/// it copy/paste-able into a shell. +/// +/// # Examples +/// ``` +/// use std::path::Path; +/// use uucore::display::{Quotable, println_verbatim}; +/// +/// let path = Path::new("foo/bar.baz"); +/// +/// println!("Found file {}", path.quote()); // Prints "Found file 'foo/bar.baz'" +/// # Ok::<(), std::io::Error>(()) +/// ``` +// spell-checker:ignore Fbar +use std::ffi::OsStr; +#[cfg(any(unix, target_os = "wasi", windows))] +use std::fmt::Write as FmtWrite; +use std::fmt::{self, Display, Formatter}; + +#[cfg(unix)] +use std::os::unix::ffi::OsStrExt; +#[cfg(target_os = "wasi")] +use std::os::wasi::ffi::OsStrExt; +#[cfg(any(unix, target_os = "wasi"))] +use std::str::from_utf8; + +/// An extension trait for displaying filenames to users. +pub trait Quotable { + /// Returns an object that implements [`Display`] for printing filenames with + /// proper quoting and escaping for the platform. + /// + /// On Unix this corresponds to sh/bash syntax, on Windows Powershell syntax + /// is used. + /// + /// # Examples + /// + /// ``` + /// use std::path::Path; + /// use uucore::display::Quotable; + /// + /// let path = Path::new("foo/bar.baz"); + /// + /// println!("Found file {}", path.quote()); // Prints "Found file 'foo/bar.baz'" + /// ``` + fn quote(&self) -> Quoted<'_>; +} + +impl Quotable for T +where + T: AsRef, +{ + fn quote(&self) -> Quoted<'_> { + Quoted(self.as_ref()) + } +} + +/// A wrapper around [`OsStr`] for printing paths with quoting and escaping applied. +#[derive(Debug)] +pub struct Quoted<'a>(&'a OsStr); + +impl Display for Quoted<'_> { + #[cfg(any(unix, target_os = "wasi"))] + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + let text = self.0.as_bytes(); + + let mut is_single_safe = true; + let mut is_double_safe = true; + for &ch in text { + match ch { + ch if ch.is_ascii_control() => return write_escaped(f, text), + b'\'' => is_single_safe = false, + // Unsafe characters according to: + // https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_02_03 + b'"' | b'`' | b'$' | b'\\' => is_double_safe = false, + _ => (), + } + } + let text = match from_utf8(text) { + Err(_) => return write_escaped(f, text), + Ok(text) => text, + }; + if is_single_safe { + return write_simple(f, text, '\''); + } else if is_double_safe { + return write_simple(f, text, '\"'); + } else { + return write_single_escaped(f, text); + } + + fn write_simple(f: &mut Formatter<'_>, text: &str, quote: char) -> fmt::Result { + f.write_char(quote)?; + f.write_str(text)?; + f.write_char(quote)?; + Ok(()) + } + + fn write_single_escaped(f: &mut Formatter<'_>, text: &str) -> fmt::Result { + let mut iter = text.split('\''); + if let Some(chunk) = iter.next() { + if !chunk.is_empty() { + write_simple(f, chunk, '\'')?; + } + } + for chunk in iter { + f.write_str("\\'")?; + if !chunk.is_empty() { + write_simple(f, chunk, '\'')?; + } + } + Ok(()) + } + + /// Write using the syntax described here: + /// https://www.gnu.org/software/bash/manual/html_node/ANSI_002dC-Quoting.html + /// + /// Supported by these shells: + /// - bash + /// - zsh + /// - busybox sh + /// - mksh + /// + /// Not supported by these: + /// - fish + /// - dash + /// - tcsh + fn write_escaped(f: &mut Formatter<'_>, text: &[u8]) -> fmt::Result { + f.write_str("$'")?; + for chunk in from_utf8_iter(text) { + match chunk { + Ok(chunk) => { + for ch in chunk.chars() { + match ch { + '\n' => f.write_str("\\n")?, + '\t' => f.write_str("\\t")?, + '\r' => f.write_str("\\r")?, + // We could do \b, \f, \v, etc., but those are + // rare enough to be confusing. + // \0 doesn't work consistently because of the + // octal \nnn syntax, and null bytes can't appear + // in filenames anyway. + ch if ch.is_ascii_control() => write!(f, "\\x{:02X}", ch as u8)?, + '\\' | '\'' => { + // '?' and '"' can also be escaped this way + // but AFAICT there's no reason to do so + f.write_char('\\')?; + f.write_char(ch)?; + } + ch => { + f.write_char(ch)?; + } + } + } + } + Err(unit) => write!(f, "\\x{:02X}", unit)?, + } + } + f.write_char('\'')?; + Ok(()) + } + } + + #[cfg(windows)] + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + // Behavior is based on PowerShell. + // ` takes the role of \ since \ is already used as the path separator. + // Things are UTF-16-oriented, so we escape code units as "`u{1234}". + use std::char::decode_utf16; + use std::os::windows::ffi::OsStrExt; + + // Getting the "raw" representation of an OsStr is actually expensive, + // so avoid it if unnecessary. + let text = match self.0.to_str() { + None => return write_escaped(f, self.0), + Some(text) => text, + }; + + let mut is_single_safe = true; + let mut is_double_safe = true; + for ch in text.chars() { + match ch { + ch if ch.is_ascii_control() => return write_escaped(f, self.0), + '\'' => is_single_safe = false, + '"' | '`' | '$' => is_double_safe = false, + _ => (), + } + } + + if is_single_safe || !is_double_safe { + return write_simple(f, text, '\''); + } else { + return write_simple(f, text, '"'); + } + + fn write_simple(f: &mut Formatter<'_>, text: &str, quote: char) -> fmt::Result { + // Quotes in Powershell can be escaped by doubling them + f.write_char(quote)?; + let mut iter = text.split(quote); + if let Some(chunk) = iter.next() { + f.write_str(chunk)?; + } + for chunk in iter { + f.write_char(quote)?; + f.write_char(quote)?; + f.write_str(chunk)?; + } + f.write_char(quote)?; + Ok(()) + } + + fn write_escaped(f: &mut Formatter<'_>, text: &OsStr) -> fmt::Result { + f.write_char('"')?; + for ch in decode_utf16(text.encode_wide()) { + match ch { + Ok(ch) => match ch { + '\0' => f.write_str("`0")?, + '\r' => f.write_str("`r")?, + '\n' => f.write_str("`n")?, + '\t' => f.write_str("`t")?, + ch if ch.is_ascii_control() => write!(f, "`u{{{:04X}}}", ch as u8)?, + '`' => f.write_str("``")?, + '$' => f.write_str("`$")?, + '"' => f.write_str("\"\"")?, + ch => f.write_char(ch)?, + }, + Err(err) => write!(f, "`u{{{:04X}}}", err.unpaired_surrogate())?, + } + } + f.write_char('"')?; + Ok(()) + } + } + + #[cfg(not(any(unix, target_os = "wasi", windows)))] + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + // As a fallback, we use Rust's own escaping rules. + // This is reasonably sane and very easy to implement. + // We use single quotes because that's hardcoded in a lot of tests. + write!(f, "'{}'", self.0.to_string_lossy().escape_debug()) + } +} + +#[cfg(any(unix, target_os = "wasi"))] +fn from_utf8_iter(mut bytes: &[u8]) -> impl Iterator> { + std::iter::from_fn(move || { + if bytes.is_empty() { + return None; + } + match from_utf8(bytes) { + Ok(text) => { + bytes = &[]; + Some(Ok(text)) + } + Err(err) if err.valid_up_to() == 0 => { + let res = bytes[0]; + bytes = &bytes[1..]; + Some(Err(res)) + } + Err(err) => { + let (valid, rest) = bytes.split_at(err.valid_up_to()); + bytes = rest; + Some(Ok(from_utf8(valid).unwrap())) + } + } + }) +} + +#[cfg(test)] +mod tests { + use super::*; + + fn verify_quote(cases: &[(impl AsRef, &str)]) { + for (case, expected) in cases { + assert_eq!(case.quote().to_string(), *expected); + } + } + + /// This should hold on any platform, or else a lot of other tests will fail. + #[test] + fn test_basic() { + verify_quote(&[ + ("foo", "'foo'"), + ("", "''"), + ("foo/bar.baz", "'foo/bar.baz'"), + ]); + } + + #[cfg(any(unix, target_os = "wasi"))] + #[test] + fn test_unix() { + verify_quote(&[ + ("can't", r#""can't""#), + (r#"can'"t"#, r#"'can'\''"t'"#), + (r#"can'$t"#, r#"'can'\''$t'"#), + ("foo\nb\ta\r\\\0`r", r#"$'foo\nb\ta\r\\\x00`r'"#), + ("foo\x02", r#"$'foo\x02'"#), + (r#"'$''"#, r#"\''$'\'\'"#), + ]); + verify_quote(&[(OsStr::from_bytes(b"foo\xFF"), r#"$'foo\xFF'"#)]); + } + + #[cfg(windows)] + #[test] + fn test_windows() { + use std::ffi::OsString; + use std::os::windows::ffi::OsStringExt; + verify_quote(&[ + (r#"foo\bar"#, r#"'foo\bar'"#), + ("can't", r#""can't""#), + (r#"can'"t"#, r#"'can''"t'"#), + (r#"can'$t"#, r#"'can''$t'"#), + ("foo\nb\ta\r\\\0`r", r#""foo`nb`ta`r\`0``r""#), + ("foo\x02", r#""foo`u{0002}""#), + (r#"'$''"#, r#"'''$'''''"#), + ]); + verify_quote(&[( + OsString::from_wide(&[b'x' as u16, 0xD800]), + r#""x`u{D800}""#, + )]) + } + + #[cfg(any(unix, target_os = "wasi"))] + #[test] + fn test_utf8_iter() { + const CASES: &[(&[u8], &[Result<&str, u8>])] = &[ + (b"", &[]), + (b"hello", &[Ok("hello")]), + // Immediately invalid + (b"\xFF", &[Err(b'\xFF')]), + // Incomplete UTF-8 + (b"\xC2", &[Err(b'\xC2')]), + (b"\xF4\x8F", &[Err(b'\xF4'), Err(b'\x8F')]), + (b"\xFF\xFF", &[Err(b'\xFF'), Err(b'\xFF')]), + (b"hello\xC2", &[Ok("hello"), Err(b'\xC2')]), + (b"\xFFhello", &[Err(b'\xFF'), Ok("hello")]), + (b"\xFF\xC2hello", &[Err(b'\xFF'), Err(b'\xC2'), Ok("hello")]), + (b"foo\xFFbar", &[Ok("foo"), Err(b'\xFF'), Ok("bar")]), + ( + b"foo\xF4\x8Fbar", + &[Ok("foo"), Err(b'\xF4'), Err(b'\x8F'), Ok("bar")], + ), + ( + b"foo\xFF\xC2bar", + &[Ok("foo"), Err(b'\xFF'), Err(b'\xC2'), Ok("bar")], + ), + ]; + for &(case, expected) in CASES { + assert_eq!( + from_utf8_iter(case).collect::>().as_slice(), + expected + ); + } + } +} From 0e1f8f7b34823ed48cf8bb4dadabbbdb77dfb1d6 Mon Sep 17 00:00:00 2001 From: Jan Verbeek Date: Sun, 29 Aug 2021 20:09:58 +0200 Subject: [PATCH 116/206] Move verbatim path printing to uucore::Display --- src/uu/pwd/src/pwd.rs | 31 +++++------------------------- src/uucore/src/lib/mods/display.rs | 30 +++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 26 deletions(-) diff --git a/src/uu/pwd/src/pwd.rs b/src/uu/pwd/src/pwd.rs index e8f0c0013..1138dba8e 100644 --- a/src/uu/pwd/src/pwd.rs +++ b/src/uu/pwd/src/pwd.rs @@ -10,9 +10,10 @@ extern crate uucore; use clap::{crate_version, App, Arg}; use std::env; -use std::io::{self, Write}; -use std::path::{Path, PathBuf}; +use std::io; +use std::path::PathBuf; +use uucore::display::println_verbatim; use uucore::error::{FromIo, UResult}; static ABOUT: &str = "Display the full filename of the current working directory."; @@ -57,6 +58,7 @@ fn logical_path() -> io::Result { // POSIX: https://pubs.opengroup.org/onlinepubs/9699919799/utilities/pwd.html #[cfg(not(windows))] { + use std::path::Path; fn looks_reasonable(path: &Path) -> bool { // First, check if it's an absolute path. if !path.has_root() { @@ -148,30 +150,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { .map(Into::into) .unwrap_or(cwd); - print_path(&cwd).map_err_context(|| "failed to print current directory".to_owned())?; - - Ok(()) -} - -fn print_path(path: &Path) -> io::Result<()> { - let stdout = io::stdout(); - let mut stdout = stdout.lock(); - - // On Unix we print non-lossily. - #[cfg(unix)] - { - use std::os::unix::ffi::OsStrExt; - stdout.write_all(path.as_os_str().as_bytes())?; - stdout.write_all(b"\n")?; - } - - // On other platforms we potentially mangle it. - // There might be some clever way to do it correctly on Windows, but - // invalid unicode in filenames is rare there. - #[cfg(not(unix))] - { - writeln!(stdout, "{}", path.display())?; - } + println_verbatim(&cwd).map_err_context(|| "failed to print current directory".to_owned())?; Ok(()) } diff --git a/src/uucore/src/lib/mods/display.rs b/src/uucore/src/lib/mods/display.rs index 42e9fceba..b941a4b15 100644 --- a/src/uucore/src/lib/mods/display.rs +++ b/src/uucore/src/lib/mods/display.rs @@ -5,6 +5,9 @@ /// will wrap quotes around the filename and add the necessary escapes to make /// it copy/paste-able into a shell. /// +/// For writing raw paths to stdout when the output should not be quoted or escaped, +/// use `println_verbatim`. This will preserve invalid unicode. +/// /// # Examples /// ``` /// use std::path::Path; @@ -13,6 +16,7 @@ /// let path = Path::new("foo/bar.baz"); /// /// println!("Found file {}", path.quote()); // Prints "Found file 'foo/bar.baz'" +/// println_verbatim(path)?; // Prints "foo/bar.baz" /// # Ok::<(), std::io::Error>(()) /// ``` // spell-checker:ignore Fbar @@ -20,6 +24,7 @@ use std::ffi::OsStr; #[cfg(any(unix, target_os = "wasi", windows))] use std::fmt::Write as FmtWrite; use std::fmt::{self, Display, Formatter}; +use std::io::{self, Write as IoWrite}; #[cfg(unix)] use std::os::unix::ffi::OsStrExt; @@ -268,6 +273,31 @@ fn from_utf8_iter(mut bytes: &[u8]) -> impl Iterator> { }) } +/// Print a path (or `OsStr`-like object) directly to stdout, with a trailing newline, +/// without losing any information if its encoding is invalid. +/// +/// This function is appropriate for commands where printing paths is the point and the +/// output is likely to be captured, like `pwd` and `basename`. For informational output +/// use `Quotable::quote`. +/// +/// FIXME: This is lossy on Windows. It could probably be implemented using some low-level +/// API that takes UTF-16, without going through io::Write. This is not a big priority +/// because broken filenames are much rarer on Windows than on Unix. +pub fn println_verbatim>(text: S) -> io::Result<()> { + let stdout = io::stdout(); + let mut stdout = stdout.lock(); + #[cfg(any(unix, target_os = "wasi"))] + { + stdout.write_all(text.as_ref().as_bytes())?; + stdout.write_all(b"\n")?; + } + #[cfg(not(any(unix, target_os = "wasi")))] + { + writeln!(stdout, "{}", std::path::Path::new(text.as_ref()).display())?; + } + Ok(()) +} + #[cfg(test)] mod tests { use super::*; From 03b2d40154391b28114e0bd38e2d7fef31f02aec Mon Sep 17 00:00:00 2001 From: Jan Verbeek Date: Sun, 29 Aug 2021 20:18:52 +0200 Subject: [PATCH 117/206] rmdir: use modern filename display --- src/uu/rmdir/src/rmdir.rs | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/src/uu/rmdir/src/rmdir.rs b/src/uu/rmdir/src/rmdir.rs index d8cad0421..f982cf4c3 100644 --- a/src/uu/rmdir/src/rmdir.rs +++ b/src/uu/rmdir/src/rmdir.rs @@ -14,6 +14,7 @@ use clap::{crate_version, App, Arg}; use std::fs::{read_dir, remove_dir}; use std::io; use std::path::Path; +use uucore::display::Quotable; use uucore::error::{set_exit_code, strip_errno, UResult}; use uucore::util_name; @@ -77,27 +78,23 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { Ok(path.metadata()?.file_type().is_dir()) } - let path = path.as_os_str().as_bytes(); - if error.raw_os_error() == Some(libc::ENOTDIR) && path.ends_with(b"/") { + let bytes = path.as_os_str().as_bytes(); + if error.raw_os_error() == Some(libc::ENOTDIR) && bytes.ends_with(b"/") { // Strip the trailing slash or .symlink_metadata() will follow the symlink - let path: &Path = OsStr::from_bytes(&path[..path.len() - 1]).as_ref(); - if is_symlink(path).unwrap_or(false) - && points_to_directory(path).unwrap_or(true) + let no_slash: &Path = OsStr::from_bytes(&bytes[..bytes.len() - 1]).as_ref(); + if is_symlink(no_slash).unwrap_or(false) + && points_to_directory(no_slash).unwrap_or(true) { show_error!( - "failed to remove '{}/': Symbolic link not followed", - path.display() + "failed to remove {}: Symbolic link not followed", + path.quote() ); continue; } } } - show_error!( - "failed to remove '{}': {}", - path.display(), - strip_errno(&error) - ); + show_error!("failed to remove {}: {}", path.quote(), strip_errno(&error)); } } @@ -125,7 +122,7 @@ fn remove(mut path: &Path, opts: Opts) -> Result<(), Error<'_>> { fn remove_single(path: &Path, opts: Opts) -> Result<(), Error<'_>> { if opts.verbose { - println!("{}: removing directory, '{}'", util_name(), path.display()); + println!("{}: removing directory, {}", util_name(), path.quote()); } remove_dir(path).map_err(|error| Error { error, path }) } From 13bb263a502297b612352b12185d8446bd28a2cd Mon Sep 17 00:00:00 2001 From: Jan Verbeek Date: Mon, 30 Aug 2021 13:25:44 +0200 Subject: [PATCH 118/206] uucore::display: Support unquoted text --- src/uucore/src/lib/mods/display.rs | 109 ++++++++++++++++++++++++++--- 1 file changed, 98 insertions(+), 11 deletions(-) diff --git a/src/uucore/src/lib/mods/display.rs b/src/uucore/src/lib/mods/display.rs index b941a4b15..7cc48e74f 100644 --- a/src/uucore/src/lib/mods/display.rs +++ b/src/uucore/src/lib/mods/display.rs @@ -59,21 +59,55 @@ where T: AsRef, { fn quote(&self) -> Quoted<'_> { - Quoted(self.as_ref()) + Quoted { + text: self.as_ref(), + force_quote: true, + } } } /// A wrapper around [`OsStr`] for printing paths with quoting and escaping applied. -#[derive(Debug)] -pub struct Quoted<'a>(&'a OsStr); +#[derive(Debug, Copy, Clone)] +pub struct Quoted<'a> { + text: &'a OsStr, + force_quote: bool, +} + +impl Quoted<'_> { + /// Add quotes even if not strictly necessary. `true` by default. + pub fn force_quote(mut self, force: bool) -> Self { + self.force_quote = force; + self + } +} impl Display for Quoted<'_> { #[cfg(any(unix, target_os = "wasi"))] fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - let text = self.0.as_bytes(); + /// Characters with special meaning outside quotes. + // https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_02 + // % seems obscure enough to ignore, it's for job control only. + // {} were used in a version elsewhere but seem unnecessary, GNU doesn't escape them. + // ! is a common extension. + const SPECIAL_SHELL_CHARS: &[u8] = b"|&;<>()$`\\\"'*?[]=! "; + /// Characters with a special meaning at the beginning of a name. + const SPECIAL_SHELL_CHARS_START: &[u8] = b"~#"; + + let text = self.text.as_bytes(); let mut is_single_safe = true; let mut is_double_safe = true; + let mut requires_quote = self.force_quote; + + if let Some(first) = text.get(0) { + if SPECIAL_SHELL_CHARS_START.contains(first) { + requires_quote = true; + } + } else { + // Empty string + requires_quote = true; + } + for &ch in text { match ch { ch if ch.is_ascii_control() => return write_escaped(f, text), @@ -83,12 +117,21 @@ impl Display for Quoted<'_> { b'"' | b'`' | b'$' | b'\\' => is_double_safe = false, _ => (), } + if !requires_quote && SPECIAL_SHELL_CHARS.contains(&ch) { + requires_quote = true; + } } let text = match from_utf8(text) { Err(_) => return write_escaped(f, text), Ok(text) => text, }; - if is_single_safe { + if !requires_quote && text.find(char::is_whitespace).is_some() { + requires_quote = true; + } + + if !requires_quote { + return f.write_str(text); + } else if is_single_safe { return write_simple(f, text, '\''); } else if is_double_safe { return write_simple(f, text, '\"'); @@ -176,25 +219,57 @@ impl Display for Quoted<'_> { use std::char::decode_utf16; use std::os::windows::ffi::OsStrExt; + /// Characters with special meaning outside quotes. + // FIXME: I'm not a PowerShell wizard and don't know if this is correct. + // I just copied the Unix version, removed \, and added {}@ based on + // experimentation. + // I have noticed that ~?*[] only get expanded in some contexts, so watch + // out for that if doing your own tests. + // Get-ChildItem seems unwilling to quote anything so it doesn't help. + // There's the additional wrinkle that Windows has stricter requirements + // for filenames: I've been testing using a Linux build of PowerShell, but + // this code doesn't even compile on Linux. + const SPECIAL_SHELL_CHARS: &str = "|&;<>()$`\"'*?[]=!{}@ "; + /// Characters with a special meaning at the beginning of a name. + const SPECIAL_SHELL_CHARS_START: &[char] = &['~', '#']; + // Getting the "raw" representation of an OsStr is actually expensive, // so avoid it if unnecessary. - let text = match self.0.to_str() { - None => return write_escaped(f, self.0), + let text = match self.text.to_str() { + None => return write_escaped(f, self.text), Some(text) => text, }; let mut is_single_safe = true; let mut is_double_safe = true; + let mut requires_quote = self.force_quote; + + if let Some(first) = text.chars().next() { + if SPECIAL_SHELL_CHARS_START.contains(&first) { + requires_quote = true; + } + } else { + // Empty string + requires_quote = true; + } + for ch in text.chars() { match ch { - ch if ch.is_ascii_control() => return write_escaped(f, self.0), + ch if ch.is_ascii_control() => return write_escaped(f, self.text), '\'' => is_single_safe = false, '"' | '`' | '$' => is_double_safe = false, _ => (), } + if !requires_quote + && ((ch.is_ascii() && SPECIAL_SHELL_CHARS.contains(ch)) || ch.is_whitespace()) + { + requires_quote = true; + } } - if is_single_safe || !is_double_safe { + if !requires_quote { + return f.write_str(text); + } else if is_single_safe || !is_double_safe { return write_simple(f, text, '\''); } else { return write_simple(f, text, '"'); @@ -244,7 +319,12 @@ impl Display for Quoted<'_> { // As a fallback, we use Rust's own escaping rules. // This is reasonably sane and very easy to implement. // We use single quotes because that's hardcoded in a lot of tests. - write!(f, "'{}'", self.0.to_string_lossy().escape_debug()) + let text = self.text.to_string_lossy(); + if self.force_quote || !text.chars().all(|ch| ch.is_alphanumeric() || ch == '.') { + write!(f, "'{}'", text.escape_debug()) + } else { + f.write_str(&text) + } } } @@ -308,7 +388,7 @@ mod tests { } } - /// This should hold on any platform, or else a lot of other tests will fail. + /// This should hold on any platform. #[test] fn test_basic() { verify_quote(&[ @@ -316,6 +396,13 @@ mod tests { ("", "''"), ("foo/bar.baz", "'foo/bar.baz'"), ]); + assert_eq!("foo".quote().force_quote(false).to_string(), "foo"); + assert_eq!("".quote().force_quote(false).to_string(), "''"); + assert_eq!( + "foo bar".quote().force_quote(false).to_string(), + "'foo bar'" + ); + assert_eq!("$foo".quote().force_quote(false).to_string(), "'$foo'"); } #[cfg(any(unix, target_os = "wasi"))] From b5550bc4ddad5aa926f7548a9a8ebdd6b9dcdff6 Mon Sep 17 00:00:00 2001 From: Jan Verbeek Date: Mon, 30 Aug 2021 13:30:27 +0200 Subject: [PATCH 119/206] ls: Accept badly-encoded filenames --- src/uu/ls/src/ls.rs | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index bf449d96b..afb1289ee 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -248,7 +248,7 @@ struct LongFormat { impl Config { #[allow(clippy::cognitive_complexity)] - fn from(options: clap::ArgMatches) -> UResult { + fn from(options: &clap::ArgMatches) -> UResult { let (mut format, opt) = if let Some(format_) = options.value_of(options::FORMAT) { ( match format_ { @@ -599,22 +599,19 @@ impl Config { #[uucore_procs::gen_uumain] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let args = args - .collect_str(InvalidEncodingHandling::Ignore) - .accept_any(); - let usage = usage(); let app = uu_app().usage(&usage[..]); let matches = app.get_matches_from(args); + let config = Config::from(&matches)?; let locs = matches - .values_of(options::PATHS) - .map(|v| v.map(ToString::to_string).collect()) - .unwrap_or_else(|| vec![String::from(".")]); + .values_of_os(options::PATHS) + .map(|v| v.map(Path::new).collect()) + .unwrap_or_else(|| vec![Path::new(".")]); - list(locs, Config::from(matches)?) + list(locs, config) } pub fn uu_app() -> App<'static, 'static> { @@ -1249,14 +1246,14 @@ impl PathData { } } -fn list(locs: Vec, config: Config) -> UResult<()> { +fn list(locs: Vec<&Path>, config: Config) -> UResult<()> { let mut files = Vec::::new(); let mut dirs = Vec::::new(); let mut out = BufWriter::new(stdout()); for loc in &locs { - let p = PathBuf::from(&loc); + let p = PathBuf::from(loc); let path_data = PathData::new(p, None, None, &config, true); if path_data.md().is_none() { @@ -1286,6 +1283,7 @@ fn list(locs: Vec, config: Config) -> UResult<()> { sort_entries(&mut dirs, &config); for dir in dirs { if locs.len() > 1 || config.recursive { + // FIXME: This should use the quoting style and propagate errors let _ = writeln!(out, "\n{}:", dir.p_buf.display()); } enter_directory(&dir, &config, &mut out); @@ -1671,7 +1669,6 @@ fn get_inode(metadata: &Metadata) -> String { use std::sync::Mutex; #[cfg(unix)] use uucore::entries; -use uucore::InvalidEncodingHandling; #[cfg(unix)] fn cached_uid2usr(uid: u32) -> String { From a93959aa4405abc53b501e83820e252594107845 Mon Sep 17 00:00:00 2001 From: Jan Verbeek Date: Tue, 31 Aug 2021 12:49:22 +0200 Subject: [PATCH 120/206] uucore::display: impl Quotable for Cow, add escape_control --- src/uucore/src/lib/mods/display.rs | 85 ++++++++++++++++++++++-------- 1 file changed, 64 insertions(+), 21 deletions(-) diff --git a/src/uucore/src/lib/mods/display.rs b/src/uucore/src/lib/mods/display.rs index 7cc48e74f..924ed2245 100644 --- a/src/uucore/src/lib/mods/display.rs +++ b/src/uucore/src/lib/mods/display.rs @@ -20,6 +20,7 @@ /// # Ok::<(), std::io::Error>(()) /// ``` // spell-checker:ignore Fbar +use std::borrow::{Borrow, Cow}; use std::ffi::OsStr; #[cfg(any(unix, target_os = "wasi", windows))] use std::fmt::Write as FmtWrite; @@ -54,15 +55,32 @@ pub trait Quotable { fn quote(&self) -> Quoted<'_>; } -impl Quotable for T -where - T: AsRef, -{ - fn quote(&self) -> Quoted<'_> { - Quoted { - text: self.as_ref(), - force_quote: true, +macro_rules! impl_as_ref { + ($type: ty) => { + impl Quotable for $type { + fn quote(&self) -> Quoted<'_> { + Quoted::new(self.as_ref()) + } } + }; +} + +impl_as_ref!(&'_ str); +impl_as_ref!(String); +impl_as_ref!(&'_ std::path::Path); +impl_as_ref!(std::path::PathBuf); +impl_as_ref!(std::path::Component<'_>); +impl_as_ref!(std::path::Components<'_>); +impl_as_ref!(std::path::Iter<'_>); +impl_as_ref!(&'_ std::ffi::OsStr); +impl_as_ref!(std::ffi::OsString); + +// Cow<'_, str> does not implement AsRef and this is unlikely to be fixed +// for backward compatibility reasons. Otherwise we'd use a blanket impl. +impl Quotable for Cow<'_, str> { + fn quote(&self) -> Quoted<'_> { + // I suspect there's a better way to do this but I haven't found one + Quoted::new( as Borrow>::borrow(self).as_ref()) } } @@ -71,14 +89,29 @@ where pub struct Quoted<'a> { text: &'a OsStr, force_quote: bool, + escape_control: bool, } -impl Quoted<'_> { +impl<'a> Quoted<'a> { + fn new(text: &'a OsStr) -> Self { + Quoted { + text, + force_quote: true, + escape_control: true, + } + } + /// Add quotes even if not strictly necessary. `true` by default. pub fn force_quote(mut self, force: bool) -> Self { self.force_quote = force; self } + + /// Escape control characters. `true` by default. + pub fn escape_control(mut self, escape: bool) -> Self { + self.escape_control = escape; + self + } } impl Display for Quoted<'_> { @@ -89,7 +122,7 @@ impl Display for Quoted<'_> { // % seems obscure enough to ignore, it's for job control only. // {} were used in a version elsewhere but seem unnecessary, GNU doesn't escape them. // ! is a common extension. - const SPECIAL_SHELL_CHARS: &[u8] = b"|&;<>()$`\\\"'*?[]=! "; + const SPECIAL_SHELL_CHARS: &[u8] = b"|&;<>()$`\\\"'*?[]=! \t\n"; /// Characters with a special meaning at the beginning of a name. const SPECIAL_SHELL_CHARS_START: &[u8] = b"~#"; @@ -110,7 +143,9 @@ impl Display for Quoted<'_> { for &ch in text { match ch { - ch if ch.is_ascii_control() => return write_escaped(f, text), + ch if self.escape_control && ch.is_ascii_control() => { + return write_escaped(f, text, self.escape_control) + } b'\'' => is_single_safe = false, // Unsafe characters according to: // https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_02_03 @@ -122,7 +157,7 @@ impl Display for Quoted<'_> { } } let text = match from_utf8(text) { - Err(_) => return write_escaped(f, text), + Err(_) => return write_escaped(f, text, self.escape_control), Ok(text) => text, }; if !requires_quote && text.find(char::is_whitespace).is_some() { @@ -175,7 +210,7 @@ impl Display for Quoted<'_> { /// - fish /// - dash /// - tcsh - fn write_escaped(f: &mut Formatter<'_>, text: &[u8]) -> fmt::Result { + fn write_escaped(f: &mut Formatter<'_>, text: &[u8], escape_control: bool) -> fmt::Result { f.write_str("$'")?; for chunk in from_utf8_iter(text) { match chunk { @@ -190,7 +225,9 @@ impl Display for Quoted<'_> { // \0 doesn't work consistently because of the // octal \nnn syntax, and null bytes can't appear // in filenames anyway. - ch if ch.is_ascii_control() => write!(f, "\\x{:02X}", ch as u8)?, + ch if escape_control && ch.is_ascii_control() => { + write!(f, "\\x{:02X}", ch as u8)? + } '\\' | '\'' => { // '?' and '"' can also be escaped this way // but AFAICT there's no reason to do so @@ -229,14 +266,14 @@ impl Display for Quoted<'_> { // There's the additional wrinkle that Windows has stricter requirements // for filenames: I've been testing using a Linux build of PowerShell, but // this code doesn't even compile on Linux. - const SPECIAL_SHELL_CHARS: &str = "|&;<>()$`\"'*?[]=!{}@ "; + const SPECIAL_SHELL_CHARS: &str = "|&;<>()$`\"'*?[]=!{}@ \t\r\n"; /// Characters with a special meaning at the beginning of a name. const SPECIAL_SHELL_CHARS_START: &[char] = &['~', '#']; // Getting the "raw" representation of an OsStr is actually expensive, // so avoid it if unnecessary. let text = match self.text.to_str() { - None => return write_escaped(f, self.text), + None => return write_escaped(f, self.text, self.escape_control), Some(text) => text, }; @@ -255,7 +292,9 @@ impl Display for Quoted<'_> { for ch in text.chars() { match ch { - ch if ch.is_ascii_control() => return write_escaped(f, self.text), + ch if self.escape_control && ch.is_ascii_control() => { + return write_escaped(f, self.text, self.escape_control) + } '\'' => is_single_safe = false, '"' | '`' | '$' => is_double_safe = false, _ => (), @@ -291,7 +330,7 @@ impl Display for Quoted<'_> { Ok(()) } - fn write_escaped(f: &mut Formatter<'_>, text: &OsStr) -> fmt::Result { + fn write_escaped(f: &mut Formatter<'_>, text: &OsStr, escape_control: bool) -> fmt::Result { f.write_char('"')?; for ch in decode_utf16(text.encode_wide()) { match ch { @@ -300,7 +339,9 @@ impl Display for Quoted<'_> { '\r' => f.write_str("`r")?, '\n' => f.write_str("`n")?, '\t' => f.write_str("`t")?, - ch if ch.is_ascii_control() => write!(f, "`u{{{:04X}}}", ch as u8)?, + ch if escape_control && ch.is_ascii_control() => { + write!(f, "`u{{{:04X}}}", ch as u8)? + } '`' => f.write_str("``")?, '$' => f.write_str("`$")?, '"' => f.write_str("\"\"")?, @@ -382,7 +423,7 @@ pub fn println_verbatim>(text: S) -> io::Result<()> { mod tests { use super::*; - fn verify_quote(cases: &[(impl AsRef, &str)]) { + fn verify_quote(cases: &[(impl Quotable, &str)]) { for (case, expected) in cases { assert_eq!(case.quote().to_string(), *expected); } @@ -442,7 +483,9 @@ mod tests { #[cfg(any(unix, target_os = "wasi"))] #[test] fn test_utf8_iter() { - const CASES: &[(&[u8], &[Result<&str, u8>])] = &[ + type ByteStr = &'static [u8]; + type Chunk = Result<&'static str, u8>; + const CASES: &[(ByteStr, &[Chunk])] = &[ (b"", &[]), (b"hello", &[Ok("hello")]), // Immediately invalid From 4d6faae555711e45de8c04a1a84f68aebecc03ab Mon Sep 17 00:00:00 2001 From: Jan Verbeek Date: Tue, 31 Aug 2021 12:55:17 +0200 Subject: [PATCH 121/206] ls: Fix --{show, hide}-control-chars logic --- src/uu/ls/src/ls.rs | 5 ++--- tests/by-util/test_ls.rs | 24 +++++++----------------- 2 files changed, 9 insertions(+), 20 deletions(-) diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index afb1289ee..3cfb0ad2c 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -428,11 +428,10 @@ impl Config { #[allow(clippy::needless_bool)] let show_control = if options.is_present(options::HIDE_CONTROL_CHARS) { false - } else if options.is_present(options::SHOW_CONTROL_CHARS) || atty::is(atty::Stream::Stdout) - { + } else if options.is_present(options::SHOW_CONTROL_CHARS) { true } else { - false + !atty::is(atty::Stream::Stdout) }; let quoting_style = if let Some(style) = options.value_of(options::QUOTING_STYLE) { diff --git a/tests/by-util/test_ls.rs b/tests/by-util/test_ls.rs index 546fb86a8..eb239977d 100644 --- a/tests/by-util/test_ls.rs +++ b/tests/by-util/test_ls.rs @@ -1416,6 +1416,7 @@ fn test_ls_quoting_style() { // Default is shell-escape scene .ucmd() + .arg("--hide-control-chars") .arg("one\ntwo") .succeeds() .stdout_only("'one'$'\\n''two'\n"); @@ -1437,23 +1438,8 @@ fn test_ls_quoting_style() { ] { scene .ucmd() - .arg(arg) - .arg("one\ntwo") - .succeeds() - .stdout_only(format!("{}\n", correct)); - } - - for (arg, correct) in &[ - ("--quoting-style=literal", "one?two"), - ("-N", "one?two"), - ("--literal", "one?two"), - ("--quoting-style=shell", "one?two"), - ("--quoting-style=shell-always", "'one?two'"), - ] { - scene - .ucmd() - .arg(arg) .arg("--hide-control-chars") + .arg(arg) .arg("one\ntwo") .succeeds() .stdout_only(format!("{}\n", correct)); @@ -1463,7 +1449,7 @@ fn test_ls_quoting_style() { ("--quoting-style=literal", "one\ntwo"), ("-N", "one\ntwo"), ("--literal", "one\ntwo"), - ("--quoting-style=shell", "one\ntwo"), + ("--quoting-style=shell", "one\ntwo"), // FIXME: GNU ls quotes this case ("--quoting-style=shell-always", "'one\ntwo'"), ] { scene @@ -1490,6 +1476,7 @@ fn test_ls_quoting_style() { ] { scene .ucmd() + .arg("--hide-control-chars") .arg(arg) .arg("one\\two") .succeeds() @@ -1505,6 +1492,7 @@ fn test_ls_quoting_style() { ] { scene .ucmd() + .arg("--hide-control-chars") .arg(arg) .arg("one\n&two") .succeeds() @@ -1535,6 +1523,7 @@ fn test_ls_quoting_style() { ] { scene .ucmd() + .arg("--hide-control-chars") .arg(arg) .arg("one two") .succeeds() @@ -1558,6 +1547,7 @@ fn test_ls_quoting_style() { ] { scene .ucmd() + .arg("--hide-control-chars") .arg(arg) .arg("one") .succeeds() From 483a5fd1d4cd0508af81ef41a97cdb5500d300b6 Mon Sep 17 00:00:00 2001 From: Jan Verbeek Date: Tue, 31 Aug 2021 12:57:39 +0200 Subject: [PATCH 122/206] ls: Process OsStrings instead of Strings --- src/uu/ls/src/ls.rs | 20 +++++++++----------- src/uu/ls/src/quoting_style.rs | 16 ++++++++++------ 2 files changed, 19 insertions(+), 17 deletions(-) diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index 3cfb0ad2c..fcb0b28ff 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -21,6 +21,7 @@ use lscolors::LsColors; use number_prefix::NumberPrefix; use once_cell::unsync::OnceCell; use quoting_style::{escape_name, QuotingStyle}; +use std::ffi::OsString; #[cfg(windows)] use std::os::windows::fs::MetadataExt; use std::{ @@ -1173,7 +1174,7 @@ struct PathData { md: OnceCell>, ft: OnceCell>, // Name of the file - will be empty for . or .. - display_name: String, + display_name: OsString, // PathBuf that all above data corresponds to p_buf: PathBuf, must_dereference: bool, @@ -1183,7 +1184,7 @@ impl PathData { fn new( p_buf: PathBuf, file_type: Option>, - file_name: Option, + file_name: Option, config: &Config, command_line: bool, ) -> Self { @@ -1191,16 +1192,13 @@ impl PathData { // For '..', the filename is None let display_name = if let Some(name) = file_name { name + } else if command_line { + p_buf.clone().into() } else { - let display_os_str = if command_line { - p_buf.as_os_str() - } else { - p_buf - .file_name() - .unwrap_or_else(|| p_buf.iter().next_back().unwrap()) - }; - - display_os_str.to_string_lossy().into_owned() + p_buf + .file_name() + .unwrap_or_else(|| p_buf.iter().next_back().unwrap()) + .to_owned() }; let must_dereference = match &config.dereference { Dereference::All => true, diff --git a/src/uu/ls/src/quoting_style.rs b/src/uu/ls/src/quoting_style.rs index 5f421b2ee..4839370ee 100644 --- a/src/uu/ls/src/quoting_style.rs +++ b/src/uu/ls/src/quoting_style.rs @@ -1,4 +1,5 @@ use std::char::from_digit; +use std::ffi::OsStr; // These are characters with special meaning in the shell (e.g. bash). // The first const contains characters that only have a special meaning when they appear at the beginning of a name. @@ -255,19 +256,21 @@ fn shell_with_escape(name: &str, quotes: Quotes) -> (String, bool) { (escaped_str, must_quote) } -pub(super) fn escape_name(name: &str, style: &QuotingStyle) -> String { +pub(super) fn escape_name(name: &OsStr, style: &QuotingStyle) -> String { match style { QuotingStyle::Literal { show_control } => { if !show_control { - name.chars() + name.to_string_lossy() + .chars() .flat_map(|c| EscapedChar::new_literal(c).hide_control()) .collect() } else { - name.into() + name.to_string_lossy().into_owned() } } QuotingStyle::C { quotes } => { let escaped_str: String = name + .to_string_lossy() .chars() .flat_map(|c| EscapedChar::new_c(c, *quotes)) .collect(); @@ -283,6 +286,7 @@ pub(super) fn escape_name(name: &str, style: &QuotingStyle) -> String { always_quote, show_control, } => { + let name = name.to_string_lossy(); let (quotes, must_quote) = if name.contains('"') { (Quotes::Single, true) } else if name.contains('\'') { @@ -294,9 +298,9 @@ pub(super) fn escape_name(name: &str, style: &QuotingStyle) -> String { }; let (escaped_str, contains_quote_chars) = if *escape { - shell_with_escape(name, quotes) + shell_with_escape(&name, quotes) } else { - shell_without_escape(name, quotes, *show_control) + shell_without_escape(&name, quotes, *show_control) }; match (must_quote | contains_quote_chars, quotes) { @@ -362,7 +366,7 @@ mod tests { fn check_names(name: &str, map: Vec<(&str, &str)>) { assert_eq!( map.iter() - .map(|(_, style)| escape_name(name, &get_style(style))) + .map(|(_, style)| escape_name(name.as_ref(), &get_style(style))) .collect::>(), map.iter() .map(|(correct, _)| correct.to_string()) From f0f13fe1f0f9d0b9c385b1d38fbac42f528fa80c Mon Sep 17 00:00:00 2001 From: Jan Verbeek Date: Mon, 30 Aug 2021 21:53:44 +0200 Subject: [PATCH 123/206] uucore::display: Simplify The different quoting implementations are similar enough to merge parts of them. --- src/uucore/src/lib/mods/display.rs | 166 ++++++++++++----------------- 1 file changed, 69 insertions(+), 97 deletions(-) diff --git a/src/uucore/src/lib/mods/display.rs b/src/uucore/src/lib/mods/display.rs index 924ed2245..52cd45404 100644 --- a/src/uucore/src/lib/mods/display.rs +++ b/src/uucore/src/lib/mods/display.rs @@ -20,7 +20,7 @@ /// # Ok::<(), std::io::Error>(()) /// ``` // spell-checker:ignore Fbar -use std::borrow::{Borrow, Cow}; +use std::borrow::Cow; use std::ffi::OsStr; #[cfg(any(unix, target_os = "wasi", windows))] use std::fmt::Write as FmtWrite; @@ -79,8 +79,8 @@ impl_as_ref!(std::ffi::OsString); // for backward compatibility reasons. Otherwise we'd use a blanket impl. impl Quotable for Cow<'_, str> { fn quote(&self) -> Quoted<'_> { - // I suspect there's a better way to do this but I haven't found one - Quoted::new( as Borrow>::borrow(self).as_ref()) + let text: &str = self.as_ref(); + Quoted::new(text.as_ref()) } } @@ -115,25 +115,49 @@ impl<'a> Quoted<'a> { } impl Display for Quoted<'_> { - #[cfg(any(unix, target_os = "wasi"))] + #[cfg(any(windows, unix, target_os = "wasi"))] fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + // On Unix we emulate sh syntax. On Windows Powershell. + /// Characters with special meaning outside quotes. // https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_02 // % seems obscure enough to ignore, it's for job control only. // {} were used in a version elsewhere but seem unnecessary, GNU doesn't escape them. - // ! is a common extension. - const SPECIAL_SHELL_CHARS: &[u8] = b"|&;<>()$`\\\"'*?[]=! \t\n"; - /// Characters with a special meaning at the beginning of a name. - const SPECIAL_SHELL_CHARS_START: &[u8] = b"~#"; + // ! is a common extension for expanding the shell history. + #[cfg(any(unix, target_os = "wasi"))] + const SPECIAL_SHELL_CHARS: &str = "|&;<>()$`\\\"'*?[]=! \t\n"; + // FIXME: I'm not a PowerShell wizard and don't know if this is correct. + // I just copied the Unix version, removed \, and added {}@ based on + // experimentation. + // I have noticed that ~?*[] only get expanded in some contexts, so watch + // out for that if doing your own tests. + // Get-ChildItem seems unwilling to quote anything so it doesn't help. + // There's the additional wrinkle that Windows has stricter requirements + // for filenames: I've been testing using a Linux build of PowerShell, but + // this code doesn't even compile on Linux. + #[cfg(windows)] + const SPECIAL_SHELL_CHARS: &str = "|&;<>()$`\"'*?[]=!{}@ \t\r\n"; - let text = self.text.as_bytes(); + /// Characters with a special meaning at the beginning of a name. + const SPECIAL_SHELL_CHARS_START: &[char] = &['~', '#']; + + /// Characters that are dangerous in a double-quoted string. + #[cfg(any(unix, target_os = "wasi"))] + const DOUBLE_UNSAFE: &[char] = &['"', '`', '$', '\\']; + #[cfg(windows)] + const DOUBLE_UNSAFE: &[char] = &['"', '`', '$']; + + let text = match self.text.to_str() { + None => return write_escaped(f, self.text, self.escape_control), + Some(text) => text, + }; let mut is_single_safe = true; let mut is_double_safe = true; let mut requires_quote = self.force_quote; - if let Some(first) = text.get(0) { - if SPECIAL_SHELL_CHARS_START.contains(first) { + if let Some(first) = text.chars().next() { + if SPECIAL_SHELL_CHARS_START.contains(&first) { requires_quote = true; } } else { @@ -141,28 +165,28 @@ impl Display for Quoted<'_> { requires_quote = true; } - for &ch in text { - match ch { - ch if self.escape_control && ch.is_ascii_control() => { - return write_escaped(f, text, self.escape_control) + for ch in text.chars() { + if ch.is_ascii() { + if self.escape_control && ch.is_ascii_control() { + return write_escaped(f, self.text, self.escape_control); + } + if ch == '\'' { + is_single_safe = false; + } + if DOUBLE_UNSAFE.contains(&ch) { + is_double_safe = false; + } + if !requires_quote && SPECIAL_SHELL_CHARS.contains(ch) { + requires_quote = true; } - b'\'' => is_single_safe = false, - // Unsafe characters according to: - // https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_02_03 - b'"' | b'`' | b'$' | b'\\' => is_double_safe = false, - _ => (), } - if !requires_quote && SPECIAL_SHELL_CHARS.contains(&ch) { + if !requires_quote && ch.is_whitespace() { + // This includes unicode whitespace. + // We maybe don't have to escape it, we don't escape other lookalike + // characters either, but it's confusing if it goes unquoted. requires_quote = true; } } - let text = match from_utf8(text) { - Err(_) => return write_escaped(f, text, self.escape_control), - Ok(text) => text, - }; - if !requires_quote && text.find(char::is_whitespace).is_some() { - requires_quote = true; - } if !requires_quote { return f.write_str(text); @@ -174,6 +198,7 @@ impl Display for Quoted<'_> { return write_single_escaped(f, text); } + #[cfg(any(unix, target_os = "wasi"))] fn write_simple(f: &mut Formatter<'_>, text: &str, quote: char) -> fmt::Result { f.write_char(quote)?; f.write_str(text)?; @@ -181,6 +206,7 @@ impl Display for Quoted<'_> { Ok(()) } + #[cfg(any(unix, target_os = "wasi"))] fn write_single_escaped(f: &mut Formatter<'_>, text: &str) -> fmt::Result { let mut iter = text.split('\''); if let Some(chunk) = iter.next() { @@ -210,9 +236,10 @@ impl Display for Quoted<'_> { /// - fish /// - dash /// - tcsh - fn write_escaped(f: &mut Formatter<'_>, text: &[u8], escape_control: bool) -> fmt::Result { + #[cfg(any(unix, target_os = "wasi"))] + fn write_escaped(f: &mut Formatter<'_>, text: &OsStr, escape_control: bool) -> fmt::Result { f.write_str("$'")?; - for chunk in from_utf8_iter(text) { + for chunk in from_utf8_iter(text.as_bytes()) { match chunk { Ok(chunk) => { for ch in chunk.chars() { @@ -246,74 +273,8 @@ impl Display for Quoted<'_> { f.write_char('\'')?; Ok(()) } - } - - #[cfg(windows)] - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - // Behavior is based on PowerShell. - // ` takes the role of \ since \ is already used as the path separator. - // Things are UTF-16-oriented, so we escape code units as "`u{1234}". - use std::char::decode_utf16; - use std::os::windows::ffi::OsStrExt; - - /// Characters with special meaning outside quotes. - // FIXME: I'm not a PowerShell wizard and don't know if this is correct. - // I just copied the Unix version, removed \, and added {}@ based on - // experimentation. - // I have noticed that ~?*[] only get expanded in some contexts, so watch - // out for that if doing your own tests. - // Get-ChildItem seems unwilling to quote anything so it doesn't help. - // There's the additional wrinkle that Windows has stricter requirements - // for filenames: I've been testing using a Linux build of PowerShell, but - // this code doesn't even compile on Linux. - const SPECIAL_SHELL_CHARS: &str = "|&;<>()$`\"'*?[]=!{}@ \t\r\n"; - /// Characters with a special meaning at the beginning of a name. - const SPECIAL_SHELL_CHARS_START: &[char] = &['~', '#']; - - // Getting the "raw" representation of an OsStr is actually expensive, - // so avoid it if unnecessary. - let text = match self.text.to_str() { - None => return write_escaped(f, self.text, self.escape_control), - Some(text) => text, - }; - - let mut is_single_safe = true; - let mut is_double_safe = true; - let mut requires_quote = self.force_quote; - - if let Some(first) = text.chars().next() { - if SPECIAL_SHELL_CHARS_START.contains(&first) { - requires_quote = true; - } - } else { - // Empty string - requires_quote = true; - } - - for ch in text.chars() { - match ch { - ch if self.escape_control && ch.is_ascii_control() => { - return write_escaped(f, self.text, self.escape_control) - } - '\'' => is_single_safe = false, - '"' | '`' | '$' => is_double_safe = false, - _ => (), - } - if !requires_quote - && ((ch.is_ascii() && SPECIAL_SHELL_CHARS.contains(ch)) || ch.is_whitespace()) - { - requires_quote = true; - } - } - - if !requires_quote { - return f.write_str(text); - } else if is_single_safe || !is_double_safe { - return write_simple(f, text, '\''); - } else { - return write_simple(f, text, '"'); - } + #[cfg(windows)] fn write_simple(f: &mut Formatter<'_>, text: &str, quote: char) -> fmt::Result { // Quotes in Powershell can be escaped by doubling them f.write_char(quote)?; @@ -330,7 +291,18 @@ impl Display for Quoted<'_> { Ok(()) } + #[cfg(windows)] + fn write_single_escaped(f: &mut Formatter<'_>, text: &str) -> fmt::Result { + write_simple(f, text, '\'') + } + + #[cfg(windows)] fn write_escaped(f: &mut Formatter<'_>, text: &OsStr, escape_control: bool) -> fmt::Result { + // ` takes the role of \ since \ is already used as the path separator. + // Things are UTF-16-oriented, so we escape code units as "`u{1234}". + use std::char::decode_utf16; + use std::os::windows::ffi::OsStrExt; + f.write_char('"')?; for ch in decode_utf16(text.encode_wide()) { match ch { From 3dd6f7988044da8035567e2abc1bb270c8e2c052 Mon Sep 17 00:00:00 2001 From: Jan Verbeek Date: Tue, 31 Aug 2021 22:01:24 +0200 Subject: [PATCH 124/206] uucore::display: Remove escape_control, tweak special characters --- src/uucore/src/lib/mods/display.rs | 159 ++++++++++++++++++----------- 1 file changed, 99 insertions(+), 60 deletions(-) diff --git a/src/uucore/src/lib/mods/display.rs b/src/uucore/src/lib/mods/display.rs index 52cd45404..0245a401a 100644 --- a/src/uucore/src/lib/mods/display.rs +++ b/src/uucore/src/lib/mods/display.rs @@ -53,6 +53,29 @@ pub trait Quotable { /// println!("Found file {}", path.quote()); // Prints "Found file 'foo/bar.baz'" /// ``` fn quote(&self) -> Quoted<'_>; + + /// Like `quote()`, but don't actually add quotes unless necessary because of + /// whitespace or special characters. + /// + /// # Examples + /// + /// ``` + /// #[macro_use] + /// extern crate uucore; + /// use std::path::Path; + /// use uucore::display::Quotable; + /// + /// let foo = Path::new("foo/bar.baz"); + /// let bar = Path::new("foo bar"); + /// + /// show_error!("{}: Not found", foo); // Prints "util: foo/bar.baz: Not found" + /// show_error!("{}: Not found", bar); // Prints "util: 'foo bar': Not found" + /// ``` + fn maybe_quote(&self) -> Quoted<'_> { + let mut quoted = self.quote(); + quoted.force_quote = false; + quoted + } } macro_rules! impl_as_ref { @@ -89,7 +112,6 @@ impl Quotable for Cow<'_, str> { pub struct Quoted<'a> { text: &'a OsStr, force_quote: bool, - escape_control: bool, } impl<'a> Quoted<'a> { @@ -97,37 +119,26 @@ impl<'a> Quoted<'a> { Quoted { text, force_quote: true, - escape_control: true, } } - - /// Add quotes even if not strictly necessary. `true` by default. - pub fn force_quote(mut self, force: bool) -> Self { - self.force_quote = force; - self - } - - /// Escape control characters. `true` by default. - pub fn escape_control(mut self, escape: bool) -> Self { - self.escape_control = escape; - self - } } impl Display for Quoted<'_> { #[cfg(any(windows, unix, target_os = "wasi"))] fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { // On Unix we emulate sh syntax. On Windows Powershell. + // They're just similar enough to share some code. /// Characters with special meaning outside quotes. // https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_02 - // % seems obscure enough to ignore, it's for job control only. - // {} were used in a version elsewhere but seem unnecessary, GNU doesn't escape them. - // ! is a common extension for expanding the shell history. + // I don't know why % is in there, and GNU doesn't quote it either. + // {} were used in a version elsewhere but seem unnecessary, GNU doesn't + // quote them. They're used in function definitions but not in a way we + // have to worry about. #[cfg(any(unix, target_os = "wasi"))] - const SPECIAL_SHELL_CHARS: &str = "|&;<>()$`\\\"'*?[]=! \t\n"; + const SPECIAL_SHELL_CHARS: &[u8] = b"|&;<>()$`\\\"'*?[]="; // FIXME: I'm not a PowerShell wizard and don't know if this is correct. - // I just copied the Unix version, removed \, and added {}@ based on + // I just copied the Unix version, removed \, and added ,{} based on // experimentation. // I have noticed that ~?*[] only get expanded in some contexts, so watch // out for that if doing your own tests. @@ -136,19 +147,29 @@ impl Display for Quoted<'_> { // for filenames: I've been testing using a Linux build of PowerShell, but // this code doesn't even compile on Linux. #[cfg(windows)] - const SPECIAL_SHELL_CHARS: &str = "|&;<>()$`\"'*?[]=!{}@ \t\r\n"; + const SPECIAL_SHELL_CHARS: &[u8] = b"|&;<>()$`\"'*?[]=,{}"; /// Characters with a special meaning at the beginning of a name. - const SPECIAL_SHELL_CHARS_START: &[char] = &['~', '#']; - - /// Characters that are dangerous in a double-quoted string. + // ~ expands a home directory. + // # starts a comment. + // ! is a common extension for expanding the shell history. #[cfg(any(unix, target_os = "wasi"))] - const DOUBLE_UNSAFE: &[char] = &['"', '`', '$', '\\']; + const SPECIAL_SHELL_CHARS_START: &[char] = &['~', '#', '!']; + // Same deal as before, this is possibly incomplete. + // '-' is included because unlike in Unix, quoting an argument may stop it + // from being recognized as an option. I like that very much. + // A single stand-alone exclamation mark seems to have some special meaning. #[cfg(windows)] - const DOUBLE_UNSAFE: &[char] = &['"', '`', '$']; + const SPECIAL_SHELL_CHARS_START: &[char] = &['~', '#', '@', '-', '!']; + + /// Characters that are interpreted specially in a double-quoted string. + #[cfg(any(unix, target_os = "wasi"))] + const DOUBLE_UNSAFE: &[u8] = &[b'"', b'`', b'$', b'\\']; + #[cfg(windows)] + const DOUBLE_UNSAFE: &[u8] = &[b'"', b'`', b'$']; let text = match self.text.to_str() { - None => return write_escaped(f, self.text, self.escape_control), + None => return write_escaped(f, self.text), Some(text) => text, }; @@ -167,18 +188,19 @@ impl Display for Quoted<'_> { for ch in text.chars() { if ch.is_ascii() { - if self.escape_control && ch.is_ascii_control() { - return write_escaped(f, self.text, self.escape_control); - } - if ch == '\'' { + let ch = ch as u8; + if ch == b'\'' { is_single_safe = false; } if DOUBLE_UNSAFE.contains(&ch) { is_double_safe = false; } - if !requires_quote && SPECIAL_SHELL_CHARS.contains(ch) { + if !requires_quote && SPECIAL_SHELL_CHARS.contains(&ch) { requires_quote = true; } + if ch.is_ascii_control() { + return write_escaped(f, self.text); + } } if !requires_quote && ch.is_whitespace() { // This includes unicode whitespace. @@ -198,7 +220,6 @@ impl Display for Quoted<'_> { return write_single_escaped(f, text); } - #[cfg(any(unix, target_os = "wasi"))] fn write_simple(f: &mut Formatter<'_>, text: &str, quote: char) -> fmt::Result { f.write_char(quote)?; f.write_str(text)?; @@ -237,7 +258,7 @@ impl Display for Quoted<'_> { /// - dash /// - tcsh #[cfg(any(unix, target_os = "wasi"))] - fn write_escaped(f: &mut Formatter<'_>, text: &OsStr, escape_control: bool) -> fmt::Result { + fn write_escaped(f: &mut Formatter<'_>, text: &OsStr) -> fmt::Result { f.write_str("$'")?; for chunk in from_utf8_iter(text.as_bytes()) { match chunk { @@ -252,9 +273,7 @@ impl Display for Quoted<'_> { // \0 doesn't work consistently because of the // octal \nnn syntax, and null bytes can't appear // in filenames anyway. - ch if escape_control && ch.is_ascii_control() => { - write!(f, "\\x{:02X}", ch as u8)? - } + ch if ch.is_ascii_control() => write!(f, "\\x{:02X}", ch as u8)?, '\\' | '\'' => { // '?' and '"' can also be escaped this way // but AFAICT there's no reason to do so @@ -275,29 +294,23 @@ impl Display for Quoted<'_> { } #[cfg(windows)] - fn write_simple(f: &mut Formatter<'_>, text: &str, quote: char) -> fmt::Result { + fn write_single_escaped(f: &mut Formatter<'_>, text: &str) -> fmt::Result { // Quotes in Powershell can be escaped by doubling them - f.write_char(quote)?; - let mut iter = text.split(quote); + f.write_char('\'')?; + let mut iter = text.split('\''); if let Some(chunk) = iter.next() { f.write_str(chunk)?; } for chunk in iter { - f.write_char(quote)?; - f.write_char(quote)?; + f.write_str("''")?; f.write_str(chunk)?; } - f.write_char(quote)?; + f.write_char('\'')?; Ok(()) } #[cfg(windows)] - fn write_single_escaped(f: &mut Formatter<'_>, text: &str) -> fmt::Result { - write_simple(f, text, '\'') - } - - #[cfg(windows)] - fn write_escaped(f: &mut Formatter<'_>, text: &OsStr, escape_control: bool) -> fmt::Result { + fn write_escaped(f: &mut Formatter<'_>, text: &OsStr) -> fmt::Result { // ` takes the role of \ since \ is already used as the path separator. // Things are UTF-16-oriented, so we escape code units as "`u{1234}". use std::char::decode_utf16; @@ -311,9 +324,7 @@ impl Display for Quoted<'_> { '\r' => f.write_str("`r")?, '\n' => f.write_str("`n")?, '\t' => f.write_str("`t")?, - ch if escape_control && ch.is_ascii_control() => { - write!(f, "`u{{{:04X}}}", ch as u8)? - } + ch if ch.is_ascii_control() => write!(f, "`u{{{:04X}}}", ch as u8)?, '`' => f.write_str("``")?, '$' => f.write_str("`$")?, '"' => f.write_str("\"\"")?, @@ -401,7 +412,13 @@ mod tests { } } - /// This should hold on any platform. + fn verify_maybe(cases: &[(impl Quotable, &str)]) { + for (case, expected) in cases { + assert_eq!(case.maybe_quote().to_string(), *expected); + } + } + + /// This should hold on any platform, or else other tests fail. #[test] fn test_basic() { verify_quote(&[ @@ -409,13 +426,23 @@ mod tests { ("", "''"), ("foo/bar.baz", "'foo/bar.baz'"), ]); - assert_eq!("foo".quote().force_quote(false).to_string(), "foo"); - assert_eq!("".quote().force_quote(false).to_string(), "''"); - assert_eq!( - "foo bar".quote().force_quote(false).to_string(), - "'foo bar'" - ); - assert_eq!("$foo".quote().force_quote(false).to_string(), "'$foo'"); + verify_maybe(&[ + ("foo", "foo"), + ("", "''"), + ("foo bar", "'foo bar'"), + ("$foo", "'$foo'"), + ]); + } + + #[cfg(any(unix, target_os = "wasi", windows))] + #[test] + fn test_common() { + verify_maybe(&[ + ("a#b", "a#b"), + ("#ab", "'#ab'"), + ("a~b", "a~b"), + ("!", "'!'"), + ]); } #[cfg(any(unix, target_os = "wasi"))] @@ -430,6 +457,12 @@ mod tests { (r#"'$''"#, r#"\''$'\'\'"#), ]); verify_quote(&[(OsStr::from_bytes(b"foo\xFF"), r#"$'foo\xFF'"#)]); + verify_maybe(&[ + ("-x", "-x"), + ("a,b", "a,b"), + ("a\\b", "'a\\b'"), + ("}", ("}")), + ]); } #[cfg(windows)] @@ -449,7 +482,13 @@ mod tests { verify_quote(&[( OsString::from_wide(&[b'x' as u16, 0xD800]), r#""x`u{D800}""#, - )]) + )]); + verify_maybe(&[ + ("-x", "'-x'"), + ("a,b", "'a,b'"), + ("a\\b", "a\\b"), + ("}", "'}'"), + ]); } #[cfg(any(unix, target_os = "wasi"))] From ffe63945b7e75379c0be05c804548a016e117565 Mon Sep 17 00:00:00 2001 From: Jan Verbeek Date: Tue, 31 Aug 2021 22:02:20 +0200 Subject: [PATCH 125/206] wc: Update path display --- src/uu/wc/src/wc.rs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/uu/wc/src/wc.rs b/src/uu/wc/src/wc.rs index 01c3d8fdc..6917eb137 100644 --- a/src/uu/wc/src/wc.rs +++ b/src/uu/wc/src/wc.rs @@ -24,6 +24,8 @@ use std::fs::{self, File}; use std::io::{self, Write}; use std::path::{Path, PathBuf}; +use uucore::display::{Quotable, Quoted}; + /// The minimum character width for formatting counts when reading from stdin. const MINIMUM_WIDTH: usize = 7; @@ -122,10 +124,10 @@ impl Input { } } - fn path_display(&self) -> std::path::Display<'_> { + fn path_display(&self) -> Quoted<'_> { match self { - Input::Path(path) => path.display(), - Input::Stdin(_) => Path::display("'standard input'".as_ref()), + Input::Path(path) => path.maybe_quote(), + Input::Stdin(_) => "standard input".maybe_quote(), } } } @@ -448,7 +450,10 @@ fn wc(inputs: Vec, settings: &Settings) -> Result<(), u32> { if let Err(err) = print_stats(settings, &result, max_width) { show_warning!( "failed to print result for {}: {}", - result.title.unwrap_or_else(|| "".as_ref()).display(), + result + .title + .unwrap_or_else(|| "".as_ref()) + .maybe_quote(), err ); failure = true; @@ -526,7 +531,7 @@ fn print_stats( } if let Some(title) = result.title { - writeln!(stdout_lock, " {}", title.display())?; + writeln!(stdout_lock, " {}", title.maybe_quote())?; } else { writeln!(stdout_lock)?; } From 7ca2f4989fcca103b2663c9af97106c85227a846 Mon Sep 17 00:00:00 2001 From: Jan Verbeek Date: Tue, 31 Aug 2021 22:06:00 +0200 Subject: [PATCH 126/206] ls: Hide unused variable warnings --- tests/by-util/test_ls.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/by-util/test_ls.rs b/tests/by-util/test_ls.rs index eb239977d..e6c23acc1 100644 --- a/tests/by-util/test_ls.rs +++ b/tests/by-util/test_ls.rs @@ -354,6 +354,7 @@ fn test_ls_long_format() { at.mkdir(&at.plus_as_string("test-long-dir/test-long-dir")); for arg in &["-l", "--long", "--format=long", "--format=verbose"] { + #[allow(unused_variables)] let result = scene.ucmd().arg(arg).arg("test-long-dir").succeeds(); // Assuming sane username do not have spaces within them. // A line of the output should be: @@ -373,6 +374,7 @@ fn test_ls_long_format() { ).unwrap()); } + #[allow(unused_variables)] let result = scene.ucmd().arg("-lan").arg("test-long-dir").succeeds(); // This checks for the line with the .. entry. The uname and group should be digits. #[cfg(not(windows))] From 575fbd4cb774b691819b925451a9a7df3da82655 Mon Sep 17 00:00:00 2001 From: Justin Tracey Date: Tue, 31 Aug 2021 13:25:03 -0400 Subject: [PATCH 127/206] join: make autoformat actually construct a format Makes the -o auto option construct a format at initialization, rather than try to handle it as a special case when printing lines. Fixes bugs when combined with -e, especially when combined with -a. --- src/uu/join/src/join.rs | 58 ++++++++++++++++++++++---------------- tests/by-util/test_join.rs | 13 +++++++++ 2 files changed, 47 insertions(+), 24 deletions(-) diff --git a/src/uu/join/src/join.rs b/src/uu/join/src/join.rs index d108a08ef..ae991489f 100644 --- a/src/uu/join/src/join.rs +++ b/src/uu/join/src/join.rs @@ -11,7 +11,7 @@ extern crate uucore; use clap::{crate_version, App, Arg}; -use std::cmp::{min, Ordering}; +use std::cmp::Ordering; use std::fs::File; use std::io::{stdin, BufRead, BufReader, Lines, Stdin}; @@ -102,17 +102,12 @@ impl<'a> Repr<'a> { } /// Print each field except the one at the index. - fn print_fields(&self, line: &Line, index: usize, max_fields: Option) { - for i in 0..min(max_fields.unwrap_or(usize::max_value()), line.fields.len()) { + fn print_fields(&self, line: &Line, index: usize) { + for i in 0..line.fields.len() { if i != index { print!("{}{}", self.separator, line.fields[i]); } } - if let Some(n) = max_fields { - for _ in line.fields.len()..n { - print!("{}", self.separator) - } - } } /// Print each field or the empty filler if the field is not set. @@ -233,7 +228,6 @@ struct State<'a> { print_unpaired: bool, lines: Lines>, seq: Vec, - max_fields: Option, line_num: usize, has_failed: bool, } @@ -262,7 +256,6 @@ impl<'a> State<'a> { print_unpaired, lines: f.lines(), seq: Vec::new(), - max_fields: None, line_num: 0, has_failed: false, } @@ -329,8 +322,8 @@ impl<'a> State<'a> { }); } else { repr.print_field(key); - repr.print_fields(line1, self.key, self.max_fields); - repr.print_fields(line2, other.key, other.max_fields); + repr.print_fields(line1, self.key); + repr.print_fields(line2, other.key); } println!(); @@ -361,14 +354,15 @@ impl<'a> State<'a> { !self.seq.is_empty() } - fn initialize(&mut self, read_sep: Sep, autoformat: bool) { + fn initialize(&mut self, read_sep: Sep, autoformat: bool) -> usize { if let Some(line) = self.read_line(read_sep) { - if autoformat { - self.max_fields = Some(line.fields.len()); - } - self.seq.push(line); + + if autoformat { + return self.seq[0].fields.len(); + } } + 0 } fn finalize(&mut self, input: &Input, repr: &Repr) { @@ -431,7 +425,7 @@ impl<'a> State<'a> { }); } else { repr.print_field(line.get_field(self.key)); - repr.print_fields(line, self.key, self.max_fields); + repr.print_fields(line, self.key); } println!(); @@ -512,7 +506,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { crash!(1, "both files cannot be standard input"); } - exec(file1, file2, &settings) + exec(file1, file2, settings) } pub fn uu_app() -> App<'static, 'static> { @@ -622,7 +616,7 @@ FILENUM is 1 or 2, corresponding to FILE1 or FILE2", ) } -fn exec(file1: &str, file2: &str, settings: &Settings) -> i32 { +fn exec(file1: &str, file2: &str, settings: Settings) -> i32 { let stdin = stdin(); let mut state1 = State::new( @@ -647,18 +641,34 @@ fn exec(file1: &str, file2: &str, settings: &Settings) -> i32 { settings.check_order, ); + let format = if settings.autoformat { + let mut format = vec![Spec::Key]; + let mut initialize = |state: &mut State| { + let max_fields = state.initialize(settings.separator, settings.autoformat); + for i in 0..max_fields { + if i != state.key { + format.push(Spec::Field(state.file_num, i)); + } + } + }; + initialize(&mut state1); + initialize(&mut state2); + format + } else { + state1.initialize(settings.separator, settings.autoformat); + state2.initialize(settings.separator, settings.autoformat); + settings.format + }; + let repr = Repr::new( match settings.separator { Sep::Char(sep) => sep, _ => ' ', }, - &settings.format, + &format, &settings.empty, ); - state1.initialize(settings.separator, settings.autoformat); - state2.initialize(settings.separator, settings.autoformat); - if settings.headers { state1.print_headers(&state2, &repr); state1.reset_read_line(&input); diff --git a/tests/by-util/test_join.rs b/tests/by-util/test_join.rs index 1cab8361a..1d92bf8e7 100644 --- a/tests/by-util/test_join.rs +++ b/tests/by-util/test_join.rs @@ -227,6 +227,19 @@ fn autoformat() { .pipe_in("1 x y z\n2 p") .succeeds() .stdout_only("1 x y z a\n2 p b\n"); + + new_ucmd!() + .arg("-") + .arg("fields_2.txt") + .arg("-a") + .arg("1") + .arg("-o") + .arg("auto") + .arg("-e") + .arg(".") + .pipe_in("1 x y z\n2 p\n99 a b\n") + .succeeds() + .stdout_only("1 x y z a\n2 p . . b\n99 a b . .\n"); } #[test] From 8b74562820394359a38f849e291987176e0dc063 Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Mon, 30 Aug 2021 01:27:47 +0200 Subject: [PATCH 128/206] cp: correctly copy mode, ownership, acl and context Fix a mix-up between ownership and mode. The latter (mode / file permissions) can also be set on windows (which however only affects the read-only flag), while there doesn't seem to be a straight-forward way to change file ownership on windows. Copy the acl as well when copying the mode. This is a non-default feature and can be enabled with --features feat_acl, because it doesn't seem to work on CI. It is only available for unix so far. Copy the SELinux context if possible. --- .../cspell.dictionaries/jargon.wordlist.txt | 3 + .../workspace.wordlist.txt | 1 + Cargo.lock | 112 +++++++++++- Cargo.toml | 7 +- src/uu/cp/Cargo.toml | 8 +- src/uu/cp/src/cp.rs | 171 +++++++++++++----- src/uucore/Cargo.toml | 2 +- tests/by-util/test_cp.rs | 50 ++++- 8 files changed, 303 insertions(+), 51 deletions(-) diff --git a/.vscode/cspell.dictionaries/jargon.wordlist.txt b/.vscode/cspell.dictionaries/jargon.wordlist.txt index 34abfc511..516d5b4c5 100644 --- a/.vscode/cspell.dictionaries/jargon.wordlist.txt +++ b/.vscode/cspell.dictionaries/jargon.wordlist.txt @@ -35,6 +35,7 @@ falsey fileio flamegraph fullblock +getfacl gibi gibibytes glob @@ -49,6 +50,7 @@ iflag iflags kibi kibibytes +libacl lcase lossily mebi @@ -91,6 +93,7 @@ seedable semver semiprime semiprimes +setfacl shortcode shortcodes siginfo diff --git a/.vscode/cspell.dictionaries/workspace.wordlist.txt b/.vscode/cspell.dictionaries/workspace.wordlist.txt index 82cbbe15f..29957fb12 100644 --- a/.vscode/cspell.dictionaries/workspace.wordlist.txt +++ b/.vscode/cspell.dictionaries/workspace.wordlist.txt @@ -16,6 +16,7 @@ chrono conv corasick crossterm +exacl filetime formatteriteminfo fsext diff --git a/Cargo.lock b/Cargo.lock index c632db295..514bdf45a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -650,6 +650,17 @@ dependencies = [ "syn", ] +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote 1.0.9", + "syn", +] + [[package]] name = "diff" version = "0.1.12" @@ -721,6 +732,21 @@ dependencies = [ "termcolor", ] +[[package]] +name = "exacl" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "769bbd173781e84865b957cf83449f0d2869f4c9d2f191cbbffffb3d9751ba2b" +dependencies = [ + "bitflags", + "log", + "nix 0.21.0", + "num_enum", + "scopeguard", + "serde", + "uuid", +] + [[package]] name = "fake-simd" version = "0.1.2" @@ -964,9 +990,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" -version = "0.2.85" +version = "0.2.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ccac4b00700875e6a07c6cde370d44d32fa01c5a65cdd2fca6858c479d28bb3" +checksum = "3cb00336871be5ed2c8ed44b60ae9959dc5b9f08539422ed43f09e34ecaeba21" [[package]] name = "libloading" @@ -1115,6 +1141,19 @@ dependencies = [ "libc", ] +[[package]] +name = "nix" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c3728fec49d363a50a8828a190b379a446cc5cf085c06259bbbeb34447e4ec7" +dependencies = [ + "bitflags", + "cc", + "cfg-if 1.0.0", + "libc", + "memoffset", +] + [[package]] name = "nodrop" version = "0.1.14" @@ -1182,6 +1221,28 @@ dependencies = [ "libc", ] +[[package]] +name = "num_enum" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9bd055fb730c4f8f4f57d45d35cd6b3f0980535b056dc7ff119cee6a66ed6f" +dependencies = [ + "derivative", + "num_enum_derive", +] + +[[package]] +name = "num_enum_derive" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "486ea01961c4a818096de679a8b740b26d9033146ac5291b1c98557658f8cdd9" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote 1.0.9", + "syn", +] + [[package]] name = "number_prefix" version = "0.4.0" @@ -1349,6 +1410,16 @@ dependencies = [ "output_vt100", ] +[[package]] +name = "proc-macro-crate" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41fdbd1df62156fbc5945f4762632564d7d038153091c3fcf1067f6aef7cff92" +dependencies = [ + "thiserror", + "toml", +] + [[package]] name = "proc-macro-error" version = "1.0.4" @@ -1706,6 +1777,26 @@ dependencies = [ "walkdir", ] +[[package]] +name = "serde" +version = "1.0.130" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f12d06de37cf59146fbdecab66aa99f9fe4f78722e3607577a5375d66bd0c913" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.130" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7bc1a1ab1961464eae040d96713baa5a724a8152c1222492465b54322ec508b" +dependencies = [ + "proc-macro2", + "quote 1.0.9", + "syn", +] + [[package]] name = "sha1" version = "0.6.0" @@ -1981,6 +2072,15 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "toml" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" +dependencies = [ + "serde", +] + [[package]] name = "typenum" version = "1.13.0" @@ -2196,10 +2296,12 @@ name = "uu_cp" version = "0.0.7" dependencies = [ "clap", + "exacl", "filetime", "ioctl-sys", "libc", "quick-error 1.2.3", + "selinux", "uucore", "uucore_procs", "walkdir", @@ -3192,6 +3294,12 @@ dependencies = [ "syn", ] +[[package]] +name = "uuid" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" + [[package]] name = "vec_map" version = "0.8.2" diff --git a/Cargo.toml b/Cargo.toml index 84e1494de..3a2c5f12a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -147,7 +147,12 @@ feat_os_unix_musl = [ # NOTE: # The selinux(-sys) crate requires `libselinux` headers and shared library to be accessible in the C toolchain at compile time. # Running a uutils compiled with `feat_selinux` requires an SELinux enabled Kernel at run time. -feat_selinux = ["id/selinux", "selinux", "feat_require_selinux"] +feat_selinux = ["cp/selinux", "id/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. +# On FreeBSD and macOS this is not required. +feat_acl = ["cp/feat_acl"] ## feature sets with requirements (restricting cross-platform availability) # # ** NOTE: these `feat_require_...` sets should be minimized as much as possible to encourage cross-platform availability of utilities diff --git a/src/uu/cp/Cargo.toml b/src/uu/cp/Cargo.toml index ca292c9a1..690a01425 100644 --- a/src/uu/cp/Cargo.toml +++ b/src/uu/cp/Cargo.toml @@ -23,7 +23,8 @@ clap = { version = "2.33", features = ["wrap_help"] } filetime = "0.2" libc = "0.2.85" quick-error = "1.2.3" -uucore = { version=">=0.0.9", package="uucore", path="../../uucore", features=["fs"] } +selinux = { version="0.2.3", optional=true } +uucore = { version=">=0.0.9", package="uucore", path="../../uucore", features=["fs", "perms"] } uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_procs" } walkdir = "2.2" @@ -35,7 +36,12 @@ winapi = { version="0.3", features=["fileapi"] } [target.'cfg(unix)'.dependencies] xattr="0.2.1" +exacl= { version = "0.6.0", optional=true } [[bin]] name = "cp" path = "src/main.rs" + +[features] +feat_selinux = ["selinux"] +feat_acl = ["exacl"] diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index df70ccea8..772e104ef 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -18,6 +18,7 @@ extern crate quick_error; #[macro_use] extern crate uucore; +use quick_error::Context; #[cfg(windows)] use winapi::um::fileapi::CreateFileW; #[cfg(windows)] @@ -67,6 +68,7 @@ quick_error! { IoErrContext(err: io::Error, path: String) { display("{}: {}", path, err) context(path: &'a str, err: io::Error) -> (err, path.to_owned()) + context(context: String, err: io::Error) -> (err, context) cause(err) } @@ -180,12 +182,15 @@ pub enum CopyMode { AttrOnly, } -#[derive(Clone, Eq, PartialEq)] +// The ordering here determines the order in which attributes are (re-)applied. +// In particular, Ownership must be changed first to avoid interfering with mode change. +#[derive(Clone, Eq, PartialEq, Debug, PartialOrd, Ord)] pub enum Attribute { #[cfg(unix)] - Mode, Ownership, + Mode, Timestamps, + #[cfg(feature = "feat_selinux")] Context, Links, Xattr, @@ -240,7 +245,7 @@ mod options { pub const LINK: &str = "link"; pub const NO_CLOBBER: &str = "no-clobber"; pub const NO_DEREFERENCE: &str = "no-dereference"; - pub const NO_DEREFERENCE_PRESERVE_LINKS: &str = "no-dereference-preserve-linkgs"; + pub const NO_DEREFERENCE_PRESERVE_LINKS: &str = "no-dereference-preserve-links"; pub const NO_PRESERVE: &str = "no-preserve"; pub const NO_TARGET_DIRECTORY: &str = "no-target-directory"; pub const ONE_FILE_SYSTEM: &str = "one-file-system"; @@ -266,6 +271,7 @@ static PRESERVABLE_ATTRIBUTES: &[&str] = &[ "mode", "ownership", "timestamps", + #[cfg(feature = "feat_selinux")] "context", "links", "xattr", @@ -273,18 +279,12 @@ static PRESERVABLE_ATTRIBUTES: &[&str] = &[ ]; #[cfg(not(unix))] -static PRESERVABLE_ATTRIBUTES: &[&str] = &[ - "ownership", - "timestamps", - "context", - "links", - "xattr", - "all", -]; +static PRESERVABLE_ATTRIBUTES: &[&str] = + &["mode", "timestamps", "context", "links", "xattr", "all"]; static DEFAULT_ATTRIBUTES: &[Attribute] = &[ - #[cfg(unix)] Attribute::Mode, + #[cfg(unix)] Attribute::Ownership, Attribute::Timestamps, ]; @@ -381,13 +381,13 @@ pub fn uu_app() -> App<'static, 'static> { .conflicts_with_all(&[options::PRESERVE_DEFAULT_ATTRIBUTES, options::NO_PRESERVE]) // -d sets this option // --archive sets this option - .help("Preserve the specified attributes (default: mode (unix only), ownership, timestamps), \ + .help("Preserve the specified attributes (default: mode, ownership (unix only), timestamps), \ if possible additional attributes: context, links, xattr, all")) .arg(Arg::with_name(options::PRESERVE_DEFAULT_ATTRIBUTES) .short("-p") .long(options::PRESERVE_DEFAULT_ATTRIBUTES) .conflicts_with_all(&[options::PRESERVE, options::NO_PRESERVE, options::ARCHIVE]) - .help("same as --preserve=mode(unix only),ownership,timestamps")) + .help("same as --preserve=mode,ownership(unix only),timestamps")) .arg(Arg::with_name(options::NO_PRESERVE) .long(options::NO_PRESERVE) .takes_value(true) @@ -532,10 +532,11 @@ impl FromStr for Attribute { fn from_str(value: &str) -> CopyResult { Ok(match &*value.to_lowercase() { - #[cfg(unix)] "mode" => Attribute::Mode, + #[cfg(unix)] "ownership" => Attribute::Ownership, "timestamps" => Attribute::Timestamps, + #[cfg(feature = "feat_selinux")] "context" => Attribute::Context, "links" => Attribute::Links, "xattr" => Attribute::Xattr, @@ -552,14 +553,16 @@ impl FromStr for Attribute { fn add_all_attributes() -> Vec { use Attribute::*; - #[cfg(target_os = "windows")] - let attr = vec![Ownership, Timestamps, Context, Xattr, Links]; - - #[cfg(not(target_os = "windows"))] - let mut attr = vec![Ownership, Timestamps, Context, Xattr, Links]; - - #[cfg(unix)] - attr.insert(0, Mode); + let attr = vec![ + #[cfg(unix)] + Ownership, + Mode, + Timestamps, + #[cfg(feature = "feat_selinux")] + Context, + Links, + Xattr, + ]; attr } @@ -602,7 +605,7 @@ impl Options { .map(ToString::to_string); // Parse attributes to preserve - let preserve_attributes: Vec = if matches.is_present(options::PRESERVE) { + let mut preserve_attributes: Vec = if matches.is_present(options::PRESERVE) { match matches.values_of(options::PRESERVE) { None => DEFAULT_ATTRIBUTES.to_vec(), Some(attribute_strs) => { @@ -629,6 +632,11 @@ impl Options { vec![] }; + // Make sure ownership is changed before other attributes, + // as chown clears some of the permission and therefore could undo previous changes + // if not executed first. + preserve_attributes.sort_unstable(); + let options = Options { attributes_only: matches.is_present(options::ATTRIBUTES_ONLY), copy_contents: matches.is_present(options::COPY_CONTENTS), @@ -1048,28 +1056,79 @@ impl OverwriteMode { } } -fn copy_attribute(source: &Path, dest: &Path, attribute: &Attribute) -> CopyResult<()> { +fn copy_attribute( + source: &Path, + dest: &Path, + attribute: &Attribute, + options: &Options, +) -> CopyResult<()> { let context = &*format!("'{}' -> '{}'", source.display().to_string(), dest.display()); + let source_metadata = if options.dereference { + source.metadata() + } else { + source.symlink_metadata() + } + .context(context)?; match *attribute { - #[cfg(unix)] Attribute::Mode => { - let mode = fs::metadata(source).context(context)?.permissions().mode(); - let mut dest_metadata = fs::metadata(source).context(context)?.permissions(); - dest_metadata.set_mode(mode); + fs::set_permissions(dest, source_metadata.permissions()).context(context)?; + dest.metadata().unwrap().permissions(); + // FIXME: Implement this for windows as well + #[cfg(feature = "feat_acl")] + exacl::getfacl(source, None) + .and_then(|acl| exacl::setfacl(&[dest], &acl, None)) + .map_err(|err| Error::Error(err.to_string()))?; } + #[cfg(unix)] Attribute::Ownership => { - let metadata = fs::metadata(source).context(context)?; - fs::set_permissions(dest, metadata.permissions()).context(context)?; + use std::os::unix::prelude::MetadataExt; + use uucore::perms::wrap_chown; + use uucore::perms::Verbosity; + use uucore::perms::VerbosityLevel; + + let dest_uid = source_metadata.uid(); + let dest_gid = source_metadata.gid(); + + wrap_chown( + dest, + &dest.metadata().context(context)?, + Some(dest_uid), + Some(dest_gid), + false, + Verbosity { + groups_only: false, + level: VerbosityLevel::Normal, + }, + ) + .map_err(Error::Error)?; } Attribute::Timestamps => { - let metadata = fs::metadata(source)?; filetime::set_file_times( Path::new(dest), - FileTime::from_last_access_time(&metadata), - FileTime::from_last_modification_time(&metadata), + FileTime::from_last_access_time(&source_metadata), + FileTime::from_last_modification_time(&source_metadata), )?; } - Attribute::Context => {} + #[cfg(feature = "feat_selinux")] + Attribute::Context => { + let context = selinux::SecurityContext::of_path(source, options.dereference, false) + .map_err(|e| { + format!( + "failed to get security context of {}: {}", + source.display(), + e + ) + })?; + if let Some(context) = context { + context.set_for_path(dest, false, false).map_err(|e| { + format!( + "failed to set security context for {}: {}", + dest.display(), + e + ) + })?; + } + } Attribute::Links => {} Attribute::Xattr => { #[cfg(unix)] @@ -1087,6 +1146,7 @@ fn copy_attribute(source: &Path, dest: &Path, attribute: &Attribute) -> CopyResu } } }; + dest.metadata().unwrap().permissions(); Ok(()) } @@ -1160,17 +1220,37 @@ fn copy_file(source: &Path, dest: &Path, options: &Options) -> CopyResult<()> { if options.verbose { println!("{}", context_for(source, dest)); } + // FIXME: `source` and `dest` should be dereferenced here if appropriate, so that the rest + // of the code does not have to check `options.dereference` all the time and can work with the paths directly. - #[allow(unused)] - { - // TODO: implement --preserve flag - let mut preserve_context = false; - for attribute in &options.preserve_attributes { - if *attribute == Attribute::Context { - preserve_context = true; - } + let dest_permissions = if dest.exists() { + dest.metadata() + .map_err(|e| Context(context_for(dest, source), e))? + .permissions() + } else { + #[allow(unused_mut)] + let mut permissions = source + .metadata() + .map_err(|e| Context(context_for(dest, source), e))? + .permissions(); + #[cfg(unix)] + { + use uucore::mode::get_umask; + + let mut mode = permissions.mode(); + + // remove sticky bit, suid and gid bit + const SPECIAL_PERMS_MASK: u32 = 0o7000; + mode &= !SPECIAL_PERMS_MASK; + + // apply umask + mode &= !get_umask(); + + permissions.set_mode(mode); } - } + permissions + }; + match options.copy_mode { CopyMode::Link => { fs::hard_link(source, dest).context(&*context_for(source, dest))?; @@ -1207,8 +1287,9 @@ fn copy_file(source: &Path, dest: &Path, options: &Options) -> CopyResult<()> { .unwrap(); } }; + fs::set_permissions(dest, dest_permissions).unwrap(); for attribute in &options.preserve_attributes { - copy_attribute(source, dest, attribute)?; + copy_attribute(source, dest, attribute, options)?; } Ok(()) } diff --git a/src/uucore/Cargo.toml b/src/uucore/Cargo.toml index e45c9e5b5..fd78e6551 100644 --- a/src/uucore/Cargo.toml +++ b/src/uucore/Cargo.toml @@ -29,7 +29,7 @@ walkdir = { version="2.3.2", optional=true } data-encoding = { version="2.1", optional=true } data-encoding-macro = { version="0.1.12", optional=true } z85 = { version="3.0.3", optional=true } -libc = { version="0.2.15, <= 0.2.85", optional=true } ## libc: initial utmp support added in v0.2.15; but v0.2.68 breaks the build for MinSRV v1.31.0 +libc = { version="0.2.15", optional=true } [dev-dependencies] clap = "2.33.3" diff --git a/tests/by-util/test_cp.rs b/tests/by-util/test_cp.rs index b6b4c3311..c77f4c01d 100644 --- a/tests/by-util/test_cp.rs +++ b/tests/by-util/test_cp.rs @@ -7,7 +7,7 @@ use std::fs::set_permissions; #[cfg(not(windows))] use std::os::unix::fs; -#[cfg(target_os = "linux")] +#[cfg(unix)] use std::os::unix::fs::PermissionsExt; #[cfg(windows)] use std::os::windows::fs::symlink_file; @@ -1305,3 +1305,51 @@ fn test_copy_symlink_force() { .succeeds(); assert_eq!(at.resolve_link("copy"), "file"); } + +#[test] +#[cfg(unix)] +fn test_no_preserve_mode() { + use std::os::unix::prelude::MetadataExt; + + use uucore::mode::get_umask; + + const PERMS_ALL: u32 = 0o7777; + + let (at, mut ucmd) = at_and_ucmd!(); + at.touch("file"); + set_permissions(at.plus("file"), PermissionsExt::from_mode(PERMS_ALL)).unwrap(); + ucmd.arg("file") + .arg("dest") + .succeeds() + .no_stderr() + .no_stdout(); + let umask = get_umask(); + // remove sticky bit, setuid and setgid bit; apply umask + let expected_perms = PERMS_ALL & !0o7000 & !umask; + assert_eq!( + at.plus("dest").metadata().unwrap().mode() & 0o7777, + expected_perms + ); +} + +#[test] +#[cfg(unix)] +fn test_preserve_mode() { + use std::os::unix::prelude::MetadataExt; + + const PERMS_ALL: u32 = 0o7777; + + let (at, mut ucmd) = at_and_ucmd!(); + at.touch("file"); + set_permissions(at.plus("file"), PermissionsExt::from_mode(PERMS_ALL)).unwrap(); + ucmd.arg("file") + .arg("dest") + .arg("-p") + .succeeds() + .no_stderr() + .no_stdout(); + assert_eq!( + at.plus("dest").metadata().unwrap().mode() & 0o7777, + PERMS_ALL + ); +} From ef9c5d4fcf13fdc0c7bc8f3a8d14f84c3986ee51 Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Mon, 30 Aug 2021 22:16:36 +0200 Subject: [PATCH 129/206] cp: canonicalize paths upfront This way later code can assume `src` and `dest` to be the actual paths of source and destination, and do not have to constantly check `options.dereference`. This requires moving the error context calculation to the beginning as well, since later steps no longer operate with the same file paths as supplied by the user. --- src/uu/cp/Cargo.toml | 2 +- src/uu/cp/src/cp.rs | 136 +++++++++++++++++++++---------------------- 2 files changed, 68 insertions(+), 70 deletions(-) diff --git a/src/uu/cp/Cargo.toml b/src/uu/cp/Cargo.toml index 690a01425..66beb2501 100644 --- a/src/uu/cp/Cargo.toml +++ b/src/uu/cp/Cargo.toml @@ -24,7 +24,7 @@ filetime = "0.2" libc = "0.2.85" quick-error = "1.2.3" selinux = { version="0.2.3", optional=true } -uucore = { version=">=0.0.9", package="uucore", path="../../uucore", features=["fs", "perms"] } +uucore = { version=">=0.0.9", package="uucore", path="../../uucore", features=["fs", "perms", "mode"] } uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_procs" } walkdir = "2.2" diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index 772e104ef..e9e76237b 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -18,7 +18,6 @@ extern crate quick_error; #[macro_use] extern crate uucore; -use quick_error::Context; #[cfg(windows)] use winapi::um::fileapi::CreateFileW; #[cfg(windows)] @@ -1056,23 +1055,12 @@ impl OverwriteMode { } } -fn copy_attribute( - source: &Path, - dest: &Path, - attribute: &Attribute, - options: &Options, -) -> CopyResult<()> { +fn copy_attribute(source: &Path, dest: &Path, attribute: &Attribute) -> CopyResult<()> { let context = &*format!("'{}' -> '{}'", source.display().to_string(), dest.display()); - let source_metadata = if options.dereference { - source.metadata() - } else { - source.symlink_metadata() - } - .context(context)?; + let source_metadata = fs::symlink_metadata(source).context(context)?; match *attribute { Attribute::Mode => { fs::set_permissions(dest, source_metadata.permissions()).context(context)?; - dest.metadata().unwrap().permissions(); // FIXME: Implement this for windows as well #[cfg(feature = "feat_acl")] exacl::getfacl(source, None) @@ -1091,7 +1079,7 @@ fn copy_attribute( wrap_chown( dest, - &dest.metadata().context(context)?, + &dest.symlink_metadata().context(context)?, Some(dest_uid), Some(dest_gid), false, @@ -1111,14 +1099,13 @@ fn copy_attribute( } #[cfg(feature = "feat_selinux")] Attribute::Context => { - let context = selinux::SecurityContext::of_path(source, options.dereference, false) - .map_err(|e| { - format!( - "failed to get security context of {}: {}", - source.display(), - e - ) - })?; + let context = selinux::SecurityContext::of_path(source, false, false).map_err(|e| { + format!( + "failed to get security context of {}: {}", + source.display(), + e + ) + })?; if let Some(context) = context { context.set_for_path(dest, false, false).map_err(|e| { format!( @@ -1146,7 +1133,6 @@ fn copy_attribute( } } }; - dest.metadata().unwrap().permissions(); Ok(()) } @@ -1203,8 +1189,8 @@ fn handle_existing_dest(source: &Path, dest: &Path, options: &Options) -> CopyRe Ok(()) } -/// Copy the a file from `source` to `dest`. No path manipulation is -/// done on either `source` or `dest`, the are used as provided. +/// Copy the a file from `source` to `dest`. `source` will be dereferenced if +/// `options.dereference` is set to true. `dest` will always be dereferenced. /// /// Behavior when copying to existing files is contingent on the /// `options.overwrite` mode. If a file is skipped, the return type @@ -1220,19 +1206,24 @@ fn copy_file(source: &Path, dest: &Path, options: &Options) -> CopyResult<()> { if options.verbose { println!("{}", context_for(source, dest)); } - // FIXME: `source` and `dest` should be dereferenced here if appropriate, so that the rest - // of the code does not have to check `options.dereference` all the time and can work with the paths directly. + + // Calculate the context upfront before canonicalizing the path + let context = context_for(source, dest); + let context = context.as_str(); + + // canonicalize dest and source so that later steps can work with the paths directly + let dest = canonicalize(dest, MissingHandling::Missing, ResolveMode::Physical).unwrap(); + let source = if options.dereference { + canonicalize(source, MissingHandling::Missing, ResolveMode::Physical).unwrap() + } else { + source.to_owned() + }; let dest_permissions = if dest.exists() { - dest.metadata() - .map_err(|e| Context(context_for(dest, source), e))? - .permissions() + dest.symlink_metadata().context(context)?.permissions() } else { #[allow(unused_mut)] - let mut permissions = source - .metadata() - .map_err(|e| Context(context_for(dest, source), e))? - .permissions(); + let mut permissions = source.symlink_metadata().context(context)?.permissions(); #[cfg(unix)] { use uucore::mode::get_umask; @@ -1253,29 +1244,29 @@ fn copy_file(source: &Path, dest: &Path, options: &Options) -> CopyResult<()> { match options.copy_mode { CopyMode::Link => { - fs::hard_link(source, dest).context(&*context_for(source, dest))?; + fs::hard_link(&source, &dest).context(context)?; } CopyMode::Copy => { - copy_helper(source, dest, options)?; + copy_helper(&source, &dest, options, context)?; } CopyMode::SymLink => { - symlink_file(source, dest, &*context_for(source, dest))?; + symlink_file(&source, &dest, context)?; } CopyMode::Sparse => return Err(Error::NotImplemented(options::SPARSE.to_string())), CopyMode::Update => { if dest.exists() { - let src_metadata = fs::metadata(source)?; - let dest_metadata = fs::metadata(dest)?; + let src_metadata = fs::symlink_metadata(&source)?; + let dest_metadata = fs::symlink_metadata(&dest)?; let src_time = src_metadata.modified()?; let dest_time = dest_metadata.modified()?; if src_time <= dest_time { return Ok(()); } else { - copy_helper(source, dest, options)?; + copy_helper(&source, &dest, options, context)?; } } else { - copy_helper(source, dest, options)?; + copy_helper(&source, &dest, options, context)?; } } CopyMode::AttrOnly => { @@ -1283,54 +1274,51 @@ fn copy_file(source: &Path, dest: &Path, options: &Options) -> CopyResult<()> { .write(true) .truncate(false) .create(true) - .open(dest) + .open(&dest) .unwrap(); } }; - fs::set_permissions(dest, dest_permissions).unwrap(); + + // TODO: implement something similar to gnu's lchown + if fs::symlink_metadata(&dest) + .map(|meta| !meta.file_type().is_symlink()) + .unwrap_or(false) + { + fs::set_permissions(&dest, dest_permissions).unwrap(); + } for attribute in &options.preserve_attributes { - copy_attribute(source, dest, attribute, options)?; + copy_attribute(&source, &dest, attribute)?; } Ok(()) } /// Copy the file from `source` to `dest` either using the normal `fs::copy` or a /// copy-on-write scheme if --reflink is specified and the filesystem supports it. -fn copy_helper(source: &Path, dest: &Path, options: &Options) -> CopyResult<()> { +fn copy_helper(source: &Path, dest: &Path, options: &Options, context: &str) -> CopyResult<()> { if options.parents { let parent = dest.parent().unwrap_or(dest); fs::create_dir_all(parent)?; } let is_symlink = fs::symlink_metadata(&source)?.file_type().is_symlink(); - if source.to_string_lossy() == "/dev/null" { + if source.as_os_str() == "/dev/null" { /* workaround a limitation of fs::copy * https://github.com/rust-lang/rust/issues/79390 */ File::create(dest)?; - } else if !options.dereference && is_symlink { + } else if is_symlink { copy_link(source, dest)?; } else if options.reflink_mode != ReflinkMode::Never { #[cfg(not(any(target_os = "linux", target_os = "macos")))] return Err("--reflink is only supported on linux and macOS" .to_string() .into()); - #[cfg(any(target_os = "linux", target_os = "macos"))] - if is_symlink { - assert!(options.dereference); - let real_path = std::fs::read_link(source)?; - #[cfg(target_os = "macos")] - copy_on_write_macos(&real_path, dest, options.reflink_mode)?; - #[cfg(target_os = "linux")] - copy_on_write_linux(&real_path, dest, options.reflink_mode)?; - } else { - #[cfg(target_os = "macos")] - copy_on_write_macos(source, dest, options.reflink_mode)?; - #[cfg(target_os = "linux")] - copy_on_write_linux(source, dest, options.reflink_mode)?; - } + #[cfg(target_os = "macos")] + copy_on_write_macos(source, dest, options.reflink_mode, context)?; + #[cfg(target_os = "linux")] + copy_on_write_linux(source, dest, options.reflink_mode, context)?; } else { - fs::copy(source, dest).context(&*context_for(source, dest))?; + fs::copy(source, dest).context(context)?; } Ok(()) @@ -1361,16 +1349,21 @@ fn copy_link(source: &Path, dest: &Path) -> CopyResult<()> { /// Copies `source` to `dest` using copy-on-write if possible. #[cfg(target_os = "linux")] -fn copy_on_write_linux(source: &Path, dest: &Path, mode: ReflinkMode) -> CopyResult<()> { +fn copy_on_write_linux( + source: &Path, + dest: &Path, + mode: ReflinkMode, + context: &str, +) -> CopyResult<()> { debug_assert!(mode != ReflinkMode::Never); - let src_file = File::open(source).context(&*context_for(source, dest))?; + let src_file = File::open(source).context(context)?; let dst_file = OpenOptions::new() .write(true) .truncate(false) .create(true) .open(dest) - .context(&*context_for(source, dest))?; + .context(context)?; match mode { ReflinkMode::Always => unsafe { let result = ficlone(dst_file.as_raw_fd(), src_file.as_raw_fd() as *const i32); @@ -1389,7 +1382,7 @@ fn copy_on_write_linux(source: &Path, dest: &Path, mode: ReflinkMode) -> CopyRes ReflinkMode::Auto => unsafe { let result = ficlone(dst_file.as_raw_fd(), src_file.as_raw_fd() as *const i32); if result != 0 { - fs::copy(source, dest).context(&*context_for(source, dest))?; + fs::copy(source, dest).context(context)?; } Ok(()) }, @@ -1399,7 +1392,12 @@ fn copy_on_write_linux(source: &Path, dest: &Path, mode: ReflinkMode) -> CopyRes /// Copies `source` to `dest` using copy-on-write if possible. #[cfg(target_os = "macos")] -fn copy_on_write_macos(source: &Path, dest: &Path, mode: ReflinkMode) -> CopyResult<()> { +fn copy_on_write_macos( + source: &Path, + dest: &Path, + mode: ReflinkMode, + context: &str, +) -> CopyResult<()> { debug_assert!(mode != ReflinkMode::Never); // Extract paths in a form suitable to be passed to a syscall. @@ -1444,7 +1442,7 @@ fn copy_on_write_macos(source: &Path, dest: &Path, mode: ReflinkMode) -> CopyRes format!("failed to clone {:?} from {:?}: {}", source, dest, error).into(), ) } - ReflinkMode::Auto => fs::copy(source, dest).context(&*context_for(source, dest))?, + ReflinkMode::Auto => fs::copy(source, dest).context(context)?, ReflinkMode::Never => unreachable!(), }; } From 7bf85751b0827cd0cf78aa67a1fd9a872768bc2b Mon Sep 17 00:00:00 2001 From: Jan Verbeek Date: Wed, 1 Sep 2021 00:37:21 +0200 Subject: [PATCH 130/206] uucore::display: Fix tests --- src/uucore/src/lib/mods/display.rs | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/uucore/src/lib/mods/display.rs b/src/uucore/src/lib/mods/display.rs index 0245a401a..e647a8c71 100644 --- a/src/uucore/src/lib/mods/display.rs +++ b/src/uucore/src/lib/mods/display.rs @@ -60,16 +60,15 @@ pub trait Quotable { /// # Examples /// /// ``` - /// #[macro_use] - /// extern crate uucore; /// use std::path::Path; /// use uucore::display::Quotable; + /// use uucore::show_error; /// /// let foo = Path::new("foo/bar.baz"); /// let bar = Path::new("foo bar"); /// - /// show_error!("{}: Not found", foo); // Prints "util: foo/bar.baz: Not found" - /// show_error!("{}: Not found", bar); // Prints "util: 'foo bar': Not found" + /// show_error!("{}: Not found", foo.maybe_quote()); // Prints "util: foo/bar.baz: Not found" + /// show_error!("{}: Not found", bar.maybe_quote()); // Prints "util: 'foo bar': Not found" /// ``` fn maybe_quote(&self) -> Quoted<'_> { let mut quoted = self.quote(); @@ -156,11 +155,9 @@ impl Display for Quoted<'_> { #[cfg(any(unix, target_os = "wasi"))] const SPECIAL_SHELL_CHARS_START: &[char] = &['~', '#', '!']; // Same deal as before, this is possibly incomplete. - // '-' is included because unlike in Unix, quoting an argument may stop it - // from being recognized as an option. I like that very much. // A single stand-alone exclamation mark seems to have some special meaning. #[cfg(windows)] - const SPECIAL_SHELL_CHARS_START: &[char] = &['~', '#', '@', '-', '!']; + const SPECIAL_SHELL_CHARS_START: &[char] = &['~', '#', '@', '!']; /// Characters that are interpreted specially in a double-quoted string. #[cfg(any(unix, target_os = "wasi"))] @@ -181,6 +178,14 @@ impl Display for Quoted<'_> { if SPECIAL_SHELL_CHARS_START.contains(&first) { requires_quote = true; } + // Unlike in Unix, quoting an argument may stop it + // from being recognized as an option. I like that very much. + // But we don't want to quote "-" because that's a common + // special argument and PowerShell doesn't mind it. + #[cfg(windows)] + if first == '-' && text.len() > 1 { + requires_quote = true; + } } else { // Empty string requires_quote = true; @@ -431,6 +436,7 @@ mod tests { ("", "''"), ("foo bar", "'foo bar'"), ("$foo", "'$foo'"), + ("-", "-"), ]); } From 18fc4076cf6ca90d42d707c2f7787655ea490725 Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Wed, 1 Sep 2021 17:34:40 +0200 Subject: [PATCH 131/206] uucore/perms: correct some error messages - prevent duplicate errors from both us and `walkdir` by instructing `walkdir' to skip directories we failed to read metadata for. - don't directly display `walkdir`'s errors, but format them ourselves to match gnu's format --- src/uucore/src/lib/features/perms.rs | 70 +++++++++++++++++----------- tests/by-util/test_chgrp.rs | 39 +++++++++++++++- 2 files changed, 80 insertions(+), 29 deletions(-) diff --git a/src/uucore/src/lib/features/perms.rs b/src/uucore/src/lib/features/perms.rs index a5b2522b9..fe1c97a82 100644 --- a/src/uucore/src/lib/features/perms.rs +++ b/src/uucore/src/lib/features/perms.rs @@ -5,6 +5,7 @@ //! Common functions to manage permissions +use crate::error::strip_errno; use crate::error::UResult; pub use crate::features::entries; use crate::fs::resolve_relative_path; @@ -172,15 +173,6 @@ pub struct ChownExecutor { pub dereference: bool, } -macro_rules! unwrap { - ($m:expr, $e:ident, $err:block) => { - match $m { - Ok(meta) => meta, - Err($e) => $err, - } - }; -} - pub const FTS_COMFOLLOW: u8 = 1; pub const FTS_PHYSICAL: u8 = 1 << 1; pub const FTS_LOGICAL: u8 = 1 << 2; @@ -269,17 +261,42 @@ impl ChownExecutor { let mut ret = 0; let root = root.as_ref(); let follow = self.dereference || self.bit_flag & FTS_LOGICAL != 0; - for entry in WalkDir::new(root).follow_links(follow).min_depth(1) { - let entry = unwrap!(entry, e, { - ret = 1; - show_error!("{}", e); - continue; - }); + let mut iterator = WalkDir::new(root) + .follow_links(follow) + .min_depth(1) + .into_iter(); + // We can't use a for loop because we need to manipulate the iterator inside the loop. + while let Some(entry) = iterator.next() { + let entry = match entry { + Err(e) => { + ret = 1; + if let Some(path) = e.path() { + show_error!( + "cannot access '{}': {}", + path.display(), + if let Some(error) = e.io_error() { + strip_errno(error) + } else { + "Too many levels of symbolic links".into() + } + ) + } else { + show_error!("{}", e) + } + continue; + } + Ok(entry) => entry, + }; let path = entry.path(); let meta = match self.obtain_meta(path, follow) { Some(m) => m, _ => { ret = 1; + if entry.file_type().is_dir() { + // Instruct walkdir to skip this directory to avoid getting another error + // when walkdir tries to query the children of this directory. + iterator.skip_current_dir(); + } continue; } }; @@ -316,23 +333,20 @@ impl ChownExecutor { fn obtain_meta>(&self, path: P, follow: bool) -> Option { let path = path.as_ref(); let meta = if follow { - unwrap!(path.metadata(), e, { - match self.verbosity.level { - VerbosityLevel::Silent => (), - _ => show_error!("cannot access '{}': {}", path.display(), e), - } - return None; - }) + path.metadata() } else { - unwrap!(path.symlink_metadata(), e, { + path.symlink_metadata() + }; + match meta { + Err(e) => { match self.verbosity.level { VerbosityLevel::Silent => (), - _ => show_error!("cannot dereference '{}': {}", path.display(), e), + _ => show_error!("cannot access '{}': {}", path.display(), strip_errno(&e)), } - return None; - }) - }; - Some(meta) + None + } + Ok(meta) => Some(meta), + } } #[inline] diff --git a/tests/by-util/test_chgrp.rs b/tests/by-util/test_chgrp.rs index 0741838a4..0fc73520e 100644 --- a/tests/by-util/test_chgrp.rs +++ b/tests/by-util/test_chgrp.rs @@ -230,7 +230,7 @@ fn test_big_h() { } #[test] -#[cfg(target_os = "linux")] +#[cfg(not(target_vendor = "apple"))] fn basic_succeeds() { let (at, mut ucmd) = at_and_ucmd!(); let one_group = nix::unistd::getgroups().unwrap(); @@ -251,3 +251,40 @@ fn test_no_change() { at.touch("file"); ucmd.arg("").arg(at.plus("file")).succeeds(); } + +#[test] +#[cfg(not(target_vendor = "apple"))] +fn test_permission_denied() { + use std::os::unix::prelude::PermissionsExt; + + if let Some(group) = nix::unistd::getgroups().unwrap().first() { + let (at, mut ucmd) = at_and_ucmd!(); + at.mkdir("dir"); + at.touch("dir/file"); + std::fs::set_permissions(at.plus("dir"), PermissionsExt::from_mode(0o0000)).unwrap(); + ucmd.arg("-R") + .arg(group.as_raw().to_string()) + .arg("dir") + .fails() + .stderr_only("chgrp: cannot access 'dir': Permission denied"); + } +} + +#[test] +#[cfg(not(target_vendor = "apple"))] +fn test_subdir_permission_denied() { + use std::os::unix::prelude::PermissionsExt; + + if let Some(group) = nix::unistd::getgroups().unwrap().first() { + let (at, mut ucmd) = at_and_ucmd!(); + at.mkdir("dir"); + at.mkdir("dir/subdir"); + at.touch("dir/subdir/file"); + std::fs::set_permissions(at.plus("dir/subdir"), PermissionsExt::from_mode(0o0000)).unwrap(); + ucmd.arg("-R") + .arg(group.as_raw().to_string()) + .arg("dir") + .fails() + .stderr_only("chgrp: cannot access 'dir/subdir': Permission denied"); + } +} From 195f827cd46929f4dedba341442151e1291bdbf7 Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Thu, 2 Sep 2021 13:12:42 +0200 Subject: [PATCH 132/206] chown/chgrp: share more code Also share argument parsing code between `chgrp` and `chown` --- src/uu/chgrp/src/chgrp.rs | 158 +++---------------------- src/uu/chown/src/chown.rs | 165 ++++++--------------------- src/uucore/src/lib/features/perms.rs | 153 ++++++++++++++++++++++++- 3 files changed, 204 insertions(+), 272 deletions(-) diff --git a/src/uu/chgrp/src/chgrp.rs b/src/uu/chgrp/src/chgrp.rs index d37da578e..a077ab7a4 100644 --- a/src/uu/chgrp/src/chgrp.rs +++ b/src/uu/chgrp/src/chgrp.rs @@ -11,46 +11,16 @@ extern crate uucore; pub use uucore::entries; use uucore::error::{FromIo, UResult, USimpleError}; -use uucore::perms::{ - ChownExecutor, IfFrom, Verbosity, VerbosityLevel, FTS_COMFOLLOW, FTS_LOGICAL, FTS_PHYSICAL, -}; +use uucore::perms::{chown_base, options, IfFrom}; -use clap::{App, Arg}; +use clap::{App, Arg, ArgMatches}; use std::fs; use std::os::unix::fs::MetadataExt; -use uucore::InvalidEncodingHandling; - static ABOUT: &str = "Change the group of each FILE to GROUP."; static VERSION: &str = env!("CARGO_PKG_VERSION"); -pub mod options { - pub mod verbosity { - pub static CHANGES: &str = "changes"; - pub static QUIET: &str = "quiet"; - pub static SILENT: &str = "silent"; - pub static VERBOSE: &str = "verbose"; - } - pub mod preserve_root { - pub static PRESERVE: &str = "preserve-root"; - pub static NO_PRESERVE: &str = "no-preserve-root"; - } - pub mod dereference { - pub static DEREFERENCE: &str = "dereference"; - pub static NO_DEREFERENCE: &str = "no-dereference"; - } - pub static RECURSIVE: &str = "recursive"; - pub mod traverse { - pub static TRAVERSE: &str = "H"; - pub static NO_TRAVERSE: &str = "P"; - pub static EVERY: &str = "L"; - } - pub static REFERENCE: &str = "reference"; - pub static ARG_GROUP: &str = "GROUP"; - pub static ARG_FILES: &str = "FILE"; -} - fn get_usage() -> String { format!( "{0} [OPTION]... GROUP FILE...\n {0} [OPTION]... --reference=RFILE FILE...", @@ -58,101 +28,7 @@ fn get_usage() -> String { ) } -#[uucore_procs::gen_uumain] -pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let args = args - .collect_str(InvalidEncodingHandling::ConvertLossy) - .accept_any(); - - let usage = get_usage(); - - let mut app = uu_app().usage(&usage[..]); - - // we change the positional args based on whether - // --reference was used. - let mut reference = false; - let mut help = false; - // stop processing options on -- - for arg in args.iter().take_while(|s| *s != "--") { - if arg.starts_with("--reference=") || arg == "--reference" { - reference = true; - } else if arg == "--help" { - // we stop processing once we see --help, - // as it doesn't matter if we've seen reference or not - help = true; - break; - } - } - - if help || !reference { - // add both positional arguments - app = app.arg( - Arg::with_name(options::ARG_GROUP) - .value_name(options::ARG_GROUP) - .required(true) - .takes_value(true) - .multiple(false), - ) - } - app = app.arg( - Arg::with_name(options::ARG_FILES) - .value_name(options::ARG_FILES) - .multiple(true) - .takes_value(true) - .required(true) - .min_values(1), - ); - - let matches = app.get_matches_from(args); - - /* Get the list of files */ - let files: Vec = matches - .values_of(options::ARG_FILES) - .map(|v| v.map(ToString::to_string).collect()) - .unwrap_or_default(); - - let preserve_root = matches.is_present(options::preserve_root::PRESERVE); - - let mut derefer = if matches.is_present(options::dereference::DEREFERENCE) { - 1 - } else if matches.is_present(options::dereference::NO_DEREFERENCE) { - 0 - } else { - -1 - }; - - let mut bit_flag = if matches.is_present(options::traverse::TRAVERSE) { - FTS_COMFOLLOW | FTS_PHYSICAL - } else if matches.is_present(options::traverse::EVERY) { - FTS_LOGICAL - } else { - FTS_PHYSICAL - }; - - let recursive = matches.is_present(options::RECURSIVE); - if recursive { - if bit_flag == FTS_PHYSICAL { - if derefer == 1 { - return Err(USimpleError::new(1, "-R --dereference requires -H or -L")); - } - derefer = 0; - } - } else { - bit_flag = FTS_PHYSICAL; - } - - let verbosity_level = if matches.is_present(options::verbosity::CHANGES) { - VerbosityLevel::Changes - } else if matches.is_present(options::verbosity::SILENT) - || matches.is_present(options::verbosity::QUIET) - { - VerbosityLevel::Silent - } else if matches.is_present(options::verbosity::VERBOSE) { - VerbosityLevel::Verbose - } else { - VerbosityLevel::Normal - }; - +fn parse_gid_and_uid(matches: &ArgMatches) -> UResult<(Option, Option, IfFrom)> { let dest_gid = if let Some(file) = matches.value_of(options::REFERENCE) { fs::metadata(&file) .map(|meta| Some(meta.gid())) @@ -168,22 +44,20 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } } }; + Ok((dest_gid, None, IfFrom::All)) +} - let executor = ChownExecutor { - bit_flag, - dest_gid, - verbosity: Verbosity { - groups_only: true, - level: verbosity_level, - }, - recursive, - dereference: derefer != 0, - preserve_root, - files, - filter: IfFrom::All, - dest_uid: None, - }; - executor.exec() +#[uucore_procs::gen_uumain] +pub fn uumain(args: impl uucore::Args) -> UResult<()> { + let usage = get_usage(); + + chown_base( + uu_app().usage(&usage[..]), + args, + options::ARG_GROUP, + parse_gid_and_uid, + true, + ) } pub fn uu_app() -> App<'static, 'static> { diff --git a/src/uu/chown/src/chown.rs b/src/uu/chown/src/chown.rs index 06f0c6a32..20f3f8174 100644 --- a/src/uu/chown/src/chown.rs +++ b/src/uu/chown/src/chown.rs @@ -10,49 +10,17 @@ #[macro_use] extern crate uucore; pub use uucore::entries::{self, Group, Locate, Passwd}; -use uucore::perms::{ - ChownExecutor, IfFrom, Verbosity, VerbosityLevel, FTS_COMFOLLOW, FTS_LOGICAL, FTS_PHYSICAL, -}; +use uucore::perms::{chown_base, options, IfFrom}; use uucore::error::{FromIo, UResult, USimpleError}; -use clap::{crate_version, App, Arg}; +use clap::{crate_version, App, Arg, ArgMatches}; use std::fs; use std::os::unix::fs::MetadataExt; -use uucore::InvalidEncodingHandling; - static ABOUT: &str = "change file owner and group"; -pub mod options { - pub mod verbosity { - pub static CHANGES: &str = "changes"; - pub static QUIET: &str = "quiet"; - pub static SILENT: &str = "silent"; - pub static VERBOSE: &str = "verbose"; - } - pub mod preserve_root { - pub static PRESERVE: &str = "preserve-root"; - pub static NO_PRESERVE: &str = "no-preserve-root"; - } - pub mod dereference { - pub static DEREFERENCE: &str = "dereference"; - pub static NO_DEREFERENCE: &str = "no-dereference"; - } - pub static FROM: &str = "from"; - pub static RECURSIVE: &str = "recursive"; - pub mod traverse { - pub static TRAVERSE: &str = "H"; - pub static NO_TRAVERSE: &str = "P"; - pub static EVERY: &str = "L"; - } - pub static REFERENCE: &str = "reference"; -} - -static ARG_OWNER: &str = "owner"; -static ARG_FILES: &str = "files"; - fn get_usage() -> String { format!( "{0} [OPTION]... [OWNER][:[GROUP]] FILE...\n{0} [OPTION]... --reference=RFILE FILE...", @@ -60,65 +28,7 @@ fn get_usage() -> String { ) } -#[uucore_procs::gen_uumain] -pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let args = args - .collect_str(InvalidEncodingHandling::Ignore) - .accept_any(); - - let usage = get_usage(); - - let matches = uu_app().usage(&usage[..]).get_matches_from(args); - - /* First arg is the owner/group */ - let owner = matches.value_of(ARG_OWNER).unwrap(); - - /* Then the list of files */ - let files: Vec = matches - .values_of(ARG_FILES) - .map(|v| v.map(ToString::to_string).collect()) - .unwrap_or_default(); - - let preserve_root = matches.is_present(options::preserve_root::PRESERVE); - - let mut derefer = if matches.is_present(options::dereference::NO_DEREFERENCE) { - 1 - } else { - 0 - }; - - let mut bit_flag = if matches.is_present(options::traverse::TRAVERSE) { - FTS_COMFOLLOW | FTS_PHYSICAL - } else if matches.is_present(options::traverse::EVERY) { - FTS_LOGICAL - } else { - FTS_PHYSICAL - }; - - let recursive = matches.is_present(options::RECURSIVE); - if recursive { - if bit_flag == FTS_PHYSICAL { - if derefer == 1 { - return Err(USimpleError::new(1, "-R --dereference requires -H or -L")); - } - derefer = 0; - } - } else { - bit_flag = FTS_PHYSICAL; - } - - let verbosity = if matches.is_present(options::verbosity::CHANGES) { - VerbosityLevel::Changes - } else if matches.is_present(options::verbosity::SILENT) - || matches.is_present(options::verbosity::QUIET) - { - VerbosityLevel::Silent - } else if matches.is_present(options::verbosity::VERBOSE) { - VerbosityLevel::Verbose - } else { - VerbosityLevel::Normal - }; - +fn parse_gid_uid_and_filter(matches: &ArgMatches) -> UResult<(Option, Option, IfFrom)> { let filter = if let Some(spec) = matches.value_of(options::FROM) { match parse_spec(spec)? { (Some(uid), None) => IfFrom::User(uid), @@ -138,25 +48,24 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { dest_gid = Some(meta.gid()); dest_uid = Some(meta.uid()); } else { - let (u, g) = parse_spec(owner)?; + let (u, g) = parse_spec(matches.value_of(options::ARG_OWNER).unwrap())?; dest_uid = u; dest_gid = g; } - let executor = ChownExecutor { - bit_flag, - dest_uid, - dest_gid, - verbosity: Verbosity { - groups_only: false, - level: verbosity, - }, - recursive, - dereference: derefer != 0, - filter, - preserve_root, - files, - }; - executor.exec() + Ok((dest_gid, dest_uid, filter)) +} + +#[uucore_procs::gen_uumain] +pub fn uumain(args: impl uucore::Args) -> UResult<()> { + let usage = get_usage(); + + chown_base( + uu_app().usage(&usage[..]), + args, + options::ARG_OWNER, + parse_gid_uid_and_filter, + false, + ) } pub fn uu_app() -> App<'static, 'static> { @@ -169,22 +78,31 @@ pub fn uu_app() -> App<'static, 'static> { .long(options::verbosity::CHANGES) .help("like verbose but report only when a change is made"), ) - .arg(Arg::with_name(options::dereference::DEREFERENCE).long(options::dereference::DEREFERENCE).help( - "affect the referent of each symbolic link (this is the default), rather than the symbolic link itself", - )) + .arg( + Arg::with_name(options::dereference::DEREFERENCE) + .long(options::dereference::DEREFERENCE) + .help( + "affect the referent of each symbolic link (this is the default), \ + rather than the symbolic link itself", + ), + ) .arg( Arg::with_name(options::dereference::NO_DEREFERENCE) .short("h") .long(options::dereference::NO_DEREFERENCE) .help( - "affect symbolic links instead of any referenced file (useful only on systems that can change the ownership of a symlink)", + "affect symbolic links instead of any referenced file \ + (useful only on systems that can change the ownership of a symlink)", ), ) .arg( Arg::with_name(options::FROM) .long(options::FROM) .help( - "change the owner and/or group of each file only if its current owner and/or group match those specified here. Either may be omitted, in which case a match is not required for the omitted attribute", + "change the owner and/or group of each file only if its \ + current owner and/or group match those specified here. \ + Either may be omitted, in which case a match is not required \ + for the omitted attribute", ) .value_name("CURRENT_OWNER:CURRENT_GROUP"), ) @@ -216,7 +134,11 @@ pub fn uu_app() -> App<'static, 'static> { .value_name("RFILE") .min_values(1), ) - .arg(Arg::with_name(options::verbosity::SILENT).short("f").long(options::verbosity::SILENT)) + .arg( + Arg::with_name(options::verbosity::SILENT) + .short("f") + .long(options::verbosity::SILENT), + ) .arg( Arg::with_name(options::traverse::TRAVERSE) .short(options::traverse::TRAVERSE) @@ -240,19 +162,6 @@ pub fn uu_app() -> App<'static, 'static> { .long(options::verbosity::VERBOSE) .help("output a diagnostic for every file processed"), ) - .arg( - Arg::with_name(ARG_OWNER) - .multiple(false) - .takes_value(true) - .required(true), - ) - .arg( - Arg::with_name(ARG_FILES) - .multiple(true) - .takes_value(true) - .required(true) - .min_values(1), - ) } fn parse_spec(spec: &str) -> UResult<(Option, Option)> { diff --git a/src/uucore/src/lib/features/perms.rs b/src/uucore/src/lib/features/perms.rs index fe1c97a82..5f470d5a8 100644 --- a/src/uucore/src/lib/features/perms.rs +++ b/src/uucore/src/lib/features/perms.rs @@ -7,10 +7,14 @@ use crate::error::strip_errno; use crate::error::UResult; +use crate::error::USimpleError; pub use crate::features::entries; use crate::fs::resolve_relative_path; use crate::show_error; -use libc::{self, gid_t, lchown, uid_t}; +use clap::App; +use clap::Arg; +use clap::ArgMatches; +use libc::{self, gid_t, uid_t}; use walkdir::WalkDir; use std::io::Error as IOError; @@ -45,7 +49,7 @@ fn chown>(path: P, uid: uid_t, gid: gid_t, follow: bool) -> IORes if follow { libc::chown(s.as_ptr(), uid, gid) } else { - lchown(s.as_ptr(), uid, gid) + libc::lchown(s.as_ptr(), uid, gid) } }; if ret == 0 { @@ -359,3 +363,148 @@ impl ChownExecutor { } } } + +pub mod options { + pub mod verbosity { + pub const CHANGES: &str = "changes"; + pub const QUIET: &str = "quiet"; + pub const SILENT: &str = "silent"; + pub const VERBOSE: &str = "verbose"; + } + pub mod preserve_root { + pub const PRESERVE: &str = "preserve-root"; + pub const NO_PRESERVE: &str = "no-preserve-root"; + } + pub mod dereference { + pub const DEREFERENCE: &str = "dereference"; + pub const NO_DEREFERENCE: &str = "no-dereference"; + } + pub const FROM: &str = "from"; + pub const RECURSIVE: &str = "recursive"; + pub mod traverse { + pub const TRAVERSE: &str = "H"; + pub const NO_TRAVERSE: &str = "P"; + pub const EVERY: &str = "L"; + } + pub const REFERENCE: &str = "reference"; + pub const ARG_OWNER: &str = "OWNER"; + pub const ARG_GROUP: &str = "GROUP"; + pub const ARG_FILES: &str = "FILE"; +} + +type GidUidFilterParser<'a> = fn(&ArgMatches<'a>) -> UResult<(Option, Option, IfFrom)>; + +/// Base implementation for `chgrp` and `chown`. +/// +/// An argument called `add_arg_if_not_reference` will be added to `app` if +/// `args` does not contain the `--reference` option. +/// `parse_gid_uid_and_filter` will be called to obtain the target gid and uid, and the filter, +/// from `ArgMatches`. +/// `groups_only` determines whether verbose output will only mention the group. +pub fn chown_base<'a>( + mut app: App<'a, 'a>, + args: impl crate::Args, + add_arg_if_not_reference: &'a str, + parse_gid_uid_and_filter: GidUidFilterParser<'a>, + groups_only: bool, +) -> UResult<()> { + let args: Vec<_> = args.collect(); + let mut reference = false; + let mut help = false; + // stop processing options on -- + for arg in args.iter().take_while(|s| *s != "--") { + if arg.to_string_lossy().starts_with("--reference=") || arg == "--reference" { + reference = true; + } else if arg == "--help" { + // we stop processing once we see --help, + // as it doesn't matter if we've seen reference or not + help = true; + break; + } + } + + if help || !reference { + // add both positional arguments + // arg_group is only required if + app = app.arg( + Arg::with_name(add_arg_if_not_reference) + .value_name(add_arg_if_not_reference) + .required(true) + .takes_value(true) + .multiple(false), + ) + } + app = app.arg( + Arg::with_name(options::ARG_FILES) + .value_name(options::ARG_FILES) + .multiple(true) + .takes_value(true) + .required(true) + .min_values(1), + ); + let matches = app.get_matches_from(args); + + let files: Vec = matches + .values_of(options::ARG_FILES) + .map(|v| v.map(ToString::to_string).collect()) + .unwrap_or_default(); + + let preserve_root = matches.is_present(options::preserve_root::PRESERVE); + + let mut dereference = if matches.is_present(options::dereference::DEREFERENCE) { + 1 + } else if matches.is_present(options::dereference::NO_DEREFERENCE) { + 0 + } else { + -1 + }; + + let mut bit_flag = if matches.is_present(options::traverse::TRAVERSE) { + FTS_COMFOLLOW | FTS_PHYSICAL + } else if matches.is_present(options::traverse::EVERY) { + FTS_LOGICAL + } else { + FTS_PHYSICAL + }; + + let recursive = matches.is_present(options::RECURSIVE); + if recursive { + if bit_flag == FTS_PHYSICAL { + if dereference == 1 { + return Err(USimpleError::new(1, "-R --dereference requires -H or -L")); + } + dereference = 0; + } + } else { + bit_flag = FTS_PHYSICAL; + } + + let verbosity_level = if matches.is_present(options::verbosity::CHANGES) { + VerbosityLevel::Changes + } else if matches.is_present(options::verbosity::SILENT) + || matches.is_present(options::verbosity::QUIET) + { + VerbosityLevel::Silent + } else if matches.is_present(options::verbosity::VERBOSE) { + VerbosityLevel::Verbose + } else { + VerbosityLevel::Normal + }; + let (dest_gid, dest_uid, filter) = parse_gid_uid_and_filter(&matches)?; + + let executor = ChownExecutor { + bit_flag, + dest_gid, + dest_uid, + verbosity: Verbosity { + groups_only: true, + level: verbosity_level, + }, + recursive, + dereference: dereference != 0, + preserve_root, + files, + filter, + }; + executor.exec() +} From a4fca2d4fc4718909658f1b0ab83734a54c8d6bc Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Thu, 2 Sep 2021 18:27:01 +0200 Subject: [PATCH 133/206] uucore/perms: remove flags in favor of enums Part of the code was transliterated from GNU's implementation in C, and used flags to store settings. Instead, we can use enums to avoid magic values or binary operations to extract flags. --- src/uu/chown/src/chown.rs | 1 + src/uucore/src/lib/features/perms.rs | 56 ++++++++++++++-------------- 2 files changed, 30 insertions(+), 27 deletions(-) diff --git a/src/uu/chown/src/chown.rs b/src/uu/chown/src/chown.rs index 20f3f8174..4abb9ac61 100644 --- a/src/uu/chown/src/chown.rs +++ b/src/uu/chown/src/chown.rs @@ -160,6 +160,7 @@ pub fn uu_app() -> App<'static, 'static> { .arg( Arg::with_name(options::verbosity::VERBOSE) .long(options::verbosity::VERBOSE) + .short("v") .help("output a diagnostic for every file processed"), ) } diff --git a/src/uucore/src/lib/features/perms.rs b/src/uucore/src/lib/features/perms.rs index 5f470d5a8..b605c0cdf 100644 --- a/src/uucore/src/lib/features/perms.rs +++ b/src/uucore/src/lib/features/perms.rs @@ -165,22 +165,26 @@ pub enum IfFrom { UserGroup(u32, u32), } +#[derive(PartialEq, Eq)] +pub enum TraverseSymlinks { + None, + First, + All, +} + pub struct ChownExecutor { pub dest_uid: Option, pub dest_gid: Option, - pub bit_flag: u8, + pub traverse_symlinks: TraverseSymlinks, pub verbosity: Verbosity, pub filter: IfFrom, pub files: Vec, pub recursive: bool, pub preserve_root: bool, + // Must be true if traverse_symlinks is not None pub dereference: bool, } -pub const FTS_COMFOLLOW: u8 = 1; -pub const FTS_PHYSICAL: u8 = 1 << 1; -pub const FTS_LOGICAL: u8 = 1 << 2; - impl ChownExecutor { pub fn exec(&self) -> UResult<()> { let mut ret = 0; @@ -194,9 +198,8 @@ impl ChownExecutor { } fn traverse>(&self, root: P) -> i32 { - let follow_arg = self.dereference || self.bit_flag != FTS_PHYSICAL; let path = root.as_ref(); - let meta = match self.obtain_meta(path, follow_arg) { + let meta = match self.obtain_meta(path, self.dereference) { Some(m) => m, _ => return 1, }; @@ -208,7 +211,7 @@ impl ChownExecutor { // (argument is symlink && should follow argument && resolved to be '/') // ) if self.recursive && self.preserve_root { - let may_exist = if follow_arg { + let may_exist = if self.dereference { path.canonicalize().ok() } else { let real = resolve_relative_path(path); @@ -234,7 +237,7 @@ impl ChownExecutor { &meta, self.dest_uid, self.dest_gid, - follow_arg, + self.dereference, self.verbosity.clone(), ) { Ok(n) => { @@ -264,9 +267,8 @@ impl ChownExecutor { fn dive_into>(&self, root: P) -> i32 { let mut ret = 0; let root = root.as_ref(); - let follow = self.dereference || self.bit_flag & FTS_LOGICAL != 0; let mut iterator = WalkDir::new(root) - .follow_links(follow) + .follow_links(self.dereference) .min_depth(1) .into_iter(); // We can't use a for loop because we need to manipulate the iterator inside the loop. @@ -292,7 +294,7 @@ impl ChownExecutor { Ok(entry) => entry, }; let path = entry.path(); - let meta = match self.obtain_meta(path, follow) { + let meta = match self.obtain_meta(path, self.dereference) { Some(m) => m, _ => { ret = 1; @@ -314,7 +316,7 @@ impl ChownExecutor { &meta, self.dest_uid, self.dest_gid, - follow, + self.dereference, self.verbosity.clone(), ) { Ok(n) => { @@ -452,31 +454,31 @@ pub fn chown_base<'a>( let preserve_root = matches.is_present(options::preserve_root::PRESERVE); let mut dereference = if matches.is_present(options::dereference::DEREFERENCE) { - 1 + Some(true) } else if matches.is_present(options::dereference::NO_DEREFERENCE) { - 0 + Some(false) } else { - -1 + None }; - let mut bit_flag = if matches.is_present(options::traverse::TRAVERSE) { - FTS_COMFOLLOW | FTS_PHYSICAL + let mut traverse_symlinks = if matches.is_present(options::traverse::TRAVERSE) { + TraverseSymlinks::First } else if matches.is_present(options::traverse::EVERY) { - FTS_LOGICAL + TraverseSymlinks::All } else { - FTS_PHYSICAL + TraverseSymlinks::None }; let recursive = matches.is_present(options::RECURSIVE); if recursive { - if bit_flag == FTS_PHYSICAL { - if dereference == 1 { + if traverse_symlinks == TraverseSymlinks::None { + if dereference == Some(true) { return Err(USimpleError::new(1, "-R --dereference requires -H or -L")); } - dereference = 0; + dereference = Some(false); } } else { - bit_flag = FTS_PHYSICAL; + traverse_symlinks = TraverseSymlinks::None; } let verbosity_level = if matches.is_present(options::verbosity::CHANGES) { @@ -493,15 +495,15 @@ pub fn chown_base<'a>( let (dest_gid, dest_uid, filter) = parse_gid_uid_and_filter(&matches)?; let executor = ChownExecutor { - bit_flag, + traverse_symlinks, dest_gid, dest_uid, verbosity: Verbosity { - groups_only: true, + groups_only, level: verbosity_level, }, recursive, - dereference: dereference != 0, + dereference: dereference.unwrap_or(true), preserve_root, files, filter, From a7f6b4420a4a28148657c437aed13dda798176bd Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Thu, 2 Sep 2021 22:31:49 +0200 Subject: [PATCH 134/206] uucore/perms: take traverse_symlinks into account --- src/uucore/src/lib/features/perms.rs | 15 ++++++- tests/by-util/test_chgrp.rs | 65 ++++++++++++++++++++++++++++ 2 files changed, 78 insertions(+), 2 deletions(-) diff --git a/src/uucore/src/lib/features/perms.rs b/src/uucore/src/lib/features/perms.rs index b605c0cdf..a4a01c499 100644 --- a/src/uucore/src/lib/features/perms.rs +++ b/src/uucore/src/lib/features/perms.rs @@ -265,10 +265,21 @@ impl ChownExecutor { } fn dive_into>(&self, root: P) -> i32 { - let mut ret = 0; let root = root.as_ref(); + + // walkdir always dereferences the root directory, so we have to check it ourselves + // TODO: replace with `root.is_symlink()` once it is stable + if self.traverse_symlinks == TraverseSymlinks::None + && std::fs::symlink_metadata(root) + .map(|m| m.file_type().is_symlink()) + .unwrap_or(false) + { + return 0; + } + + let mut ret = 0; let mut iterator = WalkDir::new(root) - .follow_links(self.dereference) + .follow_links(self.traverse_symlinks == TraverseSymlinks::All) .min_depth(1) .into_iter(); // We can't use a for loop because we need to manipulate the iterator inside the loop. diff --git a/tests/by-util/test_chgrp.rs b/tests/by-util/test_chgrp.rs index 0fc73520e..1d047cfe2 100644 --- a/tests/by-util/test_chgrp.rs +++ b/tests/by-util/test_chgrp.rs @@ -288,3 +288,68 @@ fn test_subdir_permission_denied() { .stderr_only("chgrp: cannot access 'dir/subdir': Permission denied"); } } + +#[test] +#[cfg(not(target_vendor = "apple"))] +fn test_traverse_symlinks() { + use std::os::unix::prelude::MetadataExt; + let groups = nix::unistd::getgroups().unwrap(); + if groups.len() < 2 { + return; + } + let (first_group, second_group) = (groups[0], groups[1]); + + for &(args, traverse_first, traverse_second) in &[ + (&[][..] as &[&str], false, false), + (&["-H"][..], true, false), + (&["-P"][..], false, false), + (&["-L"][..], true, true), + ] { + let scenario = TestScenario::new("chgrp"); + + let (at, mut ucmd) = (scenario.fixtures.clone(), scenario.ucmd()); + + at.mkdir("dir"); + at.mkdir("dir2"); + at.touch("dir2/file"); + at.mkdir("dir3"); + at.touch("dir3/file"); + at.symlink_dir("dir2", "dir/dir2_ln"); + at.symlink_dir("dir3", "dir3_ln"); + + scenario + .ccmd("chgrp") + .arg(first_group.to_string()) + .arg("dir2/file") + .arg("dir3/file") + .succeeds(); + + assert!(at.plus("dir2/file").metadata().unwrap().gid() == first_group.as_raw()); + assert!(at.plus("dir3/file").metadata().unwrap().gid() == first_group.as_raw()); + + ucmd.arg("-R") + .args(args) + .arg(second_group.to_string()) + .arg("dir") + .arg("dir3_ln") + .succeeds() + .no_stderr(); + + assert_eq!( + at.plus("dir2/file").metadata().unwrap().gid(), + if traverse_second { + second_group.as_raw() + } else { + first_group.as_raw() + } + ); + assert_eq!( + at.plus("dir3/file").metadata().unwrap().gid(), + if traverse_first { + second_group.as_raw() + } else { + first_group.as_raw() + } + ); + } +} From 435b7a22fb8d64d1c2c0d4ddca0b387f25984c1d Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Thu, 2 Sep 2021 22:42:09 +0200 Subject: [PATCH 135/206] uucore/perms: add more information to an error message This reverts part of https://github.com/uutils/coreutils/pull/2628, because (even though it got the test passing) it was the wrong bug fix. --- src/uucore/src/lib/features/perms.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/uucore/src/lib/features/perms.rs b/src/uucore/src/lib/features/perms.rs index a4a01c499..b071cedaa 100644 --- a/src/uucore/src/lib/features/perms.rs +++ b/src/uucore/src/lib/features/perms.rs @@ -358,7 +358,12 @@ impl ChownExecutor { Err(e) => { match self.verbosity.level { VerbosityLevel::Silent => (), - _ => show_error!("cannot access '{}': {}", path.display(), strip_errno(&e)), + _ => show_error!( + "cannot {} '{}': {}", + if follow { "dereference" } else { "access" }, + path.display(), + strip_errno(&e) + ), } None } From d1c3a8f69a64188af1581c6b5c975d6042d8f736 Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Fri, 3 Sep 2021 14:37:47 +0200 Subject: [PATCH 136/206] uucore/perms: remove erroneous comment --- src/uucore/src/lib/features/perms.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/uucore/src/lib/features/perms.rs b/src/uucore/src/lib/features/perms.rs index b071cedaa..8a2f20417 100644 --- a/src/uucore/src/lib/features/perms.rs +++ b/src/uucore/src/lib/features/perms.rs @@ -181,7 +181,6 @@ pub struct ChownExecutor { pub files: Vec, pub recursive: bool, pub preserve_root: bool, - // Must be true if traverse_symlinks is not None pub dereference: bool, } From 6a6da71403269df83ef41b3d68ccd67c79230656 Mon Sep 17 00:00:00 2001 From: Michael Debertol Date: Fri, 3 Sep 2021 14:56:35 +0200 Subject: [PATCH 137/206] cp: add a test for #2631 --- tests/by-util/test_cp.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tests/by-util/test_cp.rs b/tests/by-util/test_cp.rs index c77f4c01d..08a9643ca 100644 --- a/tests/by-util/test_cp.rs +++ b/tests/by-util/test_cp.rs @@ -7,6 +7,8 @@ use std::fs::set_permissions; #[cfg(not(windows))] use std::os::unix::fs; +#[cfg(unix)] +use std::os::unix::fs::symlink as symlink_file; #[cfg(unix)] use std::os::unix::fs::PermissionsExt; #[cfg(windows)] @@ -1353,3 +1355,16 @@ fn test_preserve_mode() { PERMS_ALL ); } + +#[test] +fn test_canonicalize_symlink() { + let (at, mut ucmd) = at_and_ucmd!(); + at.mkdir("dir"); + at.touch("dir/file"); + symlink_file("../dir/file", at.plus("dir/file-ln")).unwrap(); + ucmd.arg("dir/file-ln") + .arg(".") + .succeeds() + .no_stderr() + .no_stdout(); +} From 3f6ca4723e4a49fcd9b8fd394010ed2b1d5056cc Mon Sep 17 00:00:00 2001 From: Jan Verbeek Date: Fri, 3 Sep 2021 19:24:02 +0200 Subject: [PATCH 138/206] Update num-bigint to 0.4.2 to fix CI --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c632db295..57e77b3db 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1144,9 +1144,9 @@ dependencies = [ [[package]] name = "num-bigint" -version = "0.4.0" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e0d047c1062aa51e256408c560894e5251f08925980e53cf1aa5bd00eec6512" +checksum = "74e768dff5fb39a41b3bcd30bb25cf989706c90d028d1ad71971987aa309d535" dependencies = [ "autocfg", "num-integer", From 9bc14a239c9dfb826a8f8681da03f299390259dc Mon Sep 17 00:00:00 2001 From: Mahmoud Soltan Date: Sun, 5 Sep 2021 13:25:56 +0200 Subject: [PATCH 139/206] Added support for `ls -l --color` to color symlink targets as well. (#2627) * Fixed some documentation of display_item_long that was missed in #2623 * Implemented coloring of `ls -l` symlink targets( after the arrow `->`). * Documented display_file_name to some extent. * Ran rustfmt as part of mitigating CI chain errors. * Removed unused variables and code in test_ls_long_format as per #2623 specifically, as per https://github.com/uutils/coreutils/pull/2623#pullrequestreview-742386707 * Added a thorough test for `ls -laR --color` symlink coloring implemented in this branch. * renamed test files and dirs to shorter names and ran rustfmt. * Changed the order with which files are expected to match the change in their name. * Bettered some comments. * Removed needless borrow. Fixed that one clippy warning. * Moved the cfg not windows up to the function level because this function is meant to only run in non-windows OS (with groups and unames). Fixes the unused variable warning in CI. --- src/uu/ls/src/ls.rs | 63 +++++++--- tests/by-util/test_ls.rs | 247 +++++++++++++++++++++++++++++++++++---- 2 files changed, 273 insertions(+), 37 deletions(-) diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index fcb0b28ff..6f63c2a4a 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -1570,22 +1570,12 @@ fn display_grid( /// /// That's why we have the parameters: /// ```txt -/// max_links: usize, +/// longest_link_count_len: usize, /// longest_uname_len: usize, /// longest_group_len: usize, -/// max_size: usize, +/// longest_size_len: usize, /// ``` /// that decide the maximum possible character count of each field. -/// -/// [`get_inode`]: ls::get_inode -/// [`display_permissions`]: ls::display_permissions -/// [`display_symlink_count`]: ls::display_symlink_count -/// [`display_uname`]: ls::display_uname -/// [`display_group`]: ls::display_group -/// [`display_size_or_rdev`]: ls::display_size_or_rdev -/// [`get_system_time`]: ls::get_system_time -/// [`display_file_name`]: ls::display_file_name -/// [`pad_left`]: ls::pad_left fn display_item_long( item: &PathData, longest_link_count_len: usize, @@ -1866,7 +1856,20 @@ fn classify_file(path: &PathData) -> Option { } } +/// Takes a [`PathData`] struct and returns a cell with a name ready for displaying. +/// +/// This function relies on the following parameters in the provided `&Config`: +/// * `config.quoting_style` to decide how we will escape `name` using [`escape_name`]. +/// * `config.inode` decides whether to display inode numbers beside names using [`get_inode`]. +/// * `config.color` decides whether it's going to color `name` using [`color_name`]. +/// * `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. +/// +/// 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 { + // 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)] @@ -1919,7 +1922,41 @@ fn display_file_name(path: &PathData, config: &Config) -> Option { if config.format == Format::Long && path.file_type()?.is_symlink() { if let Ok(target) = path.p_buf.read_link() { name.push_str(" -> "); - name.push_str(&target.to_string_lossy()); + + // We might as well color the symlink output after the arrow. + // This makes extra system calls, but provides important information that + // people run `ls -l --color` are very interested in. + if let Some(ls_colors) = &config.color { + // We get the absolute path to be able to construct PathData with valid Metadata. + // This is because relative symlinks will fail to get_metadata. + let mut absolute_target = target.clone(); + if target.is_relative() { + if let Some(parent) = path.p_buf.parent() { + absolute_target = parent.join(absolute_target); + } + } + + let target_data = PathData::new(absolute_target, None, None, config, false); + + // If we have a symlink to a valid file, we use the metadata of said file. + // Because we use an absolute path, we can assume this is guaranteed to exist. + // Otherwise, we use path.md(), which will guarantee we color to the same + // color of non-existent symlinks according to style_for_path_with_metadata. + let target_metadata = match target_data.md() { + Some(md) => md, + None => path.md()?, + }; + + name.push_str(&color_name( + ls_colors, + &target_data.p_buf, + target.to_string_lossy().into_owned(), + target_metadata, + )); + } else { + // If no coloring is required, we just use target as is. + name.push_str(&target.to_string_lossy()); + } } } diff --git a/tests/by-util/test_ls.rs b/tests/by-util/test_ls.rs index e6c23acc1..3d6092416 100644 --- a/tests/by-util/test_ls.rs +++ b/tests/by-util/test_ls.rs @@ -1,4 +1,4 @@ -// spell-checker:ignore (words) READMECAREFULLY birthtime doesntexist oneline somebackup somefile somegroup somehiddenbackup somehiddenfile +// spell-checker:ignore (words) READMECAREFULLY birthtime doesntexist oneline somebackup lrwx somefile somegroup somehiddenbackup somehiddenfile #[cfg(unix)] extern crate unix_socket; @@ -333,20 +333,9 @@ fn test_ls_long() { } } +#[cfg(not(windows))] #[test] fn test_ls_long_format() { - #[cfg(not(windows))] - let last; - #[cfg(not(windows))] - { - let _guard = UMASK_MUTEX.lock(); - last = unsafe { umask(0) }; - - unsafe { - umask(0o002); - } - } - let scene = TestScenario::new(util_name!()); let at = &scene.fixtures; at.mkdir(&at.plus_as_string("test-long-dir")); @@ -354,8 +343,6 @@ fn test_ls_long_format() { at.mkdir(&at.plus_as_string("test-long-dir/test-long-dir")); for arg in &["-l", "--long", "--format=long", "--format=verbose"] { - #[allow(unused_variables)] - let result = scene.ucmd().arg(arg).arg("test-long-dir").succeeds(); // Assuming sane username do not have spaces within them. // A line of the output should be: // One of the characters -bcCdDlMnpPsStTx? @@ -368,26 +355,238 @@ fn test_ls_long_format() { // Either a year or a time, currently [0-9:]+, preceded by column whitespace, // and followed by a single space. // Whatever comes after is irrelevant to this specific test. - #[cfg(not(windows))] - result.stdout_matches(&Regex::new( + 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:]+ " ).unwrap()); } - #[allow(unused_variables)] - let result = scene.ucmd().arg("-lan").arg("test-long-dir").succeeds(); // This checks for the line with the .. entry. The uname and group should be digits. - #[cfg(not(windows))] - result.stdout_matches(&Regex::new( + 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:]+ \.\." ).unwrap()); +} - #[cfg(not(windows))] +/// This test tests `ls -laR --color`. +/// 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. +#[test] +fn test_ls_long_symlink_color() { + // If you break this test after breaking mkdir, touch, or ln, do not be alarmed! + // This test is made for ls, but it attempts to run those utils in the process. + + // Having Some([2, 0]) in a color basically means that "it has the same color as whatever + // is in the 2nd expected output, the 0th color", where the 0th color is the name color, and + // the 1st color is the target color, in a fixed-size array of size 2. + // Basically these are references to be used for indexing the `colors` vector defined below. + type ColorReference = Option<[usize; 2]>; + + // The string between \x1b[ and m + type Color = String; + + // The string between the color start and the color end is the file name itself. + type Name = String; + + let scene = TestScenario::new(util_name!()); + + // . + // ├── dir1 + // │ ├── file1 + // │ ├── dir2 + // │ │ └── dir3 + // │ ├── ln-dir-invalid -> dir1/dir2 + // │ ├── ln-up2 -> ../.. + // │ └── ln-root -> / + // ├── ln-file1 -> dir1/file1 + // ├── ln-file-invalid -> dir1/invalid-target + // └── ln-dir3 -> ./dir1/dir2/dir3 + prepare_folder_structure(&scene); + + // We memoize the colors so we can refer to them later. + // Each entry will be the colors of the link name and link target of a specific output. + let mut colors: Vec<[Color; 2]> = vec![]; + + // The contents of each tuple are the expected colors and names for the link and target. + // We will loop over the ls output and compare to those. + // None values mean that we do not know what color to expect yet, as LS_COLOR might + // be set differently, and as different implementations of ls may use different codes, + // for example, our ls uses `[1;36m` while the GNU ls uses `[01;36m`. + // + // These have been sorting according to default ls sort, and this affects the order of + // discovery of colors, so be very careful when changing directory/file names being created. + let expected_output: [(ColorReference, &str, ColorReference, &str); 6] = [ + // We don't know what colors are what the first time we meet a link. + (None, "ln-dir3", None, "./dir1/dir2/dir3"), + // We have acquired [0, 0], which should be the link color, + // and [0, 1], which should be the dir color, and we can compare to them from now on. + (None, "ln-file-invalid", Some([1, 1]), "dir1/invalid-target"), + // We acquired [1, 1], the non-existent color. + (Some([0, 0]), "ln-file1", None, "dir1/file1"), + (Some([1, 1]), "ln-dir-invalid", Some([1, 1]), "dir1/dir2"), + (Some([0, 0]), "ln-root", Some([0, 1]), "/"), + (Some([0, 0]), "ln-up2", Some([0, 1]), "../.."), + ]; + + // We are only interested in lines or the ls output that are symlinks. These start with "lrwx". + let result = scene.ucmd().arg("-laR").arg("--color").arg(".").succeeds(); + let mut result_lines = result + .stdout_str() + .lines() + .filter_map(|line| match line.starts_with("lrwx") { + true => Some(line), + false => None, + }) + .enumerate(); + + // For each enumerated line, we assert that the output of ls matches the expected output. + // + // The unwraps within get_index_name_target will panic if a line starting lrwx does + // not have `colored_name -> target` within it. + while let Some((i, name, target)) = get_index_name_target(&mut result_lines) { + // The unwraps within capture_colored_string will panic if the name/target's color + // format is invalid. + let (matched_name_color, matched_name) = capture_colored_string(&name); + let (matched_target_color, matched_target) = capture_colored_string(&target); + + colors.push([matched_name_color, matched_target_color]); + + // We borrow them again after having moved them. This unwrap will never panic. + let [matched_name_color, matched_target_color] = colors.last().unwrap(); + + // We look up the Colors that are expected in `colors` using the ColorReferences + // stored in `expected_output`. + let expected_name_color = match expected_output[i].0 { + Some(color_reference) => Some(colors[color_reference[0]][color_reference[1]].as_str()), + None => None, + }; + let expected_target_color = match expected_output[i].2 { + Some(color_reference) => Some(colors[color_reference[0]][color_reference[1]].as_str()), + None => None, + }; + + // This is the important part. The asserts inside assert_names_and_colors_are_equal + // will panic if the colors or names do not match the expected colors or names. + // Keep in mind an expected color `Option<&str>` of None can mean either that we + // don't expect any color here, as in `expected_output[2], or don't know what specific + // color to expect yet, as in expected_output[0:1]. + assert_names_and_colors_are_equal( + &matched_name_color, + expected_name_color, + &matched_name, + expected_output[i].1, + &matched_target_color, + expected_target_color, + &matched_target, + expected_output[i].3, + ); + } + + // End of test, only definitions of the helper functions used above follows... + + fn get_index_name_target<'a, I>(lines: &mut I) -> Option<(usize, Name, Name)> + where + I: Iterator, { - unsafe { - umask(last); + match lines.next() { + Some((c, s)) => { + // `name` is whatever comes between \x1b (inclusive) and the arrow. + let name = String::from("\x1b") + + s.split(" -> ") + .next() + .unwrap() + .split(" \x1b") + .last() + .unwrap(); + // `target` is whatever comes after the arrow. + let target = s.split(" -> ").last().unwrap().to_string(); + Some((c, name, target)) + } + None => None, } } + + fn assert_names_and_colors_are_equal( + name_color: &str, + expected_name_color: Option<&str>, + name: &str, + expected_name: &str, + target_color: &str, + expected_target_color: Option<&str>, + target: &str, + expected_target: &str, + ) { + // Names are always compared. + assert_eq!(&name, &expected_name); + assert_eq!(&target, &expected_target); + + // Colors are only compared when we have inferred what color we are looking for. + if expected_name_color.is_some() { + assert_eq!(&name_color, &expected_name_color.unwrap()); + } + if expected_target_color.is_some() { + assert_eq!(&target_color, &expected_target_color.unwrap()); + } + } + + fn capture_colored_string(input: &str) -> (Color, Name) { + let colored_name = Regex::new(r"\x1b\[([0-9;]+)m(.+)\x1b\[0m").unwrap(); + match colored_name.captures(&input) { + Some(captures) => ( + captures.get(1).unwrap().as_str().to_string(), + captures.get(2).unwrap().as_str().to_string(), + ), + None => ("".to_string(), input.to_string()), + } + } + + fn prepare_folder_structure(scene: &TestScenario) { + // There is no way to change directory in the CI, so this is the best we can do. + // Also, keep in mind that windows might require privilege to symlink directories. + // + // We use scene.ccmd instead of scene.fixtures because we care about relative symlinks. + // So we're going to try out the built mkdir, touch, and ln here, and we expect them to succeed. + scene.ccmd("mkdir").arg("dir1").succeeds(); + scene.ccmd("mkdir").arg("dir1/dir2").succeeds(); + scene.ccmd("mkdir").arg("dir1/dir2/dir3").succeeds(); + scene.ccmd("touch").arg("dir1/file1").succeeds(); + + scene + .ccmd("ln") + .arg("-s") + .arg("dir1/dir2") + .arg("dir1/ln-dir-invalid") + .succeeds(); + scene + .ccmd("ln") + .arg("-s") + .arg("./dir1/dir2/dir3") + .arg("ln-dir3") + .succeeds(); + scene + .ccmd("ln") + .arg("-s") + .arg("../..") + .arg("dir1/ln-up2") + .succeeds(); + scene + .ccmd("ln") + .arg("-s") + .arg("/") + .arg("dir1/ln-root") + .succeeds(); + scene + .ccmd("ln") + .arg("-s") + .arg("dir1/file1") + .arg("ln-file1") + .succeeds(); + scene + .ccmd("ln") + .arg("-s") + .arg("dir1/invalid-target") + .arg("ln-file-invalid") + .succeeds(); + } } #[test] From 5587face61efb494c0603727f060d71d78066cbc Mon Sep 17 00:00:00 2001 From: David Carlier Date: Sun, 5 Sep 2021 13:06:25 +0100 Subject: [PATCH 140/206] fsext build fix for OpenBSD. prioritising statfs over statvfs for this platform. --- src/uucore/src/lib/features/fsext.rs | 75 +++++++++++++++++++++------- 1 file changed, 58 insertions(+), 17 deletions(-) diff --git a/src/uucore/src/lib/features/fsext.rs b/src/uucore/src/lib/features/fsext.rs index 72471ff9e..7525d9e41 100644 --- a/src/uucore/src/lib/features/fsext.rs +++ b/src/uucore/src/lib/features/fsext.rs @@ -88,13 +88,12 @@ use std::time::UNIX_EPOCH; target_os = "linux", target_vendor = "apple", target_os = "android", - target_os = "freebsd" + target_os = "freebsd", + target_os = "openbsd" ))] pub use libc::statfs as StatFs; #[cfg(any( - target_os = "openbsd", target_os = "netbsd", - target_os = "openbsd", target_os = "bitrig", target_os = "dragonfly", target_os = "redox" @@ -105,13 +104,12 @@ pub use libc::statvfs as StatFs; target_os = "linux", target_vendor = "apple", target_os = "android", - target_os = "freebsd" + target_os = "freebsd", + target_os = "openbsd", ))] pub use libc::statfs as statfs_fn; #[cfg(any( - target_os = "openbsd", target_os = "netbsd", - target_os = "openbsd", target_os = "bitrig", target_os = "dragonfly", target_os = "redox" @@ -311,9 +309,19 @@ impl MountInfo { } } -#[cfg(any(target_vendor = "apple", target_os = "freebsd", target_os = "netbsd"))] +#[cfg(any( + target_vendor = "apple", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" +))] use std::ffi::CStr; -#[cfg(any(target_os = "freebsd", target_vendor = "apple", target_os = "netbsd"))] +#[cfg(any( + target_os = "freebsd", + target_vendor = "apple", + target_os = "netbsd", + target_os = "openbsd" +))] impl From for MountInfo { fn from(statfs: StatFs) -> Self { let mut info = MountInfo { @@ -346,9 +354,19 @@ impl From for MountInfo { } } -#[cfg(any(target_os = "freebsd", target_vendor = "apple", target_os = "netbsd"))] +#[cfg(any( + target_os = "freebsd", + target_vendor = "apple", + target_os = "netbsd", + target_os = "openbsd" +))] use libc::c_int; -#[cfg(any(target_os = "freebsd", target_vendor = "apple", target_os = "netbsd"))] +#[cfg(any( + target_os = "freebsd", + target_vendor = "apple", + target_os = "netbsd", + target_os = "openbsd" +))] extern "C" { #[cfg(all(target_vendor = "apple", target_arch = "x86_64"))] #[link_name = "getmntinfo$INODE64"] // spell-checker:disable-line @@ -357,6 +375,7 @@ extern "C" { #[cfg(any( all(target_os = "freebsd"), all(target_os = "netbsd"), + all(target_os = "openbsd"), all(target_vendor = "apple", target_arch = "aarch64") ))] #[link_name = "getmntinfo"] // spell-checker:disable-line @@ -371,10 +390,16 @@ use std::io::{BufRead, BufReader}; target_vendor = "apple", target_os = "freebsd", target_os = "windows", - target_os = "netbsd" + target_os = "netbsd", + target_os = "openbsd" ))] use std::ptr; -#[cfg(any(target_vendor = "apple", target_os = "freebsd", target_os = "netbsd"))] +#[cfg(any( + target_vendor = "apple", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" +))] use std::slice; /// Read file system list. pub fn read_fs_list() -> Vec { @@ -394,7 +419,12 @@ pub fn read_fs_list() -> Vec { }) .collect::>() } - #[cfg(any(target_os = "freebsd", target_vendor = "apple", target_os = "netbsd"))] + #[cfg(any( + target_os = "freebsd", + target_vendor = "apple", + target_os = "netbsd", + target_os = "openbsd" + ))] { let mut mount_buffer_ptr: *mut StatFs = ptr::null_mut(); let len = unsafe { get_mount_info(&mut mount_buffer_ptr, 1_i32) }; @@ -611,13 +641,23 @@ impl FsMeta for StatFs { // // Solaris, Irix and POSIX have a system call statvfs(2) that returns a // struct statvfs, containing an unsigned long f_fsid - #[cfg(any(target_vendor = "apple", target_os = "freebsd", target_os = "linux"))] + #[cfg(any( + target_vendor = "apple", + target_os = "freebsd", + target_os = "linux", + target_os = "openbsd" + ))] fn fsid(&self) -> u64 { let f_fsid: &[u32; 2] = unsafe { &*(&self.f_fsid as *const libc::fsid_t as *const [u32; 2]) }; (u64::from(f_fsid[0])) << 32 | u64::from(f_fsid[1]) } - #[cfg(not(any(target_vendor = "apple", target_os = "freebsd", target_os = "linux")))] + #[cfg(not(any( + target_vendor = "apple", + target_os = "freebsd", + target_os = "linux", + target_os = "openbsd" + )))] fn fsid(&self) -> u64 { self.f_fsid as u64 } @@ -630,7 +670,7 @@ impl FsMeta for StatFs { fn namelen(&self) -> u64 { 1024 } - #[cfg(any(target_os = "freebsd", target_os = "netbsd"))] + #[cfg(any(target_os = "freebsd", target_os = "netbsd", target_os = "openbsd"))] fn namelen(&self) -> u64 { self.f_namemax as u64 // spell-checker:disable-line } @@ -639,7 +679,8 @@ impl FsMeta for StatFs { target_vendor = "apple", target_os = "freebsd", target_os = "linux", - target_os = "netbsd" + target_os = "netbsd", + target_os = "openbsd" )))] fn namelen(&self) -> u64 { self.f_namemax as u64 // spell-checker:disable-line From 7acdf31e2b17b32b0b11547bccbd9655e94c2d91 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sun, 29 Aug 2021 18:35:57 +0200 Subject: [PATCH 141/206] freebsd: also fails the script when the tests fail --- .github/workflows/CICD.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/CICD.yml b/.github/workflows/CICD.yml index 4f92d7d73..68eaf3d06 100644 --- a/.github/workflows/CICD.yml +++ b/.github/workflows/CICD.yml @@ -553,6 +553,7 @@ jobs: # Needs to be done in a sudo as we are changing users sudo -i -u cuuser sh << EOF + set -e whoami curl https://sh.rustup.rs -sSf --output rustup.sh sh rustup.sh -y --profile=minimal From 00221a4d3628fe743e8e8482fac5243ae85b3f9e Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sun, 29 Aug 2021 21:00:05 +0200 Subject: [PATCH 142/206] Silent test_chown_only_group_id on freebsd --- tests/by-util/test_chown.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/by-util/test_chown.rs b/tests/by-util/test_chown.rs index 84a0d1c97..11f4d30ae 100644 --- a/tests/by-util/test_chown.rs +++ b/tests/by-util/test_chown.rs @@ -326,6 +326,8 @@ fn test_chown_only_user_id() { } #[test] +// FixME: stderr = chown: ownership of 'test_chown_file1' retained as cuuser:wheel +// #[cfg(not(target_os = "freebsd"))] fn test_chown_only_group_id() { // test chown :1111 file.txt From 1986d346dcf889de79fd7e0a27789b3e5e511fd4 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sun, 29 Aug 2021 21:01:12 +0200 Subject: [PATCH 143/206] Silent 'test_more_than_2_sets' on freebsd --- tests/by-util/test_tr.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/by-util/test_tr.rs b/tests/by-util/test_tr.rs index 8a3e36625..bd022b1c2 100644 --- a/tests/by-util/test_tr.rs +++ b/tests/by-util/test_tr.rs @@ -286,6 +286,8 @@ fn test_interpret_backslash_at_eol_literally() { } #[test] +// FixME: panicked at 'failed to write to stdin of child: Broken pipe (os error 32) +#[cfg(not(target_os = "freebsd"))] fn test_more_than_2_sets() { new_ucmd!() .args(&["'abcdefgh'", "'a", "'b'"]) From 99764b8e6e2da2935c2224b1e0b58e08e266d875 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Thu, 2 Sep 2021 09:14:17 +0200 Subject: [PATCH 144/206] Add "cuuser" to the spell ignore in tests --- tests/by-util/test_chown.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/by-util/test_chown.rs b/tests/by-util/test_chown.rs index 11f4d30ae..62ce43030 100644 --- a/tests/by-util/test_chown.rs +++ b/tests/by-util/test_chown.rs @@ -1,4 +1,4 @@ -// spell-checker:ignore (words) agroupthatdoesntexist auserthatdoesntexist groupname notexisting passgrp +// spell-checker:ignore (words) agroupthatdoesntexist auserthatdoesntexist cuuser groupname notexisting passgrp use crate::common::util::*; #[cfg(target_os = "linux")] From f8c4b56174ea585a9228521c59e4b82129af384f Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sun, 5 Sep 2021 17:27:29 +0200 Subject: [PATCH 145/206] Remove comment on test_chown_only_group_id --- tests/by-util/test_chown.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/by-util/test_chown.rs b/tests/by-util/test_chown.rs index 62ce43030..725e4b244 100644 --- a/tests/by-util/test_chown.rs +++ b/tests/by-util/test_chown.rs @@ -327,7 +327,7 @@ fn test_chown_only_user_id() { #[test] // FixME: stderr = chown: ownership of 'test_chown_file1' retained as cuuser:wheel -// #[cfg(not(target_os = "freebsd"))] +#[cfg(not(target_os = "freebsd"))] fn test_chown_only_group_id() { // test chown :1111 file.txt From 9c22def6932fbc0e99b51054f637bf447b26797d Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sun, 5 Sep 2021 18:51:47 +0200 Subject: [PATCH 146/206] cp: ignore test_no_preserve_mode & test_preserve_mode --- tests/by-util/test_cp.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/by-util/test_cp.rs b/tests/by-util/test_cp.rs index 08a9643ca..f126517fe 100644 --- a/tests/by-util/test_cp.rs +++ b/tests/by-util/test_cp.rs @@ -1309,7 +1309,7 @@ fn test_copy_symlink_force() { } #[test] -#[cfg(unix)] +#[cfg(all(unix, not(target_os = "freebsd")))] fn test_no_preserve_mode() { use std::os::unix::prelude::MetadataExt; @@ -1335,7 +1335,7 @@ fn test_no_preserve_mode() { } #[test] -#[cfg(unix)] +#[cfg(all(unix, not(target_os = "freebsd")))] fn test_preserve_mode() { use std::os::unix::prelude::MetadataExt; From 625c3f2330739e90b107a32fe3fe51e4c2d2a4b3 Mon Sep 17 00:00:00 2001 From: James Robson Date: Sun, 29 Aug 2021 15:08:45 +0100 Subject: [PATCH 147/206] Add -e/-m to realpath --- src/uu/realpath/src/realpath.rs | 39 +++++++++++++++++++++++++++--- tests/by-util/test_realpath.rs | 43 +++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+), 3 deletions(-) diff --git a/src/uu/realpath/src/realpath.rs b/src/uu/realpath/src/realpath.rs index cbe1d40da..451253d50 100644 --- a/src/uu/realpath/src/realpath.rs +++ b/src/uu/realpath/src/realpath.rs @@ -21,6 +21,8 @@ static OPT_STRIP: &str = "strip"; static OPT_ZERO: &str = "zero"; static OPT_PHYSICAL: &str = "physical"; static OPT_LOGICAL: &str = "logical"; +const OPT_CANONICALIZE_MISSING: &str = "canonicalize-missing"; +const OPT_CANONICALIZE_EXISTING: &str = "canonicalize-existing"; static ARG_FILES: &str = "files"; @@ -45,9 +47,16 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let zero = matches.is_present(OPT_ZERO); let quiet = matches.is_present(OPT_QUIET); let logical = matches.is_present(OPT_LOGICAL); + let can_mode = if matches.is_present(OPT_CANONICALIZE_EXISTING) { + MissingHandling::Existing + } else if matches.is_present(OPT_CANONICALIZE_MISSING) { + MissingHandling::Missing + } else { + MissingHandling::Normal + }; let mut retcode = 0; for path in &paths { - if let Err(e) = resolve_path(path, strip, zero, logical) { + if let Err(e) = resolve_path(path, strip, zero, logical, can_mode) { if !quiet { show_error!("{}: {}", e, path.display()); } @@ -92,6 +101,24 @@ pub fn uu_app() -> App<'static, 'static> { .overrides_with_all(&[OPT_STRIP, OPT_LOGICAL]) .help("resolve symlinks as encountered (default)"), ) + .arg( + Arg::with_name(OPT_CANONICALIZE_EXISTING) + .short("e") + .long(OPT_CANONICALIZE_EXISTING) + .help( + "canonicalize by following every symlink in every component of the \ + given name recursively, all components must exist", + ), + ) + .arg( + Arg::with_name(OPT_CANONICALIZE_MISSING) + .short("m") + .long(OPT_CANONICALIZE_MISSING) + .help( + "canonicalize by following every symlink in every component of the \ + given name recursively, without requirements on components existence", + ), + ) .arg( Arg::with_name(ARG_FILES) .multiple(true) @@ -112,7 +139,13 @@ pub fn uu_app() -> App<'static, 'static> { /// /// This function returns an error if there is a problem resolving /// symbolic links. -fn resolve_path(p: &Path, strip: bool, zero: bool, logical: bool) -> std::io::Result<()> { +fn resolve_path( + p: &Path, + strip: bool, + zero: bool, + logical: bool, + can_mode: MissingHandling, +) -> std::io::Result<()> { let resolve = if strip { ResolveMode::None } else if logical { @@ -120,7 +153,7 @@ fn resolve_path(p: &Path, strip: bool, zero: bool, logical: bool) -> std::io::Re } else { ResolveMode::Physical }; - let abs = canonicalize(p, MissingHandling::Normal, resolve)?; + let abs = canonicalize(p, can_mode, resolve)?; let line_ending = if zero { '\0' } else { '\n' }; print!("{}{}", abs.display(), line_ending); diff --git a/tests/by-util/test_realpath.rs b/tests/by-util/test_realpath.rs index 7b6da5d36..72bf5b6ea 100644 --- a/tests/by-util/test_realpath.rs +++ b/tests/by-util/test_realpath.rs @@ -1,5 +1,9 @@ use crate::common::util::*; +use std::path::Path; + +static GIBBERISH: &str = "supercalifragilisticexpialidocious"; + #[test] fn test_realpath_current_directory() { let (at, mut ucmd) = at_and_ucmd!(); @@ -159,3 +163,42 @@ fn test_realpath_loop() { .succeeds() .stdout_only(at.plus_as_string("2\n")); } + +#[test] +fn test_realpath_default_allows_final_non_existent() { + let p = Path::new("").join(GIBBERISH); + let (at, mut ucmd) = at_and_ucmd!(); + let expect = path_concat!(at.root_dir_resolved(), p.to_str().unwrap()) + "\n"; + ucmd.arg(p.as_os_str()).succeeds().stdout_only(expect); +} + +#[test] +fn test_realpath_default_forbids_non_final_non_existent() { + let p = Path::new("").join(GIBBERISH).join(GIBBERISH); + new_ucmd!().arg(p.to_str().unwrap()).fails(); +} + +#[test] +fn test_realpath_existing() { + let (at, mut ucmd) = at_and_ucmd!(); + ucmd.arg("-e") + .arg(".") + .succeeds() + .stdout_only(at.plus_as_string(&format!("{}\n", at.root_dir_resolved()))); +} + +#[test] +fn test_realpath_existing_error() { + new_ucmd!().arg("-e").arg(GIBBERISH).fails(); +} + +#[test] +fn test_realpath_missing() { + let p = Path::new("").join(GIBBERISH).join(GIBBERISH); + let (at, mut ucmd) = at_and_ucmd!(); + let expect = path_concat!(at.root_dir_resolved(), p.to_str().unwrap()) + "\n"; + ucmd.arg("-m") + .arg(p.as_os_str()) + .succeeds() + .stdout_only(expect); +} From 90fd900b8ffee801db037260610d661a47ed638a Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Mon, 6 Sep 2021 20:41:41 +0200 Subject: [PATCH 148/206] Fix clippy warnings in test.rs --- src/uu/test/src/test.rs | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/uu/test/src/test.rs b/src/uu/test/src/test.rs index b1367b6ed..50563ba49 100644 --- a/src/uu/test/src/test.rs +++ b/src/uu/test/src/test.rs @@ -11,10 +11,9 @@ mod parser; use clap::{crate_version, App, AppSettings}; -use parser::{parse, Op, Symbol, UnaryOp}; +use parser::{parse, Operator, Symbol, UnaryOperator}; use std::ffi::{OsStr, OsString}; use std::path::Path; -use uucore::executable; const USAGE: &str = "test EXPRESSION or: test @@ -87,7 +86,7 @@ the version described here. Please refer to your shell's documentation for details about the options it supports."; pub fn uu_app() -> App<'static, 'static> { - App::new(executable!()) + App::new(uucore::util_name()) .setting(AppSettings::DisableHelpFlags) .setting(AppSettings::DisableVersion) } @@ -160,19 +159,19 @@ fn eval(stack: &mut Vec) -> Result { Ok(!result) } - Some(Symbol::Op(Op::StringOp(op))) => { + Some(Symbol::Op(Operator::String(op))) => { let b = stack.pop(); let a = stack.pop(); Ok(if op == "!=" { a != b } else { a == b }) } - Some(Symbol::Op(Op::IntOp(op))) => { + Some(Symbol::Op(Operator::Int(op))) => { let b = pop_literal!(); let a = pop_literal!(); Ok(integers(&a, &b, &op)?) } - Some(Symbol::Op(Op::FileOp(_op))) => unimplemented!(), - Some(Symbol::UnaryOp(UnaryOp::StrlenOp(op))) => { + Some(Symbol::Op(Operator::File(_op))) => unimplemented!(), + Some(Symbol::UnaryOp(UnaryOperator::StrlenOp(op))) => { let s = match stack.pop() { Some(Symbol::Literal(s)) => s, Some(Symbol::None) => OsString::from(""), @@ -190,7 +189,7 @@ fn eval(stack: &mut Vec) -> Result { !s.is_empty() }) } - Some(Symbol::UnaryOp(UnaryOp::FiletestOp(op))) => { + Some(Symbol::UnaryOp(UnaryOperator::FiletestOp(op))) => { let op = op.to_string_lossy(); let f = pop_literal!(); From 17a435c7a68dad05b19fbaa4b1a676d448b54633 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Mon, 6 Sep 2021 20:42:14 +0200 Subject: [PATCH 149/206] Fix clippy warnings --- src/uu/test/src/parser.rs | 51 ++++++++++++++++++--------------------- 1 file changed, 24 insertions(+), 27 deletions(-) diff --git a/src/uu/test/src/parser.rs b/src/uu/test/src/parser.rs index 7d06035d5..7b7d77469 100644 --- a/src/uu/test/src/parser.rs +++ b/src/uu/test/src/parser.rs @@ -12,15 +12,15 @@ use std::iter::Peekable; /// Represents one of the binary comparison operators for strings, integers, or files #[derive(Debug, PartialEq)] -pub enum Op { - StringOp(OsString), - IntOp(OsString), - FileOp(OsString), +pub enum Operator { + String(OsString), + Int(OsString), + File(OsString), } /// Represents one of the unary test operators for strings or files #[derive(Debug, PartialEq)] -pub enum UnaryOp { +pub enum UnaryOperator { StrlenOp(OsString), FiletestOp(OsString), } @@ -32,8 +32,8 @@ pub enum Symbol { Bang, BoolOp(OsString), Literal(OsString), - Op(Op), - UnaryOp(UnaryOp), + Op(Operator), + UnaryOp(UnaryOperator), None, } @@ -47,13 +47,13 @@ impl Symbol { "(" => Symbol::LParen, "!" => Symbol::Bang, "-a" | "-o" => Symbol::BoolOp(s), - "=" | "==" | "!=" => Symbol::Op(Op::StringOp(s)), - "-eq" | "-ge" | "-gt" | "-le" | "-lt" | "-ne" => Symbol::Op(Op::IntOp(s)), - "-ef" | "-nt" | "-ot" => Symbol::Op(Op::FileOp(s)), - "-n" | "-z" => Symbol::UnaryOp(UnaryOp::StrlenOp(s)), + "=" | "==" | "!=" => Symbol::Op(Operator::String(s)), + "-eq" | "-ge" | "-gt" | "-le" | "-lt" | "-ne" => Symbol::Op(Operator::Int(s)), + "-ef" | "-nt" | "-ot" => Symbol::Op(Operator::File(s)), + "-n" | "-z" => Symbol::UnaryOp(UnaryOperator::StrlenOp(s)), "-b" | "-c" | "-d" | "-e" | "-f" | "-g" | "-G" | "-h" | "-k" | "-L" | "-O" | "-p" | "-r" | "-s" | "-S" | "-t" | "-u" | "-w" | "-x" => { - Symbol::UnaryOp(UnaryOp::FiletestOp(s)) + Symbol::UnaryOp(UnaryOperator::FiletestOp(s)) } _ => Symbol::Literal(s), }, @@ -74,11 +74,11 @@ impl Symbol { Symbol::Bang => OsString::from("!"), Symbol::BoolOp(s) | Symbol::Literal(s) - | Symbol::Op(Op::StringOp(s)) - | Symbol::Op(Op::IntOp(s)) - | Symbol::Op(Op::FileOp(s)) - | Symbol::UnaryOp(UnaryOp::StrlenOp(s)) - | Symbol::UnaryOp(UnaryOp::FiletestOp(s)) => s, + | Symbol::Op(Operator::String(s)) + | Symbol::Op(Operator::Int(s)) + | Symbol::Op(Operator::File(s)) + | Symbol::UnaryOp(UnaryOperator::StrlenOp(s)) + | Symbol::UnaryOp(UnaryOperator::FiletestOp(s)) => s, Symbol::None => panic!(), }) } @@ -373,18 +373,15 @@ impl Parser { self.stack.push(token.into_literal()); // EXPR → str OP str - match self.peek() { - Symbol::Op(_) => { - let op = self.next_token(); + if let Symbol::Op(_) = self.peek() { + let op = self.next_token(); - match self.next_token() { - Symbol::None => panic!("missing argument after {:?}", op), - token => self.stack.push(token.into_literal()), - } - - self.stack.push(op); + match self.next_token() { + Symbol::None => panic!("missing argument after {:?}", op), + token => self.stack.push(token.into_literal()), } - _ => {} + + self.stack.push(op); } } From 779120787042a916b2b5419e72ab37cd60089c26 Mon Sep 17 00:00:00 2001 From: Jan Verbeek Date: Mon, 6 Sep 2021 23:24:31 +0200 Subject: [PATCH 150/206] Preserve error message for "other" I/O errors These errors still have a unique message even if the ErrorKind enum doesn't classify them. --- src/uucore/src/lib/mods/error.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/uucore/src/lib/mods/error.rs b/src/uucore/src/lib/mods/error.rs index 9a697d822..04019e234 100644 --- a/src/uucore/src/lib/mods/error.rs +++ b/src/uucore/src/lib/mods/error.rs @@ -399,7 +399,6 @@ impl Display for UIoError { TimedOut => "Timed out", WriteZero => "Write zero", Interrupted => "Interrupted", - Other => "Other", UnexpectedEof => "Unexpected end of file", _ => { // TODO: using `strip_errno()` causes the error message From 06ff6ac4f1a259b6e6c4d131bd101a4ed129de96 Mon Sep 17 00:00:00 2001 From: 353fc443 <353fc443@pm.me> Date: Tue, 7 Sep 2021 08:10:06 +0000 Subject: [PATCH 151/206] whoami: Added UResult --- src/uu/whoami/src/whoami.rs | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/src/uu/whoami/src/whoami.rs b/src/uu/whoami/src/whoami.rs index bd2eea1e3..3033a078a 100644 --- a/src/uu/whoami/src/whoami.rs +++ b/src/uu/whoami/src/whoami.rs @@ -14,9 +14,12 @@ extern crate clap; #[macro_use] extern crate uucore; +use uucore::error::{UResult, USimpleError}; + mod platform; -pub fn uumain(args: impl uucore::Args) -> i32 { +#[uucore_procs::gen_uumain] +pub fn uumain(args: impl uucore::Args) -> UResult<()> { let app = uu_app(); if let Err(err) = app.get_matches_from_safe(args) { @@ -24,15 +27,12 @@ pub fn uumain(args: impl uucore::Args) -> i32 { || err.kind == clap::ErrorKind::VersionDisplayed { println!("{}", err); - 0 + Ok(()) } else { - show_error!("{}", err); - 1 + return Err(USimpleError::new(1, format!("{}", err))); } } else { - exec(); - - 0 + exec() } } @@ -40,13 +40,19 @@ pub fn uu_app() -> App<'static, 'static> { app_from_crate!() } -pub fn exec() { +pub fn exec() -> UResult<()> { unsafe { match platform::get_username() { - Ok(username) => println!("{}", username), + Ok(username) => { + println!("{}", username); + Ok(()) + } Err(err) => match err.raw_os_error() { - Some(0) | None => crash!(1, "failed to get username"), - Some(_) => crash!(1, "failed to get username: {}", err), + Some(0) | None => Err(USimpleError::new(1, "failed to get username")), + Some(_) => Err(USimpleError::new( + 1, + format!("failed to get username: {}", err), + )), }, } } From 60df3c6b7c2601ef2d1015f8ab03c0388a402816 Mon Sep 17 00:00:00 2001 From: Jan Verbeek Date: Wed, 1 Sep 2021 16:34:20 +0200 Subject: [PATCH 152/206] uucore: Cache args_os(), util_name(), execution_phrase() And remove args() because there's no valid use for it, invalid unicode handling is specified in a different way. --- Cargo.lock | 1 + src/uu/base32/src/base32.rs | 6 ++--- src/uu/base64/src/base64.rs | 4 +-- src/uu/basenc/src/basenc.rs | 4 +-- src/uu/du/src/du.rs | 2 +- src/uu/logname/src/logname.rs | 5 ++-- src/uucore/Cargo.toml | 1 + src/uucore/src/lib/lib.rs | 49 ++++++++++++++++++----------------- 8 files changed, 37 insertions(+), 35 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 704d1eea1..808f62e15 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3276,6 +3276,7 @@ dependencies = [ "getopts", "lazy_static", "libc", + "once_cell", "termion", "thiserror", "time", diff --git a/src/uu/base32/src/base32.rs b/src/uu/base32/src/base32.rs index ac9ed1075..667fd927e 100644 --- a/src/uu/base32/src/base32.rs +++ b/src/uu/base32/src/base32.rs @@ -38,7 +38,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let name = uucore::util_name(); let config_result: Result = - base_common::parse_base_cmd_args(args, &name, VERSION, ABOUT, &usage); + 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)); // Create a reference to stdin so we can return a locked stdin from @@ -52,12 +52,12 @@ pub fn uumain(args: impl uucore::Args) -> i32 { config.wrap_cols, config.ignore_garbage, config.decode, - &name, + name, ); 0 } pub fn uu_app() -> App<'static, 'static> { - base_common::base_app(&uucore::util_name(), VERSION, ABOUT) + base_common::base_app(uucore::util_name(), VERSION, ABOUT) } diff --git a/src/uu/base64/src/base64.rs b/src/uu/base64/src/base64.rs index e303f9d29..ded157362 100644 --- a/src/uu/base64/src/base64.rs +++ b/src/uu/base64/src/base64.rs @@ -38,7 +38,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let usage = usage(); let name = uucore::util_name(); let config_result: Result = - base_common::parse_base_cmd_args(args, &name, VERSION, ABOUT, &usage); + 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)); // Create a reference to stdin so we can return a locked stdin from @@ -52,7 +52,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { config.wrap_cols, config.ignore_garbage, config.decode, - &name, + name, ); 0 diff --git a/src/uu/basenc/src/basenc.rs b/src/uu/basenc/src/basenc.rs index 2b3193d49..86c251ad1 100644 --- a/src/uu/basenc/src/basenc.rs +++ b/src/uu/basenc/src/basenc.rs @@ -47,7 +47,7 @@ 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(uucore::util_name(), crate_version!(), ABOUT); for encoding in ENCODINGS { app = app.arg(Arg::with_name(encoding.0).long(encoding.0)); } @@ -88,7 +88,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { config.wrap_cols, config.ignore_garbage, config.decode, - &name, + name, ); 0 diff --git a/src/uu/du/src/du.rs b/src/uu/du/src/du.rs index 258d58bae..685064dfc 100644 --- a/src/uu/du/src/du.rs +++ b/src/uu/du/src/du.rs @@ -466,7 +466,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let options = Options { all: matches.is_present(options::ALL), - util_name: uucore::util_name(), + util_name: uucore::util_name().to_owned(), max_depth, total: matches.is_present(options::TOTAL), separate_dirs: matches.is_present(options::SEPARATE_DIRS), diff --git a/src/uu/logname/src/logname.rs b/src/uu/logname/src/logname.rs index f8dd3fc5d..56866ff62 100644 --- a/src/uu/logname/src/logname.rs +++ b/src/uu/logname/src/logname.rs @@ -35,7 +35,7 @@ fn get_userlogin() -> Option { static SUMMARY: &str = "Print user's login name"; -fn usage() -> String { +fn usage() -> &'static str { uucore::execution_phrase() } @@ -44,8 +44,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .collect_str(InvalidEncodingHandling::Ignore) .accept_any(); - let usage = usage(); - let _ = uu_app().usage(&usage[..]).get_matches_from(args); + let _ = uu_app().usage(usage()).get_matches_from(args); match get_userlogin() { Some(userlogin) => println!("{}", userlogin), diff --git a/src/uucore/Cargo.toml b/src/uucore/Cargo.toml index fd78e6551..a04f565aa 100644 --- a/src/uucore/Cargo.toml +++ b/src/uucore/Cargo.toml @@ -30,6 +30,7 @@ data-encoding = { version="2.1", optional=true } data-encoding-macro = { version="0.1.12", optional=true } z85 = { version="3.0.3", optional=true } libc = { version="0.2.15", optional=true } +once_cell = "1.8.0" [dev-dependencies] clap = "2.33.3" diff --git a/src/uucore/src/lib/lib.rs b/src/uucore/src/lib/lib.rs index 5352a6356..129ff9106 100644 --- a/src/uucore/src/lib/lib.rs +++ b/src/uucore/src/lib/lib.rs @@ -70,6 +70,8 @@ pub use crate::features::wide; use std::ffi::OsString; use std::sync::atomic::Ordering; +use once_cell::sync::Lazy; + pub fn get_utility_is_second_arg() -> bool { crate::macros::UTILITY_IS_SECOND_ARG.load(Ordering::SeqCst) } @@ -78,36 +80,40 @@ pub fn set_utility_is_second_arg() { crate::macros::UTILITY_IS_SECOND_ARG.store(true, Ordering::SeqCst) } -/// Get the executable path (as `OsString`). -fn executable_os() -> OsString { - args_os().next().unwrap() -} +// args_os() can be expensive to call, it copies all of argv before iterating. +// So if we want only the first arg or so it's overkill. We cache it. +static ARGV: Lazy> = Lazy::new(|| wild::args_os().collect()); -/// Get the executable path (as `String`). -fn executable() -> String { - executable_os().to_string_lossy().into_owned() -} +static UTIL_NAME: Lazy = Lazy::new(|| { + if get_utility_is_second_arg() { + &ARGV[1] + } else { + &ARGV[0] + } + .to_string_lossy() + .into_owned() +}); /// Derive the utility name. -pub fn util_name() -> String { - if get_utility_is_second_arg() { - args_os().nth(1).unwrap().to_string_lossy().into_owned() - } else { - executable() - } +pub fn util_name() -> &'static str { + &UTIL_NAME } -/// Derive the complete execution phrase for "usage". -pub fn execution_phrase() -> String { +static EXECUTION_PHRASE: Lazy = Lazy::new(|| { if get_utility_is_second_arg() { - args_os() + ARGV.iter() .take(2) .map(|os_str| os_str.to_string_lossy().into_owned()) .collect::>() .join(" ") } else { - executable() + ARGV[0].to_string_lossy().into_owned() } +}); + +/// Derive the complete execution phrase for "usage". +pub fn execution_phrase() -> &'static str { + &EXECUTION_PHRASE } pub enum InvalidEncodingHandling { @@ -204,13 +210,8 @@ pub trait Args: Iterator + Sized { impl + Sized> Args for T {} -// args() ... -pub fn args() -> impl Iterator { - wild::args() -} - pub fn args_os() -> impl Iterator { - wild::args_os() + ARGV.iter().cloned() } #[cfg(test)] From 259f18fcabf2405067f9444fc7bff520cda2dc0c Mon Sep 17 00:00:00 2001 From: Jan Verbeek Date: Fri, 3 Sep 2021 16:10:39 +0200 Subject: [PATCH 153/206] Update message quoting and filename printing --- .../cspell.dictionaries/jargon.wordlist.txt | 2 + src/bin/coreutils.rs | 29 ++++++---- src/uu/base32/src/base_common.rs | 10 ++-- src/uu/cat/src/cat.rs | 3 +- src/uu/chcon/src/chcon.rs | 16 +++--- src/uu/chcon/src/errors.rs | 4 +- src/uu/chgrp/src/chgrp.rs | 10 +++- src/uu/chmod/src/chmod.rs | 42 ++++++++------ src/uu/chown/src/chown.rs | 7 ++- src/uu/chroot/src/chroot.rs | 17 ++++-- src/uu/cksum/src/cksum.rs | 5 +- src/uu/cp/src/cp.rs | 35 ++++++------ src/uu/csplit/src/csplit.rs | 3 +- src/uu/csplit/src/csplit_error.rs | 14 +++-- src/uu/cut/src/cut.rs | 7 ++- src/uu/date/src/date.rs | 7 ++- src/uu/dircolors/src/dircolors.rs | 19 +++++-- src/uu/dirname/src/dirname.rs | 3 +- src/uu/du/src/du.rs | 54 +++++++++--------- src/uu/env/src/env.rs | 10 +++- src/uu/expand/src/expand.rs | 3 +- src/uu/factor/src/cli.rs | 5 +- src/uu/fmt/src/fmt.rs | 3 +- src/uu/groups/src/groups.rs | 7 ++- src/uu/hashsum/src/hashsum.rs | 7 ++- src/uu/head/src/head.rs | 9 +-- src/uu/id/src/id.rs | 3 +- src/uu/install/src/install.rs | 43 ++++++--------- src/uu/install/src/mode.rs | 3 +- src/uu/join/src/join.rs | 19 ++++--- src/uu/kill/src/kill.rs | 7 ++- src/uu/ln/src/ln.rs | 46 ++++++++-------- src/uu/ls/src/ls.rs | 29 +++++----- src/uu/mkdir/src/mkdir.rs | 11 ++-- src/uu/mkfifo/src/mkfifo.rs | 4 +- src/uu/mknod/src/mknod.rs | 3 +- src/uu/mktemp/src/mktemp.rs | 23 ++++---- src/uu/more/src/more.rs | 5 +- src/uu/mv/src/mv.rs | 44 +++++++-------- src/uu/nohup/src/nohup.rs | 17 ++++-- src/uu/numfmt/src/format.rs | 6 +- src/uu/numfmt/src/numfmt.rs | 3 +- src/uu/od/src/multifilereader.rs | 4 +- src/uu/od/src/od.rs | 5 +- src/uu/od/src/parse_formats.rs | 17 ++++-- src/uu/pathchk/src/pathchk.rs | 21 +++---- src/uu/pr/src/pr.rs | 19 +++++-- src/uu/printf/src/cli.rs | 11 +--- src/uu/printf/src/memo.rs | 11 ++-- .../src/tokenize/num_format/formatter.rs | 5 +- .../src/tokenize/num_format/num_format.rs | 11 ++-- src/uu/printf/src/tokenize/sub.rs | 3 +- src/uu/ptx/src/ptx.rs | 7 ++- src/uu/readlink/src/readlink.rs | 20 ++----- src/uu/realpath/src/realpath.rs | 17 ++++-- src/uu/relpath/src/relpath.rs | 5 +- src/uu/rm/src/rm.rs | 50 ++++++++--------- src/uu/runcon/src/errors.rs | 4 +- src/uu/seq/src/seq.rs | 9 +-- src/uu/shred/src/shred.rs | 48 ++++++++-------- src/uu/shuf/src/shuf.rs | 17 +++--- src/uu/sort/src/sort.rs | 55 +++++++++++++------ src/uu/split/src/split.rs | 13 +++-- src/uu/stat/src/stat.rs | 17 +++--- src/uu/sum/src/sum.rs | 3 +- src/uu/sync/src/sync.rs | 7 ++- src/uu/tac/src/tac.rs | 11 ++-- src/uu/tee/src/tee.rs | 9 +-- src/uu/test/src/parser.rs | 33 ++++++----- src/uu/test/src/test.rs | 55 +++++++++---------- src/uu/timeout/src/timeout.rs | 9 +-- src/uu/touch/src/touch.rs | 21 +++++-- src/uu/tr/src/tr.rs | 6 +- src/uu/truncate/src/truncate.rs | 5 +- src/uu/tsort/src/tsort.rs | 9 ++- src/uu/unexpand/src/unexpand.rs | 5 +- src/uu/uniq/src/uniq.rs | 10 +++- src/uu/unlink/src/unlink.rs | 8 ++- src/uucore/src/lib/features/perms.rs | 33 +++++------ src/uucore/src/lib/lib.rs | 11 ++-- src/uucore/src/lib/mods/backup_control.rs | 19 +++++-- src/uucore/src/lib/mods/display.rs | 23 ++++++++ src/uucore/src/lib/mods/error.rs | 36 ++++++++---- src/uucore/src/lib/mods/ranges.rs | 4 +- src/uucore/src/lib/parser/parse_size.rs | 14 ++++- src/uucore/src/lib/parser/parse_time.rs | 6 +- tests/by-util/test_chroot.rs | 4 +- tests/by-util/test_cksum.rs | 4 +- tests/by-util/test_sort.rs | 8 +-- tests/by-util/test_sum.rs | 4 +- tests/by-util/test_test.rs | 4 +- 91 files changed, 777 insertions(+), 550 deletions(-) diff --git a/.vscode/cspell.dictionaries/jargon.wordlist.txt b/.vscode/cspell.dictionaries/jargon.wordlist.txt index 6561dc63b..cd1cc18b3 100644 --- a/.vscode/cspell.dictionaries/jargon.wordlist.txt +++ b/.vscode/cspell.dictionaries/jargon.wordlist.txt @@ -9,6 +9,8 @@ bytewise canonicalization canonicalize canonicalizing +codepoint +codepoints colorizable colorize coprime diff --git a/src/bin/coreutils.rs b/src/bin/coreutils.rs index 477931dbd..1de1b6354 100644 --- a/src/bin/coreutils.rs +++ b/src/bin/coreutils.rs @@ -10,10 +10,12 @@ use clap::Arg; use clap::Shell; use std::cmp; use std::collections::hash_map::HashMap; +use std::ffi::OsStr; use std::ffi::OsString; use std::io::{self, Write}; use std::path::{Path, PathBuf}; use std::process; +use uucore::display::Quotable; const VERSION: &str = env!("CARGO_PKG_VERSION"); @@ -76,13 +78,21 @@ fn main() { // 0th argument equals util name? if let Some(util_os) = util_name { - let util = util_os.as_os_str().to_string_lossy(); + fn not_found(util: &OsStr) -> ! { + println!("{}: function/utility not found", util.maybe_quote()); + process::exit(1); + } + + let util = match util_os.to_str() { + Some(util) => util, + None => not_found(&util_os), + }; if util == "completion" { gen_completions(args, utils); } - match utils.get(&util[..]) { + match utils.get(util) { Some(&(uumain, _)) => { process::exit(uumain((vec![util_os].into_iter()).chain(args))); } @@ -90,9 +100,12 @@ fn main() { if util == "--help" || util == "-h" { // see if they want help on a specific util if let Some(util_os) = args.next() { - let util = util_os.as_os_str().to_string_lossy(); + let util = match util_os.to_str() { + Some(util) => util, + None => not_found(&util_os), + }; - match utils.get(&util[..]) { + match utils.get(util) { Some(&(uumain, _)) => { let code = uumain( (vec![util_os, OsString::from("--help")].into_iter()) @@ -101,17 +114,13 @@ fn main() { io::stdout().flush().expect("could not flush stdout"); process::exit(code); } - None => { - println!("{}: function/utility not found", util); - process::exit(1); - } + None => not_found(&util_os), } } usage(&utils, binary_as_util); process::exit(0); } else { - println!("{}: function/utility not found", util); - process::exit(1); + not_found(&util_os); } } } diff --git a/src/uu/base32/src/base_common.rs b/src/uu/base32/src/base_common.rs index 78962d9db..0fd0fa5c4 100644 --- a/src/uu/base32/src/base_common.rs +++ b/src/uu/base32/src/base_common.rs @@ -9,6 +9,7 @@ use std::io::{stdout, Read, Write}; +use uucore::display::Quotable; use uucore::encoding::{wrap_print, Data, Format}; use uucore::InvalidEncodingHandling; @@ -40,8 +41,9 @@ impl Config { let name = values.next().unwrap(); if let Some(extra_op) = values.next() { return Err(format!( - "extra operand '{}'\nTry '{} --help' for more information.", - extra_op, app_name + "extra operand {}\nTry '{} --help' for more information.", + extra_op.quote(), + app_name )); } @@ -49,7 +51,7 @@ impl Config { None } else { if !Path::exists(Path::new(name)) { - return Err(format!("{}: No such file or directory", name)); + return Err(format!("{}: No such file or directory", name.maybe_quote())); } Some(name.to_owned()) } @@ -61,7 +63,7 @@ impl Config { .value_of(options::WRAP) .map(|num| { num.parse::() - .map_err(|_| format!("invalid wrap size: '{}'", num)) + .map_err(|_| format!("invalid wrap size: {}", num.quote())) }) .transpose()?; diff --git a/src/uu/cat/src/cat.rs b/src/uu/cat/src/cat.rs index a176b8c5d..9459ee86a 100644 --- a/src/uu/cat/src/cat.rs +++ b/src/uu/cat/src/cat.rs @@ -20,6 +20,7 @@ use clap::{crate_version, App, Arg}; use std::fs::{metadata, File}; use std::io::{self, Read, Write}; use thiserror::Error; +use uucore::display::Quotable; use uucore::error::UResult; #[cfg(unix)] @@ -386,7 +387,7 @@ fn cat_files(files: Vec, options: &OutputOptions) -> UResult<()> { for path in &files { if let Err(err) = cat_path(path, options, &mut state, &out_info) { - error_messages.push(format!("{}: {}", path, err)); + error_messages.push(format!("{}: {}", path.maybe_quote(), err)); } } if state.skipped_carriage_return { diff --git a/src/uu/chcon/src/chcon.rs b/src/uu/chcon/src/chcon.rs index 5f6a80bde..98ebebf34 100644 --- a/src/uu/chcon/src/chcon.rs +++ b/src/uu/chcon/src/chcon.rs @@ -2,7 +2,7 @@ #![allow(clippy::upper_case_acronyms)] -use uucore::{show_error, show_usage_error, show_warning}; +use uucore::{display::Quotable, show_error, show_usage_error, show_warning}; use clap::{App, Arg}; use selinux::{OpaqueSecurityContext, SecurityContext}; @@ -111,13 +111,13 @@ pub fn uumain(args: impl uucore::Args) -> i32 { Ok(context) => context, Err(_r) => { - show_error!("Invalid security context '{}'.", context.to_string_lossy()); + show_error!("Invalid security context {}.", context.quote()); return libc::EXIT_FAILURE; } }; if SecurityContext::from_c_str(&c_context, false).check() == Some(false) { - show_error!("Invalid security context '{}'.", context.to_string_lossy()); + show_error!("Invalid security context {}.", context.quote()); return libc::EXIT_FAILURE; } @@ -564,7 +564,7 @@ fn process_file( println!( "{}: Changing security context of: {}", uucore::util_name(), - file_full_name.to_string_lossy() + file_full_name.quote() ); } @@ -699,9 +699,9 @@ fn root_dev_ino_warn(dir_name: &Path) { ); } else { show_warning!( - "It is dangerous to operate recursively on '{}' (same as '/'). \ + "It is dangerous to operate recursively on {} (same as '/'). \ Use --{} to override this failsafe.", - dir_name.to_string_lossy(), + dir_name.quote(), options::preserve_root::NO_PRESERVE_ROOT, ); } @@ -726,8 +726,8 @@ fn emit_cycle_warning(file_name: &Path) { "Circular directory structure.\n\ This almost certainly means that you have a corrupted file system.\n\ NOTIFY YOUR SYSTEM MANAGER.\n\ -The following directory is part of the cycle '{}'.", - file_name.display() +The following directory is part of the cycle {}.", + file_name.quote() ) } diff --git a/src/uu/chcon/src/errors.rs b/src/uu/chcon/src/errors.rs index ecbd7d409..2d8f72e67 100644 --- a/src/uu/chcon/src/errors.rs +++ b/src/uu/chcon/src/errors.rs @@ -2,6 +2,8 @@ use std::ffi::OsString; use std::fmt::Write; use std::io; +use uucore::display::Quotable; + pub(crate) type Result = std::result::Result; #[derive(thiserror::Error, Debug)] @@ -30,7 +32,7 @@ pub(crate) enum Error { source: io::Error, }, - #[error("{operation} failed on '{}'", .operand1.to_string_lossy())] + #[error("{operation} failed on {}", .operand1.quote())] Io1 { operation: &'static str, operand1: OsString, diff --git a/src/uu/chgrp/src/chgrp.rs b/src/uu/chgrp/src/chgrp.rs index a077ab7a4..1795ad0d5 100644 --- a/src/uu/chgrp/src/chgrp.rs +++ b/src/uu/chgrp/src/chgrp.rs @@ -9,6 +9,7 @@ #[macro_use] extern crate uucore; +use uucore::display::Quotable; pub use uucore::entries; use uucore::error::{FromIo, UResult, USimpleError}; use uucore::perms::{chown_base, options, IfFrom}; @@ -32,7 +33,7 @@ fn parse_gid_and_uid(matches: &ArgMatches) -> UResult<(Option, Option, let dest_gid = if let Some(file) = matches.value_of(options::REFERENCE) { fs::metadata(&file) .map(|meta| Some(meta.gid())) - .map_err_context(|| format!("failed to get attributes of '{}'", file))? + .map_err_context(|| format!("failed to get attributes of {}", file.quote()))? } else { let group = matches.value_of(options::ARG_GROUP).unwrap_or_default(); if group.is_empty() { @@ -40,7 +41,12 @@ fn parse_gid_and_uid(matches: &ArgMatches) -> UResult<(Option, Option, } else { match entries::grp2gid(group) { Ok(g) => Some(g), - _ => return Err(USimpleError::new(1, format!("invalid group: '{}'", group))), + _ => { + return Err(USimpleError::new( + 1, + format!("invalid group: {}", group.quote()), + )) + } } } }; diff --git a/src/uu/chmod/src/chmod.rs b/src/uu/chmod/src/chmod.rs index 26b3fd85b..e25202fbe 100644 --- a/src/uu/chmod/src/chmod.rs +++ b/src/uu/chmod/src/chmod.rs @@ -14,6 +14,7 @@ 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::fs::display_permissions_unix; use uucore::libc::mode_t; #[cfg(not(windows))] @@ -75,7 +76,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .value_of(options::REFERENCE) .and_then(|fref| match fs::metadata(fref) { Ok(meta) => Some(meta.mode()), - Err(err) => crash!(1, "cannot stat attributes of '{}': {}", fref, err), + Err(err) => crash!(1, "cannot stat attributes of {}: {}", fref.quote(), err), }); let modes = matches.value_of(options::MODE).unwrap(); // should always be Some because required let cmode = if mode_had_minus_prefix { @@ -223,21 +224,24 @@ impl Chmoder { if !file.exists() { if is_symlink(file) { println!( - "failed to change mode of '{}' from 0000 (---------) to 0000 (---------)", - filename + "failed to change mode of {} from 0000 (---------) to 0000 (---------)", + filename.quote() ); if !self.quiet { - show_error!("cannot operate on dangling symlink '{}'", filename); + show_error!("cannot operate on dangling symlink {}", filename.quote()); } } else if !self.quiet { - show_error!("cannot access '{}': No such file or directory", filename); + show_error!( + "cannot access {}: No such file or directory", + filename.quote() + ); } return Err(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 + "it is dangerous to operate recursively on {}\nuse --no-preserve-root to override this failsafe", + filename.quote() ); return Err(1); } @@ -270,15 +274,17 @@ impl Chmoder { if is_symlink(file) { if self.verbose { println!( - "neither symbolic link '{}' nor referent has been changed", - file.display() + "neither symbolic link {} nor referent has been changed", + file.quote() ); } return Ok(()); } else if err.kind() == std::io::ErrorKind::PermissionDenied { - show_error!("'{}': Permission denied", file.display()); + // These two filenames would normally be conditionally + // quoted, but GNU's tests expect them to always be quoted + show_error!("{}: Permission denied", file.quote()); } else { - show_error!("'{}': {}", file.display(), err); + show_error!("{}: {}", file.quote(), err); } return Err(1); } @@ -325,7 +331,7 @@ impl Chmoder { if (new_mode & !naively_expected_new_mode) != 0 { show_error!( "{}: new permissions are {}, not {}", - file.display(), + file.maybe_quote(), display_permissions_unix(new_mode as mode_t, false), display_permissions_unix(naively_expected_new_mode as mode_t, false) ); @@ -342,8 +348,8 @@ impl Chmoder { if fperm == mode { if self.verbose && !self.changes { println!( - "mode of '{}' retained as {:04o} ({})", - file.display(), + "mode of {} retained as {:04o} ({})", + file.quote(), fperm, display_permissions_unix(fperm as mode_t, false), ); @@ -355,8 +361,8 @@ impl Chmoder { } if self.verbose { println!( - "failed to change mode of file '{}' from {:04o} ({}) to {:04o} ({})", - file.display(), + "failed to change mode of file {} from {:04o} ({}) to {:04o} ({})", + file.quote(), fperm, display_permissions_unix(fperm as mode_t, false), mode, @@ -367,8 +373,8 @@ impl Chmoder { } else { if self.verbose || self.changes { println!( - "mode of '{}' changed from {:04o} ({}) to {:04o} ({})", - file.display(), + "mode of {} changed from {:04o} ({}) to {:04o} ({})", + file.quote(), fperm, display_permissions_unix(fperm as mode_t, false), mode, diff --git a/src/uu/chown/src/chown.rs b/src/uu/chown/src/chown.rs index 4abb9ac61..5525f9325 100644 --- a/src/uu/chown/src/chown.rs +++ b/src/uu/chown/src/chown.rs @@ -9,6 +9,7 @@ #[macro_use] extern crate uucore; +use uucore::display::Quotable; pub use uucore::entries::{self, Group, Locate, Passwd}; use uucore::perms::{chown_base, options, IfFrom}; @@ -44,7 +45,7 @@ fn parse_gid_uid_and_filter(matches: &ArgMatches) -> UResult<(Option, Optio let dest_gid: Option; if let Some(file) = matches.value_of(options::REFERENCE) { let meta = fs::metadata(&file) - .map_err_context(|| format!("failed to get attributes of '{}'", file))?; + .map_err_context(|| format!("failed to get attributes of {}", file.quote()))?; dest_gid = Some(meta.gid()); dest_uid = Some(meta.uid()); } else { @@ -173,7 +174,7 @@ fn parse_spec(spec: &str) -> UResult<(Option, Option)> { let uid = if usr_only || usr_grp { Some( Passwd::locate(args[0]) - .map_err(|_| USimpleError::new(1, format!("invalid user: '{}'", spec)))? + .map_err(|_| USimpleError::new(1, format!("invalid user: {}", spec.quote())))? .uid(), ) } else { @@ -182,7 +183,7 @@ fn parse_spec(spec: &str) -> UResult<(Option, Option)> { let gid = if grp_only || usr_grp { Some( Group::locate(args[1]) - .map_err(|_| USimpleError::new(1, format!("invalid group: '{}'", spec)))? + .map_err(|_| USimpleError::new(1, format!("invalid group: {}", spec.quote())))? .gid(), ) } else { diff --git a/src/uu/chroot/src/chroot.rs b/src/uu/chroot/src/chroot.rs index c0f392913..240b5eafe 100644 --- a/src/uu/chroot/src/chroot.rs +++ b/src/uu/chroot/src/chroot.rs @@ -15,6 +15,7 @@ use std::ffi::CString; use std::io::Error; use std::path::Path; use std::process::Command; +use uucore::display::Quotable; use uucore::libc::{self, chroot, setgid, setgroups, setuid}; use uucore::{entries, InvalidEncodingHandling}; @@ -53,8 +54,8 @@ pub fn uumain(args: impl uucore::Args) -> i32 { if !newroot.is_dir() { crash!( 1, - "cannot change root directory to `{}`: no such directory", - newroot.display() + "cannot change root directory to {}: no such directory", + newroot.quote() ); } @@ -170,7 +171,6 @@ fn set_context(root: &Path, options: &clap::ArgMatches) { } fn enter_chroot(root: &Path) { - let root_str = root.display(); std::env::set_current_dir(root).unwrap(); let err = unsafe { chroot(CString::new(".").unwrap().as_bytes_with_nul().as_ptr() as *const libc::c_char) @@ -179,7 +179,7 @@ fn enter_chroot(root: &Path) { crash!( 1, "cannot chroot to {}: {}", - root_str, + root.quote(), Error::last_os_error() ) }; @@ -189,7 +189,7 @@ fn set_main_group(group: &str) { if !group.is_empty() { let group_id = match entries::grp2gid(group) { Ok(g) => g, - _ => crash!(1, "no such group: {}", group), + _ => crash!(1, "no such group: {}", group.maybe_quote()), }; let err = unsafe { setgid(group_id) }; if err != 0 { @@ -234,7 +234,12 @@ fn set_user(user: &str) { let user_id = entries::usr2uid(user).unwrap(); let err = unsafe { setuid(user_id as libc::uid_t) }; if err != 0 { - crash!(1, "cannot set user to {}: {}", user, Error::last_os_error()) + crash!( + 1, + "cannot set user to {}: {}", + user.maybe_quote(), + Error::last_os_error() + ) } } } diff --git a/src/uu/cksum/src/cksum.rs b/src/uu/cksum/src/cksum.rs index a5beec368..e682aa70c 100644 --- a/src/uu/cksum/src/cksum.rs +++ b/src/uu/cksum/src/cksum.rs @@ -14,6 +14,7 @@ use clap::{crate_version, App, Arg}; use std::fs::File; use std::io::{self, stdin, BufReader, Read}; use std::path::Path; +use uucore::display::Quotable; use uucore::InvalidEncodingHandling; // NOTE: CRC_TABLE_LEN *must* be <= 256 as we cast 0..CRC_TABLE_LEN to u8 @@ -191,7 +192,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { match cksum("-") { Ok((crc, size)) => println!("{} {}", crc, size), Err(err) => { - show_error!("{}", err); + show_error!("-: {}", err); return 2; } } @@ -203,7 +204,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { match cksum(fname.as_ref()) { Ok((crc, size)) => println!("{} {} {}", crc, size, fname), Err(err) => { - show_error!("'{}' {}", fname, err); + show_error!("{}: {}", fname.maybe_quote(), err); exit_code = 2; } } diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index e9e76237b..cd33f9fa6 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -18,6 +18,7 @@ extern crate quick_error; #[macro_use] extern crate uucore; +use uucore::display::Quotable; #[cfg(windows)] use winapi::um::fileapi::CreateFileW; #[cfg(windows)] @@ -541,8 +542,8 @@ impl FromStr for Attribute { "xattr" => Attribute::Xattr, _ => { return Err(Error::InvalidArgument(format!( - "invalid attribute '{}'", - value + "invalid attribute {}", + value.quote() ))); } }) @@ -659,8 +660,8 @@ impl Options { "never" => ReflinkMode::Never, value => { return Err(Error::InvalidArgument(format!( - "invalid argument '{}' for \'reflink\'", - value + "invalid argument {} for \'reflink\'", + value.quote() ))); } } @@ -832,7 +833,7 @@ fn copy(sources: &[Source], target: &TargetSlice, options: &Options) -> CopyResu let mut seen_sources = HashSet::with_capacity(sources.len()); for source in sources { if seen_sources.contains(source) { - show_warning!("source '{}' specified more than once", source.display()); + show_warning!("source {} specified more than once", source.quote()); } else { let mut found_hard_link = false; if preserve_hard_links { @@ -873,8 +874,8 @@ fn construct_dest_path( ) -> CopyResult { if options.no_target_dir && target.is_dir() { return Err(format!( - "cannot overwrite directory '{}' with non-directory", - target.display() + "cannot overwrite directory {} with non-directory", + target.quote() ) .into()); } @@ -941,7 +942,7 @@ fn adjust_canonicalization(p: &Path) -> Cow { /// will not cause a short-circuit. fn copy_directory(root: &Path, target: &TargetSlice, options: &Options) -> CopyResult<()> { if !options.recursive { - return Err(format!("omitting directory '{}'", root.display()).into()); + return Err(format!("omitting directory {}", root.quote()).into()); } // if no-dereference is enabled and this is a symlink, copy it as a file @@ -1041,12 +1042,12 @@ impl OverwriteMode { match *self { OverwriteMode::NoClobber => Err(Error::NotAllFilesCopied), OverwriteMode::Interactive(_) => { - if prompt_yes!("{}: overwrite {}? ", uucore::util_name(), path.display()) { + if prompt_yes!("{}: overwrite {}? ", uucore::util_name(), path.quote()) { Ok(()) } else { Err(Error::Skipped(format!( "Not overwriting {} at user request", - path.display() + path.quote() ))) } } @@ -1056,7 +1057,7 @@ impl OverwriteMode { } fn copy_attribute(source: &Path, dest: &Path, attribute: &Attribute) -> CopyResult<()> { - let context = &*format!("'{}' -> '{}'", source.display().to_string(), dest.display()); + let context = &*format!("{} -> {}", source.quote(), dest.quote()); let source_metadata = fs::symlink_metadata(source).context(context)?; match *attribute { Attribute::Mode => { @@ -1152,7 +1153,7 @@ fn symlink_file(source: &Path, dest: &Path, context: &str) -> CopyResult<()> { } fn context_for(src: &Path, dest: &Path) -> String { - format!("'{}' -> '{}'", src.display(), dest.display()) + format!("{} -> {}", src.quote(), dest.quote()) } /// Implements a simple backup copy for the destination file. @@ -1332,8 +1333,8 @@ fn copy_link(source: &Path, dest: &Path) -> CopyResult<()> { Some(name) => dest.join(name).into(), None => crash!( EXIT_ERR, - "cannot stat '{}': No such file or directory", - source.display() + "cannot stat {}: No such file or directory", + source.quote() ), } } else { @@ -1454,11 +1455,11 @@ fn copy_on_write_macos( pub fn verify_target_type(target: &Path, target_type: &TargetType) -> CopyResult<()> { match (target_type, target.is_dir()) { (&TargetType::Directory, false) => { - Err(format!("target: '{}' is not a directory", target.display()).into()) + Err(format!("target: {} is not a directory", target.quote()).into()) } (&TargetType::File, true) => Err(format!( - "cannot overwrite directory '{}' with non-directory", - target.display() + "cannot overwrite directory {} with non-directory", + target.quote() ) .into()), _ => Ok(()), diff --git a/src/uu/csplit/src/csplit.rs b/src/uu/csplit/src/csplit.rs index 977583a2f..dbf65b71d 100644 --- a/src/uu/csplit/src/csplit.rs +++ b/src/uu/csplit/src/csplit.rs @@ -10,6 +10,7 @@ use std::{ fs::{remove_file, File}, io::{BufRead, BufWriter, Write}, }; +use uucore::display::Quotable; mod csplit_error; mod patterns; @@ -734,7 +735,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let file = crash_if_err!(1, File::open(file_name)); let file_metadata = crash_if_err!(1, file.metadata()); if !file_metadata.is_file() { - crash!(1, "'{}' is not a regular file", file_name); + crash!(1, "{} is not a regular file", file_name.quote()); } crash_if_err!(1, csplit(&options, patterns, BufReader::new(file))); }; diff --git a/src/uu/csplit/src/csplit_error.rs b/src/uu/csplit/src/csplit_error.rs index 637cf8890..1d4823ee2 100644 --- a/src/uu/csplit/src/csplit_error.rs +++ b/src/uu/csplit/src/csplit_error.rs @@ -1,26 +1,28 @@ use std::io; use thiserror::Error; +use uucore::display::Quotable; + /// Errors thrown by the csplit command #[derive(Debug, Error)] pub enum CsplitError { #[error("IO error: {}", _0)] IoError(io::Error), - #[error("'{}': line number out of range", _0)] + #[error("{}: line number out of range", ._0.quote())] LineOutOfRange(String), - #[error("'{}': line number out of range on repetition {}", _0, _1)] + #[error("{}: line number out of range on repetition {}", ._0.quote(), _1)] LineOutOfRangeOnRepetition(String, usize), - #[error("'{}': match not found", _0)] + #[error("{}: match not found", ._0.quote())] MatchNotFound(String), - #[error("'{}': match not found on repetition {}", _0, _1)] + #[error("{}: match not found on repetition {}", ._0.quote(), _1)] MatchNotFoundOnRepetition(String, usize), #[error("line number must be greater than zero")] LineNumberIsZero, #[error("line number '{}' is smaller than preceding line number, {}", _0, _1)] LineNumberSmallerThanPrevious(usize, usize), - #[error("invalid pattern: {}", _0)] + #[error("{}: invalid pattern", ._0.quote())] InvalidPattern(String), - #[error("invalid number: '{}'", _0)] + #[error("invalid number: {}", ._0.quote())] InvalidNumber(String), #[error("incorrect conversion specification in suffix")] SuffixFormatIncorrect, diff --git a/src/uu/cut/src/cut.rs b/src/uu/cut/src/cut.rs index 783502e3d..35d92b83f 100644 --- a/src/uu/cut/src/cut.rs +++ b/src/uu/cut/src/cut.rs @@ -15,6 +15,7 @@ use clap::{crate_version, App, Arg}; use std::fs::File; use std::io::{stdin, stdout, BufReader, BufWriter, Read, Write}; use std::path::Path; +use uucore::display::Quotable; use self::searcher::Searcher; use uucore::ranges::Range; @@ -351,19 +352,19 @@ fn cut_files(mut filenames: Vec, mode: Mode) -> i32 { let path = Path::new(&filename[..]); if path.is_dir() { - show_error!("{}: Is a directory", filename); + show_error!("{}: Is a directory", filename.maybe_quote()); continue; } if path.metadata().is_err() { - show_error!("{}: No such file or directory", filename); + show_error!("{}: No such file or directory", filename.maybe_quote()); continue; } let file = match File::open(&path) { Ok(f) => f, Err(e) => { - show_error!("opening '{}': {}", &filename[..], e); + show_error!("opening {}: {}", filename.quote(), e); continue; } }; diff --git a/src/uu/date/src/date.rs b/src/uu/date/src/date.rs index 7bf6298c0..b2ccf2e9f 100644 --- a/src/uu/date/src/date.rs +++ b/src/uu/date/src/date.rs @@ -17,6 +17,7 @@ use libc::{clock_settime, timespec, CLOCK_REALTIME}; use std::fs::File; use std::io::{BufRead, BufReader}; use std::path::PathBuf; +use uucore::display::Quotable; #[cfg(windows)] use winapi::{ shared::minwindef::WORD, @@ -145,7 +146,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let format = if let Some(form) = matches.value_of(OPT_FORMAT) { if !form.starts_with('+') { - eprintln!("date: invalid date '{}'", form); + eprintln!("date: invalid date {}", form.quote()); return 1; } let form = form[1..].to_string(); @@ -174,7 +175,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let set_to = match matches.value_of(OPT_SET).map(parse_date) { None => None, Some(Err((input, _err))) => { - eprintln!("date: invalid date '{}'", input); + eprintln!("date: invalid date {}", input.quote()); return 1; } Some(Ok(date)) => Some(date), @@ -240,7 +241,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { println!("{}", formatted); } Err((input, _err)) => { - println!("date: invalid date '{}'", input); + println!("date: invalid date {}", input.quote()); } } } diff --git a/src/uu/dircolors/src/dircolors.rs b/src/uu/dircolors/src/dircolors.rs index 2b9c25ba3..5d0b3ac3e 100644 --- a/src/uu/dircolors/src/dircolors.rs +++ b/src/uu/dircolors/src/dircolors.rs @@ -17,6 +17,7 @@ use std::fs::File; use std::io::{BufRead, BufReader}; use clap::{crate_version, App, Arg}; +use uucore::display::Quotable; mod options { pub const BOURNE_SHELL: &str = "bourne-shell"; @@ -94,9 +95,9 @@ pub fn uumain(args: impl uucore::Args) -> i32 { if matches.is_present(options::PRINT_DATABASE) { if !files.is_empty() { show_usage_error!( - "extra operand '{}'\nfile operands cannot be combined with \ + "extra operand {}\nfile operands cannot be combined with \ --print-database (-p)", - files[0] + files[0].quote() ); return 1; } @@ -126,7 +127,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { result = parse(INTERNAL_DB.lines(), out_format, "") } else { if files.len() > 1 { - show_usage_error!("extra operand '{}'", files[1]); + show_usage_error!("extra operand {}", files[1].quote()); return 1; } match File::open(files[0]) { @@ -135,7 +136,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { result = parse(fin.lines().filter_map(Result::ok), out_format, files[0]) } Err(e) => { - show_error!("{}: {}", files[0], e); + show_error!("{}: {}", files[0].maybe_quote(), e); return 1; } } @@ -314,7 +315,8 @@ where if val.is_empty() { return Err(format!( "{}:{}: invalid line; missing second token", - fp, num + fp.maybe_quote(), + num )); } let lower = key.to_lowercase(); @@ -341,7 +343,12 @@ where } else if let Some(s) = table.get(lower.as_str()) { result.push_str(format!("{}={}:", s, val).as_str()); } else { - return Err(format!("{}:{}: unrecognized keyword {}", fp, num, key)); + return Err(format!( + "{}:{}: unrecognized keyword {}", + fp.maybe_quote(), + num, + key + )); } } } diff --git a/src/uu/dirname/src/dirname.rs b/src/uu/dirname/src/dirname.rs index 3e91d598a..601d93ac0 100644 --- a/src/uu/dirname/src/dirname.rs +++ b/src/uu/dirname/src/dirname.rs @@ -10,6 +10,7 @@ extern crate uucore; use clap::{crate_version, App, Arg}; use std::path::Path; +use uucore::display::print_verbatim; use uucore::error::{UResult, UUsageError}; use uucore::InvalidEncodingHandling; @@ -65,7 +66,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { if d.components().next() == None { print!(".") } else { - print!("{}", d.to_string_lossy()); + print_verbatim(d).unwrap(); } } None => { diff --git a/src/uu/du/src/du.rs b/src/uu/du/src/du.rs index 685064dfc..9fd44b001 100644 --- a/src/uu/du/src/du.rs +++ b/src/uu/du/src/du.rs @@ -32,6 +32,7 @@ use std::path::PathBuf; use std::str::FromStr; use std::time::{Duration, UNIX_EPOCH}; use std::{error::Error, fmt::Display}; +use uucore::display::{print_verbatim, Quotable}; use uucore::error::{UError, UResult}; use uucore::parse_size::{parse_size, ParseSizeError}; use uucore::InvalidEncodingHandling; @@ -293,9 +294,9 @@ fn du( Err(e) => { safe_writeln!( stderr(), - "{}: cannot read directory '{}': {}", + "{}: cannot read directory {}: {}", options.util_name, - my_stat.path.display(), + my_stat.path.quote(), e ); return Box::new(iter::once(my_stat)); @@ -334,11 +335,11 @@ fn du( } Err(error) => match error.kind() { ErrorKind::PermissionDenied => { - let description = format!("cannot access '{}'", entry.path().display()); + let description = format!("cannot access {}", entry.path().quote()); let error_message = "Permission denied"; show_error_custom_description!(description, "{}", error_message) } - _ => show_error!("cannot access '{}': {}", entry.path().display(), error), + _ => show_error!("cannot access {}: {}", entry.path().quote(), error), }, }, Err(error) => show_error!("{}", error), @@ -411,26 +412,30 @@ enum DuError { impl Display for DuError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - DuError::InvalidMaxDepthArg(s) => write!(f, "invalid maximum depth '{}'", s), + DuError::InvalidMaxDepthArg(s) => write!(f, "invalid maximum depth {}", s.quote()), DuError::SummarizeDepthConflict(s) => { - write!(f, "summarizing conflicts with --max-depth={}", s) + write!( + f, + "summarizing conflicts with --max-depth={}", + s.maybe_quote() + ) } DuError::InvalidTimeStyleArg(s) => write!( f, - "invalid argument '{}' for 'time style' + "invalid argument {} for 'time style' Valid arguments are: - 'full-iso' - 'long-iso' - 'iso' Try '{} --help' for more information.", - s, + s.quote(), uucore::execution_phrase() ), DuError::InvalidTimeArg(s) => write!( f, - "Invalid argument '{}' for --time. + "Invalid argument {} for --time. 'birth' and 'creation' arguments are not supported on this platform.", - s + s.quote() ), } } @@ -566,21 +571,14 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { }; if !summarize || index == len - 1 { let time_str = tm.format(time_format_str).to_string(); - print!( - "{}\t{}\t{}{}", - convert_size(size), - time_str, - stat.path.display(), - line_separator - ); + print!("{}\t{}\t", convert_size(size), time_str); + print_verbatim(stat.path).unwrap(); + print!("{}", line_separator); } } else if !summarize || index == len - 1 { - print!( - "{}\t{}{}", - convert_size(size), - stat.path.display(), - line_separator - ); + print!("{}\t", convert_size(size)); + print_verbatim(stat.path).unwrap(); + print!("{}", line_separator); } if options.total && index == (len - 1) { // The last element will be the total size of the the path under @@ -590,7 +588,11 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } } Err(_) => { - show_error!("{}: {}", path_string, "No such file or directory"); + show_error!( + "{}: {}", + path_string.maybe_quote(), + "No such file or directory" + ); } } } @@ -837,8 +839,8 @@ fn format_error_message(error: ParseSizeError, s: &str, option: &str) -> String // GNU's du echos affected flag, -B or --block-size (-t or --threshold), depending user's selection // GNU's du does distinguish between "invalid (suffix in) argument" match error { - ParseSizeError::ParseFailure(_) => format!("invalid --{} argument '{}'", option, s), - ParseSizeError::SizeTooBig(_) => format!("--{} argument '{}' too large", option, s), + ParseSizeError::ParseFailure(_) => format!("invalid --{} argument {}", option, s.quote()), + ParseSizeError::SizeTooBig(_) => format!("--{} argument {} too large", option, s.quote()), } } diff --git a/src/uu/env/src/env.rs b/src/uu/env/src/env.rs index baa3ca86c..af889d093 100644 --- a/src/uu/env/src/env.rs +++ b/src/uu/env/src/env.rs @@ -65,8 +65,14 @@ fn parse_name_value_opt<'a>(opts: &mut Options<'a>, opt: &'a str) -> Result(opts: &mut Options<'a>, opt: &'a str) -> Result<(), i32> { if opts.null { - eprintln!("{}: cannot specify --null (-0) with command", crate_name!()); - eprintln!("Type \"{} --help\" for detailed information", crate_name!()); + eprintln!( + "{}: cannot specify --null (-0) with command", + uucore::util_name() + ); + eprintln!( + "Type \"{} --help\" for detailed information", + uucore::execution_phrase() + ); Err(1) } else { opts.program.push(opt); diff --git a/src/uu/expand/src/expand.rs b/src/uu/expand/src/expand.rs index f5a5686f6..b09b8aaab 100644 --- a/src/uu/expand/src/expand.rs +++ b/src/uu/expand/src/expand.rs @@ -17,6 +17,7 @@ use std::fs::File; use std::io::{stdin, stdout, BufRead, BufReader, BufWriter, Read, Write}; use std::str::from_utf8; use unicode_width::UnicodeWidthChar; +use uucore::display::Quotable; static ABOUT: &str = "Convert tabs in each FILE to spaces, writing to standard output. With no FILE, or when FILE is -, read standard input."; @@ -216,7 +217,7 @@ fn open(path: String) -> BufReader> { } else { file_buf = match File::open(&path[..]) { Ok(a) => a, - Err(e) => crash!(1, "{}: {}\n", &path[..], e), + Err(e) => crash!(1, "{}: {}\n", path.maybe_quote(), e), }; BufReader::new(Box::new(file_buf) as Box) } diff --git a/src/uu/factor/src/cli.rs b/src/uu/factor/src/cli.rs index 95b8bb866..30541c244 100644 --- a/src/uu/factor/src/cli.rs +++ b/src/uu/factor/src/cli.rs @@ -16,6 +16,7 @@ use std::io::{self, stdin, stdout, BufRead, Write}; mod factor; use clap::{crate_version, App, Arg}; pub use factor::*; +use uucore::display::Quotable; mod miller_rabin; pub mod numeric; @@ -52,7 +53,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { if let Some(values) = matches.values_of(options::NUMBER) { for number in values { if let Err(e) = print_factors_str(number, &mut w, &mut factors_buffer) { - show_warning!("{}: {}", number, e); + show_warning!("{}: {}", number.maybe_quote(), e); } } } else { @@ -61,7 +62,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { for line in stdin.lock().lines() { for number in line.unwrap().split_whitespace() { if let Err(e) = print_factors_str(number, &mut w, &mut factors_buffer) { - show_warning!("{}: {}", number, e); + show_warning!("{}: {}", number.maybe_quote(), e); } } } diff --git a/src/uu/fmt/src/fmt.rs b/src/uu/fmt/src/fmt.rs index 5203745a1..df5d971e5 100644 --- a/src/uu/fmt/src/fmt.rs +++ b/src/uu/fmt/src/fmt.rs @@ -15,6 +15,7 @@ use std::cmp; use std::fs::File; use std::io::{stdin, stdout, Write}; use std::io::{BufReader, BufWriter, Read}; +use uucore::display::Quotable; use self::linebreak::break_lines; use self::parasplit::ParagraphStream; @@ -187,7 +188,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { _ => match File::open(i) { Ok(f) => BufReader::new(Box::new(f) as Box), Err(e) => { - show_warning!("{}: {}", i, e); + show_warning!("{}: {}", i.maybe_quote(), e); continue; } }, diff --git a/src/uu/groups/src/groups.rs b/src/uu/groups/src/groups.rs index c2af5c4b0..43e2a2239 100644 --- a/src/uu/groups/src/groups.rs +++ b/src/uu/groups/src/groups.rs @@ -17,7 +17,10 @@ #[macro_use] extern crate uucore; -use uucore::entries::{get_groups_gnu, gid2grp, Locate, Passwd}; +use uucore::{ + display::Quotable, + entries::{get_groups_gnu, gid2grp, Locate, Passwd}, +}; use clap::{crate_version, App, Arg}; @@ -77,7 +80,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .join(" ") ); } else { - show_error!("'{}': no such user", user); + show_error!("{}: no such user", user.quote()); exit_code = 1; } } diff --git a/src/uu/hashsum/src/hashsum.rs b/src/uu/hashsum/src/hashsum.rs index f308da300..2081d7612 100644 --- a/src/uu/hashsum/src/hashsum.rs +++ b/src/uu/hashsum/src/hashsum.rs @@ -34,6 +34,7 @@ use std::io::{self, stdin, BufRead, BufReader, Read}; use std::iter; use std::num::ParseIntError; use std::path::Path; +use uucore::display::Quotable; const NAME: &str = "hashsum"; @@ -525,7 +526,7 @@ where if options.warn { show_warning!( "{}: {}: improperly formatted {} checksum line", - filename.display(), + filename.maybe_quote(), i + 1, options.algoname ); @@ -546,6 +547,10 @@ where ) ) .to_ascii_lowercase(); + // FIXME: (How) should these be quoted? + // They seem like they would be processed programmatically, and + // our ordinary quoting might interfere, but newlines should be + // sanitized probably if sum == real_sum { if !options.quiet { println!("{}: OK", ck_filename); diff --git a/src/uu/head/src/head.rs b/src/uu/head/src/head.rs index 760cb44d5..ead734088 100644 --- a/src/uu/head/src/head.rs +++ b/src/uu/head/src/head.rs @@ -9,6 +9,7 @@ use clap::{crate_version, App, Arg}; use std::convert::TryFrom; use std::ffi::OsString; use std::io::{self, ErrorKind, Read, Seek, SeekFrom, Write}; +use uucore::display::Quotable; use uucore::{crash, show_error_custom_description}; const EXIT_FAILURE: i32 = 1; @@ -127,10 +128,10 @@ fn arg_iterate<'a>( 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)), + parse::ParseError::Syntax => Err(format!("bad argument format: {}", s.quote())), parse::ParseError::Overflow => Err(format!( - "invalid argument: '{}' Value too large for defined datatype", - s + "invalid argument: {} Value too large for defined datatype", + s.quote() )), }, None => Ok(Box::new(vec![first, second].into_iter().chain(args))), @@ -418,7 +419,7 @@ fn uu_head(options: &HeadOptions) -> Result<(), u32> { let mut file = match std::fs::File::open(name) { Ok(f) => f, Err(err) => { - let prefix = format!("cannot open '{}' for reading", name); + let prefix = format!("cannot open {} for reading", name.quote()); match err.kind() { ErrorKind::NotFound => { show_error_custom_description!(prefix, "No such file or directory"); diff --git a/src/uu/id/src/id.rs b/src/uu/id/src/id.rs index 4e8e30e52..1229b577e 100644 --- a/src/uu/id/src/id.rs +++ b/src/uu/id/src/id.rs @@ -41,6 +41,7 @@ extern crate uucore; use clap::{crate_version, App, Arg}; use std::ffi::CStr; +use uucore::display::Quotable; use uucore::entries::{self, Group, Locate, Passwd}; use uucore::error::UResult; use uucore::error::{set_exit_code, USimpleError}; @@ -230,7 +231,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { match Passwd::locate(users[i].as_str()) { Ok(p) => Some(p), Err(_) => { - show_error!("'{}': no such user", users[i]); + show_error!("{}: no such user", users[i].quote()); set_exit_code(1); if i + 1 >= users.len() { break; diff --git a/src/uu/install/src/install.rs b/src/uu/install/src/install.rs index b72721d20..a9f91f658 100644 --- a/src/uu/install/src/install.rs +++ b/src/uu/install/src/install.rs @@ -16,6 +16,7 @@ use clap::{crate_version, App, Arg, ArgMatches}; use file_diff::diff; use filetime::{set_file_times, FileTime}; use uucore::backup_control::{self, BackupMode}; +use uucore::display::Quotable; use uucore::entries::{grp2gid, usr2uid}; use uucore::error::{FromIo, UError, UIoError, UResult}; use uucore::mode::get_umask; @@ -95,40 +96,30 @@ impl Display for InstallError { ) } IE::CreateDirFailed(dir, e) => { - Display::fmt(&uio_error!(e, "failed to create {}", dir.display()), f) + Display::fmt(&uio_error!(e, "failed to create {}", dir.quote()), f) } - IE::ChmodFailed(file) => write!(f, "failed to chmod {}", file.display()), + IE::ChmodFailed(file) => write!(f, "failed to chmod {}", file.quote()), IE::InvalidTarget(target) => write!( f, "invalid target {}: No such file or directory", - target.display() + target.quote() ), IE::TargetDirIsntDir(target) => { - write!(f, "target '{}' is not a directory", target.display()) + write!(f, "target {} is not a directory", target.quote()) } IE::BackupFailed(from, to, e) => Display::fmt( - &uio_error!( - e, - "cannot backup '{}' to '{}'", - from.display(), - to.display() - ), + &uio_error!(e, "cannot backup {} to {}", from.quote(), to.quote()), f, ), IE::InstallFailed(from, to, e) => Display::fmt( - &uio_error!( - e, - "cannot install '{}' to '{}'", - from.display(), - to.display() - ), + &uio_error!(e, "cannot install {} to {}", from.quote(), to.quote()), f, ), IE::StripProgramFailed(msg) => write!(f, "strip program failed: {}", msg), IE::MetadataFailed(e) => Display::fmt(&uio_error!(e, ""), f), - IE::NoSuchUser(user) => write!(f, "no such user: {}", user), - IE::NoSuchGroup(group) => write!(f, "no such group: {}", group), - IE::OmittingDirectory(dir) => write!(f, "omitting directory '{}'", dir.display()), + IE::NoSuchUser(user) => write!(f, "no such user: {}", user.maybe_quote()), + IE::NoSuchGroup(group) => write!(f, "no such group: {}", group.maybe_quote()), + IE::OmittingDirectory(dir) => write!(f, "omitting directory {}", dir.quote()), } } } @@ -416,14 +407,14 @@ fn directory(paths: Vec, b: Behavior) -> UResult<()> { // the default mode. Hence it is safe to use fs::create_dir_all // and then only modify the target's dir mode. if let Err(e) = - fs::create_dir_all(path).map_err_context(|| format!("{}", path.display())) + fs::create_dir_all(path).map_err_context(|| path.maybe_quote().to_string()) { show!(e); continue; } if b.verbose { - println!("creating directory '{}'", path.display()); + println!("creating directory {}", path.quote()); } } @@ -445,7 +436,7 @@ fn directory(paths: Vec, b: Behavior) -> UResult<()> { fn is_new_file_path(path: &Path) -> bool { !path.exists() && (path.parent().map(Path::is_dir).unwrap_or(true) - || path.parent().unwrap().to_string_lossy().is_empty()) // In case of a simple file + || path.parent().unwrap().as_os_str().is_empty()) // In case of a simple file } /// Perform an install, given a list of paths and behavior. @@ -502,7 +493,7 @@ fn copy_files_into_dir(files: &[PathBuf], target_dir: &Path, b: &Behavior) -> UR if !sourcepath.exists() { let err = UIoError::new( std::io::ErrorKind::NotFound, - format!("cannot stat '{}'", sourcepath.display()), + format!("cannot stat {}", sourcepath.quote()), ); show!(err); continue; @@ -566,7 +557,7 @@ fn copy(from: &Path, to: &Path, b: &Behavior) -> UResult<()> { } } - if from.to_string_lossy() == "/dev/null" { + if from.as_os_str() == "/dev/null" { /* workaround a limitation of fs::copy * https://github.com/rust-lang/rust/issues/79390 */ @@ -674,9 +665,9 @@ fn copy(from: &Path, to: &Path, b: &Behavior) -> UResult<()> { } if b.verbose { - print!("'{}' -> '{}'", from.display(), to.display()); + print!("{} -> {}", from.quote(), to.quote()); match backup_path { - Some(path) => println!(" (backup: '{}')", path.display()), + Some(path) => println!(" (backup: {})", path.quote()), None => println!(), } } diff --git a/src/uu/install/src/mode.rs b/src/uu/install/src/mode.rs index fd4cee50e..310e1fb43 100644 --- a/src/uu/install/src/mode.rs +++ b/src/uu/install/src/mode.rs @@ -22,8 +22,9 @@ pub fn parse(mode_string: &str, considering_dir: bool, umask: u32) -> Result Result<(), ()> { use std::os::unix::fs::PermissionsExt; + use uucore::display::Quotable; fs::set_permissions(path, fs::Permissions::from_mode(mode)).map_err(|err| { - show_error!("{}: chmod failed with error {}", path.display(), err); + show_error!("{}: chmod failed with error {}", path.maybe_quote(), err); }) } diff --git a/src/uu/join/src/join.rs b/src/uu/join/src/join.rs index ae991489f..51dfeec6f 100644 --- a/src/uu/join/src/join.rs +++ b/src/uu/join/src/join.rs @@ -14,6 +14,7 @@ use clap::{crate_version, App, Arg}; use std::cmp::Ordering; use std::fs::File; use std::io::{stdin, BufRead, BufReader, Lines, Stdin}; +use uucore::display::Quotable; static NAME: &str = "join"; @@ -181,18 +182,18 @@ impl Spec { return Spec::Key; } - crash!(1, "invalid field specifier: '{}'", format); + crash!(1, "invalid field specifier: {}", format.quote()); } Some('1') => FileNum::File1, Some('2') => FileNum::File2, - _ => crash!(1, "invalid file number in field spec: '{}'", format), + _ => crash!(1, "invalid file number in field spec: {}", format.quote()), }; if let Some('.') = chars.next() { return Spec::Field(file_num, parse_field_number(chars.as_str())); } - crash!(1, "invalid field specifier: '{}'", format); + crash!(1, "invalid field specifier: {}", format.quote()); } } @@ -245,7 +246,7 @@ impl<'a> State<'a> { } else { match File::open(name) { Ok(file) => Box::new(BufReader::new(file)) as Box, - Err(err) => crash!(1, "{}: {}", name, err), + Err(err) => crash!(1, "{}: {}", name.maybe_quote(), err), } }; @@ -393,7 +394,11 @@ impl<'a> State<'a> { let diff = input.compare(self.get_current_key(), line.get_field(self.key)); if diff == Ordering::Greater { - eprintln!("{}:{}: is not sorted", self.file_name, self.line_num); + eprintln!( + "{}:{}: is not sorted", + self.file_name.maybe_quote(), + self.line_num + ); // This is fatal if the check is enabled. if input.check_order == CheckOrder::Enabled { @@ -727,7 +732,7 @@ fn get_field_number(keys: Option, key: Option) -> usize { fn parse_field_number(value: &str) -> usize { match value.parse::() { Ok(result) if result > 0 => result - 1, - _ => crash!(1, "invalid field number: '{}'", value), + _ => crash!(1, "invalid field number: {}", value.quote()), } } @@ -735,7 +740,7 @@ fn parse_file_number(value: &str) -> FileNum { match value { "1" => FileNum::File1, "2" => FileNum::File2, - value => crash!(1, "invalid file number: '{}'", value), + value => crash!(1, "invalid file number: {}", value.quote()), } } diff --git a/src/uu/kill/src/kill.rs b/src/uu/kill/src/kill.rs index 47bd97dbc..494dc0602 100644 --- a/src/uu/kill/src/kill.rs +++ b/src/uu/kill/src/kill.rs @@ -13,6 +13,7 @@ extern crate uucore; use clap::{crate_version, App, Arg}; use libc::{c_int, pid_t}; use std::io::Error; +use uucore::display::Quotable; use uucore::error::{UResult, USimpleError}; use uucore::signals::ALL_SIGNALS; use uucore::InvalidEncodingHandling; @@ -154,7 +155,7 @@ fn print_signal(signal_name_or_value: &str) -> UResult<()> { } Err(USimpleError::new( 1, - format!("unknown signal name {}", signal_name_or_value), + format!("unknown signal name {}", signal_name_or_value.quote()), )) } @@ -190,7 +191,7 @@ fn kill(signalname: &str, pids: &[String]) -> UResult<()> { None => { return Err(USimpleError::new( 1, - format!("unknown signal name {}", signalname), + format!("unknown signal name {}", signalname.quote()), )); } }; @@ -204,7 +205,7 @@ fn kill(signalname: &str, pids: &[String]) -> UResult<()> { Err(e) => { return Err(USimpleError::new( 1, - format!("failed to parse argument {}: {}", pid, e), + format!("failed to parse argument {}: {}", pid.quote(), e), )); } }; diff --git a/src/uu/ln/src/ln.rs b/src/uu/ln/src/ln.rs index 4eeb637e7..e480d8f13 100644 --- a/src/uu/ln/src/ln.rs +++ b/src/uu/ln/src/ln.rs @@ -11,11 +11,12 @@ extern crate uucore; use clap::{crate_version, App, Arg}; +use uucore::display::Quotable; use uucore::error::{UError, UResult}; use std::borrow::Cow; use std::error::Error; -use std::ffi::OsStr; +use std::ffi::{OsStr, OsString}; use std::fmt::Display; use std::fs; @@ -49,26 +50,26 @@ pub enum OverwriteMode { #[derive(Debug)] enum LnError { - TargetIsDirectory(String), + TargetIsDirectory(PathBuf), SomeLinksFailed, FailedToLink(String), - MissingDestination(String), - ExtraOperand(String), + MissingDestination(PathBuf), + ExtraOperand(OsString), } impl Display for LnError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - Self::TargetIsDirectory(s) => write!(f, "target '{}' is not a directory", s), - Self::FailedToLink(s) => write!(f, "failed to link '{}'", s), + Self::TargetIsDirectory(s) => write!(f, "target {} is not a directory", s.quote()), + Self::FailedToLink(e) => write!(f, "failed to link: {}", e), Self::SomeLinksFailed => write!(f, "some links failed to create"), Self::MissingDestination(s) => { - write!(f, "missing destination file operand after '{}'", s) + write!(f, "missing destination file operand after {}", s.quote()) } Self::ExtraOperand(s) => write!( f, - "extra operand '{}'\nTry '{} --help' for more information.", - s, + "extra operand {}\nTry '{} --help' for more information.", + s.quote(), uucore::execution_phrase() ), } @@ -279,10 +280,10 @@ fn exec(files: &[PathBuf], settings: &Settings) -> UResult<()> { // 1st form. Now there should be only two operands, but if -T is // specified we may have a wrong number of operands. if files.len() == 1 { - return Err(LnError::MissingDestination(files[0].to_string_lossy().into()).into()); + return Err(LnError::MissingDestination(files[0].clone()).into()); } if files.len() > 2 { - return Err(LnError::ExtraOperand(files[2].display().to_string()).into()); + return Err(LnError::ExtraOperand(files[2].clone().into()).into()); } assert!(!files.is_empty()); @@ -294,7 +295,7 @@ fn exec(files: &[PathBuf], settings: &Settings) -> UResult<()> { fn link_files_in_dir(files: &[PathBuf], target_dir: &Path, settings: &Settings) -> UResult<()> { if !target_dir.is_dir() { - return Err(LnError::TargetIsDirectory(target_dir.display().to_string()).into()); + return Err(LnError::TargetIsDirectory(target_dir.to_owned()).into()); } let mut all_successful = true; @@ -306,7 +307,7 @@ fn link_files_in_dir(files: &[PathBuf], target_dir: &Path, settings: &Settings) if is_symlink(target_dir) { if target_dir.is_file() { if let Err(e) = fs::remove_file(target_dir) { - show_error!("Could not update {}: {}", target_dir.display(), e) + show_error!("Could not update {}: {}", target_dir.quote(), e) }; } if target_dir.is_dir() { @@ -314,7 +315,7 @@ fn link_files_in_dir(files: &[PathBuf], target_dir: &Path, settings: &Settings) // considered as a dir // See test_ln::test_symlink_no_deref_dir if let Err(e) = fs::remove_dir(target_dir) { - show_error!("Could not update {}: {}", target_dir.display(), e) + show_error!("Could not update {}: {}", target_dir.quote(), e) }; } } @@ -332,10 +333,7 @@ fn link_files_in_dir(files: &[PathBuf], target_dir: &Path, settings: &Settings) } } None => { - show_error!( - "cannot stat '{}': No such file or directory", - srcpath.display() - ); + show_error!("cannot stat {}: No such file or directory", srcpath.quote()); all_successful = false; continue; } @@ -344,9 +342,9 @@ fn link_files_in_dir(files: &[PathBuf], target_dir: &Path, settings: &Settings) if let Err(e) = link(srcpath, &targetpath, settings) { show_error!( - "cannot link '{}' to '{}': {}", - targetpath.display(), - srcpath.display(), + "cannot link {} to {}: {}", + targetpath.quote(), + srcpath.quote(), e ); all_successful = false; @@ -399,7 +397,7 @@ fn link(src: &Path, dst: &Path, settings: &Settings) -> Result<()> { match settings.overwrite { OverwriteMode::NoClobber => {} OverwriteMode::Interactive => { - print!("{}: overwrite '{}'? ", uucore::util_name(), dst.display()); + print!("{}: overwrite {}? ", uucore::util_name(), dst.quote()); if !read_yes() { return Ok(()); } @@ -426,9 +424,9 @@ fn link(src: &Path, dst: &Path, settings: &Settings) -> Result<()> { } if settings.verbose { - print!("'{}' -> '{}'", dst.display(), &source.display()); + print!("{} -> {}", dst.quote(), source.quote()); match backup_path { - Some(path) => println!(" (backup: '{}')", path.display()), + Some(path) => println!(" (backup: {})", path.quote()), None => println!(), } } diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index 6f63c2a4a..fad30157c 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -40,7 +40,10 @@ use std::{ time::Duration, }; use term_grid::{Cell, Direction, Filling, Grid, GridOptions}; -use uucore::error::{set_exit_code, FromIo, UError, UResult}; +use uucore::{ + display::Quotable, + error::{set_exit_code, FromIo, UError, UResult}, +}; use unicode_width::UnicodeWidthStr; #[cfg(unix)] @@ -150,8 +153,8 @@ impl Error for LsError {} impl Display for LsError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - LsError::InvalidLineWidth(s) => write!(f, "invalid line width: '{}'", s), - LsError::NoMetadata(p) => write!(f, "could not open file: '{}'", p.display()), + LsError::InvalidLineWidth(s) => write!(f, "invalid line width: {}", s.quote()), + LsError::NoMetadata(p) => write!(f, "could not open file: {}", p.quote()), } } } @@ -410,18 +413,18 @@ impl Config { }, None => match termsize::get() { Some(size) => size.cols, - None => match std::env::var("COLUMNS") { - Ok(columns) => match columns.parse() { - Ok(columns) => columns, - Err(_) => { + None => match std::env::var_os("COLUMNS") { + Some(columns) => match columns.to_str().and_then(|s| s.parse().ok()) { + Some(columns) => columns, + None => { show_error!( - "ignoring invalid width in environment variable COLUMNS: '{}'", - columns + "ignoring invalid width in environment variable COLUMNS: {}", + columns.quote() ); DEFAULT_TERM_WIDTH } }, - Err(_) => DEFAULT_TERM_WIDTH, + None => DEFAULT_TERM_WIDTH, }, }, }; @@ -538,7 +541,7 @@ impl Config { Ok(p) => { ignore_patterns.add(p); } - Err(_) => show_warning!("Invalid pattern for ignore: '{}'", pattern), + Err(_) => show_warning!("Invalid pattern for ignore: {}", pattern.quote()), } } @@ -548,7 +551,7 @@ impl Config { Ok(p) => { ignore_patterns.add(p); } - Err(_) => show_warning!("Invalid pattern for hide: '{}'", pattern), + Err(_) => show_warning!("Invalid pattern for hide: {}", pattern.quote()), } } } @@ -1255,7 +1258,7 @@ fn list(locs: Vec<&Path>, config: Config) -> UResult<()> { if path_data.md().is_none() { show!(std::io::ErrorKind::NotFound - .map_err_context(|| format!("cannot access '{}'", path_data.p_buf.display()))); + .map_err_context(|| format!("cannot access {}", path_data.p_buf.quote()))); // We found an error, no need to continue the execution continue; } diff --git a/src/uu/mkdir/src/mkdir.rs b/src/uu/mkdir/src/mkdir.rs index 848f79988..92c068408 100644 --- a/src/uu/mkdir/src/mkdir.rs +++ b/src/uu/mkdir/src/mkdir.rs @@ -12,6 +12,7 @@ use clap::OsValues; use clap::{crate_version, App, Arg}; use std::fs; use std::path::Path; +use uucore::display::Quotable; use uucore::error::{FromIo, UResult, USimpleError}; static ABOUT: &str = "Create the given DIRECTORY(ies) if they do not exist"; @@ -43,7 +44,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { // Not tested on Windows let mode: u16 = match matches.value_of(options::MODE) { Some(m) => u16::from_str_radix(m, 8) - .map_err(|_| USimpleError::new(1, format!("invalid mode '{}'", m)))?, + .map_err(|_| USimpleError::new(1, format!("invalid mode {}", m.quote())))?, None => 0o755_u16, }; @@ -100,13 +101,13 @@ fn mkdir(path: &Path, recursive: bool, mode: u16, verbose: bool) -> UResult<()> fs::create_dir }; - create_dir(path).map_err_context(|| format!("cannot create directory '{}'", path.display()))?; + create_dir(path).map_err_context(|| format!("cannot create directory {}", path.quote()))?; if verbose { println!( - "{}: created directory '{}'", + "{}: created directory {}", uucore::util_name(), - path.display() + path.quote() ); } @@ -121,7 +122,7 @@ fn chmod(path: &Path, mode: u16) -> UResult<()> { let mode = Permissions::from_mode(u32::from(mode)); set_permissions(path, mode) - .map_err_context(|| format!("cannot set permissions '{}'", path.display())) + .map_err_context(|| format!("cannot set permissions {}", path.quote())) } #[cfg(windows)] diff --git a/src/uu/mkfifo/src/mkfifo.rs b/src/uu/mkfifo/src/mkfifo.rs index 009675811..66c3f7bb6 100644 --- a/src/uu/mkfifo/src/mkfifo.rs +++ b/src/uu/mkfifo/src/mkfifo.rs @@ -11,7 +11,7 @@ extern crate uucore; use clap::{crate_version, App, Arg}; use libc::mkfifo; use std::ffi::CString; -use uucore::InvalidEncodingHandling; +use uucore::{display::Quotable, InvalidEncodingHandling}; static NAME: &str = "mkfifo"; static USAGE: &str = "mkfifo [OPTION]... NAME..."; @@ -61,7 +61,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { mkfifo(name.as_ptr(), mode as libc::mode_t) }; if err == -1 { - show_error!("cannot create fifo '{}': File exists", f); + show_error!("cannot create fifo {}: File exists", f.quote()); exit_code = 1; } } diff --git a/src/uu/mknod/src/mknod.rs b/src/uu/mknod/src/mknod.rs index f8fb9c469..dd529c3ba 100644 --- a/src/uu/mknod/src/mknod.rs +++ b/src/uu/mknod/src/mknod.rs @@ -16,6 +16,7 @@ use clap::{crate_version, App, Arg, ArgMatches}; use libc::{dev_t, mode_t}; use libc::{S_IFBLK, S_IFCHR, S_IFIFO, S_IRGRP, S_IROTH, S_IRUSR, S_IWGRP, S_IWOTH, S_IWUSR}; +use uucore::display::Quotable; use uucore::InvalidEncodingHandling; static ABOUT: &str = "Create the special file NAME of the given TYPE."; @@ -219,7 +220,7 @@ fn valid_type(tpe: String) -> Result<(), String> { if vec!['b', 'c', 'u', 'p'].contains(&first_char) { Ok(()) } else { - Err(format!("invalid device type '{}'", tpe)) + Err(format!("invalid device type {}", tpe.quote())) } }) } diff --git a/src/uu/mktemp/src/mktemp.rs b/src/uu/mktemp/src/mktemp.rs index 0b30f0087..59e82569a 100644 --- a/src/uu/mktemp/src/mktemp.rs +++ b/src/uu/mktemp/src/mktemp.rs @@ -12,6 +12,7 @@ extern crate uucore; use clap::{crate_version, App, Arg}; +use uucore::display::{println_verbatim, Quotable}; use uucore::error::{FromIo, UError, UResult}; use std::env; @@ -57,16 +58,20 @@ impl Display for MkTempError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { use MkTempError::*; match self { - PersistError(p) => write!(f, "could not persist file '{}'", p.display()), - MustEndInX(s) => write!(f, "with --suffix, template '{}' must end in X", s), - TooFewXs(s) => write!(f, "too few X's in template '{}'", s), + PersistError(p) => write!(f, "could not persist file {}", p.quote()), + MustEndInX(s) => write!(f, "with --suffix, template {} must end in X", s.quote()), + TooFewXs(s) => write!(f, "too few X's in template {}", s.quote()), ContainsDirSeparator(s) => { - write!(f, "invalid suffix '{}', contains directory separator", s) + write!( + f, + "invalid suffix {}, contains directory separator", + s.quote() + ) } InvalidTemplate(s) => write!( f, - "invalid template, '{}'; with --tmpdir, it may not be absolute", - s + "invalid template, {}; with --tmpdir, it may not be absolute", + s.quote() ), } } @@ -244,8 +249,7 @@ pub fn dry_exec(mut tmpdir: PathBuf, prefix: &str, rand: usize, suffix: &str) -> } } tmpdir.push(buf); - println!("{}", tmpdir.display()); - Ok(()) + println_verbatim(tmpdir).map_err_context(|| "failed to print directory name".to_owned()) } fn exec(dir: PathBuf, prefix: &str, rand: usize, suffix: &str, make_dir: bool) -> UResult<()> { @@ -274,6 +278,5 @@ fn exec(dir: PathBuf, prefix: &str, rand: usize, suffix: &str, make_dir: bool) - .map_err(|e| MkTempError::PersistError(e.file.path().to_path_buf()))? .1 }; - println!("{}", path.display()); - Ok(()) + println_verbatim(path).map_err_context(|| "failed to print directory name".to_owned()) } diff --git a/src/uu/more/src/more.rs b/src/uu/more/src/more.rs index 8097d1402..3a601c1e8 100644 --- a/src/uu/more/src/more.rs +++ b/src/uu/more/src/more.rs @@ -30,6 +30,7 @@ use crossterm::{ use unicode_segmentation::UnicodeSegmentation; use unicode_width::UnicodeWidthStr; +use uucore::display::Quotable; const BELL: &str = "\x07"; @@ -64,12 +65,12 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let file = Path::new(file); if file.is_dir() { terminal::disable_raw_mode().unwrap(); - show_usage_error!("'{}' is a directory.", file.display()); + show_usage_error!("{} is a directory.", file.quote()); return 1; } if !file.exists() { terminal::disable_raw_mode().unwrap(); - show_error!("cannot open {}: No such file or directory", file.display()); + show_error!("cannot open {}: No such file or directory", file.quote()); return 1; } if length > 1 { diff --git a/src/uu/mv/src/mv.rs b/src/uu/mv/src/mv.rs index e2e2352a0..9d23f86de 100644 --- a/src/uu/mv/src/mv.rs +++ b/src/uu/mv/src/mv.rs @@ -21,6 +21,7 @@ use std::os::unix; use std::os::windows; use std::path::{Path, PathBuf}; use uucore::backup_control::{self, BackupMode}; +use uucore::display::Quotable; use fs_extra::dir::{move_dir, CopyOptions as DirCopyOptions}; @@ -223,10 +224,7 @@ fn exec(files: &[PathBuf], b: Behavior) -> i32 { // `Ok()` results unless the source does not exist, or the user // lacks permission to access metadata. if source.symlink_metadata().is_err() { - show_error!( - "cannot stat '{}': No such file or directory", - source.display() - ); + show_error!("cannot stat {}: No such file or directory", source.quote()); return 1; } @@ -234,8 +232,8 @@ fn exec(files: &[PathBuf], b: Behavior) -> i32 { if b.no_target_dir { if !source.is_dir() { show_error!( - "cannot overwrite directory '{}' with non-directory", - target.display() + "cannot overwrite directory {} with non-directory", + target.quote() ); return 1; } @@ -243,9 +241,9 @@ fn exec(files: &[PathBuf], b: Behavior) -> i32 { return match rename(source, target, &b) { Err(e) => { show_error!( - "cannot move '{}' to '{}': {}", - source.display(), - target.display(), + "cannot move {} to {}: {}", + source.quote(), + target.quote(), e.to_string() ); 1 @@ -257,9 +255,9 @@ fn exec(files: &[PathBuf], b: Behavior) -> i32 { return move_files_into_dir(&[source.clone()], target, &b); } else if target.exists() && source.is_dir() { show_error!( - "cannot overwrite non-directory '{}' with directory '{}'", - target.display(), - source.display() + "cannot overwrite non-directory {} with directory {}", + target.quote(), + source.quote() ); return 1; } @@ -272,9 +270,9 @@ fn exec(files: &[PathBuf], b: Behavior) -> i32 { _ => { if b.no_target_dir { show_error!( - "mv: extra operand '{}'\n\ + "mv: extra operand {}\n\ Try '{} --help' for more information.", - files[2].display(), + files[2].quote(), uucore::execution_phrase() ); return 1; @@ -288,7 +286,7 @@ fn exec(files: &[PathBuf], b: Behavior) -> i32 { fn move_files_into_dir(files: &[PathBuf], target_dir: &Path, b: &Behavior) -> i32 { if !target_dir.is_dir() { - show_error!("target '{}' is not a directory", target_dir.display()); + show_error!("target {} is not a directory", target_dir.quote()); return 1; } @@ -298,8 +296,8 @@ fn move_files_into_dir(files: &[PathBuf], target_dir: &Path, b: &Behavior) -> i3 Some(name) => target_dir.join(name), None => { show_error!( - "cannot stat '{}': No such file or directory", - sourcepath.display() + "cannot stat {}: No such file or directory", + sourcepath.quote() ); all_successful = false; @@ -309,9 +307,9 @@ fn move_files_into_dir(files: &[PathBuf], target_dir: &Path, b: &Behavior) -> i3 if let Err(e) = rename(sourcepath, &targetpath, b) { show_error!( - "cannot move '{}' to '{}': {}", - sourcepath.display(), - targetpath.display(), + "cannot move {} to {}: {}", + sourcepath.quote(), + targetpath.quote(), e.to_string() ); all_successful = false; @@ -332,7 +330,7 @@ fn rename(from: &Path, to: &Path, b: &Behavior) -> io::Result<()> { match b.overwrite { OverwriteMode::NoClobber => return Ok(()), OverwriteMode::Interactive => { - println!("{}: overwrite '{}'? ", uucore::util_name(), to.display()); + println!("{}: overwrite {}? ", uucore::util_name(), to.quote()); if !read_yes() { return Ok(()); } @@ -365,9 +363,9 @@ fn rename(from: &Path, to: &Path, b: &Behavior) -> io::Result<()> { rename_with_fallback(from, to)?; if b.verbose { - print!("'{}' -> '{}'", from.display(), to.display()); + print!("{} -> {}", from.quote(), to.quote()); match backup_path { - Some(path) => println!(" (backup: '{}')", path.display()), + Some(path) => println!(" (backup: {})", path.quote()), None => println!(), } } diff --git a/src/uu/nohup/src/nohup.rs b/src/uu/nohup/src/nohup.rs index 1ecb9914f..d83170ae8 100644 --- a/src/uu/nohup/src/nohup.rs +++ b/src/uu/nohup/src/nohup.rs @@ -19,6 +19,7 @@ use std::fs::{File, OpenOptions}; use std::io::Error; use std::os::unix::prelude::*; use std::path::{Path, PathBuf}; +use uucore::display::Quotable; use uucore::InvalidEncodingHandling; static ABOUT: &str = "Run COMMAND ignoring hangup signals."; @@ -122,13 +123,16 @@ fn find_stdout() -> File { .open(Path::new(NOHUP_OUT)) { Ok(t) => { - show_error!("ignoring input and appending output to '{}'", NOHUP_OUT); + show_error!( + "ignoring input and appending output to {}", + NOHUP_OUT.quote() + ); t } Err(e1) => { let home = match env::var("HOME") { Err(_) => { - show_error!("failed to open '{}': {}", NOHUP_OUT, e1); + show_error!("failed to open {}: {}", NOHUP_OUT.quote(), e1); exit!(internal_failure_code) } Ok(h) => h, @@ -143,12 +147,15 @@ fn find_stdout() -> File { .open(&homeout) { Ok(t) => { - show_error!("ignoring input and appending output to '{}'", homeout_str); + show_error!( + "ignoring input and appending output to {}", + homeout_str.quote() + ); t } Err(e2) => { - show_error!("failed to open '{}': {}", NOHUP_OUT, e1); - show_error!("failed to open '{}': {}", homeout_str, e2); + show_error!("failed to open {}: {}", NOHUP_OUT.quote(), e1); + show_error!("failed to open {}: {}", homeout_str.quote(), e2); exit!(internal_failure_code) } } diff --git a/src/uu/numfmt/src/format.rs b/src/uu/numfmt/src/format.rs index e44446818..bdee83e12 100644 --- a/src/uu/numfmt/src/format.rs +++ b/src/uu/numfmt/src/format.rs @@ -1,3 +1,5 @@ +use uucore::display::Quotable; + use crate::options::{NumfmtOptions, RoundMethod}; use crate::units::{DisplayableSuffix, RawSuffix, Result, Suffix, Unit, IEC_BASES, SI_BASES}; @@ -78,7 +80,7 @@ fn parse_suffix(s: &str) -> Result<(f64, Option)> { Some('Z') => Some((RawSuffix::Z, with_i)), Some('Y') => Some((RawSuffix::Y, with_i)), Some('0'..='9') => None, - _ => return Err(format!("invalid suffix in input: '{}'", s)), + _ => return Err(format!("invalid suffix in input: {}", s.quote())), }; let suffix_len = match suffix { @@ -89,7 +91,7 @@ fn parse_suffix(s: &str) -> Result<(f64, Option)> { let number = s[..s.len() - suffix_len] .parse::() - .map_err(|_| format!("invalid number: '{}'", s))?; + .map_err(|_| format!("invalid number: {}", s.quote()))?; Ok((number, suffix)) } diff --git a/src/uu/numfmt/src/numfmt.rs b/src/uu/numfmt/src/numfmt.rs index 1798975dc..da2fa8130 100644 --- a/src/uu/numfmt/src/numfmt.rs +++ b/src/uu/numfmt/src/numfmt.rs @@ -15,6 +15,7 @@ use crate::options::*; use crate::units::{Result, Unit}; use clap::{crate_version, App, AppSettings, Arg, ArgMatches}; use std::io::{BufRead, Write}; +use uucore::display::Quotable; use uucore::ranges::Range; pub mod format; @@ -113,7 +114,7 @@ fn parse_options(args: &ArgMatches) -> Result { 0 => Err(value), _ => Ok(n), }) - .map_err(|value| format!("invalid header value '{}'", value)) + .map_err(|value| format!("invalid header value {}", value.quote())) } }?; diff --git a/src/uu/od/src/multifilereader.rs b/src/uu/od/src/multifilereader.rs index 0f8616467..90796b2eb 100644 --- a/src/uu/od/src/multifilereader.rs +++ b/src/uu/od/src/multifilereader.rs @@ -5,6 +5,8 @@ use std::io; use std::io::BufReader; use std::vec::Vec; +use uucore::display::Quotable; + pub enum InputSource<'a> { FileName(&'a str), Stdin, @@ -57,7 +59,7 @@ impl<'b> MultifileReader<'b> { // print an error at the time that the file is needed, // then move on the the next file. // This matches the behavior of the original `od` - eprintln!("{}: '{}': {}", uucore::util_name(), fname, e); + eprintln!("{}: {}: {}", uucore::util_name(), fname.maybe_quote(), e); self.any_err = true } } diff --git a/src/uu/od/src/od.rs b/src/uu/od/src/od.rs index 6c1110362..e9983f991 100644 --- a/src/uu/od/src/od.rs +++ b/src/uu/od/src/od.rs @@ -43,6 +43,7 @@ use crate::partialreader::*; use crate::peekreader::*; use crate::prn_char::format_ascii_dump; use clap::{self, crate_version, AppSettings, Arg, ArgMatches}; +use uucore::display::Quotable; use uucore::parse_size::ParseSizeError; use uucore::InvalidEncodingHandling; @@ -635,7 +636,7 @@ fn format_error_message(error: ParseSizeError, s: &str, option: &str) -> String // GNU's od echos affected flag, -N or --read-bytes (-j or --skip-bytes, etc.), depending user's selection // GNU's od does distinguish between "invalid (suffix in) argument" match error { - ParseSizeError::ParseFailure(_) => format!("invalid --{} argument '{}'", option, s), - ParseSizeError::SizeTooBig(_) => format!("--{} argument '{}' too large", option, s), + ParseSizeError::ParseFailure(_) => format!("invalid --{} argument {}", option, s.quote()), + ParseSizeError::SizeTooBig(_) => format!("--{} argument {} too large", option, s.quote()), } } diff --git a/src/uu/od/src/parse_formats.rs b/src/uu/od/src/parse_formats.rs index 80d477b27..301bb5154 100644 --- a/src/uu/od/src/parse_formats.rs +++ b/src/uu/od/src/parse_formats.rs @@ -1,5 +1,7 @@ // spell-checker:ignore formatteriteminfo docopt fvox fvoxw vals acdx +use uucore::display::Quotable; + use crate::formatteriteminfo::FormatterItemInfo; use crate::prn_char::*; use crate::prn_float::*; @@ -272,8 +274,9 @@ fn parse_type_string(params: &str) -> Result, Strin while let Some(type_char) = ch { let type_char = format_type(type_char).ok_or_else(|| { format!( - "unexpected char '{}' in format specification '{}'", - type_char, params + "unexpected char '{}' in format specification {}", + type_char, + params.quote() ) })?; @@ -293,8 +296,9 @@ fn parse_type_string(params: &str) -> Result, Strin if !decimal_size.is_empty() { byte_size = decimal_size.parse().map_err(|_| { format!( - "invalid number '{}' in format specification '{}'", - decimal_size, params + "invalid number {} in format specification {}", + decimal_size.quote(), + params.quote() ) })?; } @@ -305,8 +309,9 @@ fn parse_type_string(params: &str) -> Result, Strin let ft = od_format_type(type_char, byte_size).ok_or_else(|| { format!( - "invalid size '{}' in format specification '{}'", - byte_size, params + "invalid size '{}' in format specification {}", + byte_size, + params.quote() ) })?; formats.push(ParsedFormatterItemInfo::new(ft, show_ascii_dump)); diff --git a/src/uu/pathchk/src/pathchk.rs b/src/uu/pathchk/src/pathchk.rs index 863bb6bf2..8afeaff18 100644 --- a/src/uu/pathchk/src/pathchk.rs +++ b/src/uu/pathchk/src/pathchk.rs @@ -15,6 +15,7 @@ extern crate uucore; use clap::{crate_version, App, Arg}; use std::fs; use std::io::{ErrorKind, Write}; +use uucore::display::Quotable; use uucore::InvalidEncodingHandling; // operating mode @@ -153,10 +154,10 @@ fn check_basic(path: &[String]) -> bool { if component_len > POSIX_NAME_MAX { writeln!( &mut std::io::stderr(), - "limit {} exceeded by length {} of file name component '{}'", + "limit {} exceeded by length {} of file name component {}", POSIX_NAME_MAX, component_len, - p + p.quote() ); return false; } @@ -175,8 +176,8 @@ fn check_extra(path: &[String]) -> bool { if p.starts_with('-') { writeln!( &mut std::io::stderr(), - "leading hyphen in file name component '{}'", - p + "leading hyphen in file name component {}", + p.quote() ); return false; } @@ -197,10 +198,10 @@ fn check_default(path: &[String]) -> bool { if total_len > libc::PATH_MAX as usize { writeln!( &mut std::io::stderr(), - "limit {} exceeded by length {} of file name '{}'", + "limit {} exceeded by length {} of file name {}", libc::PATH_MAX, total_len, - joined_path + joined_path.quote() ); return false; } @@ -210,10 +211,10 @@ fn check_default(path: &[String]) -> bool { if component_len > libc::FILENAME_MAX as usize { writeln!( &mut std::io::stderr(), - "limit {} exceeded by length {} of file name component '{}'", + "limit {} exceeded by length {} of file name component {}", libc::FILENAME_MAX, component_len, - p + p.quote() ); return false; } @@ -246,9 +247,9 @@ fn check_portable_chars(path_segment: &str) -> bool { let invalid = path_segment[i..].chars().next().unwrap(); writeln!( &mut std::io::stderr(), - "nonportable character '{}' in file name component '{}'", + "nonportable character '{}' in file name component {}", invalid, - path_segment + path_segment.quote() ); return false; } diff --git a/src/uu/pr/src/pr.rs b/src/uu/pr/src/pr.rs index 1358cef6c..45d9480a7 100644 --- a/src/uu/pr/src/pr.rs +++ b/src/uu/pr/src/pr.rs @@ -24,6 +24,8 @@ use std::io::{stdin, stdout, BufRead, BufReader, Lines, Read, Stdout, Write}; #[cfg(unix)] use std::os::unix::fs::FileTypeExt; +use uucore::display::Quotable; + type IOError = std::io::Error; const NAME: &str = "pr"; @@ -517,7 +519,7 @@ fn parse_usize(matches: &Matches, opt: &str) -> Option> { let i = value_to_parse.0; let option = value_to_parse.1; i.parse().map_err(|_e| { - PrError::EncounteredErrors(format!("invalid {} argument '{}'", option, i)) + PrError::EncounteredErrors(format!("invalid {} argument {}", option, i.quote())) }) }; matches @@ -619,7 +621,7 @@ fn build_options( let unparsed_num = i.get(1).unwrap().as_str().trim(); let x: Vec<_> = unparsed_num.split(':').collect(); x[0].to_string().parse::().map_err(|_e| { - PrError::EncounteredErrors(format!("invalid {} argument '{}'", "+", unparsed_num)) + PrError::EncounteredErrors(format!("invalid {} argument {}", "+", unparsed_num.quote())) }) }) { Some(res) => res?, @@ -633,7 +635,11 @@ fn build_options( .map(|unparsed_num| { let x: Vec<_> = unparsed_num.split(':').collect(); x[1].to_string().parse::().map_err(|_e| { - PrError::EncounteredErrors(format!("invalid {} argument '{}'", "+", unparsed_num)) + PrError::EncounteredErrors(format!( + "invalid {} argument {}", + "+", + unparsed_num.quote() + )) }) }) { Some(res) => Some(res?), @@ -643,7 +649,10 @@ fn build_options( let invalid_pages_map = |i: String| { let unparsed_value = matches.opt_str(options::PAGE_RANGE_OPTION).unwrap(); i.parse::().map_err(|_e| { - PrError::EncounteredErrors(format!("invalid --pages argument '{}'", unparsed_value)) + PrError::EncounteredErrors(format!( + "invalid --pages argument {}", + unparsed_value.quote() + )) }) }; @@ -741,7 +750,7 @@ fn build_options( let start_column_option = match re_col.captures(&free_args).map(|i| { let unparsed_num = i.get(1).unwrap().as_str().trim(); unparsed_num.parse::().map_err(|_e| { - PrError::EncounteredErrors(format!("invalid {} argument '{}'", "-", unparsed_num)) + PrError::EncounteredErrors(format!("invalid {} argument {}", "-", unparsed_num.quote())) }) }) { Some(res) => Some(res?), diff --git a/src/uu/printf/src/cli.rs b/src/uu/printf/src/cli.rs index a5e9c9775..fa2fda1d2 100644 --- a/src/uu/printf/src/cli.rs +++ b/src/uu/printf/src/cli.rs @@ -2,20 +2,11 @@ // spell-checker:ignore (ToDO) bslice -use std::env; -use std::io::{stderr, stdout, Write}; +use std::io::{stdout, Write}; pub const EXIT_OK: i32 = 0; pub const EXIT_ERR: i32 = 1; -pub fn err_msg(msg: &str) { - let exe_path = match env::current_exe() { - Ok(p) => p.to_string_lossy().into_owned(), - _ => String::from(""), - }; - writeln!(&mut stderr(), "{}: {}", exe_path, msg).unwrap(); -} - // by default stdout only flushes // to console when a newline is passed. pub fn flush_char(c: char) { diff --git a/src/uu/printf/src/memo.rs b/src/uu/printf/src/memo.rs index 2f12f9192..f5d41aeb6 100644 --- a/src/uu/printf/src/memo.rs +++ b/src/uu/printf/src/memo.rs @@ -8,8 +8,9 @@ use itertools::put_back_n; use std::iter::Peekable; use std::slice::Iter; +use uucore::display::Quotable; +use uucore::show_error; -use crate::cli; use crate::tokenize::sub::Sub; use crate::tokenize::token::{Token, Tokenizer}; use crate::tokenize::unescaped_text::UnescapedText; @@ -19,10 +20,10 @@ pub struct Memo { } fn warn_excess_args(first_arg: &str) { - cli::err_msg(&format!( - "warning: ignoring excess arguments, starting with '{}'", - first_arg - )); + show_error!( + "warning: ignoring excess arguments, starting with {}", + first_arg.quote() + ); } impl Memo { diff --git a/src/uu/printf/src/tokenize/num_format/formatter.rs b/src/uu/printf/src/tokenize/num_format/formatter.rs index f5f5d71b1..0438f78bf 100644 --- a/src/uu/printf/src/tokenize/num_format/formatter.rs +++ b/src/uu/printf/src/tokenize/num_format/formatter.rs @@ -3,11 +3,10 @@ use itertools::{put_back_n, PutBackN}; use std::str::Chars; +use uucore::{display::Quotable, show_error}; use super::format_field::FormatField; -use crate::cli; - // contains the rough ingredients to final // output for a number, organized together // to allow for easy generalization of output manipulation @@ -66,5 +65,5 @@ pub fn get_it_at(offset: usize, str_in: &str) -> PutBackN { // TODO: put this somewhere better pub fn warn_incomplete_conv(pf_arg: &str) { // important: keep println here not print - cli::err_msg(&format!("{}: value not completely converted", pf_arg)) + show_error!("{}: value not completely converted", pf_arg.maybe_quote()); } diff --git a/src/uu/printf/src/tokenize/num_format/num_format.rs b/src/uu/printf/src/tokenize/num_format/num_format.rs index b32731f2d..74666ad8e 100644 --- a/src/uu/printf/src/tokenize/num_format/num_format.rs +++ b/src/uu/printf/src/tokenize/num_format/num_format.rs @@ -7,6 +7,9 @@ use std::env; use std::vec::Vec; +use uucore::display::Quotable; +use uucore::show_error; + use super::format_field::{FieldType, FormatField}; use super::formatter::{Base, FormatPrimitive, Formatter, InitialPrefix}; use super::formatters::cninetyninehexfloatf::CninetyNineHexFloatf; @@ -15,11 +18,9 @@ use super::formatters::floatf::Floatf; use super::formatters::intf::Intf; use super::formatters::scif::Scif; -use crate::cli; - pub fn warn_expected_numeric(pf_arg: &str) { // important: keep println here not print - cli::err_msg(&format!("{}: expected a numeric value", pf_arg)); + show_error!("{}: expected a numeric value", pf_arg.maybe_quote()); } // when character constant arguments have excess characters @@ -29,11 +30,11 @@ fn warn_char_constant_ign(remaining_bytes: Vec) { Ok(_) => {} Err(e) => { if let env::VarError::NotPresent = e { - cli::err_msg(&format!( + show_error!( "warning: {:?}: character(s) following character \ constant have been ignored", &*remaining_bytes - )); + ); } } } diff --git a/src/uu/printf/src/tokenize/sub.rs b/src/uu/printf/src/tokenize/sub.rs index d01e00699..48d854fab 100644 --- a/src/uu/printf/src/tokenize/sub.rs +++ b/src/uu/printf/src/tokenize/sub.rs @@ -10,6 +10,7 @@ use std::iter::Peekable; use std::process::exit; use std::slice::Iter; use std::str::Chars; +use uucore::show_error; // use std::collections::HashSet; use super::num_format::format_field::{FieldType, FormatField}; @@ -19,7 +20,7 @@ use super::unescaped_text::UnescapedText; use crate::cli; fn err_conv(sofar: &str) { - cli::err_msg(&format!("%{}: invalid conversion specification", sofar)); + show_error!("%{}: invalid conversion specification", sofar); exit(cli::EXIT_ERR); } diff --git a/src/uu/ptx/src/ptx.rs b/src/uu/ptx/src/ptx.rs index 264a37d72..3619b8d4d 100644 --- a/src/uu/ptx/src/ptx.rs +++ b/src/uu/ptx/src/ptx.rs @@ -17,6 +17,7 @@ use std::collections::{BTreeSet, HashMap, HashSet}; use std::default::Default; use std::fs::File; use std::io::{stdin, stdout, BufRead, BufReader, BufWriter, Read, Write}; +use uucore::display::Quotable; use uucore::InvalidEncodingHandling; static NAME: &str = "ptx"; @@ -292,7 +293,11 @@ fn create_word_set(config: &Config, filter: &WordFilter, file_map: &FileMap) -> fn get_reference(config: &Config, word_ref: &WordRef, line: &str, context_reg: &Regex) -> String { if config.auto_ref { - format!("{}:{}", word_ref.filename, word_ref.local_line_nr + 1) + format!( + "{}:{}", + word_ref.filename.maybe_quote(), + word_ref.local_line_nr + 1 + ) } else if config.input_ref { let (beg, end) = match context_reg.find(line) { Some(x) => (x.start(), x.end()), diff --git a/src/uu/readlink/src/readlink.rs b/src/uu/readlink/src/readlink.rs index 1c62d8278..d6dd1634a 100644 --- a/src/uu/readlink/src/readlink.rs +++ b/src/uu/readlink/src/readlink.rs @@ -14,6 +14,7 @@ use clap::{crate_version, App, Arg}; use std::fs; use std::io::{stdout, Write}; use std::path::{Path, PathBuf}; +use uucore::display::Quotable; use uucore::fs::{canonicalize, MissingHandling, ResolveMode}; const ABOUT: &str = "Print value of a symbolic link or canonical file name."; @@ -71,10 +72,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } if no_newline && files.len() > 1 && !silent { - eprintln!( - "{}: ignoring --no-newline with multiple arguments", - uucore::util_name() - ); + show_error!("ignoring --no-newline with multiple arguments"); no_newline = false; } @@ -85,12 +83,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { Ok(path) => show(&path, no_newline, use_zero), Err(err) => { if verbose { - eprintln!( - "{}: {}: errno {}", - uucore::util_name(), - f, - err.raw_os_error().unwrap() - ); + show_error!("{}: errno {}", f.maybe_quote(), err.raw_os_error().unwrap()); } return 1; } @@ -100,12 +93,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { Ok(path) => show(&path, no_newline, use_zero), Err(err) => { if verbose { - eprintln!( - "{}: {}: errno {:?}", - uucore::util_name(), - f, - err.raw_os_error().unwrap() - ); + show_error!("{}: errno {}", f.maybe_quote(), err.raw_os_error().unwrap()); } return 1; } diff --git a/src/uu/realpath/src/realpath.rs b/src/uu/realpath/src/realpath.rs index 451253d50..d13aed6c7 100644 --- a/src/uu/realpath/src/realpath.rs +++ b/src/uu/realpath/src/realpath.rs @@ -11,8 +11,14 @@ extern crate uucore; use clap::{crate_version, App, Arg}; -use std::path::{Path, PathBuf}; -use uucore::fs::{canonicalize, MissingHandling, ResolveMode}; +use std::{ + io::{stdout, Write}, + path::{Path, PathBuf}, +}; +use uucore::{ + display::{print_verbatim, Quotable}, + fs::{canonicalize, MissingHandling, ResolveMode}, +}; static ABOUT: &str = "print the resolved path"; @@ -58,7 +64,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { for path in &paths { if let Err(e) = resolve_path(path, strip, zero, logical, can_mode) { if !quiet { - show_error!("{}: {}", e, path.display()); + show_error!("{}: {}", path.maybe_quote(), e); } retcode = 1 }; @@ -154,8 +160,9 @@ fn resolve_path( ResolveMode::Physical }; let abs = canonicalize(p, can_mode, resolve)?; - let line_ending = if zero { '\0' } else { '\n' }; + let line_ending = if zero { b'\0' } else { b'\n' }; - print!("{}{}", abs.display(), line_ending); + print_verbatim(&abs)?; + stdout().write_all(&[line_ending])?; Ok(()) } diff --git a/src/uu/relpath/src/relpath.rs b/src/uu/relpath/src/relpath.rs index 95f03e423..16b920861 100644 --- a/src/uu/relpath/src/relpath.rs +++ b/src/uu/relpath/src/relpath.rs @@ -10,6 +10,7 @@ use clap::{crate_version, App, Arg}; use std::env; use std::path::{Path, PathBuf}; +use uucore::display::println_verbatim; use uucore::fs::{canonicalize, MissingHandling, ResolveMode}; use uucore::InvalidEncodingHandling; @@ -48,7 +49,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { if !absto.as_path().starts_with(absbase.as_path()) || !absfrom.as_path().starts_with(absbase.as_path()) { - println!("{}", absto.display()); + println_verbatim(absto).unwrap(); return 0; } } @@ -74,7 +75,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .map(|x| result.push(x.as_os_str())) .last(); - println!("{}", result.display()); + println_verbatim(result).unwrap(); 0 } diff --git a/src/uu/rm/src/rm.rs b/src/uu/rm/src/rm.rs index 0613ff857..54fce52ff 100644 --- a/src/uu/rm/src/rm.rs +++ b/src/uu/rm/src/rm.rs @@ -17,6 +17,7 @@ use std::fs; use std::io::{stderr, stdin, BufRead, Write}; use std::ops::BitOr; use std::path::{Path, PathBuf}; +use uucore::display::Quotable; use walkdir::{DirEntry, WalkDir}; #[derive(Eq, PartialEq, Clone, Copy)] @@ -236,7 +237,10 @@ fn remove(files: Vec, options: Options) -> bool { // (e.g., permission), even rm -f should fail with // outputting the error, but there's no easy eay. if !options.force { - show_error!("cannot remove '{}': No such file or directory", filename); + show_error!( + "cannot remove {}: No such file or directory", + filename.quote() + ); true } else { false @@ -263,13 +267,9 @@ fn handle_dir(path: &Path, options: &Options) -> bool { // GNU compatibility (rm/fail-eacces.sh) // here, GNU doesn't use some kind of remove_dir_all // It will show directory+file - show_error!( - "cannot remove '{}': {}", - path.display(), - "Permission denied" - ); + show_error!("cannot remove {}: {}", path.quote(), "Permission denied"); } else { - show_error!("cannot remove '{}': {}", path.display(), e); + show_error!("cannot remove {}: {}", path.quote(), e); } } } else { @@ -287,7 +287,7 @@ fn handle_dir(path: &Path, options: &Options) -> bool { } Err(e) => { had_err = true; - show_error!("recursing in '{}': {}", path.display(), e); + show_error!("recursing in {}: {}", path.quote(), e); } } } @@ -299,12 +299,12 @@ fn handle_dir(path: &Path, options: &Options) -> bool { } else if options.dir && (!is_root || !options.preserve_root) { had_err = remove_dir(path, options).bitor(had_err); } else if options.recursive { - show_error!("could not remove directory '{}'", path.display()); + show_error!("could not remove directory {}", path.quote()); had_err = true; } else { show_error!( - "cannot remove '{}': Is a directory", // GNU's rm error message does not include help - path.display() + "cannot remove {}: Is a directory", // GNU's rm error message does not include help + path.quote() ); had_err = true; } @@ -325,36 +325,36 @@ fn remove_dir(path: &Path, options: &Options) -> bool { match fs::remove_dir(path) { Ok(_) => { if options.verbose { - println!("removed directory '{}'", normalize(path).display()); + println!("removed directory {}", normalize(path).quote()); } } Err(e) => { if e.kind() == std::io::ErrorKind::PermissionDenied { // GNU compatibility (rm/fail-eacces.sh) show_error!( - "cannot remove '{}': {}", - path.display(), + "cannot remove {}: {}", + path.quote(), "Permission denied" ); } else { - show_error!("cannot remove '{}': {}", path.display(), e); + show_error!("cannot remove {}: {}", path.quote(), e); } return true; } } } else { // directory can be read but is not empty - show_error!("cannot remove '{}': Directory not empty", path.display()); + show_error!("cannot remove {}: Directory not empty", path.quote()); return true; } } else { // called to remove a symlink_dir (windows) without "-r"/"-R" or "-d" - show_error!("cannot remove '{}': Is a directory", path.display()); + show_error!("cannot remove {}: Is a directory", path.quote()); return true; } } else { // GNU's rm shows this message if directory is empty but not readable - show_error!("cannot remove '{}': Directory not empty", path.display()); + show_error!("cannot remove {}: Directory not empty", path.quote()); return true; } } @@ -372,19 +372,15 @@ fn remove_file(path: &Path, options: &Options) -> bool { match fs::remove_file(path) { Ok(_) => { if options.verbose { - println!("removed '{}'", normalize(path).display()); + println!("removed {}", normalize(path).quote()); } } Err(e) => { if e.kind() == std::io::ErrorKind::PermissionDenied { // GNU compatibility (rm/fail-eacces.sh) - show_error!( - "cannot remove '{}': {}", - path.display(), - "Permission denied" - ); + show_error!("cannot remove {}: {}", path.quote(), "Permission denied"); } else { - show_error!("cannot remove '{}': {}", path.display(), e); + show_error!("cannot remove {}: {}", path.quote(), e); } return true; } @@ -396,9 +392,9 @@ fn remove_file(path: &Path, options: &Options) -> bool { fn prompt_file(path: &Path, is_dir: bool) -> bool { if is_dir { - prompt(&(format!("rm: remove directory '{}'? ", path.display()))) + prompt(&(format!("rm: remove directory {}? ", path.quote()))) } else { - prompt(&(format!("rm: remove file '{}'? ", path.display()))) + prompt(&(format!("rm: remove file {}? ", path.quote()))) } } diff --git a/src/uu/runcon/src/errors.rs b/src/uu/runcon/src/errors.rs index bc10a2f3e..5f8258de0 100644 --- a/src/uu/runcon/src/errors.rs +++ b/src/uu/runcon/src/errors.rs @@ -3,6 +3,8 @@ use std::fmt::Write; use std::io; use std::str::Utf8Error; +use uucore::display::Quotable; + pub(crate) type Result = std::result::Result; #[derive(thiserror::Error, Debug)] @@ -31,7 +33,7 @@ pub(crate) enum Error { source: io::Error, }, - #[error("{operation} failed on '{}'", .operand1.to_string_lossy())] + #[error("{operation} failed on {}", .operand1.quote())] Io1 { operation: &'static str, operand1: OsString, diff --git a/src/uu/seq/src/seq.rs b/src/uu/seq/src/seq.rs index 730c5efc7..501b12ac0 100644 --- a/src/uu/seq/src/seq.rs +++ b/src/uu/seq/src/seq.rs @@ -14,6 +14,7 @@ use num_traits::{Num, ToPrimitive}; use std::cmp; use std::io::{stdout, Write}; use std::str::FromStr; +use uucore::display::Quotable; static ABOUT: &str = "Display numbers from FIRST to LAST, in steps of INCREMENT."; static OPT_SEPARATOR: &str = "separator"; @@ -115,14 +116,14 @@ impl FromStr for Number { } Err(_) => match s.parse::() { Ok(value) if value.is_nan() => Err(format!( - "invalid 'not-a-number' argument: '{}'\nTry '{} --help' for more information.", - s, + "invalid 'not-a-number' argument: {}\nTry '{} --help' for more information.", + s.quote(), uucore::execution_phrase(), )), Ok(value) => Ok(Number::F64(value)), Err(_) => Err(format!( - "invalid floating point argument: '{}'\nTry '{} --help' for more information.", - s, + "invalid floating point argument: {}\nTry '{} --help' for more information.", + s.quote(), uucore::execution_phrase(), )), }, diff --git a/src/uu/shred/src/shred.rs b/src/uu/shred/src/shred.rs index 0464c5d50..fa455f027 100644 --- a/src/uu/shred/src/shred.rs +++ b/src/uu/shred/src/shred.rs @@ -18,12 +18,12 @@ use std::io; use std::io::prelude::*; use std::io::SeekFrom; use std::path::{Path, PathBuf}; -use uucore::InvalidEncodingHandling; +use uucore::display::Quotable; +use uucore::{util_name, InvalidEncodingHandling}; #[macro_use] extern crate uucore; -static NAME: &str = "shred"; const BLOCK_SIZE: usize = 512; const NAME_CHARSET: &[u8] = b"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_."; @@ -281,7 +281,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { if !matches.is_present(options::FILE) { show_error!("Missing an argument"); - show_error!("For help, try '{} --help'", NAME); + show_error!("For help, try '{} --help'", uucore::execution_phrase()); return 0; } @@ -289,7 +289,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { Some(s) => match s.parse::() { Ok(u) => u, Err(_) => { - errs.push(format!("invalid number of passes: '{}'", s)); + errs.push(format!("invalid number of passes: {}", s.quote())); 0 } }, @@ -414,7 +414,11 @@ fn get_size(size_str_opt: Option) -> Option { let coefficient = match size_str.parse::() { Ok(u) => u, Err(_) => { - println!("{}: {}: Invalid file size", NAME, size_str_opt.unwrap()); + println!( + "{}: {}: Invalid file size", + util_name(), + size_str_opt.unwrap().maybe_quote() + ); exit!(1); } }; @@ -452,11 +456,11 @@ fn wipe_file( // Get these potential errors out of the way first let path: &Path = Path::new(path_str); if !path.exists() { - show_error!("{}: No such file or directory", path.display()); + show_error!("{}: No such file or directory", path.maybe_quote()); return; } if !path.is_file() { - show_error!("{}: Not a file", path.display()); + show_error!("{}: Not a file", path.maybe_quote()); return; } @@ -520,7 +524,7 @@ fn wipe_file( let mut file: File = match OpenOptions::new().write(true).truncate(false).open(path) { Ok(f) => f, Err(e) => { - show_error!("{}: failed to open for writing: {}", path.display(), e); + show_error!("{}: failed to open for writing: {}", path.maybe_quote(), e); return; } }; @@ -535,8 +539,8 @@ fn wipe_file( if total_passes.to_string().len() == 1 { println!( "{}: {}: pass {}/{} ({})... ", - NAME, - path.display(), + util_name(), + path.maybe_quote(), i + 1, total_passes, pass_name @@ -544,8 +548,8 @@ fn wipe_file( } else { println!( "{}: {}: pass {:2.0}/{:2.0} ({})... ", - NAME, - path.display(), + util_name(), + path.maybe_quote(), i + 1, total_passes, pass_name @@ -556,7 +560,7 @@ fn wipe_file( match do_pass(&mut file, path, &mut generator, *pass_type, size) { Ok(_) => {} Err(e) => { - show_error!("{}: File write pass failed: {}", path.display(), e); + show_error!("{}: File write pass failed: {}", path.maybe_quote(), e); } } // Ignore failed writes; just keep trying @@ -567,7 +571,7 @@ fn wipe_file( match do_remove(path, path_str, verbose) { Ok(_) => {} Err(e) => { - show_error!("{}: failed to remove file: {}", path.display(), e); + show_error!("{}: failed to remove file: {}", path.maybe_quote(), e); } } } @@ -622,9 +626,9 @@ fn wipe_name(orig_path: &Path, verbose: bool) -> Option { if verbose { println!( "{}: {}: renamed to {}", - NAME, - last_path.display(), - new_path.display() + util_name(), + last_path.maybe_quote(), + new_path.quote() ); } @@ -641,9 +645,9 @@ fn wipe_name(orig_path: &Path, verbose: bool) -> Option { Err(e) => { println!( "{}: {}: Couldn't rename to {}: {}", - NAME, - last_path.display(), - new_path.display(), + util_name(), + last_path.maybe_quote(), + new_path.quote(), e ); return None; @@ -657,7 +661,7 @@ fn wipe_name(orig_path: &Path, verbose: bool) -> Option { fn do_remove(path: &Path, orig_filename: &str, verbose: bool) -> Result<(), io::Error> { if verbose { - println!("{}: {}: removing", NAME, orig_filename); + println!("{}: {}: removing", util_name(), orig_filename.maybe_quote()); } let renamed_path: Option = wipe_name(path, verbose); @@ -666,7 +670,7 @@ fn do_remove(path: &Path, orig_filename: &str, verbose: bool) -> Result<(), io:: } if verbose { - println!("{}: {}: removed", NAME, orig_filename); + println!("{}: {}: removed", util_name(), orig_filename.maybe_quote()); } Ok(()) diff --git a/src/uu/shuf/src/shuf.rs b/src/uu/shuf/src/shuf.rs index 7125014b1..9a899d746 100644 --- a/src/uu/shuf/src/shuf.rs +++ b/src/uu/shuf/src/shuf.rs @@ -14,6 +14,7 @@ use clap::{crate_version, App, Arg}; use rand::Rng; use std::fs::File; use std::io::{stdin, stdout, BufReader, BufWriter, Read, Write}; +use uucore::display::Quotable; use uucore::InvalidEncodingHandling; enum Mode { @@ -76,7 +77,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { Some(count) => match count.parse::() { Ok(val) => val, Err(_) => { - show_error!("invalid line count: '{}'", count); + show_error!("invalid line count: {}", count.quote()); return 1; } }, @@ -185,13 +186,13 @@ fn read_input_file(filename: &str) -> Vec { } else { match File::open(filename) { Ok(f) => Box::new(f) as Box, - Err(e) => crash!(1, "failed to open '{}': {}", filename, e), + Err(e) => crash!(1, "failed to open {}: {}", filename.quote(), e), } }); let mut data = Vec::new(); if let Err(e) = file.read_to_end(&mut data) { - crash!(1, "failed reading '{}': {}", filename, e) + crash!(1, "failed reading {}: {}", filename.quote(), e) }; data @@ -235,7 +236,7 @@ fn shuf_bytes(input: &mut Vec<&[u8]>, opts: Options) { None => Box::new(stdout()) as Box, Some(s) => match File::create(&s[..]) { Ok(f) => Box::new(f) as Box, - Err(e) => crash!(1, "failed to open '{}' for writing: {}", &s[..], e), + Err(e) => crash!(1, "failed to open {} for writing: {}", s.quote(), e), }, }); @@ -243,7 +244,7 @@ fn shuf_bytes(input: &mut Vec<&[u8]>, opts: Options) { Some(r) => WrappedRng::RngFile(rand::rngs::adapter::ReadRng::new( match File::open(&r[..]) { Ok(f) => f, - Err(e) => crash!(1, "failed to open random source '{}': {}", &r[..], e), + Err(e) => crash!(1, "failed to open random source {}: {}", r.quote(), e), }, )), None => WrappedRng::RngDefault(rand::thread_rng()), @@ -288,14 +289,14 @@ fn shuf_bytes(input: &mut Vec<&[u8]>, opts: Options) { fn parse_range(input_range: &str) -> Result<(usize, usize), String> { let split: Vec<&str> = input_range.split('-').collect(); if split.len() != 2 { - Err(format!("invalid input range: '{}'", input_range)) + Err(format!("invalid input range: {}", input_range.quote())) } else { let begin = split[0] .parse::() - .map_err(|_| format!("invalid input range: '{}'", split[0]))?; + .map_err(|_| format!("invalid input range: {}", split[0].quote()))?; let end = split[1] .parse::() - .map_err(|_| format!("invalid input range: '{}'", split[1]))?; + .map_err(|_| format!("invalid input range: {}", split[1].quote()))?; Ok((begin, end + 1)) } } diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index abba1e63b..bab6dd770 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -45,6 +45,7 @@ use std::path::Path; use std::path::PathBuf; use std::str::Utf8Error; use unicode_width::UnicodeWidthStr; +use uucore::display::Quotable; use uucore::error::{set_exit_code, strip_errno, UError, UResult, USimpleError, UUsageError}; use uucore::parse_size::{parse_size, ParseSizeError}; use uucore::version_cmp::version_cmp; @@ -139,7 +140,7 @@ enum SortError { error: std::io::Error, }, ReadFailed { - path: String, + path: PathBuf, error: std::io::Error, }, ParseKeyError { @@ -189,7 +190,7 @@ impl Display for SortError { write!( f, "{}:{}: disorder: {}", - file.to_string_lossy(), + file.maybe_quote(), line_number, line ) @@ -198,13 +199,23 @@ impl Display for SortError { } } SortError::OpenFailed { path, error } => { - write!(f, "open failed: {}: {}", path, strip_errno(error)) + write!( + f, + "open failed: {}: {}", + path.maybe_quote(), + strip_errno(error) + ) } SortError::ParseKeyError { key, msg } => { - write!(f, "failed to parse key `{}`: {}", key, msg) + write!(f, "failed to parse key {}: {}", key.quote(), msg) } SortError::ReadFailed { path, error } => { - write!(f, "cannot read: {}: {}", path, strip_errno(error)) + write!( + f, + "cannot read: {}: {}", + path.maybe_quote(), + strip_errno(error) + ) } SortError::OpenTmpFileFailed { error } => { write!(f, "failed to open temporary file: {}", strip_errno(error)) @@ -213,7 +224,7 @@ impl Display for SortError { write!(f, "couldn't execute compress program: errno {}", code) } SortError::CompressProgTerminatedAbnormally { prog } => { - write!(f, "'{}' terminated abnormally", prog) + write!(f, "{} terminated abnormally", prog.quote()) } SortError::TmpDirCreationFailed => write!(f, "could not create temporary directory"), SortError::Uft8Error { error } => write!(f, "{}", error), @@ -1179,7 +1190,10 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { if let Some(n_merge) = matches.value_of(options::BATCH_SIZE) { settings.merge_batch_size = n_merge.parse().map_err(|_| { - UUsageError::new(2, format!("invalid --batch-size argument '{}'", n_merge)) + UUsageError::new( + 2, + format!("invalid --batch-size argument {}", n_merge.quote()), + ) })?; } @@ -1211,23 +1225,30 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } else if settings.check && files.len() != 1 { return Err(UUsageError::new( 2, - format!( - "extra operand `{}' not allowed with -c", - files[1].to_string_lossy() - ), + format!("extra operand {} not allowed with -c", files[1].quote()), )); } if let Some(arg) = matches.args.get(options::SEPARATOR) { - let separator = arg.vals[0].to_string_lossy(); - let mut separator = separator.as_ref(); + let mut separator = arg.vals[0].to_str().ok_or_else(|| { + UUsageError::new( + 2, + format!("separator is not valid unicode: {}", arg.vals[0].quote()), + ) + })?; if separator == "\\0" { separator = "\0"; } + // This rejects non-ASCII codepoints, but perhaps we don't have to. + // On the other hand GNU accepts any single byte, valid unicode or not. + // (Supporting multi-byte chars would require changes in tokenize_with_separator().) if separator.len() != 1 { return Err(UUsageError::new( 2, - "separator must be exactly one character long", + format!( + "separator must be exactly one character long: {}", + separator.quote() + ), )); } settings.separator = Some(separator.chars().next().unwrap()) @@ -1816,7 +1837,7 @@ fn open(path: impl AsRef) -> UResult> { match File::open(path) { Ok(f) => Ok(Box::new(f) as Box), Err(error) => Err(SortError::ReadFailed { - path: path.to_string_lossy().to_string(), + path: path.to_owned(), error, } .into()), @@ -1828,8 +1849,8 @@ fn format_error_message(error: ParseSizeError, s: &str, option: &str) -> String // GNU's sort echos affected flag, -S or --buffer-size, depending user's selection // GNU's sort does distinguish between "invalid (suffix in) argument" match error { - ParseSizeError::ParseFailure(_) => format!("invalid --{} argument '{}'", option, s), - ParseSizeError::SizeTooBig(_) => format!("--{} argument '{}' too large", option, s), + ParseSizeError::ParseFailure(_) => format!("invalid --{} argument {}", option, s.quote()), + ParseSizeError::SizeTooBig(_) => format!("--{} argument {} too large", option, s.quote()), } } diff --git a/src/uu/split/src/split.rs b/src/uu/split/src/split.rs index e405c757a..581b632d2 100644 --- a/src/uu/split/src/split.rs +++ b/src/uu/split/src/split.rs @@ -19,6 +19,7 @@ use std::fs::File; use std::io::{stdin, BufRead, BufReader, BufWriter, Read, Write}; use std::path::Path; use std::{char, fs::remove_file}; +use uucore::display::Quotable; use uucore::parse_size::parse_size; static OPT_BYTES: &str = "bytes"; @@ -238,7 +239,11 @@ impl LineSplitter { fn new(settings: &Settings) -> LineSplitter { LineSplitter { lines_per_split: settings.strategy_param.parse().unwrap_or_else(|_| { - crash!(1, "invalid number of lines: '{}'", settings.strategy_param) + crash!( + 1, + "invalid number of lines: {}", + settings.strategy_param.quote() + ) }), } } @@ -373,8 +378,8 @@ fn split(settings: &Settings) -> i32 { let r = File::open(Path::new(&settings.input)).unwrap_or_else(|_| { crash!( 1, - "cannot open '{}' for reading: No such file or directory", - settings.input + "cannot open {} for reading: No such file or directory", + settings.input.quote() ) }); Box::new(r) as Box @@ -383,7 +388,7 @@ fn split(settings: &Settings) -> i32 { let mut splitter: Box = match settings.strategy.as_str() { s if s == OPT_LINES => Box::new(LineSplitter::new(settings)), s if (s == OPT_BYTES || s == OPT_LINE_BYTES) => Box::new(ByteSplitter::new(settings)), - a => crash!(1, "strategy {} not supported", a), + a => crash!(1, "strategy {} not supported", a.quote()), }; let mut fileno = 0; diff --git a/src/uu/stat/src/stat.rs b/src/uu/stat/src/stat.rs index d017999b4..fd4a6443d 100644 --- a/src/uu/stat/src/stat.rs +++ b/src/uu/stat/src/stat.rs @@ -7,6 +7,7 @@ #[macro_use] extern crate uucore; +use uucore::display::Quotable; use uucore::entries; use uucore::fs::display_permissions; use uucore::fsext::{ @@ -24,7 +25,7 @@ use std::{cmp, fs, iter}; macro_rules! check_bound { ($str: ident, $bound:expr, $beg: expr, $end: expr) => { if $end >= $bound { - return Err(format!("'{}': invalid directive", &$str[$beg..$end])); + return Err(format!("{}: invalid directive", $str[$beg..$end].quote())); } }; } @@ -652,11 +653,7 @@ impl Stater { return 1; } }; - arg = format!( - "`{}' -> `{}'", - file, - dst.to_string_lossy() - ); + arg = format!("{} -> {}", file.quote(), dst.quote()); } else { arg = file.to_string(); } @@ -750,7 +747,7 @@ impl Stater { } } Err(e) => { - show_error!("cannot stat '{}': {}", file, e); + show_error!("cannot stat {}: {}", file.quote(), e); return 1; } } @@ -843,7 +840,11 @@ impl Stater { } } Err(e) => { - show_error!("cannot read file system information for '{}': {}", file, e); + show_error!( + "cannot read file system information for {}: {}", + file.quote(), + e + ); return 1; } } diff --git a/src/uu/sum/src/sum.rs b/src/uu/sum/src/sum.rs index f1147ca2e..15c20f09d 100644 --- a/src/uu/sum/src/sum.rs +++ b/src/uu/sum/src/sum.rs @@ -14,6 +14,7 @@ use clap::{crate_version, App, Arg}; use std::fs::File; use std::io::{stdin, Read, Result}; use std::path::Path; +use uucore::display::Quotable; use uucore::InvalidEncodingHandling; static NAME: &str = "sum"; @@ -118,7 +119,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let reader = match open(file) { Ok(f) => f, Err(error) => { - show_error!("'{}' {}", file, error); + show_error!("{}: {}", file.maybe_quote(), error); exit_code = 2; continue; } diff --git a/src/uu/sync/src/sync.rs b/src/uu/sync/src/sync.rs index f49f51728..d6a21f280 100644 --- a/src/uu/sync/src/sync.rs +++ b/src/uu/sync/src/sync.rs @@ -14,6 +14,7 @@ extern crate uucore; use clap::{crate_version, App, Arg}; use std::path::Path; +use uucore::display::Quotable; static EXIT_ERR: i32 = 1; @@ -175,7 +176,11 @@ pub fn uumain(args: impl uucore::Args) -> i32 { for f in &files { if !Path::new(&f).exists() { - crash!(EXIT_ERR, "cannot stat '{}': No such file or directory", f); + crash!( + EXIT_ERR, + "cannot stat {}: No such file or directory", + f.quote() + ); } } diff --git a/src/uu/tac/src/tac.rs b/src/uu/tac/src/tac.rs index 0568f1601..e54697f2b 100644 --- a/src/uu/tac/src/tac.rs +++ b/src/uu/tac/src/tac.rs @@ -14,6 +14,7 @@ use clap::{crate_version, App, Arg}; use memchr::memmem; use std::io::{stdin, stdout, BufReader, Read, Write}; use std::{fs::File, path::Path}; +use uucore::display::Quotable; use uucore::InvalidEncodingHandling; static NAME: &str = "tac"; @@ -141,11 +142,11 @@ fn tac(filenames: Vec, before: bool, _: bool, separator: &str) -> i32 { let path = Path::new(filename); if path.is_dir() || path.metadata().is_err() { if path.is_dir() { - show_error!("{}: read error: Invalid argument", filename); + show_error!("{}: read error: Invalid argument", filename.maybe_quote()); } else { show_error!( - "failed to open '{}' for reading: No such file or directory", - filename + "failed to open {} for reading: No such file or directory", + filename.quote() ); } exit_code = 1; @@ -154,7 +155,7 @@ fn tac(filenames: Vec, before: bool, _: bool, separator: &str) -> i32 { match File::open(path) { Ok(f) => Box::new(f) as Box, Err(e) => { - show_error!("failed to open '{}' for reading: {}", filename, e); + show_error!("failed to open {} for reading: {}", filename.quote(), e); exit_code = 1; continue; } @@ -163,7 +164,7 @@ fn tac(filenames: Vec, before: bool, _: bool, separator: &str) -> i32 { let mut data = Vec::new(); if let Err(e) = file.read_to_end(&mut data) { - show_error!("failed to read '{}': {}", filename, e); + show_error!("failed to read {}: {}", filename.quote(), e); exit_code = 1; continue; }; diff --git a/src/uu/tee/src/tee.rs b/src/uu/tee/src/tee.rs index d98835265..d2fb015bf 100644 --- a/src/uu/tee/src/tee.rs +++ b/src/uu/tee/src/tee.rs @@ -12,7 +12,8 @@ use clap::{crate_version, App, Arg}; use retain_mut::RetainMut; use std::fs::OpenOptions; use std::io::{copy, sink, stdin, stdout, Error, ErrorKind, Read, Result, Write}; -use std::path::{Path, PathBuf}; +use std::path::PathBuf; +use uucore::display::Quotable; #[cfg(unix)] use uucore::libc; @@ -167,7 +168,7 @@ impl Write for MultiWriter { let result = writer.write_all(buf); match result { Err(f) => { - show_error!("{}: {}", writer.name, f.to_string()); + show_error!("{}: {}", writer.name.maybe_quote(), f); false } _ => true, @@ -181,7 +182,7 @@ impl Write for MultiWriter { let result = writer.flush(); match result { Err(f) => { - show_error!("{}: {}", writer.name, f.to_string()); + show_error!("{}: {}", writer.name.maybe_quote(), f); false } _ => true, @@ -214,7 +215,7 @@ impl Read for NamedReader { fn read(&mut self, buf: &mut [u8]) -> Result { match self.inner.read(buf) { Err(f) => { - show_error!("{}: {}", Path::new("stdin").display(), f.to_string()); + show_error!("stdin: {}", f); Err(f) } okay => okay, diff --git a/src/uu/test/src/parser.rs b/src/uu/test/src/parser.rs index 7b7d77469..0d68cea39 100644 --- a/src/uu/test/src/parser.rs +++ b/src/uu/test/src/parser.rs @@ -10,6 +10,8 @@ use std::ffi::OsString; use std::iter::Peekable; +use uucore::display::Quotable; + /// Represents one of the binary comparison operators for strings, integers, or files #[derive(Debug, PartialEq)] pub enum Operator { @@ -43,19 +45,22 @@ impl Symbol { /// Returns Symbol::None in place of None fn new(token: Option) -> Symbol { match token { - Some(s) => match s.to_string_lossy().as_ref() { - "(" => Symbol::LParen, - "!" => Symbol::Bang, - "-a" | "-o" => Symbol::BoolOp(s), - "=" | "==" | "!=" => Symbol::Op(Operator::String(s)), - "-eq" | "-ge" | "-gt" | "-le" | "-lt" | "-ne" => Symbol::Op(Operator::Int(s)), - "-ef" | "-nt" | "-ot" => Symbol::Op(Operator::File(s)), - "-n" | "-z" => Symbol::UnaryOp(UnaryOperator::StrlenOp(s)), - "-b" | "-c" | "-d" | "-e" | "-f" | "-g" | "-G" | "-h" | "-k" | "-L" | "-O" - | "-p" | "-r" | "-s" | "-S" | "-t" | "-u" | "-w" | "-x" => { - Symbol::UnaryOp(UnaryOperator::FiletestOp(s)) - } - _ => Symbol::Literal(s), + Some(s) => match s.to_str() { + Some(t) => match t { + "(" => Symbol::LParen, + "!" => Symbol::Bang, + "-a" | "-o" => Symbol::BoolOp(s), + "=" | "==" | "!=" => Symbol::Op(Operator::String(s)), + "-eq" | "-ge" | "-gt" | "-le" | "-lt" | "-ne" => Symbol::Op(Operator::Int(s)), + "-ef" | "-nt" | "-ot" => Symbol::Op(Operator::File(s)), + "-n" | "-z" => Symbol::UnaryOp(UnaryOperator::StrlenOp(s)), + "-b" | "-c" | "-d" | "-e" | "-f" | "-g" | "-G" | "-h" | "-k" | "-L" | "-O" + | "-p" | "-r" | "-s" | "-S" | "-t" | "-u" | "-w" | "-x" => { + Symbol::UnaryOp(UnaryOperator::FiletestOp(s)) + } + _ => Symbol::Literal(s), + }, + None => Symbol::Literal(s), }, None => Symbol::None, } @@ -391,7 +396,7 @@ impl Parser { self.expr(); match self.tokens.next() { - Some(token) => Err(format!("extra argument '{}'", token.to_string_lossy())), + Some(token) => Err(format!("extra argument {}", token.quote())), None => Ok(()), } } diff --git a/src/uu/test/src/test.rs b/src/uu/test/src/test.rs index 50563ba49..5ce798bfa 100644 --- a/src/uu/test/src/test.rs +++ b/src/uu/test/src/test.rs @@ -13,7 +13,7 @@ mod parser; use clap::{crate_version, App, AppSettings}; use parser::{parse, Operator, Symbol, UnaryOperator}; use std::ffi::{OsStr, OsString}; -use std::path::Path; +use uucore::{display::Quotable, show_error}; const USAGE: &str = "test EXPRESSION or: test @@ -93,10 +93,7 @@ pub fn uu_app() -> App<'static, 'static> { pub fn uumain(mut args: impl uucore::Args) -> i32 { let program = args.next().unwrap_or_else(|| OsString::from("test")); - let binary_name = Path::new(&program) - .file_name() - .unwrap_or_else(|| OsStr::new("test")) - .to_string_lossy(); + let binary_name = uucore::util_name(); let mut args: Vec<_> = args.collect(); if binary_name.ends_with('[') { @@ -116,8 +113,8 @@ pub fn uumain(mut args: impl uucore::Args) -> i32 { } // If invoked via name '[', matching ']' must be in the last arg let last = args.pop(); - if last != Some(OsString::from("]")) { - eprintln!("[: missing ']'"); + if last.as_deref() != Some(OsStr::new("]")) { + show_error!("missing ']'"); return 2; } } @@ -133,7 +130,7 @@ pub fn uumain(mut args: impl uucore::Args) -> i32 { } } Err(e) => { - eprintln!("test: {}", e); + show_error!("{}", e); 2 } } @@ -190,11 +187,11 @@ fn eval(stack: &mut Vec) -> Result { }) } Some(Symbol::UnaryOp(UnaryOperator::FiletestOp(op))) => { - let op = op.to_string_lossy(); + let op = op.to_str().unwrap(); let f = pop_literal!(); - Ok(match op.as_ref() { + Ok(match op { "-b" => path(&f, PathCondition::BlockSpecial), "-c" => path(&f, PathCondition::CharacterSpecial), "-d" => path(&f, PathCondition::Directory), @@ -231,31 +228,33 @@ fn eval(stack: &mut Vec) -> Result { } fn integers(a: &OsStr, b: &OsStr, op: &OsStr) -> Result { - let format_err = |value| format!("invalid integer '{}'", value); + let format_err = |value: &OsStr| format!("invalid integer {}", value.quote()); - let a = a.to_string_lossy(); - let a: i64 = a.parse().map_err(|_| format_err(a))?; + let a: i64 = a + .to_str() + .and_then(|s| s.parse().ok()) + .ok_or_else(|| format_err(a))?; - let b = b.to_string_lossy(); - let b: i64 = b.parse().map_err(|_| format_err(b))?; + let b: i64 = b + .to_str() + .and_then(|s| s.parse().ok()) + .ok_or_else(|| format_err(b))?; - let operator = op.to_string_lossy(); - Ok(match operator.as_ref() { - "-eq" => a == b, - "-ne" => a != b, - "-gt" => a > b, - "-ge" => a >= b, - "-lt" => a < b, - "-le" => a <= b, - _ => return Err(format!("unknown operator '{}'", operator)), + Ok(match op.to_str() { + Some("-eq") => a == b, + Some("-ne") => a != b, + Some("-gt") => a > b, + Some("-ge") => a >= b, + Some("-lt") => a < b, + Some("-le") => a <= b, + _ => return Err(format!("unknown operator {}", op.quote())), }) } fn isatty(fd: &OsStr) -> Result { - let fd = fd.to_string_lossy(); - - fd.parse() - .map_err(|_| format!("invalid integer '{}'", fd)) + fd.to_str() + .and_then(|s| s.parse().ok()) + .ok_or_else(|| format!("invalid integer {}", fd.quote())) .map(|i| { #[cfg(not(target_os = "redox"))] unsafe { diff --git a/src/uu/timeout/src/timeout.rs b/src/uu/timeout/src/timeout.rs index 21b0a0c37..f686dde3b 100644 --- a/src/uu/timeout/src/timeout.rs +++ b/src/uu/timeout/src/timeout.rs @@ -16,6 +16,7 @@ use clap::{crate_version, App, AppSettings, Arg}; use std::io::ErrorKind; use std::process::{Command, Stdio}; use std::time::Duration; +use uucore::display::Quotable; use uucore::process::ChildExt; use uucore::signals::{signal_by_name_or_value, signal_name_by_value}; use uucore::InvalidEncodingHandling; @@ -61,7 +62,7 @@ impl Config { let signal_result = signal_by_name_or_value(signal_); match signal_result { None => { - unreachable!("invalid signal '{}'", signal_); + unreachable!("invalid signal {}", signal_.quote()); } Some(signal_value) => signal_value, } @@ -216,9 +217,9 @@ fn timeout( Ok(None) => { if verbose { show_error!( - "sending signal {} to command '{}'", + "sending signal {} to command {}", signal_name_by_value(signal).unwrap(), - cmd[0] + cmd[0].quote() ); } crash_if_err!(ERR_EXIT_STATUS, process.send_signal(signal)); @@ -233,7 +234,7 @@ fn timeout( } Ok(None) => { if verbose { - show_error!("sending signal KILL to command '{}'", cmd[0]); + show_error!("sending signal KILL to command {}", cmd[0].quote()); } crash_if_err!( ERR_EXIT_STATUS, diff --git a/src/uu/touch/src/touch.rs b/src/uu/touch/src/touch.rs index 7c0903665..8dd7bf7d2 100644 --- a/src/uu/touch/src/touch.rs +++ b/src/uu/touch/src/touch.rs @@ -17,6 +17,7 @@ use clap::{crate_version, App, Arg, ArgGroup}; use filetime::*; use std::fs::{self, File}; use std::path::Path; +use uucore::display::Quotable; use uucore::error::{FromIo, UError, UResult, USimpleError}; static ABOUT: &str = "Update the access and modification times of each FILE to the current time."; @@ -82,7 +83,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } if let Err(e) = File::create(path) { - show!(e.map_err_context(|| format!("cannot touch '{}'", path.display()))); + show!(e.map_err_context(|| format!("cannot touch {}", path.quote()))); continue; }; @@ -122,7 +123,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } else { filetime::set_file_times(path, atime, mtime) } - .map_err_context(|| format!("setting times of '{}'", path.display()))?; + .map_err_context(|| format!("setting times of {}", path.quote()))?; } Ok(()) @@ -209,7 +210,7 @@ fn stat(path: &Path, follow: bool) -> UResult<(FileTime, FileTime)> { } else { fs::metadata(path) } - .map_err_context(|| format!("failed to get attributes of '{}'", path.display()))?; + .map_err_context(|| format!("failed to get attributes of {}", path.quote()))?; Ok(( FileTime::from_last_access_time(&metadata), @@ -249,11 +250,16 @@ fn parse_timestamp(s: &str) -> UResult { 10 => ("%y%m%d%H%M", s.to_owned()), 11 => ("%Y%m%d%H%M.%S", format!("{}{}", now.tm_year + 1900, s)), 8 => ("%Y%m%d%H%M", format!("{}{}", now.tm_year + 1900, s)), - _ => return Err(USimpleError::new(1, format!("invalid date format '{}'", s))), + _ => { + return Err(USimpleError::new( + 1, + format!("invalid date format {}", s.quote()), + )) + } }; let tm = time::strptime(&ts, format) - .map_err(|_| USimpleError::new(1, format!("invalid date format '{}'", s)))?; + .map_err(|_| USimpleError::new(1, format!("invalid date format {}", s.quote())))?; let mut local = to_local(tm); local.tm_isdst = -1; @@ -269,7 +275,10 @@ fn parse_timestamp(s: &str) -> UResult { }; let tm2 = time::at(ts); if tm.tm_hour != tm2.tm_hour { - return Err(USimpleError::new(1, format!("invalid date format '{}'", s))); + return Err(USimpleError::new( + 1, + format!("invalid date format {}", s.quote()), + )); } Ok(ft) diff --git a/src/uu/tr/src/tr.rs b/src/uu/tr/src/tr.rs index d46318e38..fbc4bab9b 100644 --- a/src/uu/tr/src/tr.rs +++ b/src/uu/tr/src/tr.rs @@ -21,7 +21,7 @@ use fnv::FnvHashMap; use std::io::{stdin, stdout, BufRead, BufWriter, Write}; use crate::expand::ExpandSet; -use uucore::InvalidEncodingHandling; +use uucore::{display::Quotable, InvalidEncodingHandling}; static ABOUT: &str = "translate or delete characters"; @@ -271,8 +271,8 @@ pub fn uumain(args: impl uucore::Args) -> i32 { if !(delete_flag || squeeze_flag) && sets.len() < 2 { show_error!( - "missing operand after '{}'\nTry '{} --help' for more information.", - sets[0], + "missing operand after {}\nTry '{} --help' for more information.", + sets[0].quote(), uucore::execution_phrase() ); return 1; diff --git a/src/uu/truncate/src/truncate.rs b/src/uu/truncate/src/truncate.rs index 062ef3811..6fb1f06f6 100644 --- a/src/uu/truncate/src/truncate.rs +++ b/src/uu/truncate/src/truncate.rs @@ -15,6 +15,7 @@ use std::convert::TryFrom; use std::fs::{metadata, OpenOptions}; use std::io::ErrorKind; use std::path::Path; +use uucore::display::Quotable; use uucore::parse_size::{parse_size, ParseSizeError}; #[derive(Debug, Eq, PartialEq)] @@ -120,8 +121,8 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let reference = matches.value_of(options::REFERENCE).map(String::from); crash!( 1, - "cannot stat '{}': No such file or directory", - reference.unwrap_or_else(|| "".to_string()) + "cannot stat {}: No such file or directory", + reference.as_deref().unwrap_or("").quote() ); // TODO: fix '--no-create' see test_reference and test_truncate_bytes_size } _ => crash!(1, "{}", e.to_string()), diff --git a/src/uu/tsort/src/tsort.rs b/src/uu/tsort/src/tsort.rs index c0ef66598..11798db13 100644 --- a/src/uu/tsort/src/tsort.rs +++ b/src/uu/tsort/src/tsort.rs @@ -14,6 +14,7 @@ use std::collections::{HashMap, HashSet}; use std::fs::File; use std::io::{stdin, BufRead, BufReader, Read}; use std::path::Path; +use uucore::display::Quotable; use uucore::InvalidEncodingHandling; static SUMMARY: &str = "Topological sort the strings in FILE. @@ -45,7 +46,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { file_buf = match File::open(Path::new(&input)) { Ok(a) => a, _ => { - show_error!("{}: No such file or directory", input); + show_error!("{}: No such file or directory", input.maybe_quote()); return 1; } }; @@ -68,7 +69,11 @@ pub fn uumain(args: impl uucore::Args) -> i32 { for ab in tokens.chunks(2) { match ab.len() { 2 => g.add_edge(&ab[0], &ab[1]), - _ => crash!(1, "{}: input contains an odd number of tokens", input), + _ => crash!( + 1, + "{}: input contains an odd number of tokens", + input.maybe_quote() + ), } } } diff --git a/src/uu/unexpand/src/unexpand.rs b/src/uu/unexpand/src/unexpand.rs index 7fb9b2590..95383b89d 100644 --- a/src/uu/unexpand/src/unexpand.rs +++ b/src/uu/unexpand/src/unexpand.rs @@ -16,6 +16,7 @@ use std::fs::File; use std::io::{stdin, stdout, BufRead, BufReader, BufWriter, Read, Stdout, Write}; use std::str::from_utf8; use unicode_width::UnicodeWidthChar; +use uucore::display::Quotable; use uucore::InvalidEncodingHandling; static NAME: &str = "unexpand"; @@ -141,9 +142,9 @@ fn open(path: String) -> BufReader> { if path == "-" { BufReader::new(Box::new(stdin()) as Box) } else { - file_buf = match File::open(&path[..]) { + file_buf = match File::open(&path) { Ok(a) => a, - Err(e) => crash!(1, "{}: {}", &path[..], e), + Err(e) => crash!(1, "{}: {}", path.maybe_quote(), e), }; BufReader::new(Box::new(file_buf) as Box) } diff --git a/src/uu/uniq/src/uniq.rs b/src/uu/uniq/src/uniq.rs index 5c3fa3b1e..f84bfc26d 100644 --- a/src/uu/uniq/src/uniq.rs +++ b/src/uu/uniq/src/uniq.rs @@ -14,6 +14,7 @@ use std::io::{stdin, stdout, BufRead, BufReader, BufWriter, Read, Result, Write} use std::path::Path; use std::str::FromStr; use strum_macros::{AsRefStr, EnumString}; +use uucore::display::Quotable; static ABOUT: &str = "Report or omit repeated lines."; pub mod options { @@ -217,7 +218,14 @@ fn get_line_string(io_line: Result>) -> String { fn opt_parsed(opt_name: &str, matches: &ArgMatches) -> Option { matches.value_of(opt_name).map(|arg_str| { let opt_val: Option = arg_str.parse().ok(); - opt_val.unwrap_or_else(|| crash!(1, "Invalid argument for {}: {}", opt_name, arg_str)) + opt_val.unwrap_or_else(|| { + crash!( + 1, + "Invalid argument for {}: {}", + opt_name, + arg_str.maybe_quote() + ) + }) }) } diff --git a/src/uu/unlink/src/unlink.rs b/src/uu/unlink/src/unlink.rs index c7fc00639..7c66708e0 100644 --- a/src/uu/unlink/src/unlink.rs +++ b/src/uu/unlink/src/unlink.rs @@ -17,6 +17,7 @@ use libc::{lstat, stat, unlink}; use libc::{S_IFLNK, S_IFMT, S_IFREG}; use std::ffi::CString; use std::io::{Error, ErrorKind}; +use uucore::display::Quotable; use uucore::InvalidEncodingHandling; static ABOUT: &str = "Unlink the file at [FILE]."; @@ -63,7 +64,12 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let result = unsafe { lstat(c_string.as_ptr(), &mut buf as *mut stat) }; if result < 0 { - crash!(1, "Cannot stat '{}': {}", paths[0], Error::last_os_error()); + crash!( + 1, + "Cannot stat {}: {}", + paths[0].quote(), + Error::last_os_error() + ); } buf.st_mode & S_IFMT diff --git a/src/uucore/src/lib/features/perms.rs b/src/uucore/src/lib/features/perms.rs index 8a2f20417..16ee01b88 100644 --- a/src/uucore/src/lib/features/perms.rs +++ b/src/uucore/src/lib/features/perms.rs @@ -5,6 +5,7 @@ //! Common functions to manage permissions +use crate::display::Quotable; use crate::error::strip_errno; use crate::error::UResult; use crate::error::USimpleError; @@ -80,29 +81,29 @@ pub fn wrap_chown>( VerbosityLevel::Silent => (), level => { out = format!( - "changing {} of '{}': {}", + "changing {} of {}: {}", if verbosity.groups_only { "group" } else { "ownership" }, - path.display(), + path.quote(), e ); if level == VerbosityLevel::Verbose { out = if verbosity.groups_only { format!( - "{}\nfailed to change group of '{}' from {} to {}", + "{}\nfailed to change group of {} from {} to {}", out, - path.display(), + path.quote(), entries::gid2grp(meta.gid()).unwrap(), entries::gid2grp(dest_gid).unwrap() ) } else { format!( - "{}\nfailed to change ownership of '{}' from {}:{} to {}:{}", + "{}\nfailed to change ownership of {} from {}:{} to {}:{}", out, - path.display(), + path.quote(), entries::uid2usr(meta.uid()).unwrap(), entries::gid2grp(meta.gid()).unwrap(), entries::uid2usr(dest_uid).unwrap(), @@ -120,15 +121,15 @@ pub fn wrap_chown>( VerbosityLevel::Changes | VerbosityLevel::Verbose => { out = if verbosity.groups_only { format!( - "changed group of '{}' from {} to {}", - path.display(), + "changed group of {} from {} to {}", + path.quote(), entries::gid2grp(meta.gid()).unwrap(), entries::gid2grp(dest_gid).unwrap() ) } else { format!( - "changed ownership of '{}' from {}:{} to {}:{}", - path.display(), + "changed ownership of {} from {}:{} to {}:{}", + path.quote(), entries::uid2usr(meta.uid()).unwrap(), entries::gid2grp(meta.gid()).unwrap(), entries::uid2usr(dest_uid).unwrap(), @@ -141,14 +142,14 @@ pub fn wrap_chown>( } else if verbosity.level == VerbosityLevel::Verbose { out = if verbosity.groups_only { format!( - "group of '{}' retained as {}", - path.display(), + "group of {} retained as {}", + path.quote(), entries::gid2grp(dest_gid).unwrap_or_default() ) } else { format!( - "ownership of '{}' retained as {}:{}", - path.display(), + "ownership of {} retained as {}:{}", + path.quote(), entries::uid2usr(dest_uid).unwrap(), entries::gid2grp(dest_gid).unwrap() ) @@ -358,9 +359,9 @@ impl ChownExecutor { match self.verbosity.level { VerbosityLevel::Silent => (), _ => show_error!( - "cannot {} '{}': {}", + "cannot {} {}: {}", if follow { "dereference" } else { "access" }, - path.display(), + path.quote(), strip_errno(&e) ), } diff --git a/src/uucore/src/lib/lib.rs b/src/uucore/src/lib/lib.rs index 129ff9106..925246c24 100644 --- a/src/uucore/src/lib/lib.rs +++ b/src/uucore/src/lib/lib.rs @@ -72,6 +72,8 @@ use std::sync::atomic::Ordering; use once_cell::sync::Lazy; +use crate::display::Quotable; + pub fn get_utility_is_second_arg() -> bool { crate::macros::UTILITY_IS_SECOND_ARG.load(Ordering::SeqCst) } @@ -171,14 +173,15 @@ pub trait Args: Iterator + Sized { Ok(string) => Ok(string), Err(s_ret) => { full_conversion = false; - let lossy_conversion = s_ret.to_string_lossy(); eprintln!( - "Input with broken encoding occurred! (s = '{}') ", - &lossy_conversion + "Input with broken encoding occurred! (s = {}) ", + s_ret.quote() ); match handling { InvalidEncodingHandling::Ignore => Err(String::new()), - InvalidEncodingHandling::ConvertLossy => Err(lossy_conversion.to_string()), + InvalidEncodingHandling::ConvertLossy => { + Err(s_ret.to_string_lossy().into_owned()) + } InvalidEncodingHandling::Panic => { panic!("Broken encoding found but caller cannot handle it") } diff --git a/src/uucore/src/lib/mods/backup_control.rs b/src/uucore/src/lib/mods/backup_control.rs index 83617f8ed..acb7342b7 100644 --- a/src/uucore/src/lib/mods/backup_control.rs +++ b/src/uucore/src/lib/mods/backup_control.rs @@ -78,7 +78,10 @@ // spell-checker:ignore backupopt -use crate::error::{UError, UResult}; +use crate::{ + display::Quotable, + error::{UError, UResult}, +}; use clap::ArgMatches; use std::{ env, @@ -167,18 +170,22 @@ impl Display for BackupError { match self { BE::InvalidArgument(arg, origin) => write!( f, - "invalid argument '{}' for '{}'\n{}", - arg, origin, VALID_ARGS_HELP + "invalid argument {} for '{}'\n{}", + arg.quote(), + origin, + VALID_ARGS_HELP ), BE::AmbiguousArgument(arg, origin) => write!( f, - "ambiguous argument '{}' for '{}'\n{}", - arg, origin, VALID_ARGS_HELP + "ambiguous argument {} for '{}'\n{}", + arg.quote(), + origin, + VALID_ARGS_HELP ), BE::BackupImpossible() => write!(f, "cannot create backup"), // Placeholder for later // BE::BackupFailed(from, to, e) => Display::fmt( - // &uio_error!(e, "failed to backup '{}' to '{}'", from.display(), to.display()), + // &uio_error!(e, "failed to backup {} to {}", from.quote(), to.quote()), // f // ), } diff --git a/src/uucore/src/lib/mods/display.rs b/src/uucore/src/lib/mods/display.rs index e647a8c71..dfe64184f 100644 --- a/src/uucore/src/lib/mods/display.rs +++ b/src/uucore/src/lib/mods/display.rs @@ -87,13 +87,16 @@ macro_rules! impl_as_ref { }; } +impl_as_ref!(str); impl_as_ref!(&'_ str); impl_as_ref!(String); +impl_as_ref!(std::path::Path); impl_as_ref!(&'_ std::path::Path); impl_as_ref!(std::path::PathBuf); impl_as_ref!(std::path::Component<'_>); impl_as_ref!(std::path::Components<'_>); impl_as_ref!(std::path::Iter<'_>); +impl_as_ref!(std::ffi::OsStr); impl_as_ref!(&'_ std::ffi::OsStr); impl_as_ref!(std::ffi::OsString); @@ -106,6 +109,13 @@ impl Quotable for Cow<'_, str> { } } +impl Quotable for Cow<'_, std::path::Path> { + fn quote(&self) -> Quoted<'_> { + let text: &std::path::Path = self.as_ref(); + Quoted::new(text.as_ref()) + } +} + /// A wrapper around [`OsStr`] for printing paths with quoting and escaping applied. #[derive(Debug, Copy, Clone)] pub struct Quoted<'a> { @@ -407,6 +417,19 @@ pub fn println_verbatim>(text: S) -> io::Result<()> { Ok(()) } +/// Like `println_verbatim`, without the trailing newline. +pub fn print_verbatim>(text: S) -> io::Result<()> { + let mut stdout = io::stdout(); + #[cfg(any(unix, target_os = "wasi"))] + { + stdout.write_all(text.as_ref().as_bytes()) + } + #[cfg(not(any(unix, target_os = "wasi")))] + { + write!(stdout, "{}", std::path::Path::new(text.as_ref()).display()) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/uucore/src/lib/mods/error.rs b/src/uucore/src/lib/mods/error.rs index 04019e234..11ec91bdf 100644 --- a/src/uucore/src/lib/mods/error.rs +++ b/src/uucore/src/lib/mods/error.rs @@ -99,7 +99,10 @@ pub type UResult = Result>; /// An example of a custom error from `ls`: /// /// ``` -/// use uucore::error::{UError, UResult}; +/// use uucore::{ +/// display::Quotable, +/// error::{UError, UResult} +/// }; /// use std::{ /// error::Error, /// fmt::{Display, Debug}, @@ -126,8 +129,8 @@ pub type UResult = Result>; /// impl Display for LsError { /// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { /// match self { -/// LsError::InvalidLineWidth(s) => write!(f, "invalid line width: '{}'", s), -/// LsError::NoMetadata(p) => write!(f, "could not open file: '{}'", p.display()), +/// LsError::InvalidLineWidth(s) => write!(f, "invalid line width: {}", s.quote()), +/// LsError::NoMetadata(p) => write!(f, "could not open file: {}", p.quote()), /// } /// } /// } @@ -158,7 +161,10 @@ pub trait UError: Error + Send { /// # Example /// /// ``` - /// use uucore::error::{UError}; + /// use uucore::{ + /// display::Quotable, + /// error::UError + /// }; /// use std::{ /// error::Error, /// fmt::{Display, Debug}, @@ -189,8 +195,8 @@ pub trait UError: Error + Send { /// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { /// use MyError as ME; /// match self { - /// ME::Foo(s) => write!(f, "Unknown Foo: '{}'", s), - /// ME::Bar(p) => write!(f, "Couldn't find Bar: '{}'", p.display()), + /// ME::Foo(s) => write!(f, "Unknown Foo: {}", s.quote()), + /// ME::Bar(p) => write!(f, "Couldn't find Bar: {}", p.quote()), /// ME::Bing() => write!(f, "Exterminate!"), /// } /// } @@ -209,7 +215,10 @@ pub trait UError: Error + Send { /// # Example /// /// ``` - /// use uucore::error::{UError}; + /// use uucore::{ + /// display::Quotable, + /// error::UError + /// }; /// use std::{ /// error::Error, /// fmt::{Display, Debug}, @@ -240,8 +249,8 @@ pub trait UError: Error + Send { /// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { /// use MyError as ME; /// match self { - /// ME::Foo(s) => write!(f, "Unknown Foo: '{}'", s), - /// ME::Bar(p) => write!(f, "Couldn't find Bar: '{}'", p.display()), + /// ME::Foo(s) => write!(f, "Unknown Foo: {}", s.quote()), + /// ME::Bar(p) => write!(f, "Couldn't find Bar: {}", p.quote()), /// ME::Bing() => write!(f, "Exterminate!"), /// } /// } @@ -342,7 +351,10 @@ impl UError for UUsageError { /// There are two ways to construct this type: with [`UIoError::new`] or by calling the /// [`FromIo::map_err_context`] method on a [`std::io::Result`] or [`std::io::Error`]. /// ``` -/// use uucore::error::{FromIo, UResult, UIoError, UError}; +/// use uucore::{ +/// display::Quotable, +/// error::{FromIo, UResult, UIoError, UError} +/// }; /// use std::fs::File; /// use std::path::Path; /// let path = Path::new("test.txt"); @@ -350,12 +362,12 @@ impl UError for UUsageError { /// // Manual construction /// let e: Box = UIoError::new( /// std::io::ErrorKind::NotFound, -/// format!("cannot access '{}'", path.display()) +/// format!("cannot access {}", path.quote()) /// ); /// let res: UResult<()> = Err(e.into()); /// /// // Converting from an `std::io::Error`. -/// let res: UResult = File::open(path).map_err_context(|| format!("cannot access '{}'", path.display())); +/// let res: UResult = File::open(path).map_err_context(|| format!("cannot access {}", path.quote())); /// ``` #[derive(Debug)] pub struct UIoError { diff --git a/src/uucore/src/lib/mods/ranges.rs b/src/uucore/src/lib/mods/ranges.rs index 9e1e67d5a..f142e14fb 100644 --- a/src/uucore/src/lib/mods/ranges.rs +++ b/src/uucore/src/lib/mods/ranges.rs @@ -9,6 +9,8 @@ use std::str::FromStr; +use crate::display::Quotable; + #[derive(PartialEq, Eq, PartialOrd, Ord, Debug)] pub struct Range { pub low: usize, @@ -86,7 +88,7 @@ impl Range { for item in list.split(',') { let range_item = FromStr::from_str(item) - .map_err(|e| format!("range '{}' was invalid: {}", item, e))?; + .map_err(|e| format!("range {} was invalid: {}", item.quote(), e))?; ranges.push(range_item); } diff --git a/src/uucore/src/lib/parser/parse_size.rs b/src/uucore/src/lib/parser/parse_size.rs index ec0b08c9e..c05c0d3f1 100644 --- a/src/uucore/src/lib/parser/parse_size.rs +++ b/src/uucore/src/lib/parser/parse_size.rs @@ -9,6 +9,8 @@ use std::convert::TryFrom; use std::error::Error; use std::fmt; +use crate::display::Quotable; + /// Parse a size string into a number of bytes. /// /// A size string comprises an integer and an optional unit. The unit @@ -107,6 +109,9 @@ impl fmt::Display for ParseSizeError { } } +// FIXME: It's more idiomatic to move the formatting into the Display impl, +// but there's a lot of downstream code that constructs these errors manually +// that would be affected impl ParseSizeError { fn parse_failure(s: &str) -> ParseSizeError { // stderr on linux (GNU coreutils 8.32) (LC_ALL=C) @@ -140,7 +145,7 @@ impl ParseSizeError { // --width // --strings // etc. - ParseSizeError::ParseFailure(format!("'{}'", s)) + ParseSizeError::ParseFailure(format!("{}", s.quote())) } fn size_too_big(s: &str) -> ParseSizeError { @@ -160,7 +165,10 @@ impl ParseSizeError { // stderr on macos (brew - GNU coreutils 8.32) also differs for the same version, e.g.: // ghead: invalid number of bytes: '1Y': Value too large to be stored in data type // gtail: invalid number of bytes: '1Y': Value too large to be stored in data type - ParseSizeError::SizeTooBig(format!("'{}': Value too large for defined data type", s)) + ParseSizeError::SizeTooBig(format!( + "{}: Value too large for defined data type", + s.quote() + )) } } @@ -262,7 +270,7 @@ mod tests { for &test_string in &test_strings { assert_eq!( parse_size(test_string).unwrap_err(), - ParseSizeError::ParseFailure(format!("'{}'", test_string)) + ParseSizeError::ParseFailure(format!("{}", test_string.quote())) ); } } diff --git a/src/uucore/src/lib/parser/parse_time.rs b/src/uucore/src/lib/parser/parse_time.rs index fdf43b727..68f0ca8d0 100644 --- a/src/uucore/src/lib/parser/parse_time.rs +++ b/src/uucore/src/lib/parser/parse_time.rs @@ -9,6 +9,8 @@ use std::time::Duration; +use crate::display::Quotable; + pub fn from_str(string: &str) -> Result { let len = string.len(); if len == 0 { @@ -25,13 +27,13 @@ pub fn from_str(string: &str) -> Result { if string == "inf" || string == "infinity" { ("inf", 1) } else { - return Err(format!("invalid time interval '{}'", string)); + return Err(format!("invalid time interval {}", string.quote())); } } }; let num = numstr .parse::() - .map_err(|e| format!("invalid time interval '{}': {}", string, e))?; + .map_err(|e| format!("invalid time interval {}: {}", string.quote(), e))?; const NANOS_PER_SEC: u32 = 1_000_000_000; let whole_secs = num.trunc(); diff --git a/tests/by-util/test_chroot.rs b/tests/by-util/test_chroot.rs index 3bac07d44..65d821d01 100644 --- a/tests/by-util/test_chroot.rs +++ b/tests/by-util/test_chroot.rs @@ -23,7 +23,7 @@ fn test_enter_chroot_fails() { assert!(result .stderr_str() - .starts_with("chroot: cannot chroot to jail: Operation not permitted (os error 1)")); + .starts_with("chroot: cannot chroot to 'jail': Operation not permitted (os error 1)")); } #[test] @@ -34,7 +34,7 @@ fn test_no_such_directory() { ucmd.arg("a") .fails() - .stderr_is("chroot: cannot change root directory to `a`: no such directory"); + .stderr_is("chroot: cannot change root directory to 'a': no such directory"); } #[test] diff --git a/tests/by-util/test_cksum.rs b/tests/by-util/test_cksum.rs index 9590c1ac5..bf31ceb18 100644 --- a/tests/by-util/test_cksum.rs +++ b/tests/by-util/test_cksum.rs @@ -68,7 +68,7 @@ fn test_invalid_file() { .arg(folder_name) .fails() .no_stdout() - .stderr_contains("cksum: 'asdf' No such file or directory"); + .stderr_contains("cksum: asdf: No such file or directory"); // Then check when the file is of an invalid type at.mkdir(folder_name); @@ -76,7 +76,7 @@ fn test_invalid_file() { .arg(folder_name) .fails() .no_stdout() - .stderr_contains("cksum: 'asdf' Is a directory"); + .stderr_contains("cksum: asdf: Is a directory"); } // Make sure crc is correct for files larger than 32 bytes diff --git a/tests/by-util/test_sort.rs b/tests/by-util/test_sort.rs index cfe96d3c5..b389c9011 100644 --- a/tests/by-util/test_sort.rs +++ b/tests/by-util/test_sort.rs @@ -531,7 +531,7 @@ fn test_keys_invalid_field() { new_ucmd!() .args(&["-k", "1."]) .fails() - .stderr_only("sort: failed to parse key `1.`: failed to parse character index ``: cannot parse integer from empty string"); + .stderr_only("sort: failed to parse key '1.': failed to parse character index ``: cannot parse integer from empty string"); } #[test] @@ -539,7 +539,7 @@ fn test_keys_invalid_field_option() { new_ucmd!() .args(&["-k", "1.1x"]) .fails() - .stderr_only("sort: failed to parse key `1.1x`: invalid option: `x`"); + .stderr_only("sort: failed to parse key '1.1x': invalid option: `x`"); } #[test] @@ -547,7 +547,7 @@ fn test_keys_invalid_field_zero() { new_ucmd!() .args(&["-k", "0.1"]) .fails() - .stderr_only("sort: failed to parse key `0.1`: field index can not be 0"); + .stderr_only("sort: failed to parse key '0.1': field index can not be 0"); } #[test] @@ -555,7 +555,7 @@ fn test_keys_invalid_char_zero() { new_ucmd!() .args(&["-k", "1.0"]) .fails() - .stderr_only("sort: failed to parse key `1.0`: invalid character index 0 for the start position of a field"); + .stderr_only("sort: failed to parse key '1.0': invalid character index 0 for the start position of a field"); } #[test] diff --git a/tests/by-util/test_sum.rs b/tests/by-util/test_sum.rs index f09ba9d00..0248c05cf 100644 --- a/tests/by-util/test_sum.rs +++ b/tests/by-util/test_sum.rs @@ -59,7 +59,7 @@ fn test_invalid_file() { at.mkdir("a"); - ucmd.arg("a").fails().stderr_is("sum: 'a' Is a directory"); + ucmd.arg("a").fails().stderr_is("sum: a: Is a directory"); } #[test] @@ -68,5 +68,5 @@ fn test_invalid_metadata() { ucmd.arg("b") .fails() - .stderr_is("sum: 'b' No such file or directory"); + .stderr_is("sum: b: No such file or directory"); } diff --git a/tests/by-util/test_test.rs b/tests/by-util/test_test.rs index 23facd610..db74265a4 100644 --- a/tests/by-util/test_test.rs +++ b/tests/by-util/test_test.rs @@ -320,7 +320,7 @@ fn test_invalid_utf8_integer_compare() { cmd.run() .status_code(2) - .stderr_is("test: invalid integer 'fo�o'"); + .stderr_is("test: invalid integer $'fo\\x80o'"); let mut cmd = new_ucmd!(); cmd.raw.arg(arg); @@ -328,7 +328,7 @@ fn test_invalid_utf8_integer_compare() { cmd.run() .status_code(2) - .stderr_is("test: invalid integer 'fo�o'"); + .stderr_is("test: invalid integer $'fo\\x80o'"); } #[test] From 741f065a12ffe10014bd489ab41a8394c5faaa0b Mon Sep 17 00:00:00 2001 From: Jan Verbeek Date: Tue, 7 Sep 2021 19:37:03 +0200 Subject: [PATCH 154/206] test_ls: Fix clippy warnings --- tests/by-util/test_ls.rs | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/tests/by-util/test_ls.rs b/tests/by-util/test_ls.rs index 3d6092416..fe737512c 100644 --- a/tests/by-util/test_ls.rs +++ b/tests/by-util/test_ls.rs @@ -432,10 +432,7 @@ fn test_ls_long_symlink_color() { let mut result_lines = result .stdout_str() .lines() - .filter_map(|line| match line.starts_with("lrwx") { - true => Some(line), - false => None, - }) + .filter(|line| line.starts_with("lrwx")) .enumerate(); // For each enumerated line, we assert that the output of ls matches the expected output. @@ -455,14 +452,12 @@ fn test_ls_long_symlink_color() { // We look up the Colors that are expected in `colors` using the ColorReferences // stored in `expected_output`. - let expected_name_color = match expected_output[i].0 { - Some(color_reference) => Some(colors[color_reference[0]][color_reference[1]].as_str()), - None => None, - }; - let expected_target_color = match expected_output[i].2 { - Some(color_reference) => Some(colors[color_reference[0]][color_reference[1]].as_str()), - None => None, - }; + let expected_name_color = expected_output[i] + .0 + .map(|color_reference| colors[color_reference[0]][color_reference[1]].as_str()); + let expected_target_color = expected_output[i] + .2 + .map(|color_reference| colors[color_reference[0]][color_reference[1]].as_str()); // This is the important part. The asserts inside assert_names_and_colors_are_equal // will panic if the colors or names do not match the expected colors or names. @@ -470,11 +465,11 @@ fn test_ls_long_symlink_color() { // don't expect any color here, as in `expected_output[2], or don't know what specific // color to expect yet, as in expected_output[0:1]. assert_names_and_colors_are_equal( - &matched_name_color, + matched_name_color, expected_name_color, &matched_name, expected_output[i].1, - &matched_target_color, + matched_target_color, expected_target_color, &matched_target, expected_output[i].3, @@ -505,6 +500,7 @@ fn test_ls_long_symlink_color() { } } + #[allow(clippy::too_many_arguments)] fn assert_names_and_colors_are_equal( name_color: &str, expected_name_color: Option<&str>, @@ -530,7 +526,7 @@ fn test_ls_long_symlink_color() { fn capture_colored_string(input: &str) -> (Color, Name) { let colored_name = Regex::new(r"\x1b\[([0-9;]+)m(.+)\x1b\[0m").unwrap(); - match colored_name.captures(&input) { + match colored_name.captures(input) { Some(captures) => ( captures.get(1).unwrap().as_str().to_string(), captures.get(2).unwrap().as_str().to_string(), From 6d346b2307c2a1b337bf3b9741a0a93320a7b618 Mon Sep 17 00:00:00 2001 From: Jan Verbeek Date: Tue, 7 Sep 2021 19:48:06 +0200 Subject: [PATCH 155/206] Replace manual formatting by show_error!()/show_warning!() --- src/uu/date/src/date.rs | 15 ++++++++------- src/uu/env/src/env.rs | 3 ++- src/uu/od/src/multifilereader.rs | 4 ++-- src/uu/printf/src/memo.rs | 6 +++--- .../printf/src/tokenize/num_format/num_format.rs | 6 +++--- src/uu/test/src/parser.rs | 3 ++- 6 files changed, 20 insertions(+), 17 deletions(-) diff --git a/src/uu/date/src/date.rs b/src/uu/date/src/date.rs index b2ccf2e9f..adcf77024 100644 --- a/src/uu/date/src/date.rs +++ b/src/uu/date/src/date.rs @@ -18,6 +18,7 @@ use std::fs::File; use std::io::{BufRead, BufReader}; use std::path::PathBuf; use uucore::display::Quotable; +use uucore::show_error; #[cfg(windows)] use winapi::{ shared::minwindef::WORD, @@ -146,7 +147,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let format = if let Some(form) = matches.value_of(OPT_FORMAT) { if !form.starts_with('+') { - eprintln!("date: invalid date {}", form.quote()); + show_error!("invalid date {}", form.quote()); return 1; } let form = form[1..].to_string(); @@ -175,7 +176,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { let set_to = match matches.value_of(OPT_SET).map(parse_date) { None => None, Some(Err((input, _err))) => { - eprintln!("date: invalid date {}", input.quote()); + show_error!("invalid date {}", input.quote()); return 1; } Some(Ok(date)) => Some(date), @@ -241,7 +242,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { println!("{}", formatted); } Err((input, _err)) => { - println!("date: invalid date {}", input.quote()); + show_error!("invalid date {}", input.quote()); } } } @@ -353,13 +354,13 @@ fn set_system_datetime(_date: DateTime) -> i32 { #[cfg(target_os = "macos")] fn set_system_datetime(_date: DateTime) -> i32 { - eprintln!("date: setting the date is not supported by macOS"); + show_error!("setting the date is not supported by macOS"); 1 } #[cfg(target_os = "redox")] fn set_system_datetime(_date: DateTime) -> i32 { - eprintln!("date: setting the date is not supported by Redox"); + show_error!("setting the date is not supported by Redox"); 1 } @@ -379,7 +380,7 @@ fn set_system_datetime(date: DateTime) -> i32 { if result != 0 { let error = std::io::Error::last_os_error(); - eprintln!("date: cannot set date: {}", error); + show_error!("cannot set date: {}", error); error.raw_os_error().unwrap() } else { 0 @@ -409,7 +410,7 @@ fn set_system_datetime(date: DateTime) -> i32 { if result == 0 { let error = std::io::Error::last_os_error(); - eprintln!("date: cannot set date: {}", error); + show_error!("cannot set date: {}", error); error.raw_os_error().unwrap() } else { 0 diff --git a/src/uu/env/src/env.rs b/src/uu/env/src/env.rs index af889d093..aec97fc24 100644 --- a/src/uu/env/src/env.rs +++ b/src/uu/env/src/env.rs @@ -22,6 +22,7 @@ use std::env; use std::io::{self, Write}; use std::iter::Iterator; use std::process::Command; +use uucore::display::Quotable; use uucore::error::{UResult, USimpleError}; const USAGE: &str = "env [OPTION]... [-] [NAME=VALUE]... [COMMAND [ARG]...]"; @@ -93,7 +94,7 @@ fn load_config_file(opts: &mut Options) -> UResult<()> { }; let conf = conf.map_err(|error| { - eprintln!("env: error: \"{}\": {}", file, error); + show_error!("{}: {}", file.maybe_quote(), error); 1 })?; diff --git a/src/uu/od/src/multifilereader.rs b/src/uu/od/src/multifilereader.rs index 90796b2eb..1b3aba03d 100644 --- a/src/uu/od/src/multifilereader.rs +++ b/src/uu/od/src/multifilereader.rs @@ -59,7 +59,7 @@ impl<'b> MultifileReader<'b> { // print an error at the time that the file is needed, // then move on the the next file. // This matches the behavior of the original `od` - eprintln!("{}: {}: {}", uucore::util_name(), fname.maybe_quote(), e); + show_error!("{}: {}", fname.maybe_quote(), e); self.any_err = true } } @@ -92,7 +92,7 @@ impl<'b> io::Read for MultifileReader<'b> { Ok(0) => break, Ok(n) => n, Err(e) => { - eprintln!("{}: I/O: {}", uucore::util_name(), e); + show_error!("I/O: {}", e); self.any_err = true; break; } diff --git a/src/uu/printf/src/memo.rs b/src/uu/printf/src/memo.rs index f5d41aeb6..a87d4fa89 100644 --- a/src/uu/printf/src/memo.rs +++ b/src/uu/printf/src/memo.rs @@ -9,7 +9,7 @@ use itertools::put_back_n; use std::iter::Peekable; use std::slice::Iter; use uucore::display::Quotable; -use uucore::show_error; +use uucore::show_warning; use crate::tokenize::sub::Sub; use crate::tokenize::token::{Token, Tokenizer}; @@ -20,8 +20,8 @@ pub struct Memo { } fn warn_excess_args(first_arg: &str) { - show_error!( - "warning: ignoring excess arguments, starting with {}", + show_warning!( + "ignoring excess arguments, starting with {}", first_arg.quote() ); } diff --git a/src/uu/printf/src/tokenize/num_format/num_format.rs b/src/uu/printf/src/tokenize/num_format/num_format.rs index 74666ad8e..a9fee58e1 100644 --- a/src/uu/printf/src/tokenize/num_format/num_format.rs +++ b/src/uu/printf/src/tokenize/num_format/num_format.rs @@ -8,7 +8,7 @@ use std::env; use std::vec::Vec; use uucore::display::Quotable; -use uucore::show_error; +use uucore::{show_error, show_warning}; use super::format_field::{FieldType, FormatField}; use super::formatter::{Base, FormatPrimitive, Formatter, InitialPrefix}; @@ -30,8 +30,8 @@ fn warn_char_constant_ign(remaining_bytes: Vec) { Ok(_) => {} Err(e) => { if let env::VarError::NotPresent = e { - show_error!( - "warning: {:?}: character(s) following character \ + show_warning!( + "{:?}: character(s) following character \ constant have been ignored", &*remaining_bytes ); diff --git a/src/uu/test/src/parser.rs b/src/uu/test/src/parser.rs index 0d68cea39..ce4c0dec0 100644 --- a/src/uu/test/src/parser.rs +++ b/src/uu/test/src/parser.rs @@ -11,6 +11,7 @@ use std::ffi::OsString; use std::iter::Peekable; use uucore::display::Quotable; +use uucore::show_error; /// Represents one of the binary comparison operators for strings, integers, or files #[derive(Debug, PartialEq)] @@ -207,7 +208,7 @@ impl Parser { // case 2: error if end of stream is `( ` [symbol] => { - eprintln!("test: missing argument after ‘{:?}’", symbol); + show_error!("missing argument after ‘{:?}’", symbol); std::process::exit(2); } From 918603909bd32e6ac7a3f95c5140d17b02239579 Mon Sep 17 00:00:00 2001 From: Jan Verbeek Date: Tue, 7 Sep 2021 19:48:28 +0200 Subject: [PATCH 156/206] Update hashsum quoting FIXME instructions --- src/uu/hashsum/src/hashsum.rs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/uu/hashsum/src/hashsum.rs b/src/uu/hashsum/src/hashsum.rs index 2081d7612..f820b083e 100644 --- a/src/uu/hashsum/src/hashsum.rs +++ b/src/uu/hashsum/src/hashsum.rs @@ -547,10 +547,15 @@ where ) ) .to_ascii_lowercase(); - // FIXME: (How) should these be quoted? - // They seem like they would be processed programmatically, and - // our ordinary quoting might interfere, but newlines should be - // sanitized probably + // FIXME: Filenames with newlines should be treated specially. + // GNU appears to replace newlines by \n and backslashes by + // \\ and prepend a backslash (to the hash or filename) if it did + // this escaping. + // Different sorts of output (checking vs outputting hashes) may + // handle this differently. Compare carefully to GNU. + // If you can, try to preserve invalid unicode using OsStr(ing)Ext + // and display it using uucore::display::print_verbatim(). This is + // easier (and more important) on Unix than on Windows. if sum == real_sum { if !options.quiet { println!("{}: OK", ck_filename); From 1ef2574b085f11643522c720b6351ae88221001a Mon Sep 17 00:00:00 2001 From: Jan Verbeek Date: Tue, 7 Sep 2021 20:15:30 +0200 Subject: [PATCH 157/206] Replace backtick quoting --- src/uu/chroot/src/chroot.rs | 2 +- src/uu/fmt/src/fmt.rs | 6 +++--- src/uu/sort/src/sort.rs | 8 ++++---- tests/by-util/test_sort.rs | 4 ++-- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/uu/chroot/src/chroot.rs b/src/uu/chroot/src/chroot.rs index 240b5eafe..40799d009 100644 --- a/src/uu/chroot/src/chroot.rs +++ b/src/uu/chroot/src/chroot.rs @@ -150,7 +150,7 @@ fn set_context(root: &Path, options: &clap::ArgMatches) { Some(u) => { let s: Vec<&str> = u.split(':').collect(); if s.len() != 2 || s.iter().any(|&spec| spec.is_empty()) { - crash!(1, "invalid userspec: `{}`", u) + crash!(1, "invalid userspec: {}", u.quote()) }; s } diff --git a/src/uu/fmt/src/fmt.rs b/src/uu/fmt/src/fmt.rs index df5d971e5..669c98b14 100644 --- a/src/uu/fmt/src/fmt.rs +++ b/src/uu/fmt/src/fmt.rs @@ -133,7 +133,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { fmt_opts.width = match s.parse::() { Ok(t) => t, Err(e) => { - crash!(1, "Invalid WIDTH specification: `{}': {}", s, e); + crash!(1, "Invalid WIDTH specification: {}: {}", s.quote(), e); } }; if fmt_opts.width > MAX_WIDTH { @@ -150,7 +150,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { fmt_opts.goal = match s.parse::() { Ok(t) => t, Err(e) => { - crash!(1, "Invalid GOAL specification: `{}': {}", s, e); + crash!(1, "Invalid GOAL specification: {}: {}", s.quote(), e); } }; if !matches.is_present(OPT_WIDTH) { @@ -164,7 +164,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { fmt_opts.tabwidth = match s.parse::() { Ok(t) => t, Err(e) => { - crash!(1, "Invalid TABWIDTH specification: `{}': {}", s, e); + crash!(1, "Invalid TABWIDTH specification: {}: {}", s.quote(), e); } }; }; diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index bab6dd770..c8a429e53 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -767,19 +767,19 @@ impl KeyPosition { let field = field_and_char .next() - .ok_or_else(|| format!("invalid key `{}`", key))?; + .ok_or_else(|| format!("invalid key {}", key.quote()))?; let char = field_and_char.next(); let field = field .parse() - .map_err(|e| format!("failed to parse field index `{}`: {}", field, e))?; + .map_err(|e| format!("failed to parse field index {}: {}", field.quote(), e))?; if field == 0 { return Err("field index can not be 0".to_string()); } let char = char.map_or(Ok(default_char_index), |char| { char.parse() - .map_err(|e| format!("failed to parse character index `{}`: {}", char, e)) + .map_err(|e| format!("failed to parse character index {}: {}", char.quote(), e)) })?; Ok(Self { @@ -890,7 +890,7 @@ impl FieldSelector { 'R' => key_settings.set_sort_mode(SortMode::Random)?, 'r' => key_settings.reverse = true, 'V' => key_settings.set_sort_mode(SortMode::Version)?, - c => return Err(format!("invalid option: `{}`", c)), + c => return Err(format!("invalid option: '{}'", c)), } } Ok(ignore_blanks) diff --git a/tests/by-util/test_sort.rs b/tests/by-util/test_sort.rs index b389c9011..2aa26ad24 100644 --- a/tests/by-util/test_sort.rs +++ b/tests/by-util/test_sort.rs @@ -531,7 +531,7 @@ fn test_keys_invalid_field() { new_ucmd!() .args(&["-k", "1."]) .fails() - .stderr_only("sort: failed to parse key '1.': failed to parse character index ``: cannot parse integer from empty string"); + .stderr_only("sort: failed to parse key '1.': failed to parse character index '': cannot parse integer from empty string"); } #[test] @@ -539,7 +539,7 @@ fn test_keys_invalid_field_option() { new_ucmd!() .args(&["-k", "1.1x"]) .fails() - .stderr_only("sort: failed to parse key '1.1x': invalid option: `x`"); + .stderr_only("sort: failed to parse key '1.1x': invalid option: 'x'"); } #[test] From cd0d23752a2d3f0ce8e4094dbfa66ce1705ff6c1 Mon Sep 17 00:00:00 2001 From: Jan Scheer Date: Wed, 8 Sep 2021 02:09:09 +0200 Subject: [PATCH 158/206] tail: add fixes to pass "gnu/tests/tail-2/tail-c.sh" from GNU's test suite --- src/uu/tail/src/tail.rs | 20 ++++++++++++++++++-- tests/by-util/test_tail.rs | 20 ++++++++++++++++++++ 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/src/uu/tail/src/tail.rs b/src/uu/tail/src/tail.rs index 44f910ea0..89fbe4d36 100644 --- a/src/uu/tail/src/tail.rs +++ b/src/uu/tail/src/tail.rs @@ -22,7 +22,7 @@ use chunks::ReverseChunks; use clap::{App, Arg}; use std::collections::VecDeque; use std::fmt; -use std::fs::File; +use std::fs::{File, Metadata}; use std::io::{stdin, stdout, BufRead, BufReader, Read, Seek, SeekFrom, Write}; use std::path::Path; use std::thread::sleep; @@ -32,6 +32,8 @@ use uucore::ringbuffer::RingBuffer; #[cfg(unix)] use crate::platform::stdin_is_pipe_or_fifo; +#[cfg(unix)] +use std::os::unix::fs::MetadataExt; pub mod options { pub mod verbosity { @@ -189,7 +191,8 @@ pub fn uumain(args: impl uucore::Args) -> i32 { continue; } let mut file = File::open(&path).unwrap(); - if is_seekable(&mut file) { + let md = file.metadata().unwrap(); + if is_seekable(&mut file) && get_block_size(&md) > 0 { bounded_tail(&mut file, &settings); if settings.follow { let reader = BufReader::new(file); @@ -437,6 +440,8 @@ fn unbounded_tail(reader: &mut BufReader, settings: &Settings) { fn is_seekable(file: &mut T) -> bool { file.seek(SeekFrom::Current(0)).is_ok() + && file.seek(SeekFrom::End(0)).is_ok() + && file.seek(SeekFrom::Start(0)).is_ok() } #[inline] @@ -464,3 +469,14 @@ fn parse_num(src: &str) -> Result<(usize, bool), ParseSizeError> { parse_size(size_string).map(|n| (n, starting_with)) } + +fn get_block_size(md: &Metadata) -> u64 { + #[cfg(unix)] + { + md.blocks() + } + #[cfg(not(unix))] + { + md.len() + } +} diff --git a/tests/by-util/test_tail.rs b/tests/by-util/test_tail.rs index 28c3580bb..26d8106f0 100644 --- a/tests/by-util/test_tail.rs +++ b/tests/by-util/test_tail.rs @@ -425,3 +425,23 @@ fn test_tail_num_with_undocumented_sign_bytes() { .succeeds() .stdout_is("efghijklmnopqrstuvwxyz"); } + +#[test] +#[cfg(unix)] +fn test_tail_bytes_for_funny_files() { + // gnu/tests/tail-2/tail-c.sh + let ts = TestScenario::new(util_name!()); + let at = &ts.fixtures; + for &file in &["/proc/version", "/sys/kernel/profiling"] { + if !at.file_exists(file) { + continue; + } + let args = ["--bytes", "1", file]; + let result = ts.ucmd().args(&args).run(); + let exp_result = unwrap_or_return!(expected_result(&ts, &args)); + result + .stdout_is(exp_result.stdout_str()) + .stderr_is(exp_result.stderr_str()) + .code_is(exp_result.code()); + } +} From e4aad6d971ae79f84504f98809154801e5d25530 Mon Sep 17 00:00:00 2001 From: 353fc443 <353fc443@pm.me> Date: Thu, 9 Sep 2021 17:59:02 +0000 Subject: [PATCH 159/206] uptime: added UResult --- src/uu/uptime/src/uptime.rs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/uu/uptime/src/uptime.rs b/src/uu/uptime/src/uptime.rs index e58f398db..f649b96b6 100644 --- a/src/uu/uptime/src/uptime.rs +++ b/src/uu/uptime/src/uptime.rs @@ -17,6 +17,8 @@ extern crate uucore; pub use uucore::libc; use uucore::libc::time_t; +use uucore::error::{UResult, USimpleError}; + static ABOUT: &str = "Display the current time, the length of time the system has been up,\n\ the number of users on the system, and the average number of jobs\n\ in the run queue over the last 1, 5 and 15 minutes."; @@ -36,21 +38,20 @@ fn usage() -> String { format!("{0} [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); let (boot_time, user_count) = process_utmpx(); let uptime = get_uptime(boot_time); if uptime < 0 { - show_error!("could not retrieve system uptime"); - - 1 + Err(USimpleError::new(1, "could not retrieve system uptime")) } else { if matches.is_present(options::SINCE) { let initial_date = Local.timestamp(Utc::now().timestamp() - uptime, 0); println!("{}", initial_date.format("%Y-%m-%d %H:%M:%S")); - return 0; + return Ok(()); } print_time(); @@ -59,7 +60,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { print_nusers(user_count); print_loadavg(); - 0 + Ok(()) } } From ed258e3c9c411f5d9c41950d10d6f094b8fa07e6 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Thu, 9 Sep 2021 21:48:17 +0200 Subject: [PATCH 160/206] touch: add --ref as an alias (#2641) --- src/uu/touch/src/touch.rs | 3 ++- tests/by-util/test_touch.rs | 24 ++++++++++++++++-------- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/src/uu/touch/src/touch.rs b/src/uu/touch/src/touch.rs index 8dd7bf7d2..6997def09 100644 --- a/src/uu/touch/src/touch.rs +++ b/src/uu/touch/src/touch.rs @@ -6,7 +6,7 @@ // For the full copyright and license information, please view the LICENSE file // that was distributed with this source code. -// spell-checker:ignore (ToDO) filetime strptime utcoff strs datetime MMDDhhmm +// spell-checker:ignore (ToDO) filetime strptime utcoff strs datetime MMDDhhmm clapv pub extern crate filetime; @@ -176,6 +176,7 @@ pub fn uu_app() -> App<'static, 'static> { Arg::with_name(options::sources::REFERENCE) .short("r") .long(options::sources::REFERENCE) + .alias("ref") // clapv3 .help("use this file's times instead of the current time") .value_name("FILE"), ) diff --git a/tests/by-util/test_touch.rs b/tests/by-util/test_touch.rs index e8a17bbca..983d14fe2 100644 --- a/tests/by-util/test_touch.rs +++ b/tests/by-util/test_touch.rs @@ -6,6 +6,7 @@ use self::touch::filetime::{self, FileTime}; extern crate time; use crate::common::util::*; +use std::fs::remove_file; use std::path::PathBuf; fn get_file_times(at: &AtPath, path: &str) -> (FileTime, FileTime) { @@ -323,7 +324,8 @@ fn test_touch_no_dereference() { #[test] fn test_touch_reference() { - let (at, mut ucmd) = at_and_ucmd!(); + let scenario = TestScenario::new("touch"); + let (at, mut _ucmd) = (scenario.fixtures.clone(), scenario.ucmd()); let file_a = "test_touch_reference_a"; let file_b = "test_touch_reference_b"; let start_of_year = str_to_filetime("%Y%m%d%H%M", "201501010000"); @@ -331,15 +333,21 @@ fn test_touch_reference() { at.touch(file_a); set_file_times(&at, file_a, start_of_year, start_of_year); assert!(at.file_exists(file_a)); + for &opt in &["-r", "--ref", "--reference"] { + scenario + .ccmd("touch") + .args(&[opt, file_a, file_b]) + .succeeds() + .no_stderr(); - ucmd.args(&["-r", file_a, file_b]).succeeds().no_stderr(); + assert!(at.file_exists(file_b)); - assert!(at.file_exists(file_b)); - - let (atime, mtime) = get_file_times(&at, file_b); - assert_eq!(atime, mtime); - assert_eq!(atime, start_of_year); - assert_eq!(mtime, start_of_year); + let (atime, mtime) = get_file_times(&at, file_b); + assert_eq!(atime, mtime); + assert_eq!(atime, start_of_year); + assert_eq!(mtime, start_of_year); + let _ = remove_file(file_b); + } } #[test] From 75e5c42e4038d79bb0fbc69a22815a8042d2a699 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Fri, 10 Sep 2021 08:15:23 +0200 Subject: [PATCH 161/206] chown: support '.' as 'user.group' separator (like ':') (#2638) * chown: support '.' as 'user.group' separator (like ':') It also manages the case: user.name:group (valid too) Should fix: gnu/tests/chown/separator.sh --- src/uu/chown/src/chown.rs | 65 ++++++++++++++++------ tests/by-util/test_chown.rs | 106 ++++++++++++++++++++++++++++++++++++ 2 files changed, 153 insertions(+), 18 deletions(-) diff --git a/src/uu/chown/src/chown.rs b/src/uu/chown/src/chown.rs index 5525f9325..f9412a768 100644 --- a/src/uu/chown/src/chown.rs +++ b/src/uu/chown/src/chown.rs @@ -5,7 +5,7 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -// spell-checker:ignore (ToDO) COMFOLLOW Passwd RFILE RFILE's derefer dgid duid +// spell-checker:ignore (ToDO) COMFOLLOW Passwd RFILE RFILE's derefer dgid duid groupname #[macro_use] extern crate uucore; @@ -31,7 +31,7 @@ fn get_usage() -> String { fn parse_gid_uid_and_filter(matches: &ArgMatches) -> UResult<(Option, Option, IfFrom)> { let filter = if let Some(spec) = matches.value_of(options::FROM) { - match parse_spec(spec)? { + match parse_spec(spec, ':')? { (Some(uid), None) => IfFrom::User(uid), (None, Some(gid)) => IfFrom::Group(gid), (Some(uid), Some(gid)) => IfFrom::UserGroup(uid, gid), @@ -49,7 +49,7 @@ fn parse_gid_uid_and_filter(matches: &ArgMatches) -> UResult<(Option, Optio dest_gid = Some(meta.gid()); dest_uid = Some(meta.uid()); } else { - let (u, g) = parse_spec(matches.value_of(options::ARG_OWNER).unwrap())?; + let (u, g) = parse_spec(matches.value_of(options::ARG_OWNER).unwrap(), ':')?; dest_uid = u; dest_gid = g; } @@ -166,23 +166,49 @@ pub fn uu_app() -> App<'static, 'static> { ) } -fn parse_spec(spec: &str) -> UResult<(Option, Option)> { - let args = spec.split_terminator(':').collect::>(); - let usr_only = args.len() == 1 && !args[0].is_empty(); - let grp_only = args.len() == 2 && args[0].is_empty(); - let usr_grp = args.len() == 2 && !args[0].is_empty() && !args[1].is_empty(); - let uid = if usr_only || usr_grp { - Some( - Passwd::locate(args[0]) - .map_err(|_| USimpleError::new(1, format!("invalid user: {}", spec.quote())))? - .uid(), - ) +/// Parse the username and groupname +/// +/// In theory, it should be username:groupname +/// but ... +/// it can user.name:groupname +/// or username.groupname +/// +/// # Arguments +/// +/// * `spec` - The input from the user +/// * `sep` - Should be ':' or '.' +fn parse_spec(spec: &str, sep: char) -> UResult<(Option, Option)> { + assert!(['.', ':'].contains(&sep)); + let mut args = spec.splitn(2, sep); + let user = args.next().unwrap_or(""); + let group = args.next().unwrap_or(""); + + let uid = if !user.is_empty() { + Some(match Passwd::locate(user) { + Ok(u) => u.uid(), // We have been able to get the uid + Err(_) => + // we have NOT been able to find the uid + // but we could be in the case where we have user.group + { + if spec.contains('.') && !spec.contains(':') && sep == ':' { + // but the input contains a '.' but not a ':' + // we might have something like username.groupname + // So, try to parse it this way + return parse_spec(spec, '.'); + } else { + return Err(USimpleError::new( + 1, + format!("invalid user: {}", spec.quote()), + ))?; + } + } + }) } else { None }; - let gid = if grp_only || usr_grp { + let gid = if !group.is_empty() { Some( - Group::locate(args[1]) + Group::locate(group) .map_err(|_| USimpleError::new(1, format!("invalid group: {}", spec.quote())))? .gid(), ) @@ -198,7 +224,10 @@ mod test { #[test] fn test_parse_spec() { - assert!(matches!(parse_spec(":"), Ok((None, None)))); - assert!(format!("{}", parse_spec("::").err().unwrap()).starts_with("invalid group: ")); + assert!(matches!(parse_spec(":", ':'), Ok((None, None)))); + assert!(matches!(parse_spec(".", ':'), Ok((None, None)))); + assert!(matches!(parse_spec(".", '.'), Ok((None, None)))); + assert!(format!("{}", parse_spec("::", ':').err().unwrap()).starts_with("invalid group: ")); + assert!(format!("{}", parse_spec("..", ':').err().unwrap()).starts_with("invalid group: ")); } } diff --git a/tests/by-util/test_chown.rs b/tests/by-util/test_chown.rs index 725e4b244..d5ebb4600 100644 --- a/tests/by-util/test_chown.rs +++ b/tests/by-util/test_chown.rs @@ -139,6 +139,14 @@ fn test_chown_only_owner_colon() { .succeeds() .stderr_contains(&"retained as"); + scene + .ucmd() + .arg(format!("{}.", user_name)) + .arg("--verbose") + .arg(file1) + .succeeds() + .stderr_contains(&"retained as"); + scene .ucmd() .arg("root:") @@ -180,6 +188,14 @@ fn test_chown_only_colon() { .arg(file1) .fails() .stderr_contains(&"invalid group: '::'"); + + scene + .ucmd() + .arg("..") + .arg("--verbose") + .arg(file1) + .fails() + .stderr_contains(&"invalid group: '..'"); } #[test] @@ -232,6 +248,22 @@ fn test_chown_owner_group() { } result.stderr_contains(&"retained as"); + scene + .ucmd() + .arg("root:root:root") + .arg("--verbose") + .arg(file1) + .fails() + .stderr_contains(&"invalid group"); + + scene + .ucmd() + .arg("root.root.root") + .arg("--verbose") + .arg(file1) + .fails() + .stderr_contains(&"invalid group"); + // TODO: on macos group name is not recognized correctly: "chown: invalid group: 'root:root' #[cfg(any(windows, all(unix, not(target_os = "macos"))))] scene @@ -243,6 +275,67 @@ fn test_chown_owner_group() { .stderr_contains(&"failed to change"); } +#[test] +// FixME: Fails on freebsd because of chown: invalid group: 'root:root' +#[cfg(not(target_os = "freebsd"))] +fn test_chown_various_input() { + // test chown username:group file.txt + + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + let result = scene.cmd("whoami").run(); + if skipping_test_is_okay(&result, "whoami: cannot find name for user ID") { + return; + } + + let user_name = String::from(result.stdout_str().trim()); + assert!(!user_name.is_empty()); + + let file1 = "test_chown_file1"; + at.touch(file1); + + let result = scene.cmd("id").arg("-gn").run(); + if skipping_test_is_okay(&result, "id: cannot find name for group ID") { + return; + } + let group_name = String::from(result.stdout_str().trim()); + assert!(!group_name.is_empty()); + + let result = scene + .ucmd() + .arg(format!("{}:{}", user_name, group_name)) + .arg("--verbose") + .arg(file1) + .run(); + if skipping_test_is_okay(&result, "chown: invalid group:") { + return; + } + result.stderr_contains(&"retained as"); + + // check that username.groupname is understood + let result = scene + .ucmd() + .arg(format!("{}.{}", user_name, group_name)) + .arg("--verbose") + .arg(file1) + .run(); + if skipping_test_is_okay(&result, "chown: invalid group:") { + return; + } + result.stderr_contains(&"retained as"); + + // Fails as user.name doesn't exist in the CI + // but it is valid + scene + .ucmd() + .arg(format!("{}:{}", "user.name", "groupname")) + .arg("--verbose") + .arg(file1) + .fails() + .stderr_contains(&"chown: invalid user: 'user.name:groupname'"); +} + #[test] // FixME: on macos & freebsd group name is not recognized correctly: "chown: invalid group: ':groupname' #[cfg(any( @@ -405,6 +498,19 @@ fn test_chown_owner_group_id() { } result.stderr_contains(&"retained as"); + let result = scene + .ucmd() + .arg(format!("{}.{}", user_id, group_id)) + .arg("--verbose") + .arg(file1) + .run(); + if skipping_test_is_okay(&result, "invalid user") { + // From the Logs: "Build (ubuntu-18.04, x86_64-unknown-linux-gnu, feat_os_unix, use-cross)" + // stderr: "chown: invalid user: '1001.116' + return; + } + result.stderr_contains(&"retained as"); + scene .ucmd() .arg("0:0") From 46fc91af19d756bca02d460455d685d5a66b801b Mon Sep 17 00:00:00 2001 From: Jan Scheer Date: Fri, 10 Sep 2021 18:36:51 +0200 Subject: [PATCH 162/206] test_ls: add features for uutils called by ccmd --- tests/by-util/test_ls.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/by-util/test_ls.rs b/tests/by-util/test_ls.rs index 3d6092416..01c60e3fd 100644 --- a/tests/by-util/test_ls.rs +++ b/tests/by-util/test_ls.rs @@ -371,6 +371,7 @@ fn test_ls_long_format() { /// 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. #[test] +#[cfg(all(feature = "ln", feature = "mkdir", feature = "touch"))] fn test_ls_long_symlink_color() { // If you break this test after breaking mkdir, touch, or ln, do not be alarmed! // This test is made for ls, but it attempts to run those utils in the process. From d05410383fa5f82ceaab25fd8e83da0bca184e55 Mon Sep 17 00:00:00 2001 From: Jan Verbeek Date: Fri, 10 Sep 2021 18:56:12 +0200 Subject: [PATCH 163/206] chown: Fix clippy warning to fix CI --- src/uu/chown/src/chown.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uu/chown/src/chown.rs b/src/uu/chown/src/chown.rs index f9412a768..1cd71d3f5 100644 --- a/src/uu/chown/src/chown.rs +++ b/src/uu/chown/src/chown.rs @@ -199,7 +199,7 @@ fn parse_spec(spec: &str, sep: char) -> UResult<(Option, Option)> { return Err(USimpleError::new( 1, format!("invalid user: {}", spec.quote()), - ))?; + )); } } }) From fc77e51b64dde3c59012d91910a1feb3f7bb3fa7 Mon Sep 17 00:00:00 2001 From: Jeffrey Finkelstein Date: Thu, 9 Sep 2021 22:24:51 -0400 Subject: [PATCH 164/206] printf: derive Default impl for FormatPrimitive --- src/uu/printf/src/tokenize/num_format/formatter.rs | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/src/uu/printf/src/tokenize/num_format/formatter.rs b/src/uu/printf/src/tokenize/num_format/formatter.rs index 0438f78bf..790c338ae 100644 --- a/src/uu/printf/src/tokenize/num_format/formatter.rs +++ b/src/uu/printf/src/tokenize/num_format/formatter.rs @@ -11,6 +11,7 @@ use super::format_field::FormatField; // output for a number, organized together // to allow for easy generalization of output manipulation // (e.g. max number of digits after decimal) +#[derive(Default)] pub struct FormatPrimitive { pub prefix: Option, pub pre_decimal: Option, @@ -18,17 +19,6 @@ pub struct FormatPrimitive { pub suffix: Option, } -impl Default for FormatPrimitive { - fn default() -> FormatPrimitive { - FormatPrimitive { - prefix: None, - pre_decimal: None, - post_decimal: None, - suffix: None, - } - } -} - #[derive(Clone, PartialEq)] pub enum Base { Ten = 10, From 91d39de16e3098e90d2e9dba8415a0083f3a24b4 Mon Sep 17 00:00:00 2001 From: Jan Verbeek Date: Fri, 10 Sep 2021 19:25:45 +0200 Subject: [PATCH 165/206] sort: derive Default impl for FieldSelector --- src/uu/sort/src/sort.rs | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index c8a429e53..fe286aa6d 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -800,7 +800,7 @@ impl Default for KeyPosition { } } -#[derive(Clone, PartialEq, Debug)] +#[derive(Clone, PartialEq, Debug, Default)] struct FieldSelector { from: KeyPosition, to: Option, @@ -812,18 +812,6 @@ struct FieldSelector { needs_selection: bool, } -impl Default for FieldSelector { - fn default() -> Self { - Self { - from: Default::default(), - to: None, - settings: Default::default(), - needs_tokens: false, - needs_selection: false, - } - } -} - impl FieldSelector { /// Splits this position into the actual position and the attached options. fn split_key_options(position: &str) -> (&str, &str) { From 8f901ee860cd8f28395a81ae7786cf6b8e74cfe7 Mon Sep 17 00:00:00 2001 From: Jan Verbeek Date: Fri, 27 Aug 2021 18:10:13 +0200 Subject: [PATCH 166/206] tty: Make test_stdout_fail() less flaky --- tests/by-util/test_tty.rs | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/tests/by-util/test_tty.rs b/tests/by-util/test_tty.rs index f31aa67ee..ed490e7ab 100644 --- a/tests/by-util/test_tty.rs +++ b/tests/by-util/test_tty.rs @@ -66,10 +66,23 @@ fn test_wrong_argument() { #[test] // FixME: freebsd panic -#[cfg(not(any(windows, target_os = "freebsd")))] +#[cfg(all(unix, not(target_os = "freebsd")))] fn test_stdout_fail() { - let mut child = new_ucmd!().run_no_wait(); - drop(child.stdout.take()); - let status = child.wait().unwrap(); + use std::process::{Command, Stdio}; + let ts = TestScenario::new(util_name!()); + // Sleep inside a shell to ensure the process doesn't finish before we've + // closed its stdout + let mut proc = Command::new("sh") + .arg("-c") + .arg(format!( + "sleep 0.2; exec {} {}", + ts.bin_path.to_str().unwrap(), + ts.util_name + )) + .stdout(Stdio::piped()) + .spawn() + .unwrap(); + drop(proc.stdout.take()); + let status = proc.wait().unwrap(); assert_eq!(status.code(), Some(3)); } From c1079e0b1cb42e01e0e11afaab31886197cb4bb5 Mon Sep 17 00:00:00 2001 From: Jan Verbeek Date: Fri, 27 Aug 2021 19:20:53 +0200 Subject: [PATCH 167/206] Move common pipe and splice functions into uucore This cuts down on repetitive unsafe code and repetitive code in general. --- Cargo.lock | 1 + src/uu/cat/Cargo.toml | 2 +- src/uu/cat/src/cat.rs | 30 ++++++++-------- src/uu/cat/src/splice.rs | 46 ++++++------------------ src/uu/wc/Cargo.toml | 2 +- src/uu/wc/src/count_fast.rs | 40 ++++++--------------- src/uu/yes/Cargo.toml | 2 +- src/uu/yes/src/splice.rs | 54 ++++------------------------ src/uucore/Cargo.toml | 2 ++ src/uucore/src/lib/features.rs | 2 ++ src/uucore/src/lib/features/pipes.rs | 54 ++++++++++++++++++++++++++++ src/uucore/src/lib/lib.rs | 2 ++ 12 files changed, 106 insertions(+), 131 deletions(-) create mode 100644 src/uucore/src/lib/features/pipes.rs diff --git a/Cargo.lock b/Cargo.lock index 808f62e15..ef00d9b9e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3276,6 +3276,7 @@ dependencies = [ "getopts", "lazy_static", "libc", + "nix 0.20.0", "once_cell", "termion", "thiserror", diff --git a/src/uu/cat/Cargo.toml b/src/uu/cat/Cargo.toml index d80514385..d4f137d7e 100644 --- a/src/uu/cat/Cargo.toml +++ b/src/uu/cat/Cargo.toml @@ -18,7 +18,7 @@ path = "src/cat.rs" clap = { version = "2.33", features = ["wrap_help"] } thiserror = "1.0" atty = "0.2" -uucore = { version=">=0.0.9", package="uucore", path="../../uucore", features=["fs"] } +uucore = { version=">=0.0.9", package="uucore", path="../../uucore", features=["fs", "pipes"] } uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_procs" } [target.'cfg(unix)'.dependencies] diff --git a/src/uu/cat/src/cat.rs b/src/uu/cat/src/cat.rs index 9459ee86a..baf8af6d5 100644 --- a/src/uu/cat/src/cat.rs +++ b/src/uu/cat/src/cat.rs @@ -29,8 +29,6 @@ use std::os::unix::io::AsRawFd; /// Linux splice support #[cfg(any(target_os = "linux", target_os = "android"))] mod splice; -#[cfg(any(target_os = "linux", target_os = "android"))] -use std::os::unix::io::RawFd; /// Unix domain socket support #[cfg(unix)] @@ -137,10 +135,18 @@ struct OutputState { one_blank_kept: bool, } +#[cfg(unix)] +trait FdReadable: Read + AsRawFd {} +#[cfg(not(unix))] +trait FdReadable: Read {} + +#[cfg(unix)] +impl FdReadable for T where T: Read + AsRawFd {} +#[cfg(not(unix))] +impl FdReadable for T where T: Read {} + /// Represents an open file handle, stream, or other device -struct InputHandle { - #[cfg(any(target_os = "linux", target_os = "android"))] - file_descriptor: RawFd, +struct InputHandle { reader: R, is_interactive: bool, } @@ -297,7 +303,7 @@ pub fn uu_app() -> App<'static, 'static> { ) } -fn cat_handle( +fn cat_handle( handle: &mut InputHandle, options: &OutputOptions, state: &mut OutputState, @@ -319,8 +325,6 @@ fn cat_path( if path == "-" { let stdin = io::stdin(); let mut handle = InputHandle { - #[cfg(any(target_os = "linux", target_os = "android"))] - file_descriptor: stdin.as_raw_fd(), reader: stdin, is_interactive: atty::is(atty::Stream::Stdin), }; @@ -333,8 +337,6 @@ fn cat_path( let socket = UnixStream::connect(path)?; socket.shutdown(Shutdown::Write)?; let mut handle = InputHandle { - #[cfg(any(target_os = "linux", target_os = "android"))] - file_descriptor: socket.as_raw_fd(), reader: socket, is_interactive: false, }; @@ -347,8 +349,6 @@ fn cat_path( return Err(CatError::OutputIsInput); } let mut handle = InputHandle { - #[cfg(any(target_os = "linux", target_os = "android"))] - file_descriptor: file.as_raw_fd(), reader: file, is_interactive: false, }; @@ -437,14 +437,14 @@ fn get_input_type(path: &str) -> CatResult { /// Writes handle to stdout with no configuration. This allows a /// simple memory copy. -fn write_fast(handle: &mut InputHandle) -> CatResult<()> { +fn write_fast(handle: &mut InputHandle) -> CatResult<()> { let stdout = io::stdout(); let mut stdout_lock = stdout.lock(); #[cfg(any(target_os = "linux", target_os = "android"))] { // If we're on Linux or Android, try to use the splice() system call // for faster writing. If it works, we're done. - if !splice::write_fast_using_splice(handle, stdout_lock.as_raw_fd())? { + if !splice::write_fast_using_splice(handle, &stdout_lock)? { return Ok(()); } } @@ -462,7 +462,7 @@ fn write_fast(handle: &mut InputHandle) -> CatResult<()> { /// Outputs file contents to stdout in a line-by-line fashion, /// propagating any errors that might occur. -fn write_lines( +fn write_lines( handle: &mut InputHandle, options: &OutputOptions, state: &mut OutputState, diff --git a/src/uu/cat/src/splice.rs b/src/uu/cat/src/splice.rs index 108997c4a..66630060c 100644 --- a/src/uu/cat/src/splice.rs +++ b/src/uu/cat/src/splice.rs @@ -1,10 +1,9 @@ -use super::{CatResult, InputHandle}; +use super::{CatResult, FdReadable, InputHandle}; -use nix::fcntl::{splice, SpliceFFlags}; -use nix::unistd::{self, pipe}; -use std::fs::File; -use std::io::Read; -use std::os::unix::io::{FromRawFd, RawFd}; +use nix::unistd; +use std::os::unix::io::{AsRawFd, RawFd}; + +use uucore::pipes::{pipe, splice, splice_exact}; const BUF_SIZE: usize = 1024 * 16; @@ -16,36 +15,25 @@ const BUF_SIZE: usize = 1024 * 16; /// The `bool` in the result value indicates if we need to fall back to normal /// copying or not. False means we don't have to. #[inline] -pub(super) fn write_fast_using_splice( +pub(super) fn write_fast_using_splice( handle: &mut InputHandle, - write_fd: RawFd, + write_fd: &impl AsRawFd, ) -> CatResult { let (pipe_rd, pipe_wr) = pipe()?; - // Ensure the pipe is closed when the function returns. - // SAFETY: The file descriptors do not have other owners. - let _handles = unsafe { (File::from_raw_fd(pipe_rd), File::from_raw_fd(pipe_wr)) }; - loop { - match splice( - handle.file_descriptor, - None, - pipe_wr, - None, - BUF_SIZE, - SpliceFFlags::empty(), - ) { + match splice(&handle.reader, &pipe_wr, BUF_SIZE) { Ok(n) => { if n == 0 { return Ok(false); } - if splice_exact(pipe_rd, write_fd, n).is_err() { + if splice_exact(&pipe_rd, write_fd, n).is_err() { // If the first splice manages to copy to the intermediate // pipe, but the second splice to stdout fails for some reason // we can recover by copying the data that we have from the // intermediate pipe to stdout using normal read/write. Then // we tell the caller to fall back. - copy_exact(pipe_rd, write_fd, n)?; + copy_exact(pipe_rd.as_raw_fd(), write_fd.as_raw_fd(), n)?; return Ok(true); } } @@ -56,20 +44,6 @@ pub(super) fn write_fast_using_splice( } } -/// Splice wrapper which handles short writes. -#[inline] -fn splice_exact(read_fd: RawFd, write_fd: RawFd, num_bytes: usize) -> nix::Result<()> { - let mut left = num_bytes; - loop { - let written = splice(read_fd, None, write_fd, None, left, SpliceFFlags::empty())?; - left -= written; - if left == 0 { - break; - } - } - Ok(()) -} - /// Caller must ensure that `num_bytes <= BUF_SIZE`, otherwise this function /// will panic. The way we use this function in `write_fast_using_splice` /// above is safe because `splice` is set to write at most `BUF_SIZE` to the diff --git a/src/uu/wc/Cargo.toml b/src/uu/wc/Cargo.toml index 80d6014da..5884f3746 100644 --- a/src/uu/wc/Cargo.toml +++ b/src/uu/wc/Cargo.toml @@ -16,7 +16,7 @@ path = "src/wc.rs" [dependencies] clap = { version = "2.33", features = ["wrap_help"] } -uucore = { version=">=0.0.9", package="uucore", path="../../uucore" } +uucore = { version=">=0.0.9", package="uucore", path="../../uucore", features=["pipes"] } uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_procs" } bytecount = "0.6.2" utf-8 = "0.7.6" diff --git a/src/uu/wc/src/count_fast.rs b/src/uu/wc/src/count_fast.rs index b4078d6be..9351b6871 100644 --- a/src/uu/wc/src/count_fast.rs +++ b/src/uu/wc/src/count_fast.rs @@ -3,7 +3,7 @@ use crate::word_count::WordCount; use super::WordCountable; #[cfg(any(target_os = "linux", target_os = "android"))] -use std::fs::{File, OpenOptions}; +use std::fs::OpenOptions; use std::io::{self, ErrorKind, Read}; #[cfg(unix)] @@ -11,34 +11,17 @@ use libc::S_IFREG; #[cfg(unix)] use nix::sys::stat; #[cfg(any(target_os = "linux", target_os = "android"))] -use std::os::unix::io::{AsRawFd, FromRawFd, RawFd}; +use std::os::unix::io::AsRawFd; #[cfg(any(target_os = "linux", target_os = "android"))] use libc::S_IFIFO; #[cfg(any(target_os = "linux", target_os = "android"))] -use nix::fcntl::{splice, SpliceFFlags}; -#[cfg(any(target_os = "linux", target_os = "android"))] -use nix::unistd::pipe; +use uucore::pipes::{pipe, splice, splice_exact}; const BUF_SIZE: usize = 16 * 1024; #[cfg(any(target_os = "linux", target_os = "android"))] const SPLICE_SIZE: usize = 128 * 1024; -/// Splice wrapper which handles short writes -#[cfg(any(target_os = "linux", target_os = "android"))] -#[inline] -fn splice_exact(read_fd: RawFd, write_fd: RawFd, num_bytes: usize) -> nix::Result<()> { - let mut left = num_bytes; - loop { - let written = splice(read_fd, None, write_fd, None, left, SpliceFFlags::empty())?; - left -= written; - if left == 0 { - break; - } - } - Ok(()) -} - /// This is a Linux-specific function to count the number of bytes using the /// `splice` system call, which is faster than using `read`. /// @@ -46,13 +29,14 @@ fn splice_exact(read_fd: RawFd, write_fd: RawFd, num_bytes: usize) -> nix::Resul /// caller will fall back to a simpler method. #[inline] #[cfg(any(target_os = "linux", target_os = "android"))] -fn count_bytes_using_splice(fd: RawFd) -> Result { +fn count_bytes_using_splice(fd: &impl AsRawFd) -> Result { let null_file = OpenOptions::new() .write(true) .open("/dev/null") .map_err(|_| 0_usize)?; - let null = null_file.as_raw_fd(); - let null_rdev = stat::fstat(null).map_err(|_| 0_usize)?.st_rdev; + let null_rdev = stat::fstat(null_file.as_raw_fd()) + .map_err(|_| 0_usize)? + .st_rdev; if (stat::major(null_rdev), stat::minor(null_rdev)) != (1, 3) { // This is not a proper /dev/null, writing to it is probably bad // Bit of an edge case, but it has been known to happen @@ -60,17 +44,13 @@ fn count_bytes_using_splice(fd: RawFd) -> Result { } let (pipe_rd, pipe_wr) = pipe().map_err(|_| 0_usize)?; - // Ensure the pipe is closed when the function returns. - // SAFETY: The file descriptors do not have other owners. - let _handles = unsafe { (File::from_raw_fd(pipe_rd), File::from_raw_fd(pipe_wr)) }; - let mut byte_count = 0; loop { - match splice(fd, None, pipe_wr, None, SPLICE_SIZE, SpliceFFlags::empty()) { + match splice(fd, &pipe_wr, SPLICE_SIZE) { Ok(0) => break, Ok(res) => { byte_count += res; - if splice_exact(pipe_rd, null, res).is_err() { + if splice_exact(&pipe_rd, &null_file, res).is_err() { return Err(byte_count); } } @@ -106,7 +86,7 @@ pub(crate) fn count_bytes_fast(handle: &mut T) -> (usize, Opti // Else, if we're on Linux and our file is a FIFO pipe // (or stdin), we use splice to count the number of bytes. if (stat.st_mode & S_IFIFO) != 0 { - match count_bytes_using_splice(fd) { + match count_bytes_using_splice(handle) { Ok(n) => return (n, None), Err(n) => byte_count = n, } diff --git a/src/uu/yes/Cargo.toml b/src/uu/yes/Cargo.toml index b963d4974..ea2aae3b1 100644 --- a/src/uu/yes/Cargo.toml +++ b/src/uu/yes/Cargo.toml @@ -16,7 +16,7 @@ path = "src/yes.rs" [dependencies] clap = { version = "2.33", features = ["wrap_help"] } -uucore = { version=">=0.0.9", package="uucore", path="../../uucore" } +uucore = { version=">=0.0.9", package="uucore", path="../../uucore", features=["pipes"] } uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_procs" } [target.'cfg(any(target_os = "linux", target_os = "android"))'.dependencies] diff --git a/src/uu/yes/src/splice.rs b/src/uu/yes/src/splice.rs index b0573bc9e..6f025d6a9 100644 --- a/src/uu/yes/src/splice.rs +++ b/src/uu/yes/src/splice.rs @@ -16,18 +16,11 @@ //! make any effort to rescue data from the pipe if splice() fails, we can //! just fall back and start over from the beginning. -use std::{ - fs::File, - io, - os::unix::io::{AsRawFd, FromRawFd}, -}; +use std::{io, os::unix::io::AsRawFd}; -use nix::{ - errno::Errno, - fcntl::SpliceFFlags, - libc::S_IFIFO, - sys::{stat::fstat, uio::IoVec}, -}; +use nix::{errno::Errno, libc::S_IFIFO, sys::stat::fstat}; + +use uucore::pipes::{pipe, splice_exact, vmsplice}; pub(crate) fn splice_data(bytes: &[u8], out: &impl AsRawFd) -> Result<()> { let is_pipe = fstat(out.as_raw_fd())?.st_mode & S_IFIFO != 0; @@ -36,7 +29,7 @@ pub(crate) fn splice_data(bytes: &[u8], out: &impl AsRawFd) -> Result<()> { loop { let mut bytes = bytes; while !bytes.is_empty() { - let len = vmsplice(out, bytes)?; + let len = vmsplice(out, bytes).map_err(maybe_unsupported)?; bytes = &bytes[len..]; } } @@ -45,14 +38,8 @@ pub(crate) fn splice_data(bytes: &[u8], out: &impl AsRawFd) -> Result<()> { loop { let mut bytes = bytes; while !bytes.is_empty() { - let len = vmsplice(&write, bytes)?; - let mut remaining = len; - while remaining > 0 { - match splice(&read, out, remaining)? { - 0 => panic!("Unexpected end of pipe"), - n => remaining -= n, - }; - } + let len = vmsplice(&write, bytes).map_err(maybe_unsupported)?; + splice_exact(&read, out, len).map_err(maybe_unsupported)?; bytes = &bytes[len..]; } } @@ -81,30 +68,3 @@ fn maybe_unsupported(error: nix::Error) -> Error { _ => error.into(), } } - -fn splice(source: &impl AsRawFd, target: &impl AsRawFd, len: usize) -> Result { - nix::fcntl::splice( - source.as_raw_fd(), - None, - target.as_raw_fd(), - None, - len, - SpliceFFlags::empty(), - ) - .map_err(maybe_unsupported) -} - -fn vmsplice(target: &impl AsRawFd, bytes: &[u8]) -> Result { - nix::fcntl::vmsplice( - target.as_raw_fd(), - &[IoVec::from_slice(bytes)], - SpliceFFlags::empty(), - ) - .map_err(maybe_unsupported) -} - -fn pipe() -> nix::Result<(File, File)> { - let (read, write) = nix::unistd::pipe()?; - // SAFETY: The file descriptors do not have other owners. - unsafe { Ok((File::from_raw_fd(read), File::from_raw_fd(write))) } -} diff --git a/src/uucore/Cargo.toml b/src/uucore/Cargo.toml index a04f565aa..80a3a115b 100644 --- a/src/uucore/Cargo.toml +++ b/src/uucore/Cargo.toml @@ -31,6 +31,7 @@ data-encoding-macro = { version="0.1.12", optional=true } z85 = { version="3.0.3", optional=true } libc = { version="0.2.15", optional=true } once_cell = "1.8.0" +nix = { version="0.20", optional=true } [dev-dependencies] clap = "2.33.3" @@ -57,3 +58,4 @@ signals = [] utf8 = [] utmpx = ["time", "libc", "dns-lookup"] wide = [] +pipes = ["nix"] diff --git a/src/uucore/src/lib/features.rs b/src/uucore/src/lib/features.rs index 60be88664..42d8ffbe7 100644 --- a/src/uucore/src/lib/features.rs +++ b/src/uucore/src/lib/features.rs @@ -19,6 +19,8 @@ pub mod mode; pub mod entries; #[cfg(all(unix, feature = "perms"))] pub mod perms; +#[cfg(all(unix, feature = "pipes"))] +pub mod pipes; #[cfg(all(unix, feature = "process"))] pub mod process; diff --git a/src/uucore/src/lib/features/pipes.rs b/src/uucore/src/lib/features/pipes.rs new file mode 100644 index 000000000..dfb062992 --- /dev/null +++ b/src/uucore/src/lib/features/pipes.rs @@ -0,0 +1,54 @@ +/// Thin pipe-related wrappers around functions from the `nix` crate. +use std::fs::File; +#[cfg(any(target_os = "linux", target_os = "android"))] +use std::os::unix::io::AsRawFd; +use std::os::unix::io::FromRawFd; + +#[cfg(any(target_os = "linux", target_os = "android"))] +use nix::{fcntl::SpliceFFlags, sys::uio::IoVec}; + +pub use nix::{Error, Result}; + +/// A wrapper around [`nix::unistd::Pipe`] that ensures the pipe is cleaned up. +pub fn pipe() -> Result<(File, File)> { + let (read, write) = nix::unistd::pipe()?; + // SAFETY: The file descriptors do not have other owners. + unsafe { Ok((File::from_raw_fd(read), File::from_raw_fd(write))) } +} + +/// Less noisy wrapper around [`nix::fcntl::splice`]. +#[cfg(any(target_os = "linux", target_os = "android"))] +pub fn splice(source: &impl AsRawFd, target: &impl AsRawFd, len: usize) -> Result { + nix::fcntl::splice( + source.as_raw_fd(), + None, + target.as_raw_fd(), + None, + len, + SpliceFFlags::empty(), + ) +} + +/// Splice wrapper which fully finishes the write. +/// +/// Panics if `source` runs out of data before `len` bytes have been moved. +#[cfg(any(target_os = "linux", target_os = "android"))] +pub fn splice_exact(source: &impl AsRawFd, target: &impl AsRawFd, len: usize) -> Result<()> { + let mut left = len; + while left != 0 { + let written = splice(source, target, left)?; + assert_ne!(written, 0, "unexpected end of data"); + left -= written; + } + Ok(()) +} + +/// Use vmsplice() to copy data from memory into a pipe. +#[cfg(any(target_os = "linux", target_os = "android"))] +pub fn vmsplice(target: &impl AsRawFd, bytes: &[u8]) -> Result { + nix::fcntl::vmsplice( + target.as_raw_fd(), + &[IoVec::from_slice(bytes)], + SpliceFFlags::empty(), + ) +} diff --git a/src/uucore/src/lib/lib.rs b/src/uucore/src/lib/lib.rs index 925246c24..79cc2afc3 100644 --- a/src/uucore/src/lib/lib.rs +++ b/src/uucore/src/lib/lib.rs @@ -49,6 +49,8 @@ pub use crate::features::mode; pub use crate::features::entries; #[cfg(all(unix, feature = "perms"))] pub use crate::features::perms; +#[cfg(all(unix, feature = "pipes"))] +pub use crate::features::pipes; #[cfg(all(unix, feature = "process"))] pub use crate::features::process; #[cfg(all(unix, not(target_os = "fuchsia"), feature = "signals"))] From d002810a47a9604f578377fead4ca2efe5eda158 Mon Sep 17 00:00:00 2001 From: Jan Verbeek Date: Fri, 27 Aug 2021 20:10:28 +0200 Subject: [PATCH 168/206] cat: Do not assume complete writes --- src/uu/cat/src/splice.rs | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/src/uu/cat/src/splice.rs b/src/uu/cat/src/splice.rs index 66630060c..5ce5f2b62 100644 --- a/src/uu/cat/src/splice.rs +++ b/src/uu/cat/src/splice.rs @@ -44,21 +44,23 @@ pub(super) fn write_fast_using_splice( } } -/// Caller must ensure that `num_bytes <= BUF_SIZE`, otherwise this function -/// will panic. The way we use this function in `write_fast_using_splice` -/// above is safe because `splice` is set to write at most `BUF_SIZE` to the -/// pipe. -#[inline] +/// Move exactly `num_bytes` bytes from `read_fd` to `write_fd`. +/// +/// Panics if not enough bytes can be read. fn copy_exact(read_fd: RawFd, write_fd: RawFd, num_bytes: usize) -> nix::Result<()> { let mut left = num_bytes; let mut buf = [0; BUF_SIZE]; - loop { - let read = unistd::read(read_fd, &mut buf[..left])?; - let written = unistd::write(write_fd, &buf[..read])?; - left -= written; - if left == 0 { - break; + while left > 0 { + let read = unistd::read(read_fd, &mut buf)?; + assert_ne!(read, 0, "unexpected end of pipe"); + let mut written = 0; + while written < read { + match unistd::write(write_fd, &buf[written..read])? { + 0 => panic!(), + n => written += n, + } } + left -= read; } Ok(()) } From 23647be07ab827491de4d9803c561e7e074bce7c Mon Sep 17 00:00:00 2001 From: Jan Verbeek Date: Fri, 27 Aug 2021 20:12:12 +0200 Subject: [PATCH 169/206] cat: Use larger splice size This raises performance by 10-20% or so. --- src/uu/cat/src/splice.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/uu/cat/src/splice.rs b/src/uu/cat/src/splice.rs index 5ce5f2b62..26802c7e6 100644 --- a/src/uu/cat/src/splice.rs +++ b/src/uu/cat/src/splice.rs @@ -5,6 +5,7 @@ use std::os::unix::io::{AsRawFd, RawFd}; use uucore::pipes::{pipe, splice, splice_exact}; +const SPLICE_SIZE: usize = 1024 * 128; const BUF_SIZE: usize = 1024 * 16; /// This function is called from `write_fast()` on Linux and Android. The @@ -22,7 +23,7 @@ pub(super) fn write_fast_using_splice( let (pipe_rd, pipe_wr) = pipe()?; loop { - match splice(&handle.reader, &pipe_wr, BUF_SIZE) { + match splice(&handle.reader, &pipe_wr, SPLICE_SIZE) { Ok(n) => { if n == 0 { return Ok(false); From b7d697753c5cf72b62443e206ca99ac8b6ccad99 Mon Sep 17 00:00:00 2001 From: Jan Verbeek Date: Sat, 28 Aug 2021 00:38:00 +0200 Subject: [PATCH 170/206] unlink: Simplify, remove unsafe, move to core This makes it no longer possible to pass multiple filenames, but every other implementation I tried (GNU, busybox, FreeBSD, sbase) also forbids that so I think it's for the best. --- Cargo.lock | 1 - Cargo.toml | 2 +- src/uu/unlink/Cargo.toml | 1 - src/uu/unlink/src/unlink.rs | 98 ++++++------------------------------ tests/by-util/test_unlink.rs | 40 ++++++++++----- 5 files changed, 42 insertions(+), 100 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 808f62e15..34b2dac52 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3197,7 +3197,6 @@ name = "uu_unlink" version = "0.0.7" dependencies = [ "clap", - "libc", "uucore", "uucore_procs", ] diff --git a/Cargo.toml b/Cargo.toml index 3a2c5f12a..58b6aa52a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -98,6 +98,7 @@ feat_common_core = [ "touch", "unexpand", "uniq", + "unlink", "wc", "yes", ] @@ -182,7 +183,6 @@ feat_require_unix = [ "timeout", "tty", "uname", - "unlink", ] # "feat_require_unix_utmpx" == set of utilities requiring unix utmp/utmpx support # * ref: diff --git a/src/uu/unlink/Cargo.toml b/src/uu/unlink/Cargo.toml index 3f13a7231..558d18422 100644 --- a/src/uu/unlink/Cargo.toml +++ b/src/uu/unlink/Cargo.toml @@ -16,7 +16,6 @@ path = "src/unlink.rs" [dependencies] clap = { version = "2.33", features = ["wrap_help"] } -libc = "0.2.42" uucore = { version=">=0.0.9", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_procs" } diff --git a/src/uu/unlink/src/unlink.rs b/src/uu/unlink/src/unlink.rs index 7c66708e0..1b4e4c998 100644 --- a/src/uu/unlink/src/unlink.rs +++ b/src/uu/unlink/src/unlink.rs @@ -7,102 +7,32 @@ /* last synced with: unlink (GNU coreutils) 8.21 */ -// spell-checker:ignore (ToDO) lstat IFLNK IFMT IFREG - #[macro_use] extern crate uucore; -use clap::{crate_version, App, Arg}; -use libc::{lstat, stat, unlink}; -use libc::{S_IFLNK, S_IFMT, S_IFREG}; -use std::ffi::CString; -use std::io::{Error, ErrorKind}; -use uucore::display::Quotable; -use uucore::InvalidEncodingHandling; +use std::fs::remove_file; +use std::path::Path; -static ABOUT: &str = "Unlink the file at [FILE]."; +use clap::{crate_version, App, Arg}; + +use uucore::display::Quotable; +use uucore::error::{FromIo, UResult}; + +static ABOUT: &str = "Unlink the file at FILE."; static OPT_PATH: &str = "FILE"; -fn usage() -> String { - format!("{} [OPTION]... FILE", uucore::execution_phrase()) -} +#[uucore_procs::gen_uumain] +pub fn uumain(args: impl uucore::Args) -> UResult<()> { + let matches = uu_app().get_matches_from(args); -pub fn uumain(args: impl uucore::Args) -> i32 { - let args = args - .collect_str(InvalidEncodingHandling::ConvertLossy) - .accept_any(); + let path: &Path = matches.value_of_os(OPT_PATH).unwrap().as_ref(); - let usage = usage(); - - let matches = uu_app().usage(&usage[..]).get_matches_from(args); - - let paths: Vec = matches - .values_of(OPT_PATH) - .map(|v| v.map(ToString::to_string).collect()) - .unwrap_or_default(); - - if paths.is_empty() { - crash!( - 1, - "missing operand\nTry '{0} --help' for more information.", - uucore::execution_phrase() - ); - } else if paths.len() > 1 { - crash!( - 1, - "extra operand: '{1}'\nTry '{0} --help' for more information.", - uucore::execution_phrase(), - paths[1] - ); - } - - let c_string = CString::new(paths[0].clone()).unwrap(); // unwrap() cannot fail, the string comes from argv so it cannot contain a \0. - - let st_mode = { - #[allow(deprecated)] - let mut buf: stat = unsafe { std::mem::uninitialized() }; - let result = unsafe { lstat(c_string.as_ptr(), &mut buf as *mut stat) }; - - if result < 0 { - crash!( - 1, - "Cannot stat {}: {}", - paths[0].quote(), - Error::last_os_error() - ); - } - - buf.st_mode & S_IFMT - }; - - let result = if st_mode != S_IFREG && st_mode != S_IFLNK { - Err(Error::new( - ErrorKind::Other, - "Not a regular file or symlink", - )) - } else { - let result = unsafe { unlink(c_string.as_ptr()) }; - - if result < 0 { - Err(Error::last_os_error()) - } else { - Ok(()) - } - }; - - match result { - Ok(_) => (), - Err(e) => { - crash!(1, "cannot unlink '{0}': {1}", paths[0], e); - } - } - - 0 + remove_file(path).map_err_context(|| format!("cannot unlink {}", path.quote())) } pub fn uu_app() -> App<'static, 'static> { App::new(uucore::util_name()) .version(crate_version!()) .about(ABOUT) - .arg(Arg::with_name(OPT_PATH).hidden(true).multiple(true)) + .arg(Arg::with_name(OPT_PATH).required(true).hidden(true)) } diff --git a/tests/by-util/test_unlink.rs b/tests/by-util/test_unlink.rs index 36c978734..6b4fc41da 100644 --- a/tests/by-util/test_unlink.rs +++ b/tests/by-util/test_unlink.rs @@ -23,23 +23,24 @@ fn test_unlink_multiple_files() { at.touch(file_a); at.touch(file_b); - ucmd.arg(file_a).arg(file_b).fails().stderr_is(&format!( - "{0}: extra operand: 'test_unlink_multiple_file_b'\nTry '{1} {0} --help' for more information.", - ts.util_name, - ts.bin_path.to_string_lossy() - )); + ucmd.arg(file_a) + .arg(file_b) + .fails() + .stderr_contains("USAGE"); } #[test] fn test_unlink_directory() { let (at, mut ucmd) = at_and_ucmd!(); - let dir = "test_unlink_empty_directory"; + let dir = "dir"; at.mkdir(dir); - ucmd.arg(dir).fails().stderr_is( - "unlink: cannot unlink 'test_unlink_empty_directory': Not a regular file \ - or symlink\n", + let res = ucmd.arg(dir).fails(); + let stderr = res.stderr_str(); + assert!( + stderr == "unlink: cannot unlink 'dir': Is a directory\n" + || stderr == "unlink: cannot unlink 'dir': Permission denied\n" ); } @@ -47,8 +48,21 @@ fn test_unlink_directory() { fn test_unlink_nonexistent() { let file = "test_unlink_nonexistent"; - new_ucmd!().arg(file).fails().stderr_is( - "unlink: Cannot stat 'test_unlink_nonexistent': No such file or directory \ - (os error 2)\n", - ); + new_ucmd!() + .arg(file) + .fails() + .stderr_is("unlink: cannot unlink 'test_unlink_nonexistent': No such file or directory\n"); +} + +#[test] +fn test_unlink_symlink() { + let (at, mut ucmd) = at_and_ucmd!(); + + at.touch("foo"); + at.symlink_file("foo", "bar"); + + ucmd.arg("bar").succeeds().no_stderr(); + + assert!(at.file_exists("foo")); + assert!(!at.file_exists("bar")); } From d6a84851159cfe9025baef192598b378d2425e71 Mon Sep 17 00:00:00 2001 From: Jan Verbeek Date: Fri, 10 Sep 2021 22:03:51 +0200 Subject: [PATCH 171/206] uucore::pipes: Expand documentation --- src/uucore/src/lib/features/pipes.rs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/uucore/src/lib/features/pipes.rs b/src/uucore/src/lib/features/pipes.rs index dfb062992..b375982dd 100644 --- a/src/uucore/src/lib/features/pipes.rs +++ b/src/uucore/src/lib/features/pipes.rs @@ -10,6 +10,9 @@ use nix::{fcntl::SpliceFFlags, sys::uio::IoVec}; pub use nix::{Error, Result}; /// A wrapper around [`nix::unistd::Pipe`] that ensures the pipe is cleaned up. +/// +/// Returns two `File` objects: everything written to the second can be read +/// from the first. pub fn pipe() -> Result<(File, File)> { let (read, write) = nix::unistd::pipe()?; // SAFETY: The file descriptors do not have other owners. @@ -17,6 +20,14 @@ pub fn pipe() -> Result<(File, File)> { } /// Less noisy wrapper around [`nix::fcntl::splice`]. +/// +/// Up to `len` bytes are moved from `source` to `target`. Returns the number +/// of successfully moved bytes. +/// +/// At least one of `source` and `target` must be some sort of pipe. +/// To get around this requirement, consider splicing from your source into +/// a [`pipe`] and then from the pipe into your target (with `splice_exact`): +/// this is still very efficient. #[cfg(any(target_os = "linux", target_os = "android"))] pub fn splice(source: &impl AsRawFd, target: &impl AsRawFd, len: usize) -> Result { nix::fcntl::splice( @@ -31,6 +42,8 @@ pub fn splice(source: &impl AsRawFd, target: &impl AsRawFd, len: usize) -> Resul /// Splice wrapper which fully finishes the write. /// +/// Exactly `len` bytes are moved from `source` into `target`. +/// /// Panics if `source` runs out of data before `len` bytes have been moved. #[cfg(any(target_os = "linux", target_os = "android"))] pub fn splice_exact(source: &impl AsRawFd, target: &impl AsRawFd, len: usize) -> Result<()> { @@ -43,7 +56,9 @@ pub fn splice_exact(source: &impl AsRawFd, target: &impl AsRawFd, len: usize) -> Ok(()) } -/// Use vmsplice() to copy data from memory into a pipe. +/// Copy data from `bytes` into `target`, which must be a pipe. +/// +/// Returns the number of successfully copied bytes. #[cfg(any(target_os = "linux", target_os = "android"))] pub fn vmsplice(target: &impl AsRawFd, bytes: &[u8]) -> Result { nix::fcntl::vmsplice( From 96b8616a1a328915b7e3b1e14fbf7fd5961d7318 Mon Sep 17 00:00:00 2001 From: Jeffrey Finkelstein Date: Thu, 9 Sep 2021 22:02:31 -0400 Subject: [PATCH 172/206] seq: use stdout.write_all() instead of print!() Change from using `print!()` to using `stdout.write_all()` in order to allow the main function to handle broken pipe errors gracefully. --- src/uu/seq/src/seq.rs | 40 ++++++++++++++++++++++++--------------- tests/by-util/test_seq.rs | 24 +++++++++++++++++++++++ 2 files changed, 49 insertions(+), 15 deletions(-) diff --git a/src/uu/seq/src/seq.rs b/src/uu/seq/src/seq.rs index 501b12ac0..493b0c589 100644 --- a/src/uu/seq/src/seq.rs +++ b/src/uu/seq/src/seq.rs @@ -12,7 +12,7 @@ use num_traits::One; use num_traits::Zero; use num_traits::{Num, ToPrimitive}; use std::cmp; -use std::io::{stdout, Write}; +use std::io::{stdout, ErrorKind, Write}; use std::str::FromStr; use uucore::display::Quotable; @@ -192,7 +192,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .num_digits() .max(increment.num_digits()) .max(last.num_digits()); - match (first, last, increment) { + let result = match (first, last, increment) { (Number::MinusZero, Number::BigInt(last), Number::BigInt(increment)) => print_seq_integers( (BigInt::zero(), increment, last), options.separator, @@ -219,8 +219,12 @@ pub fn uumain(args: impl uucore::Args) -> i32 { options.widths, padding, ), + }; + match result { + Ok(_) => 0, + Err(err) if err.kind() == ErrorKind::BrokenPipe => 0, + Err(_) => 1, } - 0 } pub fn uu_app() -> App<'static, 'static> { @@ -276,7 +280,9 @@ fn print_seq( terminator: String, pad: bool, padding: usize, -) { +) -> std::io::Result<()> { + let stdout = stdout(); + let mut stdout = stdout.lock(); let (first, increment, last) = range; let mut i = 0isize; let mut value = first + i as f64 * increment; @@ -286,20 +292,21 @@ fn print_seq( let before_dec = istr.find('.').unwrap_or(ilen); if pad && before_dec < padding { for _ in 0..(padding - before_dec) { - print!("0"); + write!(stdout, "0")?; } } - print!("{}", istr); + write!(stdout, "{}", istr)?; i += 1; value = first + i as f64 * increment; if !done_printing(&value, &increment, &last) { - print!("{}", separator); + write!(stdout, "{}", separator)?; } } if (first >= last && increment < 0f64) || (first <= last && increment > 0f64) { - print!("{}", terminator); + write!(stdout, "{}", terminator)?; } - crash_if_err!(1, stdout().flush()); + stdout.flush()?; + Ok(()) } /// Print an integer sequence. @@ -323,31 +330,34 @@ fn print_seq_integers( pad: bool, padding: usize, is_first_minus_zero: bool, -) { +) -> std::io::Result<()> { + let stdout = stdout(); + let mut stdout = stdout.lock(); let (first, increment, last) = range; let mut value = first; let mut is_first_iteration = true; while !done_printing(&value, &increment, &last) { if !is_first_iteration { - print!("{}", separator); + write!(stdout, "{}", separator)?; } let mut width = padding; if is_first_iteration && is_first_minus_zero { - print!("-"); + write!(stdout, "-")?; width -= 1; } is_first_iteration = false; if pad { - print!("{number:>0width$}", number = value, width = width); + write!(stdout, "{number:>0width$}", number = value, width = width)?; } else { - print!("{}", value); + write!(stdout, "{}", value)?; } value += &increment; } if !is_first_iteration { - print!("{}", terminator); + write!(stdout, "{}", terminator)?; } + Ok(()) } #[cfg(test)] diff --git a/tests/by-util/test_seq.rs b/tests/by-util/test_seq.rs index 458769839..4325c7fba 100644 --- a/tests/by-util/test_seq.rs +++ b/tests/by-util/test_seq.rs @@ -1,4 +1,5 @@ use crate::common::util::*; +use std::io::Read; #[test] fn test_rejects_nan() { @@ -176,3 +177,26 @@ fn test_width_negative_zero() { .stdout_is("-0\n01\n") .no_stderr(); } + +// TODO This is duplicated from `test_yes.rs`; refactor them. +/// Run `seq`, capture some of the output, close the pipe, and verify it. +fn run(args: &[&str], expected: &[u8]) { + let mut cmd = new_ucmd!(); + let mut child = cmd.args(args).run_no_wait(); + let mut stdout = child.stdout.take().unwrap(); + let mut buf = vec![0; expected.len()]; + stdout.read_exact(&mut buf).unwrap(); + drop(stdout); + assert!(child.wait().unwrap().success()); + assert_eq!(buf.as_slice(), expected); +} + +#[test] +fn test_neg_inf() { + run(&["--", "-inf", "0"], b"-inf\n-inf\n-inf\n"); +} + +#[test] +fn test_inf() { + run(&["inf"], b"1\n2\n3\n"); +} From 872c0fac1d08850e2fffbe3abcbb65df4f9f5711 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20EBL=C3=89?= Date: Sat, 11 Sep 2021 22:37:55 +0200 Subject: [PATCH 173/206] tests/kill: test old form args with signal name Add two tests of the old form signal arg using the signal name instead of the number. Bonus: add a test for the new form but with the prefix SIG- --- tests/by-util/test_kill.rs | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/tests/by-util/test_kill.rs b/tests/by-util/test_kill.rs index fe5d4557a..f5166c428 100644 --- a/tests/by-util/test_kill.rs +++ b/tests/by-util/test_kill.rs @@ -104,6 +104,26 @@ fn test_kill_with_signal_number_old_form() { assert_eq!(target.wait_for_signal(), Some(9)); } +#[test] +fn test_kill_with_signal_name_old_form() { + let mut target = Target::new(); + new_ucmd!() + .arg("-KILL") + .arg(format!("{}", target.pid())) + .succeeds(); + assert_eq!(target.wait_for_signal(), Some(libc::SIGKILL)); +} + +#[test] +fn test_kill_with_signal_prefixed_name_old_form() { + let mut target = Target::new(); + new_ucmd!() + .arg("-SIGKILL") + .arg(format!("{}", target.pid())) + .succeeds(); + assert_eq!(target.wait_for_signal(), Some(libc::SIGKILL)); +} + #[test] fn test_kill_with_signal_number_new_form() { let mut target = Target::new(); @@ -125,3 +145,14 @@ fn test_kill_with_signal_name_new_form() { .succeeds(); assert_eq!(target.wait_for_signal(), Some(libc::SIGKILL)); } + +#[test] +fn test_kill_with_signal_prefixed_name_new_form() { + let mut target = Target::new(); + new_ucmd!() + .arg("-s") + .arg("SIGKILL") + .arg(format!("{}", target.pid())) + .succeeds(); + assert_eq!(target.wait_for_signal(), Some(libc::SIGKILL)); +} From df64c191078f613726ba347e30320d4fac2ff284 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20EBL=C3=89?= Date: Sun, 12 Sep 2021 00:44:11 +0200 Subject: [PATCH 174/206] kill: kill old form with signal name This commit aim to correct #2645. The origin of the problem was that `handle_obsolete` was not looking for named signals but only for numbers preceded by '-'. I have made a few changes: - Change `handle_obsolete` to use `signal_by_name_or_value` to parse the possible signal. - Since now the signal is already parsed return an `Option` instead of `Option<&str>` to parse later. - Change the way to decide the pid to use from a `match` to a `if` because the tested element are actually not the same for each branch. - Parse the signal from the `-s` argument outside of `kill` function for consistency with the "obsolete" signal case. - Again for consistency, parse the pids outside the `kill` function. --- src/uu/kill/src/kill.rs | 99 ++++++++++++++++++++--------------------- 1 file changed, 49 insertions(+), 50 deletions(-) diff --git a/src/uu/kill/src/kill.rs b/src/uu/kill/src/kill.rs index 494dc0602..f269f7283 100644 --- a/src/uu/kill/src/kill.rs +++ b/src/uu/kill/src/kill.rs @@ -15,7 +15,7 @@ use libc::{c_int, pid_t}; use std::io::Error; use uucore::display::Quotable; use uucore::error::{UResult, USimpleError}; -use uucore::signals::ALL_SIGNALS; +use uucore::signals::{signal_by_name_or_value, ALL_SIGNALS}; use uucore::InvalidEncodingHandling; static ABOUT: &str = "Send signal to processes or list information about signals."; @@ -37,10 +37,10 @@ pub enum Mode { #[uucore_procs::gen_uumain] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let args = args + let mut args = args .collect_str(InvalidEncodingHandling::Ignore) .accept_any(); - let (args, obs_signal) = handle_obsolete(args); + let obs_signal = handle_obsolete(&mut args); let usage = format!("{} [OPTIONS]... PID...", uucore::execution_phrase()); let matches = uu_app().usage(&usage[..]).get_matches_from(args); @@ -60,13 +60,15 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { match mode { Mode::Kill => { - let sig = match (obs_signal, matches.value_of(options::SIGNAL)) { - (Some(s), Some(_)) => s, // -s takes precedence - (Some(s), None) => s, - (None, Some(s)) => s.to_owned(), - (None, None) => "TERM".to_owned(), + let sig = if let Some(signal) = obs_signal { + signal + } else if let Some(signal) = matches.value_of(options::SIGNAL) { + parse_signal_value(signal)? + } else { + 15_usize //SIGTERM }; - kill(&sig, &pids_or_signals) + let pids = parse_pids(&pids_or_signals)?; + kill(sig, &pids) } Mode::Table => { table(); @@ -109,26 +111,22 @@ pub fn uu_app() -> App<'static, 'static> { ) } -fn handle_obsolete(mut args: Vec) -> (Vec, Option) { - let mut i = 0; - while i < args.len() { - // this is safe because slice is valid when it is referenced - let slice = &args[i].clone(); - if slice.starts_with('-') && slice.chars().nth(1).map_or(false, |c| c.is_digit(10)) { - let val = &slice[1..]; - match val.parse() { - Ok(num) => { - if uucore::signals::is_signal(num) { - args.remove(i); - return (args, Some(val.to_owned())); - } - } - Err(_) => break, /* getopts will error out for us */ +fn handle_obsolete(args: &mut Vec) -> Option { + // Sanity check + if args.len() > 2 { + // Old signal can only be in the first argument position + let slice = args[1].as_str(); + if let Some(signal) = slice.strip_prefix('-') { + // Check if it is a valid signal + let opt_signal = signal_by_name_or_value(signal); + if opt_signal.is_some() { + // remove the signal before return + args.remove(1); + return opt_signal; } } - i += 1; } - (args, None) + None } fn table() { @@ -184,31 +182,32 @@ fn list(arg: Option) -> UResult<()> { } } -fn kill(signalname: &str, pids: &[String]) -> UResult<()> { - let optional_signal_value = uucore::signals::signal_by_name_or_value(signalname); - let signal_value = match optional_signal_value { - Some(x) => x, - None => { - return Err(USimpleError::new( - 1, - format!("unknown signal name {}", signalname.quote()), - )); +fn parse_signal_value(signal_name: &str) -> UResult { + let optional_signal_value = signal_by_name_or_value(signal_name); + match optional_signal_value { + Some(x) => Ok(x), + None => Err(USimpleError::new( + 1, + format!("unknown signal name {}", signal_name.quote()), + )), + } +} + +fn parse_pids(pids: &[String]) -> UResult> { + pids.iter() + .map(|x| { + x.parse::().map_err(|e| { + USimpleError::new(1, format!("failed to parse argument {}: {}", x.quote(), e)) + }) + }) + .collect() +} + +fn kill(signal_value: usize, pids: &[usize]) -> UResult<()> { + for &pid in pids { + if unsafe { libc::kill(pid as pid_t, signal_value as c_int) } != 0 { + show!(USimpleError::new(1, format!("{}", Error::last_os_error()))); } - }; - for pid in pids { - match pid.parse::() { - Ok(x) => { - if unsafe { libc::kill(x as pid_t, signal_value as c_int) } != 0 { - show!(USimpleError::new(1, format!("{}", Error::last_os_error()))); - } - } - Err(e) => { - return Err(USimpleError::new( - 1, - format!("failed to parse argument {}: {}", pid.quote(), e), - )); - } - }; } Ok(()) } From 664c7a6ec5a12a999af949f4ed1f93227ead5b6f Mon Sep 17 00:00:00 2001 From: Jeffrey Finkelstein Date: Sun, 1 Aug 2021 11:19:44 -0400 Subject: [PATCH 175/206] tac: add support for --regex option to tac Add support for `tac --regex`, where the line separator is interpreted as a regular expression. --- Cargo.lock | 1 + src/uu/tac/Cargo.toml | 1 + src/uu/tac/src/tac.rs | 90 ++++++++++++++++++++++++++++++++++++--- tests/by-util/test_tac.rs | 66 +++++++++++++++++++++++++++- 4 files changed, 152 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 808f62e15..f8de8d4a0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3051,6 +3051,7 @@ version = "0.0.7" dependencies = [ "clap", "memchr 2.4.0", + "regex", "uucore", "uucore_procs", ] diff --git a/src/uu/tac/Cargo.toml b/src/uu/tac/Cargo.toml index 3ba1497a0..4a91786aa 100644 --- a/src/uu/tac/Cargo.toml +++ b/src/uu/tac/Cargo.toml @@ -16,6 +16,7 @@ path = "src/tac.rs" [dependencies] memchr = "2" +regex = "1" clap = { version = "2.33", features = ["wrap_help"] } uucore = { version=">=0.0.9", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_procs" } diff --git a/src/uu/tac/src/tac.rs b/src/uu/tac/src/tac.rs index e54697f2b..4a93a7c65 100644 --- a/src/uu/tac/src/tac.rs +++ b/src/uu/tac/src/tac.rs @@ -69,7 +69,7 @@ pub fn uu_app() -> App<'static, 'static> { Arg::with_name(options::REGEX) .short("r") .long(options::REGEX) - .help("interpret the sequence as a regular expression (NOT IMPLEMENTED)") + .help("interpret the sequence as a regular expression") .takes_value(false), ) .arg( @@ -82,6 +82,82 @@ pub fn uu_app() -> App<'static, 'static> { .arg(Arg::with_name(options::FILE).hidden(true).multiple(true)) } +/// Print lines of a buffer in reverse, with line separator given as a regex. +/// +/// `data` contains the bytes of the file. +/// +/// `pattern` is the regular expression given as a +/// [`regex::bytes::Regex`] (not a [`regex::Regex`], since the input is +/// given as a slice of bytes). If `before` is `true`, then each match +/// of this pattern in `data` is interpreted as the start of a line. If +/// `before` is `false`, then each match of this pattern is interpreted +/// as the end of a line. +/// +/// This function writes each line in `data` to [`std::io::Stdout`] in +/// reverse. +/// +/// # Errors +/// +/// If there is a problem writing to `stdout`, then this function +/// returns [`std::io::Error`]. +fn buffer_tac_regex( + data: &[u8], + pattern: regex::bytes::Regex, + before: bool, +) -> std::io::Result<()> { + let mut out = stdout(); + + // The index of the line separator for the current line. + // + // As we scan through the `data` from right to left, we update this + // variable each time we find a new line separator. We restrict our + // regular expression search to only those bytes up to the line + // separator. + let mut this_line_end = data.len(); + + // The index of the start of the next line in the `data`. + // + // As we scan through the `data` from right to left, we update this + // variable each time we find a new line. + // + // If `before` is `true`, then each line starts immediately before + // the line separator. Otherwise, each line starts immediately after + // the line separator. + let mut following_line_start = data.len(); + + // Iterate over each byte in the buffer in reverse. When we find a + // line separator, write the line to stdout. + // + // The `before` flag controls whether the line separator appears at + // the end of the line (as in "abc\ndef\n") or at the beginning of + // the line (as in "/abc/def"). + for i in (0..data.len()).rev() { + // Determine if there is a match for `pattern` starting at index + // `i` in `data`. Only search up to the line ending that was + // found previously. + if let Some(match_) = pattern.find_at(&data[..this_line_end], i) { + // Record this index as the ending of the current line. + this_line_end = i; + + // The length of the match (that is, the line separator), in bytes. + let slen = match_.end() - match_.start(); + + if before { + out.write_all(&data[i..following_line_start])?; + following_line_start = i; + } else { + out.write_all(&data[i + slen..following_line_start])?; + following_line_start = i + slen; + } + } + } + + // After the loop terminates, write whatever bytes are remaining at + // the beginning of the buffer. + out.write_all(&data[0..following_line_start])?; + Ok(()) +} + /// Write lines from `data` to stdout in reverse. /// /// This function writes to [`stdout`] each line appearing in `data`, @@ -132,7 +208,7 @@ fn buffer_tac(data: &[u8], before: bool, separator: &str) -> std::io::Result<()> Ok(()) } -fn tac(filenames: Vec, before: bool, _: bool, separator: &str) -> i32 { +fn tac(filenames: Vec, before: bool, regex: bool, separator: &str) -> i32 { let mut exit_code = 0; for filename in &filenames { @@ -168,9 +244,13 @@ fn tac(filenames: Vec, before: bool, _: bool, separator: &str) -> i32 { exit_code = 1; continue; }; - - buffer_tac(&data, before, separator) - .unwrap_or_else(|e| crash!(1, "failed to write to stdout: {}", e)); + if regex { + let pattern = crash_if_err!(1, regex::bytes::Regex::new(separator)); + buffer_tac_regex(&data, pattern, before) + } else { + buffer_tac(&data, before, separator) + } + .unwrap_or_else(|e| crash!(1, "failed to write to stdout: {}", e)); } exit_code } diff --git a/tests/by-util/test_tac.rs b/tests/by-util/test_tac.rs index 202f76d66..323aa5149 100644 --- a/tests/by-util/test_tac.rs +++ b/tests/by-util/test_tac.rs @@ -1,4 +1,4 @@ -// spell-checker:ignore axxbxx bxxaxx axxx axxxx xxaxx xxax xxxxa +// spell-checker:ignore axxbxx bxxaxx axxx axxxx xxaxx xxax xxxxa axyz zyax zyxa use crate::common::util::*; #[test] @@ -205,3 +205,67 @@ fn test_null_separator() { .succeeds() .stdout_is("b\0a\0"); } + +#[test] +fn test_regex() { + new_ucmd!() + .args(&["-r", "-s", "[xyz]+"]) + .pipe_in("axyz") + .succeeds() + .no_stderr() + .stdout_is("zyax"); + + new_ucmd!() + .args(&["-r", "-s", ":+"]) + .pipe_in("a:b::c:::d::::") + .succeeds() + .no_stderr() + .stdout_is(":::d:::c::b:a:"); + + new_ucmd!() + .args(&["-r", "-s", r"[\+]+[-]+[\+]+"]) + // line 0 1 2 + // |--||-----||--------| + .pipe_in("a+-+b++--++c+d-e+---+") + .succeeds() + .no_stderr() + // line 2 1 0 + // |--------||-----||--| + .stdout_is("c+d-e+---+b++--++a+-+"); +} + +#[test] +fn test_regex_before() { + new_ucmd!() + .args(&["-b", "-r", "-s", "[xyz]+"]) + .pipe_in("axyz") + .succeeds() + .no_stderr() + .stdout_is("zyxa"); + + new_ucmd!() + .args(&["-b", "-r", "-s", ":+"]) + .pipe_in(":a::b:::c::::d") + .succeeds() + .stdout_is(":d::::c:::b::a"); + + // Because `tac` searches for matches of the regular expression from + // right to left, the second to last line is + // + // +--++b + // + // not + // + // ++--++b + // + new_ucmd!() + .args(&["-b", "-r", "-s", r"[\+]+[-]+[\+]+"]) + // line 0 1 2 + // |---||----||--------| + .pipe_in("+-+a++--++b+---+c+d-e") + .succeeds() + .no_stderr() + // line 2 1 0 + // |--------||----||---| + .stdout_is("+---+c+d-e+--++b+-+a+"); +} From 4a20ef1850603f6d78704a6bbd5be33cbae299d0 Mon Sep 17 00:00:00 2001 From: Jan Verbeek Date: Sun, 12 Sep 2021 13:17:55 +0200 Subject: [PATCH 176/206] chcon: Remove unused Options.dereference to fix Clippy --- src/uu/chcon/src/chcon.rs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/uu/chcon/src/chcon.rs b/src/uu/chcon/src/chcon.rs index 98ebebf34..9664f69f5 100644 --- a/src/uu/chcon/src/chcon.rs +++ b/src/uu/chcon/src/chcon.rs @@ -281,7 +281,6 @@ pub fn uu_app() -> App<'static, 'static> { #[derive(Debug)] struct Options { verbose: bool, - dereference: bool, preserve_root: bool, recursive_mode: RecursiveMode, affect_symlink_referent: bool, @@ -331,9 +330,6 @@ fn parse_command_line(config: clap::App, args: impl uucore::Args) -> Result Result Date: Sun, 12 Sep 2021 12:24:38 -0400 Subject: [PATCH 177/206] wc: remove mutable min_width parameter Remove mutability from the `min_width` parameter to the `print_stats()` function in `wc`. --- src/uu/wc/src/wc.rs | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/src/uu/wc/src/wc.rs b/src/uu/wc/src/wc.rs index 6917eb137..16522a0a7 100644 --- a/src/uu/wc/src/wc.rs +++ b/src/uu/wc/src/wc.rs @@ -424,8 +424,15 @@ fn wc(inputs: Vec, settings: &Settings) -> Result<(), u32> { // The width is the number of digits needed to print the number of // bytes in the largest file. This is true regardless of whether // the `settings` indicate that the bytes will be displayed. + // + // If we only need to display a single number, set this to 0 to + // prevent leading spaces. let mut failure = false; - let max_width = max_width(&inputs); + let max_width = if settings.number_enabled() <= 1 { + 0 + } else { + max_width(&inputs) + }; let mut total_word_count = WordCount::default(); @@ -475,20 +482,10 @@ fn wc(inputs: Vec, settings: &Settings) -> Result<(), u32> { } } -fn print_stats( - settings: &Settings, - result: &TitledWordCount, - mut min_width: usize, -) -> io::Result<()> { +fn print_stats(settings: &Settings, result: &TitledWordCount, min_width: usize) -> io::Result<()> { let stdout = io::stdout(); let mut stdout_lock = stdout.lock(); - if settings.number_enabled() <= 1 { - // Prevent a leading space in case we only need to display a single - // number. - min_width = 0; - } - let mut is_first: bool = true; if settings.show_lines { From 3e0b3c93ef5765a271111c2d49c6acfe7b10c63e Mon Sep 17 00:00:00 2001 From: Jan Verbeek Date: Fri, 27 Aug 2021 18:19:50 +0200 Subject: [PATCH 178/206] mktemp: Do not use unsafe --- src/uu/mktemp/src/mktemp.rs | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/src/uu/mktemp/src/mktemp.rs b/src/uu/mktemp/src/mktemp.rs index 59e82569a..81a3521e9 100644 --- a/src/uu/mktemp/src/mktemp.rs +++ b/src/uu/mktemp/src/mktemp.rs @@ -229,25 +229,24 @@ fn parse_template<'a>( pub fn dry_exec(mut tmpdir: PathBuf, prefix: &str, rand: usize, suffix: &str) -> UResult<()> { let len = prefix.len() + suffix.len() + rand; - let mut buf = String::with_capacity(len); - buf.push_str(prefix); - buf.extend(iter::repeat('X').take(rand)); - buf.push_str(suffix); + let mut buf = Vec::with_capacity(len); + buf.extend(prefix.as_bytes()); + buf.extend(iter::repeat(b'X').take(rand)); + buf.extend(suffix.as_bytes()); // Randomize. - unsafe { - // We guarantee utf8. - let bytes = &mut buf.as_mut_vec()[prefix.len()..prefix.len() + rand]; - rand::thread_rng().fill(bytes); - for byte in bytes.iter_mut() { - *byte = match *byte % 62 { - v @ 0..=9 => (v + b'0'), - v @ 10..=35 => (v - 10 + b'a'), - v @ 36..=61 => (v - 36 + b'A'), - _ => unreachable!(), - } + let bytes = &mut buf[prefix.len()..prefix.len() + rand]; + rand::thread_rng().fill(bytes); + for byte in bytes.iter_mut() { + *byte = match *byte % 62 { + v @ 0..=9 => (v + b'0'), + v @ 10..=35 => (v - 10 + b'a'), + v @ 36..=61 => (v - 36 + b'A'), + _ => unreachable!(), } } + // We guarantee utf8. + let buf = String::from_utf8(buf).unwrap(); tmpdir.push(buf); println_verbatim(tmpdir).map_err_context(|| "failed to print directory name".to_owned()) } From cc8522a2db080d6f2c3fb617db4f872e6f2137c4 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sun, 12 Sep 2021 23:14:34 +0200 Subject: [PATCH 179/206] Fixed a warning on freebdsd: warning: unused import: `std::os::unix::fs::PermissionsExt` --- tests/by-util/test_cp.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/by-util/test_cp.rs b/tests/by-util/test_cp.rs index f126517fe..e86f35833 100644 --- a/tests/by-util/test_cp.rs +++ b/tests/by-util/test_cp.rs @@ -9,7 +9,7 @@ use std::os::unix::fs; #[cfg(unix)] use std::os::unix::fs::symlink as symlink_file; -#[cfg(unix)] +#[cfg(all(unix, not(target_os = "freebsd")))] use std::os::unix::fs::PermissionsExt; #[cfg(windows)] use std::os::windows::fs::symlink_file; From deedb73bcbe2b9454bdb0c97572d8ddf53bf9708 Mon Sep 17 00:00:00 2001 From: Jan Scheer Date: Mon, 13 Sep 2021 12:12:32 +0200 Subject: [PATCH 180/206] tests/common: make call to `host_name_for` idempotent This fixes an issue on macOS/bsd where multiple calls to `host_name_for` each added a prefix 'g'. The issue led to some tests being skipped because `util_name` was renamed to e.g. "ggid" instead of "gid". --- tests/common/util.rs | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/tests/common/util.rs b/tests/common/util.rs index 5441b82c2..47d6a8850 100644 --- a/tests/common/util.rs +++ b/tests/common/util.rs @@ -1081,7 +1081,14 @@ pub fn host_name_for(util_name: &str) -> Cow { // In some environments, e.g. macOS/freebsd, the GNU coreutils are prefixed with "g" // to not interfere with the BSD counterparts already in `$PATH`. #[cfg(not(target_os = "linux"))] - return format!("g{}", util_name).into(); + { + // make call to `host_name_for` idempotent + if util_name.starts_with('g') && util_name != "groups" { + return util_name.into(); + } else { + return format!("g{}", util_name).into(); + } + } #[cfg(target_os = "linux")] return util_name.into(); } @@ -1195,8 +1202,8 @@ pub fn check_coreutil_version( ///``` #[cfg(unix)] pub fn expected_result(ts: &TestScenario, args: &[&str]) -> std::result::Result { + println!("{}", check_coreutil_version(&ts.util_name, VERSION_MIN)?); let util_name = &host_name_for(&ts.util_name); - println!("{}", check_coreutil_version(util_name, VERSION_MIN)?); let result = ts .cmd_keepenv(util_name.as_ref()) @@ -1493,4 +1500,25 @@ mod tests { let ts = TestScenario::new("no test name"); assert!(expected_result(&ts, &[]).is_err()); } + + #[test] + #[cfg(unix)] + fn test_host_name_for() { + #[cfg(target_os = "linux")] + { + std::assert_eq!(host_name_for("id"), "id"); + std::assert_eq!(host_name_for("groups"), "groups"); + std::assert_eq!(host_name_for("who"), "who"); + } + #[cfg(not(target_os = "linux"))] + { + // spell-checker:ignore (strings) ggroups gwho + std::assert_eq!(host_name_for("id"), "gid"); + std::assert_eq!(host_name_for("groups"), "ggroups"); + std::assert_eq!(host_name_for("who"), "gwho"); + std::assert_eq!(host_name_for("gid"), "gid"); + std::assert_eq!(host_name_for("ggroups"), "ggroups"); + std::assert_eq!(host_name_for("gwho"), "gwho"); + } + } } From 450a3faf9fa5edf061e327acf25647cf85692616 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Mon, 13 Sep 2021 12:29:28 +0200 Subject: [PATCH 181/206] Add to the spell ignore list --- tests/common/util.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/common/util.rs b/tests/common/util.rs index 47d6a8850..704e9b163 100644 --- a/tests/common/util.rs +++ b/tests/common/util.rs @@ -3,7 +3,7 @@ // * For the full copyright and license information, please view the LICENSE // * file that was distributed with this source code. -//spell-checker: ignore (linux) rlimit prlimit Rlim coreutil +//spell-checker: ignore (linux) rlimit prlimit Rlim coreutil ggroups #![allow(dead_code)] From ebe897e4d426b171763e587d92774e2118a2aabd Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Mon, 13 Sep 2021 18:05:06 +0200 Subject: [PATCH 182/206] shred: remove unused "time" dep (#2665) --- Cargo.lock | 1 - src/uu/shred/Cargo.toml | 1 - 2 files changed, 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cf710ea00..be0f3adf2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2941,7 +2941,6 @@ dependencies = [ "filetime", "libc", "rand 0.7.3", - "time", "uucore", "uucore_procs", ] diff --git a/src/uu/shred/Cargo.toml b/src/uu/shred/Cargo.toml index f515044c0..d6bb61c76 100644 --- a/src/uu/shred/Cargo.toml +++ b/src/uu/shred/Cargo.toml @@ -19,7 +19,6 @@ clap = { version = "2.33", features = ["wrap_help"] } filetime = "0.2.1" libc = "0.2.42" rand = "0.7" -time = "0.1.40" uucore = { version=">=0.0.9", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_procs" } From 68df4de1a21972c7c49c85e7a11bf6ed33e3a190 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Mon, 13 Sep 2021 18:08:41 +0200 Subject: [PATCH 183/206] nice: update to use the same version of nix as other programs (#2666) --- Cargo.lock | 21 +-------------------- src/uu/nice/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 21 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index be0f3adf2..a46207e5f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1104,19 +1104,6 @@ dependencies = [ "winapi 0.3.9", ] -[[package]] -name = "nix" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dbdc256eaac2e3bd236d93ad999d3479ef775c863dbda3068c4006a92eec51b" -dependencies = [ - "bitflags", - "cc", - "cfg-if 0.1.10", - "libc", - "void", -] - [[package]] name = "nix" version = "0.19.1" @@ -2712,7 +2699,7 @@ version = "0.0.7" dependencies = [ "clap", "libc", - "nix 0.13.1", + "nix 0.20.0", "uucore", "uucore_procs", ] @@ -3312,12 +3299,6 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" -[[package]] -name = "void" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" - [[package]] name = "walkdir" version = "2.3.2" diff --git a/src/uu/nice/Cargo.toml b/src/uu/nice/Cargo.toml index 31c310790..5f0c2c07a 100644 --- a/src/uu/nice/Cargo.toml +++ b/src/uu/nice/Cargo.toml @@ -17,7 +17,7 @@ path = "src/nice.rs" [dependencies] clap = { version = "2.33", features = ["wrap_help"] } libc = "0.2.42" -nix = { version="<=0.13" } +nix = "0.20" uucore = { version=">=0.0.9", package="uucore", path="../../uucore" } uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_procs" } From 87b6aa89e38a1b0947700f763db2e400cc031e40 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Mon, 13 Sep 2021 18:08:51 +0200 Subject: [PATCH 184/206] pr: remove unused "time" dep (#2667) --- Cargo.lock | 1 - src/uu/pr/Cargo.toml | 2 -- 2 files changed, 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a46207e5f..8759a133d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2799,7 +2799,6 @@ dependencies = [ "itertools 0.10.1", "quick-error 2.0.1", "regex", - "time", "uucore", "uucore_procs", ] diff --git a/src/uu/pr/Cargo.toml b/src/uu/pr/Cargo.toml index d446e66a0..fd46e817e 100644 --- a/src/uu/pr/Cargo.toml +++ b/src/uu/pr/Cargo.toml @@ -19,8 +19,6 @@ clap = { version = "2.33", features = ["wrap_help"] } uucore = { version=">=0.0.7", package="uucore", path="../../uucore", features=["entries"] } uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_procs" } getopts = "0.2.21" -time = "0.1.41" -# A higher version would cause a conflict with time chrono = "0.4.19" quick-error = "2.0.1" itertools = "0.10" From cca2b19b4e6c892a963e94d6c7b28211fe17d9d9 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sun, 12 Sep 2021 22:23:12 +0200 Subject: [PATCH 185/206] tail: only set the winapi dep on Windows --- src/uu/tail/Cargo.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/uu/tail/Cargo.toml b/src/uu/tail/Cargo.toml index 6928330de..0fe84670e 100644 --- a/src/uu/tail/Cargo.toml +++ b/src/uu/tail/Cargo.toml @@ -19,6 +19,8 @@ clap = { version = "2.33", features = ["wrap_help"] } libc = "0.2.42" uucore = { version=">=0.0.9", package="uucore", path="../../uucore", features=["ringbuffer"] } uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_procs" } + +[target.'cfg(windows)'.dependencies] winapi = { version="0.3", features=["fileapi", "handleapi", "processthreadsapi", "synchapi", "winbase"] } [target.'cfg(target_os = "redox")'.dependencies] From 2d67252dc4c2d282d8f311681fdc0be4fee13260 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sun, 12 Sep 2021 19:20:30 +0200 Subject: [PATCH 186/206] rm: only set the winapi dep on Windows --- src/uu/rm/Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/uu/rm/Cargo.toml b/src/uu/rm/Cargo.toml index 2c30446e8..c356f03e4 100644 --- a/src/uu/rm/Cargo.toml +++ b/src/uu/rm/Cargo.toml @@ -18,11 +18,11 @@ path = "src/rm.rs" clap = { version = "2.33", features = ["wrap_help"] } walkdir = "2.2" remove_dir_all = "0.5.1" -winapi = { version="0.3", features=[] } - uucore = { version=">=0.0.9", package="uucore", path="../../uucore", features=["fs"] } uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_procs" } +[target.'cfg(windows)'.dependencies] +winapi = { version="0.3", features=[] } [[bin]] name = "rm" From 826c948234ab903e0df731761703b8fbcadeab0a Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sun, 12 Sep 2021 19:21:40 +0200 Subject: [PATCH 187/206] ls: remove the unused dep on locale --- Cargo.lock | 10 ---------- src/uu/ls/Cargo.toml | 1 - 2 files changed, 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8759a133d..56464ef98 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1004,15 +1004,6 @@ dependencies = [ "winapi 0.3.9", ] -[[package]] -name = "locale" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fdbe492a9c0238da900a1165c42fc5067161ce292678a6fe80921f30fe307fd" -dependencies = [ - "libc", -] - [[package]] name = "lock_api" version = "0.4.4" @@ -2615,7 +2606,6 @@ dependencies = [ "clap", "globset", "lazy_static", - "locale", "lscolors", "number_prefix", "once_cell", diff --git a/src/uu/ls/Cargo.toml b/src/uu/ls/Cargo.toml index dbe6bacaa..6c4858a1c 100644 --- a/src/uu/ls/Cargo.toml +++ b/src/uu/ls/Cargo.toml @@ -15,7 +15,6 @@ edition = "2018" path = "src/ls.rs" [dependencies] -locale = "0.2.2" chrono = "0.4.19" clap = { version = "2.33", features = ["wrap_help"] } unicode-width = "0.1.8" From a6c235bcd10864db63b16556c45c74aa172ad4b0 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sun, 12 Sep 2021 19:22:22 +0200 Subject: [PATCH 188/206] csplit: remove the unused dep on glob --- Cargo.lock | 1 - src/uu/csplit/Cargo.toml | 1 - 2 files changed, 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 56464ef98..b4c8aa9b1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2292,7 +2292,6 @@ name = "uu_csplit" version = "0.0.7" dependencies = [ "clap", - "glob", "regex", "thiserror", "uucore", diff --git a/src/uu/csplit/Cargo.toml b/src/uu/csplit/Cargo.toml index 82389a93b..3bc44b90c 100644 --- a/src/uu/csplit/Cargo.toml +++ b/src/uu/csplit/Cargo.toml @@ -18,7 +18,6 @@ path = "src/csplit.rs" clap = { version = "2.33", features = ["wrap_help"] } thiserror = "1.0" regex = "1.0.0" -glob = "0.3" uucore = { version=">=0.0.9", package="uucore", path="../../uucore", features=["entries", "fs"] } uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_procs" } From a74e4bf09599d1fd8dfd16190b6317055052c52e Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sun, 12 Sep 2021 19:59:54 +0200 Subject: [PATCH 189/206] shred: remove the unused dep on filetime --- Cargo.lock | 1 - src/uu/shred/Cargo.toml | 1 - 2 files changed, 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b4c8aa9b1..eb2c709ff 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2913,7 +2913,6 @@ name = "uu_shred" version = "0.0.7" dependencies = [ "clap", - "filetime", "libc", "rand 0.7.3", "uucore", diff --git a/src/uu/shred/Cargo.toml b/src/uu/shred/Cargo.toml index d6bb61c76..5f7bebb4e 100644 --- a/src/uu/shred/Cargo.toml +++ b/src/uu/shred/Cargo.toml @@ -16,7 +16,6 @@ path = "src/shred.rs" [dependencies] clap = { version = "2.33", features = ["wrap_help"] } -filetime = "0.2.1" libc = "0.2.42" rand = "0.7" uucore = { version=">=0.0.9", package="uucore", path="../../uucore" } From 2a4422997d4e2565875e629b79d5bfe077b257ac Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Mon, 13 Sep 2021 20:09:13 +0200 Subject: [PATCH 190/206] Restrict some crates to specific OS --- Cargo.toml | 7 +++++-- src/uucore/Cargo.toml | 4 +++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 58b6aa52a..7b1399abf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -366,7 +366,6 @@ conv = "0.3" filetime = "0.2" glob = "0.3.0" libc = "0.2" -nix = "0.20.0" pretty_assertions = "0.7.2" rand = "0.7" regex = "1.0" @@ -378,8 +377,12 @@ uucore = { version=">=0.0.9", package="uucore", path="src/uucore", features=["en walkdir = "2.2" atty = "0.2" -[target.'cfg(unix)'.dev-dependencies] +[target.'cfg(target_os = "linux")'.dev-dependencies] rlimit = "0.4.0" + +[target.'cfg(unix)'.dev-dependencies] +nix = "0.20.0" + rust-users = { version="0.10", package="users" } unix_socket = "0.5.0" diff --git a/src/uucore/Cargo.toml b/src/uucore/Cargo.toml index 80a3a115b..3f2276bd3 100644 --- a/src/uucore/Cargo.toml +++ b/src/uucore/Cargo.toml @@ -24,13 +24,15 @@ wild = "2.0" # * optional thiserror = { version="1.0", optional=true } time = { version="<= 0.1.43", optional=true } -walkdir = { version="2.3.2", optional=true } # * "problem" dependencies (pinned) data-encoding = { version="2.1", optional=true } data-encoding-macro = { version="0.1.12", optional=true } z85 = { version="3.0.3", optional=true } libc = { version="0.2.15", optional=true } once_cell = "1.8.0" + +[target.'cfg(unix)'.dependencies] +walkdir = { version="2.3.2", optional=true } nix = { version="0.20", optional=true } [dev-dependencies] From 4555c8556405d18c15385c4d5fbe5dbd328f4ee4 Mon Sep 17 00:00:00 2001 From: Jan Verbeek Date: Fri, 27 Aug 2021 20:36:26 +0200 Subject: [PATCH 191/206] whoami: Cleanup - Use modern conventions - Restrict the scope of unsafe - Do not use deprecated `std::mem::unitialized()` - Do not bake unicode into design --- Cargo.lock | 1 + src/uu/whoami/Cargo.toml | 5 ++- src/uu/whoami/src/platform/unix.rs | 14 ++++---- src/uu/whoami/src/platform/windows.rs | 31 +++++++++-------- src/uu/whoami/src/whoami.rs | 49 +++++++-------------------- 5 files changed, 41 insertions(+), 59 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 91e2f1762..d767526ab 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3222,6 +3222,7 @@ name = "uu_whoami" version = "0.0.7" dependencies = [ "clap", + "libc", "uucore", "uucore_procs", "winapi 0.3.9", diff --git a/src/uu/whoami/Cargo.toml b/src/uu/whoami/Cargo.toml index 919aab2e5..d12ea1aea 100644 --- a/src/uu/whoami/Cargo.toml +++ b/src/uu/whoami/Cargo.toml @@ -16,12 +16,15 @@ path = "src/whoami.rs" [dependencies] clap = { version = "2.33", features = ["wrap_help"] } -uucore = { version=">=0.0.9", package="uucore", path="../../uucore", features=["entries", "wide"] } +uucore = { version=">=0.0.9", package="uucore", path="../../uucore", features=["entries"] } uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_procs" } [target.'cfg(target_os = "windows")'.dependencies] winapi = { version = "0.3", features = ["lmcons"] } +[target.'cfg(unix)'.dependencies] +libc = "0.2.42" + [[bin]] name = "whoami" path = "src/main.rs" diff --git a/src/uu/whoami/src/platform/unix.rs b/src/uu/whoami/src/platform/unix.rs index 3d5fc6f54..1c0ea15d5 100644 --- a/src/uu/whoami/src/platform/unix.rs +++ b/src/uu/whoami/src/platform/unix.rs @@ -8,14 +8,14 @@ * file that was distributed with this source code. */ -// spell-checker:ignore (ToDO) getusername +use std::ffi::OsString; +use std::io; -use std::io::Result; use uucore::entries::uid2usr; -use uucore::libc::geteuid; -pub unsafe fn get_username() -> Result { - // Get effective user id - let uid = geteuid(); - uid2usr(uid) +pub fn get_username() -> io::Result { + // SAFETY: getuid() does nothing with memory and is always successful. + let uid = unsafe { libc::geteuid() }; + // uid2usr should arguably return an OsString but currently doesn't + uid2usr(uid).map(Into::into) } diff --git a/src/uu/whoami/src/platform/windows.rs b/src/uu/whoami/src/platform/windows.rs index 3fe8eb1e7..8afa18b63 100644 --- a/src/uu/whoami/src/platform/windows.rs +++ b/src/uu/whoami/src/platform/windows.rs @@ -7,22 +7,23 @@ * file that was distributed with this source code. */ -extern crate winapi; +use std::ffi::OsString; +use std::io; +use std::os::windows::ffi::OsStringExt; -use self::winapi::shared::lmcons; -use self::winapi::shared::minwindef; -use self::winapi::um::{winbase, winnt}; -use std::io::{Error, Result}; -use std::mem; -use uucore::wide::FromWide; +use winapi::shared::lmcons; +use winapi::shared::minwindef::DWORD; +use winapi::um::winbase; -pub unsafe fn get_username() -> Result { - #[allow(deprecated)] - let mut buffer: [winnt::WCHAR; lmcons::UNLEN as usize + 1] = mem::uninitialized(); - let mut len = buffer.len() as minwindef::DWORD; - if winbase::GetUserNameW(buffer.as_mut_ptr(), &mut len) == 0 { - return Err(Error::last_os_error()); +pub fn get_username() -> io::Result { + const BUF_LEN: DWORD = lmcons::UNLEN + 1; + let mut buffer = [0_u16; BUF_LEN as usize]; + let mut len = BUF_LEN; + // SAFETY: buffer.len() == len + unsafe { + if winbase::GetUserNameW(buffer.as_mut_ptr(), &mut len) == 0 { + return Err(io::Error::last_os_error()); + } } - let username = String::from_wide(&buffer[..len as usize - 1]); - Ok(username) + Ok(OsString::from_wide(&buffer[..len as usize - 1])) } diff --git a/src/uu/whoami/src/whoami.rs b/src/uu/whoami/src/whoami.rs index 3033a078a..830b86e63 100644 --- a/src/uu/whoami/src/whoami.rs +++ b/src/uu/whoami/src/whoami.rs @@ -1,5 +1,3 @@ -use clap::App; - // * This file is part of the uutils coreutils package. // * // * (c) Jordi Boggiano @@ -14,46 +12,25 @@ extern crate clap; #[macro_use] extern crate uucore; -use uucore::error::{UResult, USimpleError}; +use clap::App; + +use uucore::display::println_verbatim; +use uucore::error::{FromIo, UResult}; mod platform; +static ABOUT: &str = "Print the current username."; + #[uucore_procs::gen_uumain] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let app = uu_app(); - - if let Err(err) = app.get_matches_from_safe(args) { - if err.kind == clap::ErrorKind::HelpDisplayed - || err.kind == clap::ErrorKind::VersionDisplayed - { - println!("{}", err); - Ok(()) - } else { - return Err(USimpleError::new(1, format!("{}", err))); - } - } else { - exec() - } + uu_app().get_matches_from(args); + let username = platform::get_username().map_err_context(|| "failed to get username".into())?; + println_verbatim(&username).map_err_context(|| "failed to print username".into())?; + Ok(()) } pub fn uu_app() -> App<'static, 'static> { - app_from_crate!() -} - -pub fn exec() -> UResult<()> { - unsafe { - match platform::get_username() { - Ok(username) => { - println!("{}", username); - Ok(()) - } - Err(err) => match err.raw_os_error() { - Some(0) | None => Err(USimpleError::new(1, "failed to get username")), - Some(_) => Err(USimpleError::new( - 1, - format!("failed to get username: {}", err), - )), - }, - } - } + App::new(uucore::util_name()) + .version(crate_version!()) + .about(ABOUT) } From 60025800c3cd7a1d43c23e2423c3b81110a2e30e Mon Sep 17 00:00:00 2001 From: Jeffrey Finkelstein Date: Sat, 11 Sep 2021 23:10:13 -0400 Subject: [PATCH 192/206] seq: trim leading whitespace from inputs --- src/uu/seq/src/seq.rs | 1 + tests/by-util/test_seq.rs | 24 ++++++++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/src/uu/seq/src/seq.rs b/src/uu/seq/src/seq.rs index 493b0c589..8aef226b5 100644 --- a/src/uu/seq/src/seq.rs +++ b/src/uu/seq/src/seq.rs @@ -99,6 +99,7 @@ impl Number { impl FromStr for Number { type Err = String; fn from_str(mut s: &str) -> Result { + s = s.trim_start(); if s.starts_with('+') { s = &s[1..]; } diff --git a/tests/by-util/test_seq.rs b/tests/by-util/test_seq.rs index 4325c7fba..65f2451f0 100644 --- a/tests/by-util/test_seq.rs +++ b/tests/by-util/test_seq.rs @@ -200,3 +200,27 @@ fn test_neg_inf() { fn test_inf() { run(&["inf"], b"1\n2\n3\n"); } + +#[test] +fn test_ignore_leading_whitespace() { + new_ucmd!() + .arg(" 1") + .succeeds() + .stdout_is("1\n") + .no_stderr(); +} + +#[test] +fn test_trailing_whitespace_error() { + // In some locales, the GNU error message has curly quotes (‘) + // instead of straight quotes ('). We just test the straight single + // quotes. + new_ucmd!() + .arg("1 ") + .fails() + .no_stdout() + .stderr_contains("seq: invalid floating point argument: '1 '") + // FIXME The second line of the error message is "Try 'seq + // --help' for more information." + .stderr_contains("for more information."); +} From 0a3785bf8411be00e1032b51eb9b599852ccb979 Mon Sep 17 00:00:00 2001 From: Jan Verbeek Date: Tue, 14 Sep 2021 12:25:17 +0200 Subject: [PATCH 193/206] whoami: Run tests on Windows --- tests/by-util/test_whoami.rs | 2 -- tests/common/util.rs | 10 ++++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/by-util/test_whoami.rs b/tests/by-util/test_whoami.rs index 3e8d5afa6..340c1434f 100644 --- a/tests/by-util/test_whoami.rs +++ b/tests/by-util/test_whoami.rs @@ -3,7 +3,6 @@ // * For the full copyright and license information, please view the LICENSE // * file that was distributed with this source code. -#[cfg(unix)] use crate::common::util::*; #[test] @@ -34,7 +33,6 @@ fn test_normal_compare_id() { } #[test] -#[cfg(unix)] fn test_normal_compare_env() { let whoami = whoami(); if whoami == "nobody" { diff --git a/tests/common/util.rs b/tests/common/util.rs index 704e9b163..f3cdec010 100644 --- a/tests/common/util.rs +++ b/tests/common/util.rs @@ -1069,10 +1069,12 @@ pub fn whoami() -> String { // Use environment variable to get current user instead of // invoking `whoami` and fall back to user "nobody" on error. - std::env::var("USER").unwrap_or_else(|e| { - println!("{}: {}, using \"nobody\" instead", UUTILS_WARNING, e); - "nobody".to_string() - }) + std::env::var("USER") + .or_else(|_| std::env::var("USERNAME")) + .unwrap_or_else(|e| { + println!("{}: {}, using \"nobody\" instead", UUTILS_WARNING, e); + "nobody".to_string() + }) } /// Add prefix 'g' for `util_name` if not on linux From 1edd2bf3a89e5cf027bee026341d93d660a97cbb Mon Sep 17 00:00:00 2001 From: Jan Verbeek Date: Tue, 14 Sep 2021 15:07:57 +0200 Subject: [PATCH 194/206] Do not discard non-OS error messages --- .../cspell.dictionaries/jargon.wordlist.txt | 3 + src/uu/install/src/install.rs | 9 +-- src/uu/ls/src/ls.rs | 11 ++- src/uucore/src/lib/mods/error.rs | 76 ++++++++++++------- 4 files changed, 64 insertions(+), 35 deletions(-) diff --git a/.vscode/cspell.dictionaries/jargon.wordlist.txt b/.vscode/cspell.dictionaries/jargon.wordlist.txt index cd1cc18b3..ed9e3d738 100644 --- a/.vscode/cspell.dictionaries/jargon.wordlist.txt +++ b/.vscode/cspell.dictionaries/jargon.wordlist.txt @@ -43,6 +43,9 @@ gibi gibibytes glob globbing +hardcode +hardcoded +hardcoding hardfloat hardlink hardlinks diff --git a/src/uu/install/src/install.rs b/src/uu/install/src/install.rs index a9f91f658..1c09f7f34 100644 --- a/src/uu/install/src/install.rs +++ b/src/uu/install/src/install.rs @@ -490,11 +490,10 @@ fn copy_files_into_dir(files: &[PathBuf], target_dir: &Path, b: &Behavior) -> UR return Err(InstallError::TargetDirIsntDir(target_dir.to_path_buf()).into()); } for sourcepath in files.iter() { - if !sourcepath.exists() { - let err = UIoError::new( - std::io::ErrorKind::NotFound, - format!("cannot stat {}", sourcepath.quote()), - ); + if let Err(err) = sourcepath + .metadata() + .map_err_context(|| format!("cannot stat {}", sourcepath.quote())) + { show!(err); continue; } diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index fad30157c..c5c65334e 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -42,7 +42,7 @@ use std::{ use term_grid::{Cell, Direction, Filling, Grid, GridOptions}; use uucore::{ display::Quotable, - error::{set_exit_code, FromIo, UError, UResult}, + error::{set_exit_code, UError, UResult}, }; use unicode_width::UnicodeWidthStr; @@ -1257,8 +1257,13 @@ fn list(locs: Vec<&Path>, config: Config) -> UResult<()> { let path_data = PathData::new(p, None, None, &config, true); if path_data.md().is_none() { - show!(std::io::ErrorKind::NotFound - .map_err_context(|| format!("cannot access {}", path_data.p_buf.quote()))); + // FIXME: Would be nice to use the actual error instead of hardcoding it + // Presumably other errors can happen too? + show_error!( + "cannot access {}: No such file or directory", + path_data.p_buf.quote() + ); + set_exit_code(1); // We found an error, no need to continue the execution continue; } diff --git a/src/uucore/src/lib/mods/error.rs b/src/uucore/src/lib/mods/error.rs index 11ec91bdf..c04a0f2f1 100644 --- a/src/uucore/src/lib/mods/error.rs +++ b/src/uucore/src/lib/mods/error.rs @@ -393,34 +393,56 @@ impl Display for UIoError { fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> { use std::io::ErrorKind::*; - let message; - let message = match self.inner.kind() { - NotFound => "No such file or directory", - PermissionDenied => "Permission denied", - ConnectionRefused => "Connection refused", - ConnectionReset => "Connection reset", - ConnectionAborted => "Connection aborted", - NotConnected => "Not connected", - AddrInUse => "Address in use", - AddrNotAvailable => "Address not available", - BrokenPipe => "Broken pipe", - AlreadyExists => "Already exists", - WouldBlock => "Would block", - InvalidInput => "Invalid input", - InvalidData => "Invalid data", - TimedOut => "Timed out", - WriteZero => "Write zero", - Interrupted => "Interrupted", - UnexpectedEof => "Unexpected end of file", - _ => { - // TODO: using `strip_errno()` causes the error message - // to not be capitalized. When the new error variants (https://github.com/rust-lang/rust/issues/86442) - // are stabilized, we should add them to the match statement. - message = strip_errno(&self.inner); - &message + let mut message; + let message = if self.inner.raw_os_error().is_some() { + // These are errors that come directly from the OS. + // We want to normalize their messages across systems, + // and we want to strip the "(os error X)" suffix. + match self.inner.kind() { + NotFound => "No such file or directory", + PermissionDenied => "Permission denied", + ConnectionRefused => "Connection refused", + ConnectionReset => "Connection reset", + ConnectionAborted => "Connection aborted", + NotConnected => "Not connected", + AddrInUse => "Address in use", + AddrNotAvailable => "Address not available", + BrokenPipe => "Broken pipe", + AlreadyExists => "Already exists", + WouldBlock => "Would block", + InvalidInput => "Invalid input", + InvalidData => "Invalid data", + TimedOut => "Timed out", + WriteZero => "Write zero", + Interrupted => "Interrupted", + UnexpectedEof => "Unexpected end of file", + _ => { + // TODO: When the new error variants + // (https://github.com/rust-lang/rust/issues/86442) + // are stabilized, we should add them to the match statement. + message = strip_errno(&self.inner); + capitalize(&mut message); + &message + } } + } else { + // These messages don't need as much normalization, and the above + // messages wouldn't always be a good substitute. + // For example, ErrorKind::NotFound doesn't necessarily mean it was + // a file that was not found. + // There are also errors with entirely custom messages. + message = self.inner.to_string(); + capitalize(&mut message); + &message }; - write!(f, "{}: {}", self.context, message,) + write!(f, "{}: {}", self.context, message) + } +} + +/// Capitalize the first character of an ASCII string. +fn capitalize(text: &mut str) { + if let Some(first) = text.get_mut(..1) { + first.make_ascii_uppercase(); } } @@ -428,7 +450,7 @@ impl Display for UIoError { pub fn strip_errno(err: &std::io::Error) -> String { let mut msg = err.to_string(); if let Some(pos) = msg.find(" (os error ") { - msg.drain(pos..); + msg.truncate(pos); } msg } From 519c0d16b3128cd7d5df7be3b6ea4ae289dcc69f Mon Sep 17 00:00:00 2001 From: Jan Verbeek Date: Tue, 14 Sep 2021 21:14:49 +0200 Subject: [PATCH 195/206] uucore::utmpx: Make thread-safe --- src/uu/users/src/users.rs | 15 ++-- src/uu/who/src/who.rs | 5 +- src/uucore/src/lib/features/utmpx.rs | 102 +++++++++++++++++++++------ 3 files changed, 92 insertions(+), 30 deletions(-) diff --git a/src/uu/users/src/users.rs b/src/uu/users/src/users.rs index 866093189..d374df181 100644 --- a/src/uu/users/src/users.rs +++ b/src/uu/users/src/users.rs @@ -8,6 +8,8 @@ // spell-checker:ignore (paths) wtmp +use std::path::Path; + use clap::{crate_version, App, Arg}; use uucore::utmpx::{self, Utmpx}; @@ -36,19 +38,18 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .after_help(&after_help[..]) .get_matches_from(args); - let files: Vec = matches - .values_of(ARG_FILES) - .map(|v| v.map(ToString::to_string).collect()) + let files: Vec<&Path> = matches + .values_of_os(ARG_FILES) + .map(|v| v.map(AsRef::as_ref).collect()) .unwrap_or_default(); let filename = if !files.is_empty() { - files[0].as_ref() + files[0] } else { - utmpx::DEFAULT_FILE + utmpx::DEFAULT_FILE.as_ref() }; - let mut users = Utmpx::iter_all_records() - .read_from(filename) + let mut users = Utmpx::iter_all_records_from(filename) .filter(Utmpx::is_user_process) .map(|ut| ut.user()) .collect::>(); diff --git a/src/uu/who/src/who.rs b/src/uu/who/src/who.rs index d61747127..a975c82ba 100644 --- a/src/uu/who/src/who.rs +++ b/src/uu/who/src/who.rs @@ -341,15 +341,14 @@ impl Who { utmpx::DEFAULT_FILE }; if self.short_list { - let users = Utmpx::iter_all_records() - .read_from(f) + let users = Utmpx::iter_all_records_from(f) .filter(Utmpx::is_user_process) .map(|ut| ut.user()) .collect::>(); println!("{}", users.join(" ")); println!("# users={}", users.len()); } else { - let records = Utmpx::iter_all_records().read_from(f).peekable(); + let records = Utmpx::iter_all_records_from(f).peekable(); if self.include_heading { self.print_heading() diff --git a/src/uucore/src/lib/features/utmpx.rs b/src/uucore/src/lib/features/utmpx.rs index 8f43ba769..a96c3f48c 100644 --- a/src/uucore/src/lib/features/utmpx.rs +++ b/src/uucore/src/lib/features/utmpx.rs @@ -24,7 +24,7 @@ //! //! ``` //! use uucore::utmpx::Utmpx; -//! for ut in Utmpx::iter_all_records().read_from("/some/where/else") { +//! for ut in Utmpx::iter_all_records_from("/some/where/else") { //! if ut.is_user_process() { //! println!("{}: {}", ut.host(), ut.user()) //! } @@ -35,9 +35,12 @@ pub extern crate time; use self::time::{Timespec, Tm}; use std::ffi::CString; -use std::io::Error as IOError; use std::io::Result as IOResult; +use std::marker::PhantomData; +use std::os::unix::ffi::OsStrExt; +use std::path::Path; use std::ptr; +use std::sync::{Mutex, MutexGuard}; pub use self::ut::*; use libc::utmpx; @@ -54,7 +57,9 @@ pub unsafe extern "C" fn utmpxname(_file: *const libc::c_char) -> libc::c_int { 0 } -pub use crate::*; // import macros from `../../macros.rs` +use once_cell::sync::Lazy; + +use crate::*; // import macros from `../../macros.rs` // In case the c_char array doesn't end with NULL macro_rules! chars2string { @@ -248,30 +253,76 @@ impl Utmpx { Ok(host.to_string()) } + /// Iterate through all the utmp records. + /// + /// This will use the default location, or the path [`Utmpx::iter_all_records_from`] + /// was most recently called with. + /// + /// Only one instance of [`UtmpxIter`] may be active at a time. This + /// function will block as long as one is still active. Beware! pub fn iter_all_records() -> UtmpxIter { - UtmpxIter + let iter = UtmpxIter::new(); + unsafe { + // This can technically fail, and it would be nice to detect that, + // but it doesn't return anything so we'd have to do nasty things + // with errno. + setutxent(); + } + iter + } + + /// Iterate through all the utmp records from a specific file. + /// + /// No failure is reported or detected. + /// + /// This function affects subsequent calls to [`Utmpx::iter_all_records`]. + /// + /// The same caveats as for [`Utmpx::iter_all_records`] apply. + pub fn iter_all_records_from>(path: P) -> UtmpxIter { + let iter = UtmpxIter::new(); + let path = CString::new(path.as_ref().as_os_str().as_bytes()).unwrap(); + unsafe { + // In glibc, utmpxname() only fails if there's not enough memory + // to copy the string. + // Solaris returns 1 on success instead of 0. Supposedly there also + // exist systems where it returns void. + // GNU who on Debian seems to output nothing if an invalid filename + // is specified, no warning or anything. + // So this function is pretty crazy and we don't try to detect errors. + // Not much we can do besides pray. + utmpxname(path.as_ptr()); + setutxent(); + } + iter } } +// On some systems these functions are not thread-safe. On others they're +// thread-local. Therefore we use a mutex to allow only one guard to exist at +// a time, and make sure UtmpxIter cannot be sent across threads. +// +// I believe the only technical memory unsafety that could happen is a data +// race while copying the data out of the pointer returned by getutxent(), but +// ordinary race conditions are also very much possible. +static LOCK: Lazy> = Lazy::new(|| Mutex::new(())); + /// Iterator of login records -pub struct UtmpxIter; +pub struct UtmpxIter { + #[allow(dead_code)] + guard: MutexGuard<'static, ()>, + /// Ensure UtmpxIter is !Send. Technically redundant because MutexGuard + /// is also !Send. + phantom: PhantomData>, +} impl UtmpxIter { - /// Sets the name of the utmpx-format file for the other utmpx functions to access. - /// - /// If not set, default record file will be used(file path depends on the target OS) - pub fn read_from(self, f: &str) -> Self { - let res = unsafe { - let cstring = CString::new(f).unwrap(); - utmpxname(cstring.as_ptr()) - }; - if res != 0 { - show_warning!("utmpxname: {}", IOError::last_os_error()); + fn new() -> Self { + // PoisonErrors can safely be ignored + let guard = LOCK.lock().unwrap_or_else(|err| err.into_inner()); + UtmpxIter { + guard, + phantom: PhantomData, } - unsafe { - setutxent(); - } - self } } @@ -281,13 +332,24 @@ impl Iterator for UtmpxIter { unsafe { let res = getutxent(); if !res.is_null() { + // The data behind this pointer will be replaced by the next + // call to getutxent(), so we have to read it now. + // All the strings live inline in the struct as arrays, which + // makes things easier. Some(Utmpx { inner: ptr::read(res as *const _), }) } else { - endutxent(); None } } } } + +impl Drop for UtmpxIter { + fn drop(&mut self) { + unsafe { + endutxent(); + } + } +} From 53a91be2dfe11a87c05c5de7d085e81291e8909d Mon Sep 17 00:00:00 2001 From: Jeffrey Finkelstein Date: Tue, 14 Sep 2021 20:54:31 -0400 Subject: [PATCH 196/206] seq: add is_first_iteration to avoid comparisons Add the `is_first_iteration` Boolean variable to the `print_seq()` function in order to avoid unnecessary comparisons. Specifically, before this change, the `done_printing()` function was called twice on each iteration of the main loop. After this change, it is only called once per iteration. Furthermore, this change makes the `print_seq()` function similar in structure to the `print_seq_integers()` function. Co-authored-by: Jan Verbeek --- src/uu/seq/src/seq.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/uu/seq/src/seq.rs b/src/uu/seq/src/seq.rs index 8aef226b5..0cea90838 100644 --- a/src/uu/seq/src/seq.rs +++ b/src/uu/seq/src/seq.rs @@ -287,7 +287,12 @@ fn print_seq( let (first, increment, last) = range; let mut i = 0isize; let mut value = first + i as f64 * increment; + let mut is_first_iteration = true; while !done_printing(&value, &increment, &last) { + if !is_first_iteration { + write!(stdout, "{}", separator)?; + } + is_first_iteration = false; let istr = format!("{:.*}", largest_dec, value); let ilen = istr.len(); let before_dec = istr.find('.').unwrap_or(ilen); @@ -299,11 +304,8 @@ fn print_seq( write!(stdout, "{}", istr)?; i += 1; value = first + i as f64 * increment; - if !done_printing(&value, &increment, &last) { - write!(stdout, "{}", separator)?; - } } - if (first >= last && increment < 0f64) || (first <= last && increment > 0f64) { + if !is_first_iteration { write!(stdout, "{}", terminator)?; } stdout.flush()?; From f95ab2f43caee95fe05b9f28deabd28bb34c92b5 Mon Sep 17 00:00:00 2001 From: Jeffrey Finkelstein Date: Tue, 14 Sep 2021 21:26:50 -0400 Subject: [PATCH 197/206] uucore(panic): guard against "Broken pipe" panics Add "Broken pipe" to the set of panic messages used to determine whether a panic is caused by a broken pipe error. --- src/uucore/src/lib/mods/panic.rs | 41 +++++++++++++++++++++++++------- 1 file changed, 33 insertions(+), 8 deletions(-) diff --git a/src/uucore/src/lib/mods/panic.rs b/src/uucore/src/lib/mods/panic.rs index ba0ecdf12..ebba10429 100644 --- a/src/uucore/src/lib/mods/panic.rs +++ b/src/uucore/src/lib/mods/panic.rs @@ -1,17 +1,42 @@ +//! Custom panic hooks that allow silencing certain types of errors. +//! +//! Use the [`mute_sigpipe_panic`] function to silence panics caused by +//! broken pipe errors. This can happen when a process is still +//! producing data when the consuming process terminates and closes the +//! pipe. For example, +//! +//! ```sh +//! $ seq inf | head -n 1 +//! ``` +//! use std::panic; +use std::panic::PanicInfo; -//## SIGPIPE handling background/discussions ... -//* `uutils` ~ , -//* rust and `rg` ~ , , +/// Decide whether a panic was caused by a broken pipe (SIGPIPE) error. +fn is_broken_pipe(info: &PanicInfo) -> bool { + if let Some(res) = info.payload().downcast_ref::() { + if res.contains("BrokenPipe") || res.contains("Broken pipe") { + return true; + } + } + false +} +/// Terminate without error on panics that occur due to broken pipe errors. +/// +/// For background discussions on `SIGPIPE` handling, see +/// +/// * https://github.com/uutils/coreutils/issues/374 +/// * https://github.com/uutils/coreutils/pull/1106 +/// * https://github.com/rust-lang/rust/issues/62569 +/// * https://github.com/BurntSushi/ripgrep/issues/200 +/// * https://github.com/crev-dev/cargo-crev/issues/287 +/// pub fn mute_sigpipe_panic() { let hook = panic::take_hook(); panic::set_hook(Box::new(move |info| { - if let Some(res) = info.payload().downcast_ref::() { - if res.contains("BrokenPipe") { - return; - } + if !is_broken_pipe(info) { + hook(info) } - hook(info) })); } From 7eaae75bfcd72b4022d88fbcfab9796f26a6c57d Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sun, 12 Sep 2021 20:10:39 +0200 Subject: [PATCH 198/206] add a github action job to identify unused deps --- .github/workflows/CICD.yml | 32 +++++++++++++++++++ .../cspell.dictionaries/jargon.wordlist.txt | 1 + src/uu/base32/Cargo.toml | 4 +++ src/uu/base64/Cargo.toml | 4 +++ src/uu/basename/Cargo.toml | 4 +++ src/uu/basenc/Cargo.toml | 4 +++ src/uu/cksum/Cargo.toml | 4 +++ src/uu/comm/Cargo.toml | 4 +++ src/uu/cp/Cargo.toml | 4 +++ src/uu/csplit/Cargo.toml | 4 +++ src/uu/cut/Cargo.toml | 4 +++ src/uu/date/Cargo.toml | 4 +++ src/uu/dd/Cargo.toml | 4 +++ src/uu/dircolors/Cargo.toml | 4 +++ src/uu/expand/Cargo.toml | 4 +++ src/uu/expr/Cargo.toml | 4 +++ src/uu/factor/Cargo.toml | 4 +++ src/uu/fmt/Cargo.toml | 4 +++ src/uu/fold/Cargo.toml | 4 +++ src/uu/hashsum/Cargo.toml | 4 +++ src/uu/head/Cargo.toml | 4 +++ src/uu/join/Cargo.toml | 4 +++ src/uu/link/Cargo.toml | 4 +++ src/uu/ls/Cargo.toml | 4 +++ src/uu/more/Cargo.toml | 4 +++ src/uu/mv/Cargo.toml | 4 +++ src/uu/nl/Cargo.toml | 4 +++ src/uu/numfmt/Cargo.toml | 4 +++ src/uu/od/Cargo.toml | 4 +++ src/uu/paste/Cargo.toml | 4 +++ src/uu/pr/Cargo.toml | 4 +++ src/uu/printenv/Cargo.toml | 4 +++ src/uu/printf/Cargo.toml | 4 +++ src/uu/ptx/Cargo.toml | 4 +++ src/uu/readlink/Cargo.toml | 4 +++ src/uu/realpath/Cargo.toml | 4 +++ src/uu/relpath/Cargo.toml | 4 +++ src/uu/rm/Cargo.toml | 4 +++ src/uu/seq/Cargo.toml | 4 +++ src/uu/shred/Cargo.toml | 4 +++ src/uu/shuf/Cargo.toml | 4 +++ src/uu/split/Cargo.toml | 4 +++ src/uu/sum/Cargo.toml | 4 +++ src/uu/tac/Cargo.toml | 4 +++ src/uu/tail/Cargo.toml | 4 +++ src/uu/tee/Cargo.toml | 4 +++ src/uu/test/Cargo.toml | 4 +++ src/uu/tr/Cargo.toml | 4 +++ src/uu/truncate/Cargo.toml | 4 +++ src/uu/tsort/Cargo.toml | 4 +++ src/uu/unexpand/Cargo.toml | 4 +++ src/uu/uniq/Cargo.toml | 4 +++ src/uu/wc/Cargo.toml | 4 +++ 53 files changed, 237 insertions(+) diff --git a/.github/workflows/CICD.yml b/.github/workflows/CICD.yml index 68eaf3d06..d096ff43c 100644 --- a/.github/workflows/CICD.yml +++ b/.github/workflows/CICD.yml @@ -708,3 +708,35 @@ jobs: flags: ${{ steps.vars.outputs.CODECOV_FLAGS }} name: codecov-umbrella fail_ci_if_error: false + + unused_deps: + name: Unused deps + runs-on: ${{ matrix.job.os }} + strategy: + fail-fast: false + matrix: + job: + - { os: ubuntu-latest , features: feat_os_unix } + - { os: macos-latest , features: feat_os_macos } + - { os: windows-latest , features: feat_os_windows } + steps: + - uses: actions/checkout@v2 + - name: Install `rust` toolchain + uses: actions-rs/toolchain@v1 + with: + toolchain: nightly + default: true + profile: minimal + - name: Install `cargo-udeps` + uses: actions-rs/install@v0.1 + with: + crate: cargo-udeps + version: latest + use-tool-cache: true + env: + RUSTUP_TOOLCHAIN: stable + - name: Confirms there isn't any unused deps + shell: bash + run: | + cargo +nightly udeps --all-targets &> udeps.log || cat udeps.log + grep "seem to have been used" udeps.log diff --git a/.vscode/cspell.dictionaries/jargon.wordlist.txt b/.vscode/cspell.dictionaries/jargon.wordlist.txt index ed9e3d738..9b1d0a8da 100644 --- a/.vscode/cspell.dictionaries/jargon.wordlist.txt +++ b/.vscode/cspell.dictionaries/jargon.wordlist.txt @@ -117,6 +117,7 @@ toolchain truthy ucase unbuffered +udeps unescape unintuitive unprefixed diff --git a/src/uu/base32/Cargo.toml b/src/uu/base32/Cargo.toml index 3f6f79a0b..bc896bdb2 100644 --- a/src/uu/base32/Cargo.toml +++ b/src/uu/base32/Cargo.toml @@ -22,3 +22,7 @@ uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_p [[bin]] name = "base32" path = "src/main.rs" + +[package.metadata.cargo-udeps.ignore] +# Necessary for "make all" +normal = ["uucore_procs"] diff --git a/src/uu/base64/Cargo.toml b/src/uu/base64/Cargo.toml index ff5a9aa48..011964dc1 100644 --- a/src/uu/base64/Cargo.toml +++ b/src/uu/base64/Cargo.toml @@ -23,3 +23,7 @@ uu_base32 = { version=">=0.0.6", package="uu_base32", path="../base32"} [[bin]] name = "base64" path = "src/main.rs" + +[package.metadata.cargo-udeps.ignore] +# Necessary for "make all" +normal = ["uucore_procs"] diff --git a/src/uu/basename/Cargo.toml b/src/uu/basename/Cargo.toml index b5270eba9..b5b0a462c 100644 --- a/src/uu/basename/Cargo.toml +++ b/src/uu/basename/Cargo.toml @@ -22,3 +22,7 @@ uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_p [[bin]] name = "basename" path = "src/main.rs" + +[package.metadata.cargo-udeps.ignore] +# Necessary for "make all" +normal = ["uucore_procs"] diff --git a/src/uu/basenc/Cargo.toml b/src/uu/basenc/Cargo.toml index 17cf0ec18..e8350d83d 100644 --- a/src/uu/basenc/Cargo.toml +++ b/src/uu/basenc/Cargo.toml @@ -23,3 +23,7 @@ uu_base32 = { version=">=0.0.6", package="uu_base32", path="../base32"} [[bin]] name = "basenc" path = "src/main.rs" + +[package.metadata.cargo-udeps.ignore] +# Necessary for "make all" +normal = ["uucore_procs"] diff --git a/src/uu/cksum/Cargo.toml b/src/uu/cksum/Cargo.toml index b92b680c8..287a2285f 100644 --- a/src/uu/cksum/Cargo.toml +++ b/src/uu/cksum/Cargo.toml @@ -23,3 +23,7 @@ uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_p [[bin]] name = "cksum" path = "src/main.rs" + +[package.metadata.cargo-udeps.ignore] +# Necessary for "make all" +normal = ["uucore_procs"] diff --git a/src/uu/comm/Cargo.toml b/src/uu/comm/Cargo.toml index 1deb094e2..e44c3511c 100644 --- a/src/uu/comm/Cargo.toml +++ b/src/uu/comm/Cargo.toml @@ -23,3 +23,7 @@ uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_p [[bin]] name = "comm" path = "src/main.rs" + +[package.metadata.cargo-udeps.ignore] +# Necessary for "make all" +normal = ["uucore_procs"] diff --git a/src/uu/cp/Cargo.toml b/src/uu/cp/Cargo.toml index 66beb2501..62aef932b 100644 --- a/src/uu/cp/Cargo.toml +++ b/src/uu/cp/Cargo.toml @@ -45,3 +45,7 @@ path = "src/main.rs" [features] feat_selinux = ["selinux"] feat_acl = ["exacl"] + +[package.metadata.cargo-udeps.ignore] +# Necessary for "make all" +normal = ["uucore_procs"] diff --git a/src/uu/csplit/Cargo.toml b/src/uu/csplit/Cargo.toml index 3bc44b90c..40d4eebfa 100644 --- a/src/uu/csplit/Cargo.toml +++ b/src/uu/csplit/Cargo.toml @@ -24,3 +24,7 @@ uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_p [[bin]] name = "csplit" path = "src/main.rs" + +[package.metadata.cargo-udeps.ignore] +# Necessary for "make all" +normal = ["uucore_procs"] diff --git a/src/uu/cut/Cargo.toml b/src/uu/cut/Cargo.toml index 6f92b39d1..c49450251 100644 --- a/src/uu/cut/Cargo.toml +++ b/src/uu/cut/Cargo.toml @@ -25,3 +25,7 @@ atty = "0.2" [[bin]] name = "cut" path = "src/main.rs" + +[package.metadata.cargo-udeps.ignore] +# Necessary for "make all" +normal = ["uucore_procs"] diff --git a/src/uu/date/Cargo.toml b/src/uu/date/Cargo.toml index c144d0d81..d2af8c4f1 100644 --- a/src/uu/date/Cargo.toml +++ b/src/uu/date/Cargo.toml @@ -29,3 +29,7 @@ winapi = { version = "0.3", features = ["minwinbase", "sysinfoapi", "minwindef"] [[bin]] name = "date" path = "src/main.rs" + +[package.metadata.cargo-udeps.ignore] +# Necessary for "make all" +normal = ["uucore_procs"] diff --git a/src/uu/dd/Cargo.toml b/src/uu/dd/Cargo.toml index a0ed1ab91..007ebb8ff 100644 --- a/src/uu/dd/Cargo.toml +++ b/src/uu/dd/Cargo.toml @@ -31,3 +31,7 @@ signal-hook = "0.3.9" [[bin]] name = "dd" path = "src/main.rs" + +[package.metadata.cargo-udeps.ignore] +# Necessary for "make all" +normal = ["uucore_procs"] diff --git a/src/uu/dircolors/Cargo.toml b/src/uu/dircolors/Cargo.toml index e9e333ec6..ad95564f3 100644 --- a/src/uu/dircolors/Cargo.toml +++ b/src/uu/dircolors/Cargo.toml @@ -23,3 +23,7 @@ uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_p [[bin]] name = "dircolors" path = "src/main.rs" + +[package.metadata.cargo-udeps.ignore] +# Necessary for "make all" +normal = ["uucore_procs"] diff --git a/src/uu/expand/Cargo.toml b/src/uu/expand/Cargo.toml index 5921ef679..e9b2cc747 100644 --- a/src/uu/expand/Cargo.toml +++ b/src/uu/expand/Cargo.toml @@ -23,3 +23,7 @@ uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_p [[bin]] name = "expand" path = "src/main.rs" + +[package.metadata.cargo-udeps.ignore] +# Necessary for "make all" +normal = ["uucore_procs"] diff --git a/src/uu/expr/Cargo.toml b/src/uu/expr/Cargo.toml index 65d4fa636..035f00721 100644 --- a/src/uu/expr/Cargo.toml +++ b/src/uu/expr/Cargo.toml @@ -26,3 +26,7 @@ uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_p [[bin]] name = "expr" path = "src/main.rs" + +[package.metadata.cargo-udeps.ignore] +# Necessary for "make all" +normal = ["uucore_procs"] diff --git a/src/uu/factor/Cargo.toml b/src/uu/factor/Cargo.toml index 76c06a34c..9d62e5f2b 100644 --- a/src/uu/factor/Cargo.toml +++ b/src/uu/factor/Cargo.toml @@ -34,3 +34,7 @@ path = "src/main.rs" [lib] path = "src/cli.rs" + +[package.metadata.cargo-udeps.ignore] +# Necessary for "make all" +normal = ["uucore_procs"] diff --git a/src/uu/fmt/Cargo.toml b/src/uu/fmt/Cargo.toml index dea0726a6..75b81c354 100644 --- a/src/uu/fmt/Cargo.toml +++ b/src/uu/fmt/Cargo.toml @@ -24,3 +24,7 @@ uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_p [[bin]] name = "fmt" path = "src/main.rs" + +[package.metadata.cargo-udeps.ignore] +# Necessary for "make all" +normal = ["uucore_procs"] diff --git a/src/uu/fold/Cargo.toml b/src/uu/fold/Cargo.toml index 446be290d..7ec886264 100644 --- a/src/uu/fold/Cargo.toml +++ b/src/uu/fold/Cargo.toml @@ -22,3 +22,7 @@ uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_p [[bin]] name = "fold" path = "src/main.rs" + +[package.metadata.cargo-udeps.ignore] +# Necessary for "make all" +normal = ["uucore_procs"] diff --git a/src/uu/hashsum/Cargo.toml b/src/uu/hashsum/Cargo.toml index b4da17b71..9ea142c8b 100644 --- a/src/uu/hashsum/Cargo.toml +++ b/src/uu/hashsum/Cargo.toml @@ -33,3 +33,7 @@ uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_p [[bin]] name = "hashsum" path = "src/main.rs" + +[package.metadata.cargo-udeps.ignore] +# Necessary for "make all" +normal = ["uucore_procs"] diff --git a/src/uu/head/Cargo.toml b/src/uu/head/Cargo.toml index 1019ac74f..4fa4c0c81 100644 --- a/src/uu/head/Cargo.toml +++ b/src/uu/head/Cargo.toml @@ -22,3 +22,7 @@ uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_p [[bin]] name = "head" path = "src/main.rs" + +[package.metadata.cargo-udeps.ignore] +# Necessary for "make all" +normal = ["uucore_procs"] diff --git a/src/uu/join/Cargo.toml b/src/uu/join/Cargo.toml index f108d5a4e..7e5ced498 100644 --- a/src/uu/join/Cargo.toml +++ b/src/uu/join/Cargo.toml @@ -22,3 +22,7 @@ uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_p [[bin]] name = "join" path = "src/main.rs" + +[package.metadata.cargo-udeps.ignore] +# Necessary for "make all" +normal = ["uucore_procs"] diff --git a/src/uu/link/Cargo.toml b/src/uu/link/Cargo.toml index 025ac7554..d37ac6761 100644 --- a/src/uu/link/Cargo.toml +++ b/src/uu/link/Cargo.toml @@ -23,3 +23,7 @@ clap = { version = "2.33", features = ["wrap_help"] } [[bin]] name = "link" path = "src/main.rs" + +[package.metadata.cargo-udeps.ignore] +# Necessary for "make all" +normal = ["uucore_procs"] diff --git a/src/uu/ls/Cargo.toml b/src/uu/ls/Cargo.toml index 6c4858a1c..e907e8e02 100644 --- a/src/uu/ls/Cargo.toml +++ b/src/uu/ls/Cargo.toml @@ -34,3 +34,7 @@ lazy_static = "1.4.0" [[bin]] name = "ls" path = "src/main.rs" + +[package.metadata.cargo-udeps.ignore] +# Necessary for "make all" +normal = ["uucore_procs"] diff --git a/src/uu/more/Cargo.toml b/src/uu/more/Cargo.toml index d7bbe5c75..cd292eea9 100644 --- a/src/uu/more/Cargo.toml +++ b/src/uu/more/Cargo.toml @@ -33,3 +33,7 @@ nix = "0.19" [[bin]] name = "more" path = "src/main.rs" + +[package.metadata.cargo-udeps.ignore] +# Necessary for "make all" +normal = ["uucore_procs"] diff --git a/src/uu/mv/Cargo.toml b/src/uu/mv/Cargo.toml index 9af0cb2a3..82b1da6e1 100644 --- a/src/uu/mv/Cargo.toml +++ b/src/uu/mv/Cargo.toml @@ -23,3 +23,7 @@ uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_p [[bin]] name = "mv" path = "src/main.rs" + +[package.metadata.cargo-udeps.ignore] +# Necessary for "make all" +normal = ["uucore_procs"] diff --git a/src/uu/nl/Cargo.toml b/src/uu/nl/Cargo.toml index 57676768f..ca0d7827d 100644 --- a/src/uu/nl/Cargo.toml +++ b/src/uu/nl/Cargo.toml @@ -27,3 +27,7 @@ uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_p [[bin]] name = "nl" path = "src/main.rs" + +[package.metadata.cargo-udeps.ignore] +# Necessary for "make all" +normal = ["uucore_procs"] diff --git a/src/uu/numfmt/Cargo.toml b/src/uu/numfmt/Cargo.toml index a3bdcf261..6239da7f9 100644 --- a/src/uu/numfmt/Cargo.toml +++ b/src/uu/numfmt/Cargo.toml @@ -22,3 +22,7 @@ uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_p [[bin]] name = "numfmt" path = "src/main.rs" + +[package.metadata.cargo-udeps.ignore] +# Necessary for "make all" +normal = ["uucore_procs"] diff --git a/src/uu/od/Cargo.toml b/src/uu/od/Cargo.toml index 804183025..ee785e773 100644 --- a/src/uu/od/Cargo.toml +++ b/src/uu/od/Cargo.toml @@ -25,3 +25,7 @@ uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_p [[bin]] name = "od" path = "src/main.rs" + +[package.metadata.cargo-udeps.ignore] +# Necessary for "make all" +normal = ["uucore_procs"] diff --git a/src/uu/paste/Cargo.toml b/src/uu/paste/Cargo.toml index 19a674c3e..c4873b1d0 100644 --- a/src/uu/paste/Cargo.toml +++ b/src/uu/paste/Cargo.toml @@ -22,3 +22,7 @@ uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_p [[bin]] name = "paste" path = "src/main.rs" + +[package.metadata.cargo-udeps.ignore] +# Necessary for "make all" +normal = ["uucore_procs"] diff --git a/src/uu/pr/Cargo.toml b/src/uu/pr/Cargo.toml index fd46e817e..09993c3b9 100644 --- a/src/uu/pr/Cargo.toml +++ b/src/uu/pr/Cargo.toml @@ -27,3 +27,7 @@ regex = "1.0" [[bin]] name = "pr" path = "src/main.rs" + +[package.metadata.cargo-udeps.ignore] +# Necessary for "make all" +normal = ["uucore_procs"] diff --git a/src/uu/printenv/Cargo.toml b/src/uu/printenv/Cargo.toml index 040997393..466f69af0 100644 --- a/src/uu/printenv/Cargo.toml +++ b/src/uu/printenv/Cargo.toml @@ -22,3 +22,7 @@ uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_p [[bin]] name = "printenv" path = "src/main.rs" + +[package.metadata.cargo-udeps.ignore] +# Necessary for "make all" +normal = ["uucore_procs"] diff --git a/src/uu/printf/Cargo.toml b/src/uu/printf/Cargo.toml index a0bd27d8e..f4034083a 100644 --- a/src/uu/printf/Cargo.toml +++ b/src/uu/printf/Cargo.toml @@ -26,3 +26,7 @@ uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_p [[bin]] name = "printf" path = "src/main.rs" + +[package.metadata.cargo-udeps.ignore] +# Necessary for "make all" +normal = ["uucore_procs"] diff --git a/src/uu/ptx/Cargo.toml b/src/uu/ptx/Cargo.toml index 852379e15..fea4e5c1f 100644 --- a/src/uu/ptx/Cargo.toml +++ b/src/uu/ptx/Cargo.toml @@ -27,3 +27,7 @@ uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_p [[bin]] name = "ptx" path = "src/main.rs" + +[package.metadata.cargo-udeps.ignore] +# Necessary for "make all" +normal = ["uucore_procs"] diff --git a/src/uu/readlink/Cargo.toml b/src/uu/readlink/Cargo.toml index 9e0f939d1..8552f611d 100644 --- a/src/uu/readlink/Cargo.toml +++ b/src/uu/readlink/Cargo.toml @@ -23,3 +23,7 @@ uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_p [[bin]] name = "readlink" path = "src/main.rs" + +[package.metadata.cargo-udeps.ignore] +# Necessary for "make all" +normal = ["uucore_procs"] diff --git a/src/uu/realpath/Cargo.toml b/src/uu/realpath/Cargo.toml index f5b9af2e7..3916c4ce6 100644 --- a/src/uu/realpath/Cargo.toml +++ b/src/uu/realpath/Cargo.toml @@ -22,3 +22,7 @@ uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_p [[bin]] name = "realpath" path = "src/main.rs" + +[package.metadata.cargo-udeps.ignore] +# Necessary for "make all" +normal = ["uucore_procs"] diff --git a/src/uu/relpath/Cargo.toml b/src/uu/relpath/Cargo.toml index 89376c12d..bcb048af9 100644 --- a/src/uu/relpath/Cargo.toml +++ b/src/uu/relpath/Cargo.toml @@ -22,3 +22,7 @@ uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_p [[bin]] name = "relpath" path = "src/main.rs" + +[package.metadata.cargo-udeps.ignore] +# Necessary for "make all" +normal = ["uucore_procs"] diff --git a/src/uu/rm/Cargo.toml b/src/uu/rm/Cargo.toml index c356f03e4..6099d137a 100644 --- a/src/uu/rm/Cargo.toml +++ b/src/uu/rm/Cargo.toml @@ -27,3 +27,7 @@ winapi = { version="0.3", features=[] } [[bin]] name = "rm" path = "src/main.rs" + +[package.metadata.cargo-udeps.ignore] +# Necessary for "make all" +normal = ["uucore_procs"] diff --git a/src/uu/seq/Cargo.toml b/src/uu/seq/Cargo.toml index 68aa87bad..4618115cb 100644 --- a/src/uu/seq/Cargo.toml +++ b/src/uu/seq/Cargo.toml @@ -24,3 +24,7 @@ uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_p [[bin]] name = "seq" path = "src/main.rs" + +[package.metadata.cargo-udeps.ignore] +# Necessary for "make all" +normal = ["uucore_procs"] diff --git a/src/uu/shred/Cargo.toml b/src/uu/shred/Cargo.toml index 5f7bebb4e..d87732d84 100644 --- a/src/uu/shred/Cargo.toml +++ b/src/uu/shred/Cargo.toml @@ -24,3 +24,7 @@ uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_p [[bin]] name = "shred" path = "src/main.rs" + +[package.metadata.cargo-udeps.ignore] +# Necessary for "make all" +normal = ["uucore_procs"] diff --git a/src/uu/shuf/Cargo.toml b/src/uu/shuf/Cargo.toml index 5c99c6d26..bb3ccc710 100644 --- a/src/uu/shuf/Cargo.toml +++ b/src/uu/shuf/Cargo.toml @@ -23,3 +23,7 @@ uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_p [[bin]] name = "shuf" path = "src/main.rs" + +[package.metadata.cargo-udeps.ignore] +# Necessary for "make all" +normal = ["uucore_procs"] diff --git a/src/uu/split/Cargo.toml b/src/uu/split/Cargo.toml index 6583d705e..d2168bf49 100644 --- a/src/uu/split/Cargo.toml +++ b/src/uu/split/Cargo.toml @@ -22,3 +22,7 @@ uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_p [[bin]] name = "split" path = "src/main.rs" + +[package.metadata.cargo-udeps.ignore] +# Necessary for "make all" +normal = ["uucore_procs"] diff --git a/src/uu/sum/Cargo.toml b/src/uu/sum/Cargo.toml index 5a212d0d3..41f2d0a38 100644 --- a/src/uu/sum/Cargo.toml +++ b/src/uu/sum/Cargo.toml @@ -22,3 +22,7 @@ uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_p [[bin]] name = "sum" path = "src/main.rs" + +[package.metadata.cargo-udeps.ignore] +# Necessary for "make all" +normal = ["uucore_procs"] diff --git a/src/uu/tac/Cargo.toml b/src/uu/tac/Cargo.toml index 4a91786aa..1e436e916 100644 --- a/src/uu/tac/Cargo.toml +++ b/src/uu/tac/Cargo.toml @@ -24,3 +24,7 @@ uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_p [[bin]] name = "tac" path = "src/main.rs" + +[package.metadata.cargo-udeps.ignore] +# Necessary for "make all" +normal = ["uucore_procs"] diff --git a/src/uu/tail/Cargo.toml b/src/uu/tail/Cargo.toml index 0fe84670e..6fd05b1a9 100644 --- a/src/uu/tail/Cargo.toml +++ b/src/uu/tail/Cargo.toml @@ -33,3 +33,7 @@ libc = "0.2" [[bin]] name = "tail" path = "src/main.rs" + +[package.metadata.cargo-udeps.ignore] +# Necessary for "make all" +normal = ["uucore_procs"] diff --git a/src/uu/tee/Cargo.toml b/src/uu/tee/Cargo.toml index 01c190698..900ef3564 100644 --- a/src/uu/tee/Cargo.toml +++ b/src/uu/tee/Cargo.toml @@ -24,3 +24,7 @@ uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_p [[bin]] name = "tee" path = "src/main.rs" + +[package.metadata.cargo-udeps.ignore] +# Necessary for "make all" +normal = ["uucore_procs"] diff --git a/src/uu/test/Cargo.toml b/src/uu/test/Cargo.toml index b9931185c..3fe531d1d 100644 --- a/src/uu/test/Cargo.toml +++ b/src/uu/test/Cargo.toml @@ -26,3 +26,7 @@ redox_syscall = "0.2" [[bin]] name = "test" path = "src/main.rs" + +[package.metadata.cargo-udeps.ignore] +# Necessary for "make all" +normal = ["uucore_procs"] diff --git a/src/uu/tr/Cargo.toml b/src/uu/tr/Cargo.toml index f75a540ee..450c562e0 100644 --- a/src/uu/tr/Cargo.toml +++ b/src/uu/tr/Cargo.toml @@ -24,3 +24,7 @@ uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_p [[bin]] name = "tr" path = "src/main.rs" + +[package.metadata.cargo-udeps.ignore] +# Necessary for "make all" +normal = ["uucore_procs"] diff --git a/src/uu/truncate/Cargo.toml b/src/uu/truncate/Cargo.toml index 6441f2e14..e779e32ba 100644 --- a/src/uu/truncate/Cargo.toml +++ b/src/uu/truncate/Cargo.toml @@ -22,3 +22,7 @@ uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_p [[bin]] name = "truncate" path = "src/main.rs" + +[package.metadata.cargo-udeps.ignore] +# Necessary for "make all" +normal = ["uucore_procs"] diff --git a/src/uu/tsort/Cargo.toml b/src/uu/tsort/Cargo.toml index ec9dd05f9..055615003 100644 --- a/src/uu/tsort/Cargo.toml +++ b/src/uu/tsort/Cargo.toml @@ -22,3 +22,7 @@ uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_p [[bin]] name = "tsort" path = "src/main.rs" + +[package.metadata.cargo-udeps.ignore] +# Necessary for "make all" +normal = ["uucore_procs"] diff --git a/src/uu/unexpand/Cargo.toml b/src/uu/unexpand/Cargo.toml index a0aa3c1de..8b1169151 100644 --- a/src/uu/unexpand/Cargo.toml +++ b/src/uu/unexpand/Cargo.toml @@ -23,3 +23,7 @@ uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_p [[bin]] name = "unexpand" path = "src/main.rs" + +[package.metadata.cargo-udeps.ignore] +# Necessary for "make all" +normal = ["uucore_procs"] diff --git a/src/uu/uniq/Cargo.toml b/src/uu/uniq/Cargo.toml index 856da9a63..06ba22688 100644 --- a/src/uu/uniq/Cargo.toml +++ b/src/uu/uniq/Cargo.toml @@ -24,3 +24,7 @@ uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_p [[bin]] name = "uniq" path = "src/main.rs" + +[package.metadata.cargo-udeps.ignore] +# Necessary for "make all" +normal = ["uucore_procs"] diff --git a/src/uu/wc/Cargo.toml b/src/uu/wc/Cargo.toml index 5884f3746..179b17c36 100644 --- a/src/uu/wc/Cargo.toml +++ b/src/uu/wc/Cargo.toml @@ -29,3 +29,7 @@ libc = "0.2" [[bin]] name = "wc" path = "src/main.rs" + +[package.metadata.cargo-udeps.ignore] +# Necessary for "make all" +normal = ["uucore_procs"] From 3f37ddbd22803a991ece4fa3fc78fda02e776697 Mon Sep 17 00:00:00 2001 From: Jan Verbeek Date: Sat, 28 Aug 2021 13:47:31 +0200 Subject: [PATCH 199/206] hostname: Cleanup - Attach context to I/O errors - Make flags override each other - Support invalid unicode as argument - Call WsaCleanup() even on panic - Do not use deprecated std::mem::uninitialized() --- .../workspace.wordlist.txt | 2 + src/uu/hostname/src/hostname.rs | 136 +++++++++--------- 2 files changed, 73 insertions(+), 65 deletions(-) diff --git a/.vscode/cspell.dictionaries/workspace.wordlist.txt b/.vscode/cspell.dictionaries/workspace.wordlist.txt index 29957fb12..d37a59465 100644 --- a/.vscode/cspell.dictionaries/workspace.wordlist.txt +++ b/.vscode/cspell.dictionaries/workspace.wordlist.txt @@ -68,6 +68,7 @@ structs substr splitn trunc +uninit # * uutils basenc @@ -277,6 +278,7 @@ ULONG ULONGLONG UNLEN WCHAR +WSADATA errhandlingapi fileapi handleapi diff --git a/src/uu/hostname/src/hostname.rs b/src/uu/hostname/src/hostname.rs index 8852e0f43..2de6627e8 100644 --- a/src/uu/hostname/src/hostname.rs +++ b/src/uu/hostname/src/hostname.rs @@ -10,18 +10,13 @@ #[macro_use] extern crate uucore; -use clap::{crate_version, App, Arg, ArgMatches}; use std::collections::hash_set::HashSet; use std::net::ToSocketAddrs; use std::str; -#[cfg(windows)] -use uucore::error::UUsageError; -use uucore::error::{UResult, USimpleError}; -#[cfg(windows)] -use winapi::shared::minwindef::MAKEWORD; -#[cfg(windows)] -use winapi::um::winsock2::{WSACleanup, WSAStartup}; +use clap::{crate_version, App, Arg, ArgMatches}; + +use uucore::error::{FromIo, UResult}; static ABOUT: &str = "Display or set the system's host name."; @@ -31,45 +26,52 @@ static OPT_FQDN: &str = "fqdn"; static OPT_SHORT: &str = "short"; static OPT_HOST: &str = "host"; -#[uucore_procs::gen_uumain] -pub fn uumain(args: impl uucore::Args) -> UResult<()> { - #![allow(clippy::let_and_return)] - #[cfg(windows)] - unsafe { - #[allow(deprecated)] - let mut data = std::mem::uninitialized(); - if WSAStartup(MAKEWORD(2, 2), &mut data as *mut _) != 0 { - return Err(UUsageError::new( - 1, - "Failed to start Winsock 2.2".to_string(), - )); +#[cfg(windows)] +mod wsa { + use std::io; + + use winapi::shared::minwindef::MAKEWORD; + use winapi::um::winsock2::{WSACleanup, WSAStartup, WSADATA}; + + pub(super) struct WsaHandle(()); + + pub(super) fn start() -> io::Result { + let err = unsafe { + let mut data = std::mem::MaybeUninit::::uninit(); + WSAStartup(MAKEWORD(2, 2), data.as_mut_ptr()) + }; + if err != 0 { + Err(io::Error::from_raw_os_error(err)) + } else { + Ok(WsaHandle(())) } } - let result = execute(args); - #[cfg(windows)] - unsafe { - WSACleanup(); + + impl Drop for WsaHandle { + fn drop(&mut self) { + unsafe { + // This possibly returns an error but we can't handle it + let _err = WSACleanup(); + } + } } - result } fn usage() -> String { format!("{0} [OPTION]... [HOSTNAME]", uucore::execution_phrase()) } -fn execute(args: impl uucore::Args) -> UResult<()> { +#[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); - match matches.value_of(OPT_HOST) { + #[cfg(windows)] + let _handle = wsa::start().map_err_context(|| "failed to start Winsock".to_owned())?; + + match matches.value_of_os(OPT_HOST) { None => display_hostname(&matches), - Some(host) => { - if let Err(err) = hostname::set(host) { - return Err(USimpleError::new(1, format!("{}", err))); - } else { - Ok(()) - } - } + Some(host) => hostname::set(host).map_err_context(|| "failed to set hostname".to_owned()), } } @@ -81,64 +83,68 @@ pub fn uu_app() -> App<'static, 'static> { Arg::with_name(OPT_DOMAIN) .short("d") .long("domain") + .overrides_with_all(&[OPT_DOMAIN, OPT_IP_ADDRESS, OPT_FQDN, OPT_SHORT]) .help("Display the name of the DNS domain if possible"), ) .arg( Arg::with_name(OPT_IP_ADDRESS) .short("i") .long("ip-address") + .overrides_with_all(&[OPT_DOMAIN, OPT_IP_ADDRESS, OPT_FQDN, OPT_SHORT]) .help("Display the network address(es) of the host"), ) - // TODO: support --long .arg( Arg::with_name(OPT_FQDN) .short("f") .long("fqdn") + .overrides_with_all(&[OPT_DOMAIN, OPT_IP_ADDRESS, OPT_FQDN, OPT_SHORT]) .help("Display the FQDN (Fully Qualified Domain Name) (default)"), ) - .arg(Arg::with_name(OPT_SHORT).short("s").long("short").help( - "Display the short hostname (the portion before the first dot) if \ - possible", - )) + .arg( + Arg::with_name(OPT_SHORT) + .short("s") + .long("short") + .overrides_with_all(&[OPT_DOMAIN, OPT_IP_ADDRESS, OPT_FQDN, OPT_SHORT]) + .help("Display the short hostname (the portion before the first dot) if possible"), + ) .arg(Arg::with_name(OPT_HOST)) } fn display_hostname(matches: &ArgMatches) -> UResult<()> { - let hostname = hostname::get().unwrap().into_string().unwrap(); + let hostname = hostname::get() + .map_err_context(|| "failed to get hostname".to_owned())? + .to_string_lossy() + .into_owned(); if matches.is_present(OPT_IP_ADDRESS) { // XXX: to_socket_addrs needs hostname:port so append a dummy port and remove it later. // This was originally supposed to use std::net::lookup_host, but that seems to be // deprecated. Perhaps we should use the dns-lookup crate? let hostname = hostname + ":1"; - match hostname.to_socket_addrs() { - Ok(addresses) => { - let mut hashset = HashSet::new(); - let mut output = String::new(); - for addr in addresses { - // XXX: not sure why this is necessary... - if !hashset.contains(&addr) { - let mut ip = format!("{}", addr); - if ip.ends_with(":1") { - let len = ip.len(); - ip.truncate(len - 2); - } - output.push_str(&ip); - output.push(' '); - hashset.insert(addr); - } + let addresses = hostname + .to_socket_addrs() + .map_err_context(|| "failed to resolve socket addresses".to_owned())?; + let mut hashset = HashSet::new(); + let mut output = String::new(); + for addr in addresses { + // XXX: not sure why this is necessary... + if !hashset.contains(&addr) { + let mut ip = addr.to_string(); + if ip.ends_with(":1") { + let len = ip.len(); + ip.truncate(len - 2); } - let len = output.len(); - if len > 0 { - println!("{}", &output[0..len - 1]); - } - - Ok(()) - } - Err(f) => { - return Err(USimpleError::new(1, format!("{}", f))); + output.push_str(&ip); + output.push(' '); + hashset.insert(addr); } } + let len = output.len(); + if len > 0 { + println!("{}", &output[0..len - 1]); + } + + Ok(()) } else { if matches.is_present(OPT_SHORT) || matches.is_present(OPT_DOMAIN) { let mut it = hostname.char_indices().filter(|&ci| ci.1 == '.'); From 5bb56ec528f4ed29a021777a4a36ecd4e765fd27 Mon Sep 17 00:00:00 2001 From: Jan Verbeek Date: Wed, 15 Sep 2021 15:37:15 +0200 Subject: [PATCH 200/206] whoami: Restrict scope of unsafe Co-authored-by: Jan Scheer --- src/uu/whoami/src/platform/windows.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/uu/whoami/src/platform/windows.rs b/src/uu/whoami/src/platform/windows.rs index 8afa18b63..a627bed8e 100644 --- a/src/uu/whoami/src/platform/windows.rs +++ b/src/uu/whoami/src/platform/windows.rs @@ -20,10 +20,8 @@ pub fn get_username() -> io::Result { let mut buffer = [0_u16; BUF_LEN as usize]; let mut len = BUF_LEN; // SAFETY: buffer.len() == len - unsafe { - if winbase::GetUserNameW(buffer.as_mut_ptr(), &mut len) == 0 { - return Err(io::Error::last_os_error()); - } + if unsafe { winbase::GetUserNameW(buffer.as_mut_ptr(), &mut len) } == 0 { + return Err(io::Error::last_os_error()); } Ok(OsString::from_wide(&buffer[..len as usize - 1])) } From cd5f676903606f8623fbc921489ddab7419bbadf Mon Sep 17 00:00:00 2001 From: Jeffrey Finkelstein Date: Thu, 16 Sep 2021 22:33:04 -0400 Subject: [PATCH 201/206] hashsum: add tests for Windows text mode --- tests/by-util/test_hashsum.rs | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/tests/by-util/test_hashsum.rs b/tests/by-util/test_hashsum.rs index f2cf91d45..545b4ee78 100644 --- a/tests/by-util/test_hashsum.rs +++ b/tests/by-util/test_hashsum.rs @@ -38,6 +38,30 @@ macro_rules! test_digest { .no_stderr() .stdout_is("input.txt: OK\n"); } + + #[cfg(windows)] + #[test] + fn test_text_mode() { + // TODO Replace this with hard-coded files that store the + // expected output of text mode on an input file that has + // "\r\n" line endings. + let result = new_ucmd!() + .args(&[DIGEST_ARG, BITS_ARG, "-b"]) + .pipe_in("a\nb\nc\n") + .succeeds(); + let expected = result.no_stderr().stdout(); + // Replace the "*-\n" at the end of the output with " -\n". + // The asterisk indicates that the digest was computed in + // binary mode. + let n = expected.len(); + let expected = [&expected[..n - 3], &[b' ', b'-', b'\n']].concat(); + new_ucmd!() + .args(&[DIGEST_ARG, BITS_ARG, "-t"]) + .pipe_in("a\r\nb\r\nc\r\n") + .succeeds() + .no_stderr() + .stdout_is(std::str::from_utf8(&expected).unwrap()); + } } )*) } From 2ac5dc0a70b03d68faba89486b9fc15490affee0 Mon Sep 17 00:00:00 2001 From: Jeffrey Finkelstein Date: Sat, 11 Sep 2021 12:49:12 -0400 Subject: [PATCH 202/206] seq: compute correct width for scientific notation Change the way `seq` computes the number of digits needed to print a number so that it works for inputs given in scientific notation. Specifically, this commit parses the input string to determine whether it is an integer, a float in decimal notation, or a float in scientific notation, and then computes the number of integral digits and the number of fractional digits based on that. This also supports floating point negative zero, expressed in both decimal and scientific notation. --- src/uu/seq/src/digits.rs | 190 ++++++++++++++++++++++++++++++++++++++ src/uu/seq/src/seq.rs | 72 ++++++++++++--- tests/by-util/test_seq.rs | 134 +++++++++++++++++++++++++++ 3 files changed, 381 insertions(+), 15 deletions(-) create mode 100644 src/uu/seq/src/digits.rs diff --git a/src/uu/seq/src/digits.rs b/src/uu/seq/src/digits.rs new file mode 100644 index 000000000..bde933978 --- /dev/null +++ b/src/uu/seq/src/digits.rs @@ -0,0 +1,190 @@ +//! Counting number of digits needed to represent a number. +//! +//! The [`num_integral_digits`] and [`num_fractional_digits`] functions +//! count the number of digits needed to represent a number in decimal +//! notation (like "123.456"). +use std::convert::TryInto; +use std::num::ParseIntError; + +use uucore::display::Quotable; + +/// The number of digits after the decimal point in a given number. +/// +/// The input `s` is a string representing a number, either an integer +/// or a floating point number in either decimal notation or scientific +/// notation. This function returns the number of digits after the +/// decimal point needed to print the number in decimal notation. +/// +/// # Examples +/// +/// ```rust,ignore +/// assert_eq!(num_fractional_digits("123.45e-1").unwrap(), 3); +/// ``` +pub fn num_fractional_digits(s: &str) -> Result { + match (s.find('.'), s.find('e')) { + // For example, "123456". + (None, None) => Ok(0), + + // For example, "123e456". + (None, Some(j)) => { + let exponent: i64 = s[j + 1..].parse()?; + if exponent < 0 { + Ok(-exponent as usize) + } else { + Ok(0) + } + } + + // For example, "123.456". + (Some(i), None) => Ok(s.len() - (i + 1)), + + // For example, "123.456e789". + (Some(i), Some(j)) if i < j => { + // Because of the match guard, this subtraction will not underflow. + let num_digits_between_decimal_point_and_e = (j - (i + 1)) as i64; + let exponent: i64 = s[j + 1..].parse()?; + if num_digits_between_decimal_point_and_e < exponent { + Ok(0) + } else { + Ok((num_digits_between_decimal_point_and_e - exponent) + .try_into() + .unwrap()) + } + } + _ => crash!( + 1, + "invalid floating point argument: {}\n Try '{} --help' for more information.", + s.quote(), + uucore::execution_phrase() + ), + } +} + +/// The number of digits before the decimal point in a given number. +/// +/// The input `s` is a string representing a number, either an integer +/// or a floating point number in either decimal notation or scientific +/// notation. This function returns the number of digits before the +/// decimal point needed to print the number in decimal notation. +/// +/// # Examples +/// +/// ```rust,ignore +/// assert_eq!(num_fractional_digits("123.45e-1").unwrap(), 2); +/// ``` +pub fn num_integral_digits(s: &str) -> Result { + match (s.find('.'), s.find('e')) { + // For example, "123456". + (None, None) => Ok(s.len()), + + // For example, "123e456". + (None, Some(j)) => { + let exponent: i64 = s[j + 1..].parse()?; + let total = j as i64 + exponent; + if total < 1 { + Ok(1) + } else { + Ok(total.try_into().unwrap()) + } + } + + // For example, "123.456". + (Some(i), None) => Ok(i), + + // For example, "123.456e789". + (Some(i), Some(j)) => { + let exponent: i64 = s[j + 1..].parse()?; + let minimum: usize = { + let integral_part: f64 = crash_if_err!(1, s[..j].parse()); + if integral_part == -0.0 && integral_part.is_sign_negative() { + 2 + } else { + 1 + } + }; + + let total = i as i64 + exponent; + if total < minimum as i64 { + Ok(minimum) + } else { + Ok(total.try_into().unwrap()) + } + } + } +} + +#[cfg(test)] +mod tests { + + mod test_num_integral_digits { + use crate::num_integral_digits; + + #[test] + fn test_integer() { + assert_eq!(num_integral_digits("123").unwrap(), 3); + } + + #[test] + fn test_decimal() { + assert_eq!(num_integral_digits("123.45").unwrap(), 3); + } + + #[test] + fn test_scientific_no_decimal_positive_exponent() { + assert_eq!(num_integral_digits("123e4").unwrap(), 3 + 4); + } + + #[test] + fn test_scientific_with_decimal_positive_exponent() { + assert_eq!(num_integral_digits("123.45e6").unwrap(), 3 + 6); + } + + #[test] + fn test_scientific_no_decimal_negative_exponent() { + assert_eq!(num_integral_digits("123e-4").unwrap(), 1); + } + + #[test] + fn test_scientific_with_decimal_negative_exponent() { + assert_eq!(num_integral_digits("123.45e-6").unwrap(), 1); + assert_eq!(num_integral_digits("123.45e-1").unwrap(), 2); + } + } + + mod test_num_fractional_digits { + use crate::num_fractional_digits; + + #[test] + fn test_integer() { + assert_eq!(num_fractional_digits("123").unwrap(), 0); + } + + #[test] + fn test_decimal() { + assert_eq!(num_fractional_digits("123.45").unwrap(), 2); + } + + #[test] + fn test_scientific_no_decimal_positive_exponent() { + assert_eq!(num_fractional_digits("123e4").unwrap(), 0); + } + + #[test] + fn test_scientific_with_decimal_positive_exponent() { + assert_eq!(num_fractional_digits("123.45e6").unwrap(), 0); + assert_eq!(num_fractional_digits("123.45e1").unwrap(), 1); + } + + #[test] + fn test_scientific_no_decimal_negative_exponent() { + assert_eq!(num_fractional_digits("123e-4").unwrap(), 4); + assert_eq!(num_fractional_digits("123e-1").unwrap(), 1); + } + + #[test] + fn test_scientific_with_decimal_negative_exponent() { + assert_eq!(num_fractional_digits("123.45e-6").unwrap(), 8); + assert_eq!(num_fractional_digits("123.45e-1").unwrap(), 3); + } + } +} diff --git a/src/uu/seq/src/seq.rs b/src/uu/seq/src/seq.rs index 0cea90838..18f181aa8 100644 --- a/src/uu/seq/src/seq.rs +++ b/src/uu/seq/src/seq.rs @@ -14,6 +14,11 @@ use num_traits::{Num, ToPrimitive}; use std::cmp; use std::io::{stdout, ErrorKind, Write}; use std::str::FromStr; + +mod digits; +use crate::digits::num_fractional_digits; +use crate::digits::num_integral_digits; + use uucore::display::Quotable; static ABOUT: &str = "Display numbers from FIRST to LAST, in steps of INCREMENT."; @@ -155,20 +160,49 @@ pub fn uumain(args: impl uucore::Args) -> i32 { }; let mut largest_dec = 0; + let mut padding = 0; let first = if numbers.len() > 1 { let slice = numbers[0]; - let len = slice.len(); - let dec = slice.find('.').unwrap_or(len); - largest_dec = len - dec; + largest_dec = num_fractional_digits(slice).unwrap_or_else(|_| { + crash!( + 1, + "invalid floating point argument: {}\n Try '{} --help' for more information.", + slice.quote(), + uucore::execution_phrase() + ) + }); + padding = num_integral_digits(slice).unwrap_or_else(|_| { + crash!( + 1, + "invalid floating point argument: {}\n Try '{} --help' for more information.", + slice.quote(), + uucore::execution_phrase() + ) + }); crash_if_err!(1, slice.parse()) } else { Number::BigInt(BigInt::one()) }; let increment = if numbers.len() > 2 { let slice = numbers[1]; - let len = slice.len(); - let dec = slice.find('.').unwrap_or(len); - largest_dec = cmp::max(largest_dec, len - dec); + let dec = num_fractional_digits(slice).unwrap_or_else(|_| { + crash!( + 1, + "invalid floating point argument: {}\n Try '{} --help' for more information.", + slice.quote(), + uucore::execution_phrase() + ) + }); + let int_digits = num_integral_digits(slice).unwrap_or_else(|_| { + crash!( + 1, + "invalid floating point argument: {}\n Try '{} --help' for more information.", + slice.quote(), + uucore::execution_phrase() + ) + }); + largest_dec = cmp::max(largest_dec, dec); + padding = cmp::max(padding, int_digits); crash_if_err!(1, slice.parse()) } else { Number::BigInt(BigInt::one()) @@ -183,16 +217,18 @@ pub fn uumain(args: impl uucore::Args) -> i32 { } let last: Number = { let slice = numbers[numbers.len() - 1]; + let int_digits = num_integral_digits(slice).unwrap_or_else(|_| { + crash!( + 1, + "invalid floating point argument: {}\n Try '{} --help' for more information.", + slice.quote(), + uucore::execution_phrase() + ) + }); + padding = cmp::max(padding, int_digits); crash_if_err!(1, slice.parse()) }; - if largest_dec > 0 { - largest_dec -= 1; - } - let padding = first - .num_digits() - .max(increment.num_digits()) - .max(last.num_digits()); let result = match (first, last, increment) { (Number::MinusZero, Number::BigInt(last), Number::BigInt(increment)) => print_seq_integers( (BigInt::zero(), increment, last), @@ -286,18 +322,24 @@ fn print_seq( let mut stdout = stdout.lock(); let (first, increment, last) = range; let mut i = 0isize; + let is_first_minus_zero = first == -0.0 && first.is_sign_negative(); let mut value = first + i as f64 * increment; let mut is_first_iteration = true; while !done_printing(&value, &increment, &last) { if !is_first_iteration { write!(stdout, "{}", separator)?; } + let mut width = padding; + if is_first_iteration && is_first_minus_zero { + write!(stdout, "-")?; + width -= 1; + } is_first_iteration = false; let istr = format!("{:.*}", largest_dec, value); let ilen = istr.len(); let before_dec = istr.find('.').unwrap_or(ilen); - if pad && before_dec < padding { - for _ in 0..(padding - before_dec) { + if pad && before_dec < width { + for _ in 0..(width - before_dec) { write!(stdout, "0")?; } } diff --git a/tests/by-util/test_seq.rs b/tests/by-util/test_seq.rs index 65f2451f0..7a58a950a 100644 --- a/tests/by-util/test_seq.rs +++ b/tests/by-util/test_seq.rs @@ -23,6 +23,56 @@ fn test_rejects_non_floats() { )); } +#[test] +fn test_invalid_float() { + new_ucmd!() + .args(&["1e2.3"]) + .fails() + .no_stdout() + .stderr_contains("invalid floating point argument: '1e2.3'") + .stderr_contains("for more information."); + new_ucmd!() + .args(&["1e2.3", "2"]) + .fails() + .no_stdout() + .stderr_contains("invalid floating point argument: '1e2.3'") + .stderr_contains("for more information."); + new_ucmd!() + .args(&["1", "1e2.3"]) + .fails() + .no_stdout() + .stderr_contains("invalid floating point argument: '1e2.3'") + .stderr_contains("for more information."); + new_ucmd!() + .args(&["1e2.3", "2", "3"]) + .fails() + .no_stdout() + .stderr_contains("invalid floating point argument: '1e2.3'") + .stderr_contains("for more information."); + new_ucmd!() + .args(&["1", "1e2.3", "3"]) + .fails() + .no_stdout() + .stderr_contains("invalid floating point argument: '1e2.3'") + .stderr_contains("for more information."); + new_ucmd!() + .args(&["1", "2", "1e2.3"]) + .fails() + .no_stdout() + .stderr_contains("invalid floating point argument: '1e2.3'") + .stderr_contains("for more information."); +} + +#[test] +fn test_width_invalid_float() { + new_ucmd!() + .args(&["-w", "1e2.3"]) + .fails() + .no_stdout() + .stderr_contains("invalid floating point argument: '1e2.3'") + .stderr_contains("for more information."); +} + // ---- Tests for the big integer based path ---- #[test] @@ -178,6 +228,90 @@ fn test_width_negative_zero() { .no_stderr(); } +#[test] +fn test_width_negative_zero_scientific_notation() { + new_ucmd!() + .args(&["-w", "-0e0", "1"]) + .succeeds() + .stdout_is("-0\n01\n") + .no_stderr(); + + new_ucmd!() + .args(&["-w", "-0e+1", "1"]) + .succeeds() + .stdout_is("-00\n001\n") + .no_stderr(); + + new_ucmd!() + .args(&["-w", "-0.000e0", "1"]) + .succeeds() + .stdout_is("-0.000\n01.000\n") + .no_stderr(); + + new_ucmd!() + .args(&["-w", "-0.000e-2", "1"]) + .succeeds() + .stdout_is("-0.00000\n01.00000\n") + .no_stderr(); + + new_ucmd!() + .args(&["-w", "-0.000e5", "1"]) + .succeeds() + .stdout_is("-000000\n0000001\n") + .no_stderr(); + + new_ucmd!() + .args(&["-w", "-0.000e5", "1"]) + .succeeds() + .stdout_is("-000000\n0000001\n") + .no_stderr(); +} + +#[test] +fn test_width_decimal_scientific_notation_increment() { + new_ucmd!() + .args(&["-w", ".1", "1e-2", ".11"]) + .succeeds() + .stdout_is("0.10\n0.11\n") + .no_stderr(); + + new_ucmd!() + .args(&["-w", ".0", "1.500e-1", ".2"]) + .succeeds() + .stdout_is("0.0000\n0.1500\n") + .no_stderr(); +} + +/// Test that trailing zeros in the start argument contribute to precision. +#[test] +fn test_width_decimal_scientific_notation_trailing_zeros_start() { + new_ucmd!() + .args(&["-w", ".1000", "1e-2", ".11"]) + .succeeds() + .stdout_is("0.1000\n0.1100\n") + .no_stderr(); +} + +/// Test that trailing zeros in the increment argument contribute to precision. +#[test] +fn test_width_decimal_scientific_notation_trailing_zeros_increment() { + new_ucmd!() + .args(&["-w", "1e-1", "0.0100", ".11"]) + .succeeds() + .stdout_is("0.1000\n0.1100\n") + .no_stderr(); +} + +/// Test that trailing zeros in the end argument do not contribute to width. +#[test] +fn test_width_decimal_scientific_notation_trailing_zeros_end() { + new_ucmd!() + .args(&["-w", "1e-1", "1e-2", ".1100"]) + .succeeds() + .stdout_is("0.10\n0.11\n") + .no_stderr(); +} + // TODO This is duplicated from `test_yes.rs`; refactor them. /// Run `seq`, capture some of the output, close the pipe, and verify it. fn run(args: &[&str], expected: &[u8]) { From 3854a97749cd6f5fd00a2be8ae2cde2b2bd3a010 Mon Sep 17 00:00:00 2001 From: Jeffrey Finkelstein Date: Sat, 11 Sep 2021 12:57:14 -0400 Subject: [PATCH 203/206] seq: remove unused Number::num_digits() function Remove the `Number::num_digits()` function in favor of the `digits::num_integral_digits()` functions. --- src/uu/seq/src/seq.rs | 52 ------------------------------------------- 1 file changed, 52 deletions(-) diff --git a/src/uu/seq/src/seq.rs b/src/uu/seq/src/seq.rs index 18f181aa8..8bf6cb008 100644 --- a/src/uu/seq/src/seq.rs +++ b/src/uu/seq/src/seq.rs @@ -67,38 +67,6 @@ impl Number { Number::F64(n) => n, } } - - /// Number of characters needed to print the integral part of the number. - /// - /// The number of characters includes one character to represent the - /// minus sign ("-") if this number is negative. - /// - /// # Examples - /// - /// ```rust,ignore - /// use num_bigint::{BigInt, Sign}; - /// - /// assert_eq!( - /// Number::BigInt(BigInt::new(Sign::Plus, vec![123])).num_digits(), - /// 3 - /// ); - /// assert_eq!( - /// Number::BigInt(BigInt::new(Sign::Minus, vec![123])).num_digits(), - /// 4 - /// ); - /// assert_eq!(Number::F64(123.45).num_digits(), 3); - /// assert_eq!(Number::MinusZero.num_digits(), 2); - /// ``` - fn num_digits(&self) -> usize { - match self { - Number::MinusZero => 2, - Number::BigInt(n) => n.to_string().len(), - Number::F64(n) => { - let s = n.to_string(); - s.find('.').unwrap_or_else(|| s.len()) - } - } - } } impl FromStr for Number { @@ -404,23 +372,3 @@ fn print_seq_integers( } Ok(()) } - -#[cfg(test)] -mod tests { - use crate::Number; - use num_bigint::{BigInt, Sign}; - - #[test] - fn test_number_num_digits() { - assert_eq!( - Number::BigInt(BigInt::new(Sign::Plus, vec![123])).num_digits(), - 3 - ); - assert_eq!( - Number::BigInt(BigInt::new(Sign::Minus, vec![123])).num_digits(), - 4 - ); - assert_eq!(Number::F64(123.45).num_digits(), 3); - assert_eq!(Number::MinusZero.num_digits(), 2); - } -} From 7fea771f32c0ab68e63a75de8dd4245188908e91 Mon Sep 17 00:00:00 2001 From: Jeffrey Finkelstein Date: Tue, 24 Aug 2021 21:34:30 -0400 Subject: [PATCH 204/206] hashsum: use std::io::copy() to simplify digest Create a `DigestWriter` struct that implements `Write` by passing bytes directly to `Digest::input()`, so that `hashsum` can use `std::io::copy()`. Using `std::io::copy()` eliminates some boilerplate code around reading and writing bytes. And defining `DigestWriter` makes it easier to add a `#[cfg(windows)]` guard around the Windows-specific replacement of "\r\n" with "\n". --- src/uu/hashsum/src/digest.rs | 85 +++++++++++++++++++++++++++++++++++ src/uu/hashsum/src/hashsum.rs | 53 ++++++---------------- 2 files changed, 98 insertions(+), 40 deletions(-) diff --git a/src/uu/hashsum/src/digest.rs b/src/uu/hashsum/src/digest.rs index 9093d94a7..531dc7e4f 100644 --- a/src/uu/hashsum/src/digest.rs +++ b/src/uu/hashsum/src/digest.rs @@ -1,10 +1,22 @@ +// spell-checker:ignore memmem +//! Implementations of digest functions, like md5 and sha1. +//! +//! The [`Digest`] trait represents the interface for providing inputs +//! to these digest functions and accessing the resulting hash. The +//! [`DigestWriter`] struct provides a wrapper around [`Digest`] that +//! implements the [`Write`] trait, for use in situations where calling +//! [`write`] would be useful. extern crate digest; extern crate md5; extern crate sha1; extern crate sha2; extern crate sha3; +use std::io::Write; + use hex::ToHex; +#[cfg(windows)] +use memchr::memmem; use crate::digest::digest::{ExtendableOutput, Input, XofReader}; @@ -158,3 +170,76 @@ impl_digest_sha!(sha3::Sha3_384, 384); impl_digest_sha!(sha3::Sha3_512, 512); impl_digest_shake!(sha3::Shake128); impl_digest_shake!(sha3::Shake256); + +/// A struct that writes to a digest. +/// +/// This struct wraps a [`Digest`] and provides a [`Write`] +/// implementation that passes input bytes directly to the +/// [`Digest::input`]. +/// +/// On Windows, if `binary` is `false`, then the [`write`] +/// implementation replaces instances of "\r\n" with "\n" before passing +/// the input bytes to the [`digest`]. +pub struct DigestWriter<'a> { + digest: &'a mut Box, + + /// Whether to write to the digest in binary mode or text mode on Windows. + /// + /// If this is `false`, then instances of "\r\n" are replaced with + /// "\n" before passing input bytes to the [`digest`]. + #[allow(dead_code)] + binary: bool, + // TODO This is dead code only on non-Windows operating systems. It + // might be better to use a `#[cfg(windows)]` guard here. +} + +impl<'a> DigestWriter<'a> { + pub fn new(digest: &'a mut Box, binary: bool) -> DigestWriter { + DigestWriter { digest, binary } + } +} + +impl<'a> Write for DigestWriter<'a> { + #[cfg(not(windows))] + fn write(&mut self, buf: &[u8]) -> std::io::Result { + self.digest.input(buf); + Ok(buf.len()) + } + + #[cfg(windows)] + fn write(&mut self, buf: &[u8]) -> std::io::Result { + if self.binary { + self.digest.input(buf); + return Ok(buf.len()); + } + + // In Windows text mode, replace each occurrence of "\r\n" + // with "\n". + // + // Find all occurrences of "\r\n", inputting the slice just + // before the "\n" in the previous instance of "\r\n" and + // the beginning of this "\r\n". + // + // FIXME This fails if one call to `write()` ends with the + // "\r" and the next call to `write()` begins with the "\n". + let n = buf.len(); + let mut i_prev = 0; + for i in memmem::find_iter(buf, b"\r\n") { + self.digest.input(&buf[i_prev..i]); + i_prev = i + 1; + } + self.digest.input(&buf[i_prev..n]); + + // Even though we dropped a "\r" for each "\r\n" we found, we + // still report the number of bytes written as `n`. This is + // because the meaning of the returned number is supposed to be + // the number of bytes consumed by the writer, so that if the + // calling code were calling `write()` in a loop, it would know + // where the next contiguous slice of the buffer starts. + Ok(n) + } + + fn flush(&mut self) -> std::io::Result<()> { + Ok(()) + } +} diff --git a/src/uu/hashsum/src/hashsum.rs b/src/uu/hashsum/src/hashsum.rs index f820b083e..4186043f5 100644 --- a/src/uu/hashsum/src/hashsum.rs +++ b/src/uu/hashsum/src/hashsum.rs @@ -7,7 +7,7 @@ // * For the full copyright and license information, please view the LICENSE // * file that was distributed with this source code. -// spell-checker:ignore (ToDO) algo, algoname, regexes, nread memmem +// spell-checker:ignore (ToDO) algo, algoname, regexes, nread #[macro_use] extern crate clap; @@ -18,11 +18,11 @@ extern crate uucore; mod digest; use self::digest::Digest; +use self::digest::DigestWriter; use clap::{App, Arg, ArgMatches}; use hex::ToHex; use md5::Context as Md5; -use memchr::memmem; use regex::Regex; use sha1::Sha1; use sha2::{Sha224, Sha256, Sha384, Sha512}; @@ -540,7 +540,7 @@ where let real_sum = crash_if_err!( 1, digest_reader( - &mut *options.digest, + &mut options.digest, &mut ckf, binary_check, options.output_bits @@ -571,7 +571,7 @@ where let sum = crash_if_err!( 1, digest_reader( - &mut *options.digest, + &mut options.digest, &mut file, options.binary, options.output_bits @@ -598,48 +598,21 @@ where Ok(()) } -fn digest_reader<'a, T: Read>( - digest: &mut (dyn Digest + 'a), +fn digest_reader( + digest: &mut Box, reader: &mut BufReader, binary: bool, output_bits: usize, ) -> io::Result { digest.reset(); - // Digest file, do not hold too much in memory at any given moment - let windows = cfg!(windows); - let mut buffer = Vec::with_capacity(524_288); - loop { - match reader.read_to_end(&mut buffer) { - Ok(0) => { - break; - } - Ok(nread) => { - if windows && !binary { - // In Windows text mode, replace each occurrence of - // "\r\n" with "\n". - // - // Find all occurrences of "\r\n", inputting the - // slice just before the "\n" in the previous - // instance of "\r\n" and the beginning of this - // "\r\n". - // - // FIXME This fails if one call to `read()` ends - // with the "\r" and the next call to `read()` - // begins with the "\n". - let mut i_prev = 0; - for i in memmem::find_iter(&buffer[0..nread], b"\r\n") { - digest.input(&buffer[i_prev..i]); - i_prev = i + 1; - } - digest.input(&buffer[i_prev..nread]); - } else { - digest.input(&buffer[..nread]); - } - } - Err(e) => return Err(e), - } - } + // Read bytes from `reader` and write those bytes to `digest`. + // + // If `binary` is `false` and the operating system is Windows, then + // `DigestWriter` replaces "\r\n" with "\n" before it writes the + // bytes into `digest`. Otherwise, it just inserts the bytes as-is. + let mut digest_writer = DigestWriter::new(digest, binary); + std::io::copy(reader, &mut digest_writer)?; if digest.output_bits() > 0 { Ok(digest.result_str()) From bfb1327ad4f8dd325e120f6f6a2914fc4035f598 Mon Sep 17 00:00:00 2001 From: Jeffrey Finkelstein Date: Thu, 16 Sep 2021 23:02:02 -0400 Subject: [PATCH 205/206] seq: use print_seq_integers() regardless of last Ensure that the `print_seq_integers()` function is called when the first number and the increment are integers, regardless of the type of the last value specified. --- src/uu/seq/src/seq.rs | 35 +++++++++-- tests/by-util/test_seq.rs | 120 +++++++++++++++++++++++++++++++++++++- 2 files changed, 147 insertions(+), 8 deletions(-) diff --git a/src/uu/seq/src/seq.rs b/src/uu/seq/src/seq.rs index 8bf6cb008..c4380fd3d 100644 --- a/src/uu/seq/src/seq.rs +++ b/src/uu/seq/src/seq.rs @@ -67,6 +67,18 @@ impl Number { Number::F64(n) => n, } } + + /// Convert this number into a bigint, consuming it. + /// + /// For floats, this returns the [`BigInt`] corresponding to the + /// floor of the number. + fn into_bigint(self) -> BigInt { + match self { + Number::MinusZero => BigInt::zero(), + Number::F64(x) => BigInt::from(x.floor() as i64), + Number::BigInt(n) => n, + } + } } impl FromStr for Number { @@ -197,25 +209,38 @@ pub fn uumain(args: impl uucore::Args) -> i32 { crash_if_err!(1, slice.parse()) }; + let is_negative_zero_f64 = |x: f64| x == -0.0 && x.is_sign_negative() && largest_dec == 0; let result = match (first, last, increment) { - (Number::MinusZero, Number::BigInt(last), Number::BigInt(increment)) => print_seq_integers( - (BigInt::zero(), increment, last), + // For example, `seq -0 1 2` or `seq -0 1 2.0`. + (Number::MinusZero, last, Number::BigInt(increment)) => print_seq_integers( + (BigInt::zero(), increment, last.into_bigint()), options.separator, options.terminator, options.widths, padding, true, ), - (Number::BigInt(first), Number::BigInt(last), Number::BigInt(increment)) => { + // For example, `seq -0e0 1 2` or `seq -0e0 1 2.0`. + (Number::F64(x), last, Number::BigInt(increment)) if is_negative_zero_f64(x) => { print_seq_integers( - (first, increment, last), + (BigInt::zero(), increment, last.into_bigint()), options.separator, options.terminator, options.widths, padding, - false, + true, ) } + // For example, `seq 0 1 2` or `seq 0 1 2.0`. + (Number::BigInt(first), last, Number::BigInt(increment)) => print_seq_integers( + (first, increment, last.into_bigint()), + options.separator, + options.terminator, + options.widths, + padding, + false, + ), + // For example, `seq 0 0.5 1` or `seq 0.0 0.5 1` or `seq 0.0 0.5 1.0`. (first, last, increment) => print_seq( (first.into_f64(), increment.into_f64(), last.into_f64()), largest_dec, diff --git a/tests/by-util/test_seq.rs b/tests/by-util/test_seq.rs index 7a58a950a..51262ff23 100644 --- a/tests/by-util/test_seq.rs +++ b/tests/by-util/test_seq.rs @@ -199,6 +199,16 @@ fn test_preserve_negative_zero_start() { .succeeds() .stdout_is("-0\n1\n") .no_stderr(); + new_ucmd!() + .args(&["-0", "1", "2"]) + .succeeds() + .stdout_is("-0\n1\n2\n") + .no_stderr(); + new_ucmd!() + .args(&["-0", "1", "2.0"]) + .succeeds() + .stdout_is("-0\n1\n2\n") + .no_stderr(); } #[test] @@ -226,6 +236,50 @@ fn test_width_negative_zero() { .succeeds() .stdout_is("-0\n01\n") .no_stderr(); + new_ucmd!() + .args(&["-w", "-0", "1", "2"]) + .succeeds() + .stdout_is("-0\n01\n02\n") + .no_stderr(); + new_ucmd!() + .args(&["-w", "-0", "1", "2.0"]) + .succeeds() + .stdout_is("-0\n01\n02\n") + .no_stderr(); +} + +#[test] +fn test_width_negative_zero_decimal_notation() { + new_ucmd!() + .args(&["-w", "-0.0", "1"]) + .succeeds() + .stdout_is("-0.0\n01.0\n") + .no_stderr(); + new_ucmd!() + .args(&["-w", "-0.0", "1.0"]) + .succeeds() + .stdout_is("-0.0\n01.0\n") + .no_stderr(); + new_ucmd!() + .args(&["-w", "-0.0", "1", "2"]) + .succeeds() + .stdout_is("-0.0\n01.0\n02.0\n") + .no_stderr(); + new_ucmd!() + .args(&["-w", "-0.0", "1", "2.0"]) + .succeeds() + .stdout_is("-0.0\n01.0\n02.0\n") + .no_stderr(); + new_ucmd!() + .args(&["-w", "-0.0", "1.0", "2"]) + .succeeds() + .stdout_is("-0.0\n01.0\n02.0\n") + .no_stderr(); + new_ucmd!() + .args(&["-w", "-0.0", "1.0", "2.0"]) + .succeeds() + .stdout_is("-0.0\n01.0\n02.0\n") + .no_stderr(); } #[test] @@ -235,29 +289,63 @@ fn test_width_negative_zero_scientific_notation() { .succeeds() .stdout_is("-0\n01\n") .no_stderr(); + new_ucmd!() + .args(&["-w", "-0e0", "1", "2"]) + .succeeds() + .stdout_is("-0\n01\n02\n") + .no_stderr(); + new_ucmd!() + .args(&["-w", "-0e0", "1", "2.0"]) + .succeeds() + .stdout_is("-0\n01\n02\n") + .no_stderr(); new_ucmd!() .args(&["-w", "-0e+1", "1"]) .succeeds() .stdout_is("-00\n001\n") .no_stderr(); + new_ucmd!() + .args(&["-w", "-0e+1", "1", "2"]) + .succeeds() + .stdout_is("-00\n001\n002\n") + .no_stderr(); + new_ucmd!() + .args(&["-w", "-0e+1", "1", "2.0"]) + .succeeds() + .stdout_is("-00\n001\n002\n") + .no_stderr(); new_ucmd!() .args(&["-w", "-0.000e0", "1"]) .succeeds() .stdout_is("-0.000\n01.000\n") .no_stderr(); + new_ucmd!() + .args(&["-w", "-0.000e0", "1", "2"]) + .succeeds() + .stdout_is("-0.000\n01.000\n02.000\n") + .no_stderr(); + new_ucmd!() + .args(&["-w", "-0.000e0", "1", "2.0"]) + .succeeds() + .stdout_is("-0.000\n01.000\n02.000\n") + .no_stderr(); new_ucmd!() .args(&["-w", "-0.000e-2", "1"]) .succeeds() .stdout_is("-0.00000\n01.00000\n") .no_stderr(); - new_ucmd!() - .args(&["-w", "-0.000e5", "1"]) + .args(&["-w", "-0.000e-2", "1", "2"]) .succeeds() - .stdout_is("-000000\n0000001\n") + .stdout_is("-0.00000\n01.00000\n02.00000\n") + .no_stderr(); + new_ucmd!() + .args(&["-w", "-0.000e-2", "1", "2.0"]) + .succeeds() + .stdout_is("-0.00000\n01.00000\n02.00000\n") .no_stderr(); new_ucmd!() @@ -265,6 +353,32 @@ fn test_width_negative_zero_scientific_notation() { .succeeds() .stdout_is("-000000\n0000001\n") .no_stderr(); + new_ucmd!() + .args(&["-w", "-0.000e5", "1", "2"]) + .succeeds() + .stdout_is("-000000\n0000001\n0000002\n") + .no_stderr(); + new_ucmd!() + .args(&["-w", "-0.000e5", "1", "2.0"]) + .succeeds() + .stdout_is("-000000\n0000001\n0000002\n") + .no_stderr(); + + new_ucmd!() + .args(&["-w", "-0.000e5", "1"]) + .succeeds() + .stdout_is("-000000\n0000001\n") + .no_stderr(); + new_ucmd!() + .args(&["-w", "-0.000e5", "1", "2"]) + .succeeds() + .stdout_is("-000000\n0000001\n0000002\n") + .no_stderr(); + new_ucmd!() + .args(&["-w", "-0.000e5", "1", "2.0"]) + .succeeds() + .stdout_is("-000000\n0000001\n0000002\n") + .no_stderr(); } #[test] From 7ea2bfbe2680e65e5a6774905a8f380888ca7a25 Mon Sep 17 00:00:00 2001 From: Jan Verbeek Date: Mon, 13 Sep 2021 21:19:49 -0400 Subject: [PATCH 206/206] seq: replace loops with a single format string Replace two loops that print leading and trailing 0s when printing a number in fixed-width mode with a single call to `write!()` with the appropriate formatting parameters. --- src/uu/seq/src/seq.rs | 17 ++++++++--------- tests/by-util/test_seq.rs | 9 +++++++++ 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/src/uu/seq/src/seq.rs b/src/uu/seq/src/seq.rs index c4380fd3d..aac8f2280 100644 --- a/src/uu/seq/src/seq.rs +++ b/src/uu/seq/src/seq.rs @@ -317,6 +317,7 @@ fn print_seq( let mut i = 0isize; let is_first_minus_zero = first == -0.0 && first.is_sign_negative(); let mut value = first + i as f64 * increment; + let padding = if pad { padding + 1 + largest_dec } else { 0 }; let mut is_first_iteration = true; while !done_printing(&value, &increment, &last) { if !is_first_iteration { @@ -328,15 +329,13 @@ fn print_seq( width -= 1; } is_first_iteration = false; - let istr = format!("{:.*}", largest_dec, value); - let ilen = istr.len(); - let before_dec = istr.find('.').unwrap_or(ilen); - if pad && before_dec < width { - for _ in 0..(width - before_dec) { - write!(stdout, "0")?; - } - } - write!(stdout, "{}", istr)?; + write!( + stdout, + "{value:>0width$.precision$}", + value = value, + width = width, + precision = largest_dec, + )?; i += 1; value = first + i as f64 * increment; } diff --git a/tests/by-util/test_seq.rs b/tests/by-util/test_seq.rs index 51262ff23..27b5f99bc 100644 --- a/tests/by-util/test_seq.rs +++ b/tests/by-util/test_seq.rs @@ -426,6 +426,15 @@ fn test_width_decimal_scientific_notation_trailing_zeros_end() { .no_stderr(); } +#[test] +fn test_width_floats() { + new_ucmd!() + .args(&["-w", "9.0", "10.0"]) + .succeeds() + .stdout_is("09.0\n10.0\n") + .no_stderr(); +} + // TODO This is duplicated from `test_yes.rs`; refactor them. /// Run `seq`, capture some of the output, close the pipe, and verify it. fn run(args: &[&str], expected: &[u8]) {