From 96d3a95a3920b0931a87c167b29e603779e617e3 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Sun, 30 Jan 2022 14:32:08 +0100 Subject: [PATCH 01/58] test: fix wsl executable permission --- tests/by-util/test_test.rs | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/tests/by-util/test_test.rs b/tests/by-util/test_test.rs index db74265a4..8500d63ed 100644 --- a/tests/by-util/test_test.rs +++ b/tests/by-util/test_test.rs @@ -440,7 +440,27 @@ fn test_file_is_not_writable() { #[test] fn test_file_is_not_executable() { - new_ucmd!().args(&["!", "-x", "regular_file"]).succeeds(); + #[cfg(unix)] + let (at, mut ucmd) = at_and_ucmd!(); + #[cfg(not(unix))] + let (_, mut ucmd) = at_and_ucmd!(); + + // WSL creates executable files by default, so if we are on unix, make sure + // to set make it non-executable. + // Files on other targets are non-executable by default. + #[cfg(unix)] + { + use std::os::unix::fs::PermissionsExt; + let metadata = std::fs::metadata(at.plus("regular_file")).unwrap(); + let mut permissions = metadata.permissions(); + + // The conversion is useless on some platforms and casts from u16 to + // u32 on others + #[allow(clippy::useless_conversion)] + permissions.set_mode(permissions.mode() & !u32::from(libc::S_IXUSR)); + std::fs::set_permissions(at.plus("regular_file"), permissions).unwrap(); + } + ucmd.args(&["!", "-x", "regular_file"]).succeeds(); } #[test] From be6287e3e30e14b6c9c455217846cb3acdc9c282 Mon Sep 17 00:00:00 2001 From: Narasimha Prasanna HN Date: Tue, 1 Feb 2022 17:37:04 +0530 Subject: [PATCH 02/58] Fix: Avoid infinite recursive copies when source and destination directories are same or source is a prefix of destination --- src/uu/cp/src/cp.rs | 17 +++++++++++++++ tests/by-util/test_cp.rs | 45 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+) diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index f8ce6f241..938ecfe03 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -969,6 +969,16 @@ fn copy_directory( return copy_file(root, target, options, symlinked_files); } + // check if root is a prefix of target + if path_has_prefix(target, root)? { + return Err(format!( + "cannot copy a directory, {}, into itself, {}", + root.quote(), + target.quote() + ) + .into()); + } + let current_dir = env::current_dir().unwrap_or_else(|e| crash!(1, "failed to get current directory {}", e)); @@ -1570,6 +1580,13 @@ pub fn paths_refer_to_same_file(p1: &Path, p2: &Path) -> io::Result { Ok(pathbuf1 == pathbuf2) } +pub fn path_has_prefix(p1: &Path, p2: &Path) -> io::Result { + let pathbuf1 = canonicalize(p1, MissingHandling::Normal, ResolveMode::Logical)?; + let pathbuf2 = canonicalize(p2, MissingHandling::Normal, ResolveMode::Logical)?; + + Ok(pathbuf1.starts_with(pathbuf2)) +} + #[test] fn test_cp_localize_to_target() { assert!( diff --git a/tests/by-util/test_cp.rs b/tests/by-util/test_cp.rs index 92637dfbe..0a4dfd16d 100644 --- a/tests/by-util/test_cp.rs +++ b/tests/by-util/test_cp.rs @@ -1444,3 +1444,48 @@ fn test_cp_archive_on_nonexistent_file() { "cp: cannot stat 'nonexistent_file.txt': No such file or directory (os error 2)", ); } + +#[test] +fn test_dir_recursive_copy() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + at.mkdir("parent1"); + at.mkdir("parent2"); + at.mkdir("parent1/child"); + at.mkdir("parent2/child1"); + at.mkdir("parent2/child1/child2"); + at.mkdir("parent2/child1/child2/child3"); + + // case-1: copy parent1 -> parent1: should fail + scene + .ucmd() + .arg("-R") + .arg("parent1") + .arg("parent1") + .fails() + .stderr_contains("cannot copy a directory"); + // case-2: copy parent1 -> parent1/child should fail + scene + .ucmd() + .arg("-R") + .arg("parent1") + .arg("parent1/child") + .fails() + .stderr_contains("cannot copy a directory"); + // case-3: copy parent1/child -> parent2 should pass + scene + .ucmd() + .arg("-R") + .arg("parent1/child") + .arg("parent2") + .succeeds(); + // case-4: copy parent2/child1/ -> parent2/child1/child2/child3 + scene + .ucmd() + .arg("-R") + .arg("parent2/child1/") + .arg("parent2/child1/child2/child3") + .fails() + .stderr_contains("cannot copy a directory"); +} From 66733ca99491dd7486300f1db1cb7fc2ba1e29da Mon Sep 17 00:00:00 2001 From: Andreas Molzer Date: Thu, 3 Feb 2022 23:44:43 +0100 Subject: [PATCH 03/58] seq: Add difficult cases to test suite --- tests/by-util/test_seq.rs | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/tests/by-util/test_seq.rs b/tests/by-util/test_seq.rs index 5adbc292e..ad3086b03 100644 --- a/tests/by-util/test_seq.rs +++ b/tests/by-util/test_seq.rs @@ -81,6 +81,33 @@ fn test_rejects_non_floats() { .usage_error("invalid floating point argument: 'foo'"); } +#[test] +fn test_accepts_option_argument_directly() { + new_ucmd!() + .arg("-s,") + .arg("2") + .succeeds() + .stdout_is("1,2\n"); +} + +#[test] +fn test_option_with_detected_negative_argument() { + new_ucmd!() + .arg("-s,") + .args(&["-1", "2"]) + .succeeds() + .stdout_is("-1,0,1,2\n"); +} + +#[test] +fn test_negative_number_as_separator() { + new_ucmd!() + .arg("-s") + .args(&["-1", "2"]) + .succeeds() + .stdout_is("1-12\n"); +} + #[test] fn test_invalid_float() { new_ucmd!() From a2e93299186be1911d1173cff8ea7f6163fa8f0f Mon Sep 17 00:00:00 2001 From: Andreas Molzer Date: Sun, 6 Feb 2022 03:46:31 +0100 Subject: [PATCH 04/58] seq: Allow option to receive immediate arguments WIP: this needs to be adjusted --- src/uu/seq/src/seq.rs | 2 +- tests/by-util/test_seq.rs | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/uu/seq/src/seq.rs b/src/uu/seq/src/seq.rs index af961a493..ec437dd40 100644 --- a/src/uu/seq/src/seq.rs +++ b/src/uu/seq/src/seq.rs @@ -146,7 +146,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app<'a>() -> App<'a> { App::new(uucore::util_name()) .setting(AppSettings::TrailingVarArg) - .setting(AppSettings::AllowHyphenValues) + .setting(AppSettings::AllowNegativeNumbers) .setting(AppSettings::InferLongArgs) .version(crate_version!()) .about(ABOUT) diff --git a/tests/by-util/test_seq.rs b/tests/by-util/test_seq.rs index ad3086b03..a18cbc2ad 100644 --- a/tests/by-util/test_seq.rs +++ b/tests/by-util/test_seq.rs @@ -20,14 +20,14 @@ fn test_hex_rejects_sign_after_identifier() { .args(&["-0x-123ABC"]) .fails() .no_stdout() - .stderr_contains("invalid floating point argument: '-0x-123ABC'") - .stderr_contains("for more information."); + .stderr_contains("which wasn't expected, or isn't valid in this context") + .stderr_contains("For more information try --help"); new_ucmd!() .args(&["-0x+123ABC"]) .fails() .no_stdout() - .stderr_contains("invalid floating point argument: '-0x+123ABC'") - .stderr_contains("for more information."); + .stderr_contains("which wasn't expected, or isn't valid in this context") + .stderr_contains("For more information try --help"); } #[test] From e77c8ff38154d378c2f2f7463977fc73b6485344 Mon Sep 17 00:00:00 2001 From: xxyzz Date: Sun, 13 Feb 2022 10:17:02 +0800 Subject: [PATCH 05/58] df: `-t` may appear more than once and doesn't support delimiter --- src/uu/df/src/df.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uu/df/src/df.rs b/src/uu/df/src/df.rs index 90f1b0c9a..02cfca012 100644 --- a/src/uu/df/src/df.rs +++ b/src/uu/df/src/df.rs @@ -381,7 +381,7 @@ pub fn uu_app<'a>() -> App<'a> { .long("type") .allow_invalid_utf8(true) .takes_value(true) - .use_delimiter(true) + .multiple_occurrences(true) .help("limit listing to file systems of type TYPE"), ) .arg( From c849b8722f303b5e6c773ef5c61fb0490089d58f Mon Sep 17 00:00:00 2001 From: xxyzz Date: Sun, 13 Feb 2022 10:18:40 +0800 Subject: [PATCH 06/58] df: `--output` option conflicts with `-i`, `-P`, `-T` --- src/uu/df/src/df.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/uu/df/src/df.rs b/src/uu/df/src/df.rs index 02cfca012..be33238ff 100644 --- a/src/uu/df/src/df.rs +++ b/src/uu/df/src/df.rs @@ -338,6 +338,7 @@ pub fn uu_app<'a>() -> App<'a> { Arg::new(OPT_INODES) .short('i') .long("inodes") + .conflicts_with(OPT_OUTPUT) .help("list inode information instead of block usage"), ) .arg(Arg::new(OPT_KILO).short('k').help("like --block-size=1K")) @@ -367,6 +368,7 @@ pub fn uu_app<'a>() -> App<'a> { Arg::new(OPT_PORTABILITY) .short('P') .long("portability") + .conflicts_with(OPT_OUTPUT) .help("use the POSIX output format"), ) .arg( @@ -388,6 +390,7 @@ pub fn uu_app<'a>() -> App<'a> { Arg::new(OPT_PRINT_TYPE) .short('T') .long("print-type") + .conflicts_with(OPT_OUTPUT) .help("print file system type"), ) .arg( From 18b11cb2cf9d9f5115f9d56e44e8ffbb4255642d Mon Sep 17 00:00:00 2001 From: xxyzz Date: Thu, 3 Feb 2022 18:03:48 +0800 Subject: [PATCH 07/58] Create coverage report for GNU tests --- .github/workflows/GnuTests.yml | 76 ++++++++++++++++++++++++++++++++++ DEVELOPER_INSTRUCTIONS.md | 4 +- 2 files changed, 78 insertions(+), 2 deletions(-) diff --git a/.github/workflows/GnuTests.yml b/.github/workflows/GnuTests.yml index e57204213..4d8e1db19 100644 --- a/.github/workflows/GnuTests.yml +++ b/.github/workflows/GnuTests.yml @@ -182,3 +182,79 @@ jobs: else echo "::warning ::Skipping test summary comparison; no prior reference summary is available." fi + + gnu_coverage: + name: Run GNU tests with coverage + runs-on: ubuntu-latest + if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }} + steps: + - name: Checkout code uutil + uses: actions/checkout@v2 + with: + path: 'uutils' + - name: Checkout GNU coreutils + uses: actions/checkout@v2 + with: + repository: 'coreutils/coreutils' + path: 'gnu' + ref: 'v9.0' + submodules: recursive + - name: Install `rust` toolchain + uses: actions-rs/toolchain@v1 + with: + toolchain: nightly + default: true + profile: minimal # minimal component installation (ie, no documentation) + components: rustfmt + - name: Install dependencies + run: | + sudo apt update + sudo apt install autoconf autopoint bison texinfo gperf gcc g++ gdb python-pyinotify jq valgrind libexpect-perl -y + - name: Add various locales + run: | + echo "Before:" + locale -a + ## Some tests fail with 'cannot change locale (en_US.ISO-8859-1): No such file or directory' + ## Some others need a French locale + sudo locale-gen + sudo locale-gen fr_FR + sudo locale-gen fr_FR.UTF-8 + sudo update-locale + echo "After:" + locale -a + - name: Build binaries + env: + CARGO_INCREMENTAL: "0" + RUSTFLAGS: "-Zprofile -Ccodegen-units=1 -Copt-level=0 -Clink-dead-code -Coverflow-checks=off -Zpanic_abort_tests -Cpanic=abort" + RUSTDOCFLAGS: "-Cpanic=abort" + run: | + cd uutils + UU_MAKE_PROFILE=debug bash util/build-gnu.sh + - name: Run GNU tests + run: bash uutils/util/run-gnu-test.sh + - name: "`grcov` ~ install" + uses: actions-rs/install@v0.1 + with: + crate: grcov + version: latest + use-tool-cache: false + - name: Generate coverage data (via `grcov`) + id: coverage + run: | + ## Generate coverage data + cd uutils + COVERAGE_REPORT_DIR="target/debug" + COVERAGE_REPORT_FILE="${COVERAGE_REPORT_DIR}/lcov.info" + mkdir -p "${COVERAGE_REPORT_DIR}" + # display coverage files + grcov . --output-type files --ignore build.rs --ignore "vendor/*" --ignore "/*" --ignore "[a-zA-Z]:/*" --excl-br-line "^\s*((debug_)?assert(_eq|_ne)?!|#\[derive\()" | sort --unique + # generate coverage report + grcov . --output-type lcov --output-path "${COVERAGE_REPORT_FILE}" --branch --ignore build.rs --ignore "vendor/*" --ignore "/*" --ignore "[a-zA-Z]:/*" --excl-br-line "^\s*((debug_)?assert(_eq|_ne)?!|#\[derive\()" + echo ::set-output name=report::${COVERAGE_REPORT_FILE} + - name: Upload coverage results (to Codecov.io) + uses: codecov/codecov-action@v2 + with: + file: ${{ steps.coverage.outputs.report }} + flags: gnutests + name: gnutests + working-directory: uutils diff --git a/DEVELOPER_INSTRUCTIONS.md b/DEVELOPER_INSTRUCTIONS.md index 027d4dca1..c007fba7e 100644 --- a/DEVELOPER_INSTRUCTIONS.md +++ b/DEVELOPER_INSTRUCTIONS.md @@ -21,7 +21,7 @@ Running GNU tests 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`. +- 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.sh`. Code Coverage Report Generation @@ -33,7 +33,7 @@ Code coverage report can be generated using [grcov](https://github.com/mozilla/g ### Using Nightly Rust -To generate [gcov-based](https://github.com/mozilla/grcov#example-how-to-generate-gcda-files-for-cc) coverage report +To generate [gcov-based](https://github.com/mozilla/grcov#example-how-to-generate-gcda-files-for-a-rust-project) coverage report ```bash $ export CARGO_INCREMENTAL=0 From 1dbd474339b90bb072d1a78be32379c00cd36339 Mon Sep 17 00:00:00 2001 From: xxyzz Date: Fri, 4 Feb 2022 18:34:09 +0800 Subject: [PATCH 08/58] There are four GNU tests require valgrind --- .github/workflows/GnuTests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/GnuTests.yml b/.github/workflows/GnuTests.yml index 4d8e1db19..1990bfbd3 100644 --- a/.github/workflows/GnuTests.yml +++ b/.github/workflows/GnuTests.yml @@ -66,7 +66,7 @@ jobs: run: | ## Install dependencies sudo apt-get update - sudo apt-get install autoconf autopoint bison texinfo gperf gcc g++ gdb python-pyinotify jq + sudo apt-get install autoconf autopoint bison texinfo gperf gcc g++ gdb python-pyinotify jq valgrind - name: Add various locales shell: bash run: | From ce02eae14bfdff5c0f85b2b6f80e9d0dc77eaedf Mon Sep 17 00:00:00 2001 From: xxyzz Date: Fri, 4 Feb 2022 19:17:30 +0800 Subject: [PATCH 09/58] tests/misc/tty-eof.pl requires Perl's Expect package >=1.11 --- .github/workflows/GnuTests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/GnuTests.yml b/.github/workflows/GnuTests.yml index 1990bfbd3..79e0878ac 100644 --- a/.github/workflows/GnuTests.yml +++ b/.github/workflows/GnuTests.yml @@ -66,7 +66,7 @@ jobs: run: | ## Install dependencies sudo apt-get update - sudo apt-get install autoconf autopoint bison texinfo gperf gcc g++ gdb python-pyinotify jq valgrind + sudo apt-get install autoconf autopoint bison texinfo gperf gcc g++ gdb python-pyinotify jq valgrind libexpect-perl - name: Add various locales shell: bash run: | From 1ccf94e4fa8dd334a33e4174043acbeb57730802 Mon Sep 17 00:00:00 2001 From: xxyzz Date: Mon, 14 Feb 2022 14:04:07 +0800 Subject: [PATCH 10/58] Run 33 GNU tests that require root --- util/run-gnu-test.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/util/run-gnu-test.sh b/util/run-gnu-test.sh index 4ff266833..360807013 100755 --- a/util/run-gnu-test.sh +++ b/util/run-gnu-test.sh @@ -31,6 +31,8 @@ export RUST_BACKTRACE=1 if test -n "$1"; then # if set, run only the test passed export RUN_TEST="TESTS=$1" +elif test -n "$CI"; then + sudo make -j "$(nproc)" check-root SUBDIRS=. RUN_EXPENSIVE_TESTS=yes RUN_VERY_EXPENSIVE_TESTS=yes VERBOSE=no gl_public_submodule_commit="" srcdir="${path_GNU}" TEST_SUITE_LOG="tests/test-suite-root.log" || : fi # * timeout used to kill occasionally errant/"stuck" processes (note: 'release' testing takes ~1 hour; 'debug' testing takes ~2.5 hours) From aa4c5aea50bac22393f4ba1d4eee8b7800a74fe9 Mon Sep 17 00:00:00 2001 From: Jeffrey Finkelstein Date: Sun, 30 Jan 2022 21:35:43 -0500 Subject: [PATCH 11/58] split: refactor to add SuffixType enum Refactor the code to use a `SuffixType` enumeration with two members, `Alphabetic` and `NumericDecimal`, representing the two currently supported ways of producing filename suffixes. This prepares the code to more easily support other formats, like numeric hexadecimal. --- src/uu/split/src/filenames.rs | 62 ++++++++++++++++++++++++----------- src/uu/split/src/split.rs | 20 ++++++++--- 2 files changed, 58 insertions(+), 24 deletions(-) diff --git a/src/uu/split/src/filenames.rs b/src/uu/split/src/filenames.rs index 3e2db3606..1b89190c7 100644 --- a/src/uu/split/src/filenames.rs +++ b/src/uu/split/src/filenames.rs @@ -13,12 +13,13 @@ //! //! ```rust,ignore //! use crate::filenames::FilenameIterator; +//! use crate::filenames::SuffixType; //! //! let prefix = "chunk_".to_string(); //! let suffix = ".txt".to_string(); //! let width = 2; -//! let use_numeric_suffix = false; -//! let it = FilenameIterator::new(prefix, suffix, width, use_numeric_suffix); +//! let suffix_type = SuffixType::Alphabetic; +//! let it = FilenameIterator::new(prefix, suffix, width, suffix_type); //! //! assert_eq!(it.next().unwrap(), "chunk_aa.txt"); //! assert_eq!(it.next().unwrap(), "chunk_ab.txt"); @@ -28,6 +29,26 @@ use crate::number::DynamicWidthNumber; use crate::number::FixedWidthNumber; use crate::number::Number; +/// The format to use for suffixes in the filename for each output chunk. +#[derive(Clone, Copy)] +pub enum SuffixType { + /// Lowercase ASCII alphabetic characters. + Alphabetic, + + /// Decimal numbers. + NumericDecimal, +} + +impl SuffixType { + /// The radix to use when representing the suffix string as digits. + fn radix(&self) -> u8 { + match self { + SuffixType::Alphabetic => 26, + SuffixType::NumericDecimal => 10, + } + } +} + /// Compute filenames from a given index. /// /// This iterator yields filenames for use with ``split``. @@ -42,8 +63,8 @@ use crate::number::Number; /// width in characters. In that case, after the iterator yields each /// string of that width, the iterator is exhausted. /// -/// Finally, if `use_numeric_suffix` is `true`, then numbers will be -/// used instead of lowercase ASCII alphabetic characters. +/// Finally, `suffix_type` controls which type of suffix to produce, +/// alphabetic or numeric. /// /// # Examples /// @@ -52,28 +73,30 @@ use crate::number::Number; /// /// ```rust,ignore /// use crate::filenames::FilenameIterator; +/// use crate::filenames::SuffixType; /// /// let prefix = "chunk_".to_string(); /// let suffix = ".txt".to_string(); /// let width = 2; -/// let use_numeric_suffix = false; -/// let it = FilenameIterator::new(prefix, suffix, width, use_numeric_suffix); +/// let suffix_type = SuffixType::Alphabetic; +/// let it = FilenameIterator::new(prefix, suffix, width, suffix_type); /// /// assert_eq!(it.next().unwrap(), "chunk_aa.txt"); /// assert_eq!(it.next().unwrap(), "chunk_ab.txt"); /// assert_eq!(it.next().unwrap(), "chunk_ac.txt"); /// ``` /// -/// For numeric filenames, set `use_numeric_suffix` to `true`: +/// For numeric filenames, use `SuffixType::NumericDecimal`: /// /// ```rust,ignore /// use crate::filenames::FilenameIterator; +/// use crate::filenames::SuffixType; /// /// let prefix = "chunk_".to_string(); /// let suffix = ".txt".to_string(); /// let width = 2; -/// let use_numeric_suffix = true; -/// let it = FilenameIterator::new(prefix, suffix, width, use_numeric_suffix); +/// let suffix_type = SuffixType::NumericDecimal; +/// let it = FilenameIterator::new(prefix, suffix, width, suffix_type); /// /// assert_eq!(it.next().unwrap(), "chunk_00.txt"); /// assert_eq!(it.next().unwrap(), "chunk_01.txt"); @@ -91,9 +114,9 @@ impl<'a> FilenameIterator<'a> { prefix: &'a str, additional_suffix: &'a str, suffix_length: usize, - use_numeric_suffix: bool, + suffix_type: SuffixType, ) -> FilenameIterator<'a> { - let radix = if use_numeric_suffix { 10 } else { 26 }; + let radix = suffix_type.radix(); let number = if suffix_length == 0 { Number::DynamicWidth(DynamicWidthNumber::new(radix)) } else { @@ -130,39 +153,40 @@ impl<'a> Iterator for FilenameIterator<'a> { mod tests { use crate::filenames::FilenameIterator; + use crate::filenames::SuffixType; #[test] fn test_filename_iterator_alphabetic_fixed_width() { - let mut it = FilenameIterator::new("chunk_", ".txt", 2, false); + let mut it = FilenameIterator::new("chunk_", ".txt", 2, SuffixType::Alphabetic); assert_eq!(it.next().unwrap(), "chunk_aa.txt"); assert_eq!(it.next().unwrap(), "chunk_ab.txt"); assert_eq!(it.next().unwrap(), "chunk_ac.txt"); - let mut it = FilenameIterator::new("chunk_", ".txt", 2, false); + let mut it = FilenameIterator::new("chunk_", ".txt", 2, SuffixType::Alphabetic); assert_eq!(it.nth(26 * 26 - 1).unwrap(), "chunk_zz.txt"); assert_eq!(it.next(), None); } #[test] fn test_filename_iterator_numeric_fixed_width() { - let mut it = FilenameIterator::new("chunk_", ".txt", 2, true); + let mut it = FilenameIterator::new("chunk_", ".txt", 2, SuffixType::NumericDecimal); assert_eq!(it.next().unwrap(), "chunk_00.txt"); assert_eq!(it.next().unwrap(), "chunk_01.txt"); assert_eq!(it.next().unwrap(), "chunk_02.txt"); - let mut it = FilenameIterator::new("chunk_", ".txt", 2, true); + let mut it = FilenameIterator::new("chunk_", ".txt", 2, SuffixType::NumericDecimal); assert_eq!(it.nth(10 * 10 - 1).unwrap(), "chunk_99.txt"); assert_eq!(it.next(), None); } #[test] fn test_filename_iterator_alphabetic_dynamic_width() { - let mut it = FilenameIterator::new("chunk_", ".txt", 0, false); + let mut it = FilenameIterator::new("chunk_", ".txt", 0, SuffixType::Alphabetic); assert_eq!(it.next().unwrap(), "chunk_aa.txt"); assert_eq!(it.next().unwrap(), "chunk_ab.txt"); assert_eq!(it.next().unwrap(), "chunk_ac.txt"); - let mut it = FilenameIterator::new("chunk_", ".txt", 0, false); + let mut it = FilenameIterator::new("chunk_", ".txt", 0, SuffixType::Alphabetic); assert_eq!(it.nth(26 * 25 - 1).unwrap(), "chunk_yz.txt"); assert_eq!(it.next().unwrap(), "chunk_zaaa.txt"); assert_eq!(it.next().unwrap(), "chunk_zaab.txt"); @@ -170,12 +194,12 @@ mod tests { #[test] fn test_filename_iterator_numeric_dynamic_width() { - let mut it = FilenameIterator::new("chunk_", ".txt", 0, true); + let mut it = FilenameIterator::new("chunk_", ".txt", 0, SuffixType::NumericDecimal); assert_eq!(it.next().unwrap(), "chunk_00.txt"); assert_eq!(it.next().unwrap(), "chunk_01.txt"); assert_eq!(it.next().unwrap(), "chunk_02.txt"); - let mut it = FilenameIterator::new("chunk_", ".txt", 0, true); + let mut it = FilenameIterator::new("chunk_", ".txt", 0, SuffixType::NumericDecimal); assert_eq!(it.nth(10 * 9 - 1).unwrap(), "chunk_89.txt"); assert_eq!(it.next().unwrap(), "chunk_9000.txt"); assert_eq!(it.next().unwrap(), "chunk_9001.txt"); diff --git a/src/uu/split/src/split.rs b/src/uu/split/src/split.rs index 29559f8b8..dbb537c96 100644 --- a/src/uu/split/src/split.rs +++ b/src/uu/split/src/split.rs @@ -12,6 +12,7 @@ mod number; mod platform; use crate::filenames::FilenameIterator; +use crate::filenames::SuffixType; use clap::{crate_version, App, AppSettings, Arg, ArgMatches}; use std::env; use std::fmt; @@ -250,13 +251,22 @@ impl Strategy { } } +/// Parse the suffix type from the command-line arguments. +fn suffix_type_from(matches: &ArgMatches) -> SuffixType { + if matches.occurrences_of(OPT_NUMERIC_SUFFIXES) > 0 { + SuffixType::NumericDecimal + } else { + SuffixType::Alphabetic + } +} + /// Parameters that control how a file gets split. /// /// You can convert an [`ArgMatches`] instance into a [`Settings`] /// instance by calling [`Settings::from`]. struct Settings { prefix: String, - numeric_suffix: bool, + suffix_type: SuffixType, suffix_length: usize, additional_suffix: String, input: String, @@ -324,7 +334,7 @@ impl Settings { suffix_length: suffix_length_str .parse() .map_err(|_| SettingsError::SuffixLength(suffix_length_str.to_string()))?, - numeric_suffix: matches.occurrences_of(OPT_NUMERIC_SUFFIXES) > 0, + suffix_type: suffix_type_from(matches), additional_suffix, verbose: matches.occurrences_of("verbose") > 0, strategy: Strategy::from(matches).map_err(SettingsError::Strategy)?, @@ -384,7 +394,7 @@ impl<'a> ByteChunkWriter<'a> { &settings.prefix, &settings.additional_suffix, settings.suffix_length, - settings.numeric_suffix, + settings.suffix_type, ); let filename = filename_iterator.next()?; if settings.verbose { @@ -512,7 +522,7 @@ impl<'a> LineChunkWriter<'a> { &settings.prefix, &settings.additional_suffix, settings.suffix_length, - settings.numeric_suffix, + settings.suffix_type, ); let filename = filename_iterator.next()?; if settings.verbose { @@ -604,7 +614,7 @@ where &settings.prefix, &settings.additional_suffix, settings.suffix_length, - settings.numeric_suffix, + settings.suffix_type, ); // Create one writer for each chunk. This will create each From 891c5d1ffaa7643eeed3e4ef095fe62c2a95d35d Mon Sep 17 00:00:00 2001 From: Jeffrey Finkelstein Date: Sun, 16 Jan 2022 10:36:26 -0500 Subject: [PATCH 12/58] split: add SuffixType::NumericHexadecimal Add a `NumericHexadecimal` member to the `SuffixType` enum so that a future commit can add support for hexadecimal filename suffixes to the `split` program. --- src/uu/split/src/filenames.rs | 6 +- src/uu/split/src/number.rs | 119 +++++++++++++++++++++++++++------- 2 files changed, 101 insertions(+), 24 deletions(-) diff --git a/src/uu/split/src/filenames.rs b/src/uu/split/src/filenames.rs index 1b89190c7..0121ba87f 100644 --- a/src/uu/split/src/filenames.rs +++ b/src/uu/split/src/filenames.rs @@ -37,6 +37,9 @@ pub enum SuffixType { /// Decimal numbers. NumericDecimal, + + /// Hexadecimal numbers. + NumericHexadecimal, } impl SuffixType { @@ -45,6 +48,7 @@ impl SuffixType { match self { SuffixType::Alphabetic => 26, SuffixType::NumericDecimal => 10, + SuffixType::NumericHexadecimal => 16, } } } @@ -86,7 +90,7 @@ impl SuffixType { /// assert_eq!(it.next().unwrap(), "chunk_ac.txt"); /// ``` /// -/// For numeric filenames, use `SuffixType::NumericDecimal`: +/// For decimal numeric filenames, use `SuffixType::NumericDecimal`: /// /// ```rust,ignore /// use crate::filenames::FilenameIterator; diff --git a/src/uu/split/src/number.rs b/src/uu/split/src/number.rs index ef3ccbc4b..d5427e2ca 100644 --- a/src/uu/split/src/number.rs +++ b/src/uu/split/src/number.rs @@ -40,13 +40,19 @@ impl Error for Overflow {} /// specifically for the `split` program. See the /// [`DynamicWidthNumber`] documentation for more information. /// -/// Numbers of radix 10 are displayable and rendered as decimal -/// numbers (for example, "00" or "917"). Numbers of radix 26 are -/// displayable and rendered as lowercase ASCII alphabetic characters -/// (for example, "aa" or "zax"). Numbers of other radices cannot be -/// displayed. The display of a [`DynamicWidthNumber`] includes a -/// prefix whose length depends on the width of the number. See the -/// [`DynamicWidthNumber`] documentation for more information. +/// Numbers of radix +/// +/// * 10 are displayable and rendered as decimal numbers (for example, +/// "00" or "917"), +/// * 16 are displayable and rendered as hexadecimal numbers (for example, +/// "00" or "e7f"), +/// * 26 are displayable and rendered as lowercase ASCII alphabetic +/// characters (for example, "aa" or "zax"). +/// +/// Numbers of other radices cannot be displayed. The display of a +/// [`DynamicWidthNumber`] includes a prefix whose length depends on +/// the width of the number. See the [`DynamicWidthNumber`] +/// documentation for more information. /// /// The digits of a number are accessible via the [`Number::digits`] /// method. The digits are represented as a [`Vec`] with the most @@ -169,12 +175,12 @@ impl Display for Number { /// /// # Displaying /// -/// This number is only displayable if `radix` is 10 or `radix` is -/// 26. If `radix` is 10, then the digits are concatenated and -/// displayed as a fixed-width decimal number. If `radix` is 26, then -/// each digit is translated to the corresponding lowercase ASCII -/// alphabetic character (that is, 'a', 'b', 'c', etc.) and -/// concatenated. +/// This number is only displayable if `radix` is 10, 26, or 26. If +/// `radix` is 10 or 16, then the digits are concatenated and +/// displayed as a fixed-width decimal or hexadecimal number, +/// respectively. If `radix` is 26, then each digit is translated to +/// the corresponding lowercase ASCII alphabetic character (that is, +/// 'a', 'b', 'c', etc.) and concatenated. #[derive(Clone)] pub struct FixedWidthNumber { radix: u8, @@ -228,6 +234,14 @@ impl Display for FixedWidthNumber { let digits: String = self.digits.iter().map(|d| (b'0' + d) as char).collect(); write!(f, "{}", digits) } + 16 => { + let digits: String = self + .digits + .iter() + .map(|d| (if *d < 10 { b'0' + d } else { b'a' + (d - 10) }) as char) + .collect(); + write!(f, "{}", digits) + } 26 => { let digits: String = self.digits.iter().map(|d| (b'a' + d) as char).collect(); write!(f, "{}", digits) @@ -264,14 +278,15 @@ impl Display for FixedWidthNumber { /// /// # Displaying /// -/// This number is only displayable if `radix` is 10 or `radix` is -/// 26. If `radix` is 10, then the digits are concatenated and -/// displayed as a fixed-width decimal number with a prefix of `n - 2` -/// instances of the character '9', where `n` is the number of digits. -/// If `radix` is 26, then each digit is translated to the -/// corresponding lowercase ASCII alphabetic character (that is, 'a', -/// 'b', 'c', etc.) and concatenated with a prefix of `n - 2` -/// instances of the character 'z'. +/// This number is only displayable if `radix` is 10, 16, or 26. If +/// `radix` is 10 or 16, then the digits are concatenated and +/// displayed as a fixed-width decimal or hexadecimal number, +/// respectively, with a prefix of `n - 2` instances of the character +/// '9' of 'f', respectively, where `n` is the number of digits. If +/// `radix` is 26, then each digit is translated to the corresponding +/// lowercase ASCII alphabetic character (that is, 'a', 'b', 'c', +/// etc.) and concatenated with a prefix of `n - 2` instances of the +/// character 'z'. /// /// This notion of displaying the number is specific to the `split` /// program. @@ -349,6 +364,21 @@ impl Display for DynamicWidthNumber { digits = digits, ) } + 16 => { + let num_fill_chars = self.digits.len() - 2; + let digits: String = self + .digits + .iter() + .map(|d| (if *d < 10 { b'0' + d } else { b'a' + (d - 10) }) as char) + .collect(); + write!( + f, + "{empty:f { let num_fill_chars = self.digits.len() - 2; let digits: String = self.digits.iter().map(|d| (b'a' + d) as char).collect(); @@ -424,7 +454,7 @@ mod tests { } #[test] - fn test_dynamic_width_number_display_numeric() { + fn test_dynamic_width_number_display_numeric_decimal() { fn num(n: usize) -> Number { let mut number = Number::DynamicWidth(DynamicWidthNumber::new(10)); for _ in 0..n { @@ -444,6 +474,30 @@ mod tests { assert_eq!(format!("{}", num(10 * 99 + 1)), "990001"); } + #[test] + fn test_dynamic_width_number_display_numeric_hexadecimal() { + fn num(n: usize) -> Number { + let mut number = Number::DynamicWidth(DynamicWidthNumber::new(16)); + for _ in 0..n { + number.increment().unwrap() + } + number + } + + assert_eq!(format!("{}", num(0)), "00"); + assert_eq!(format!("{}", num(15)), "0f"); + assert_eq!(format!("{}", num(16)), "10"); + assert_eq!(format!("{}", num(17)), "11"); + assert_eq!(format!("{}", num(18)), "12"); + + assert_eq!(format!("{}", num(16 * 15 - 1)), "ef"); + assert_eq!(format!("{}", num(16 * 15)), "f000"); + assert_eq!(format!("{}", num(16 * 15 + 1)), "f001"); + assert_eq!(format!("{}", num(16 * 255 - 1)), "feff"); + assert_eq!(format!("{}", num(16 * 255)), "ff0000"); + assert_eq!(format!("{}", num(16 * 255 + 1)), "ff0001"); + } + #[test] fn test_fixed_width_number_increment() { let mut n = Number::FixedWidth(FixedWidthNumber::new(3, 2)); @@ -493,7 +547,7 @@ mod tests { } #[test] - fn test_fixed_width_number_display_numeric() { + fn test_fixed_width_number_display_numeric_decimal() { fn num(n: usize) -> Result { let mut number = Number::FixedWidth(FixedWidthNumber::new(10, 2)); for _ in 0..n { @@ -510,4 +564,23 @@ mod tests { assert_eq!(format!("{}", num(10 * 10 - 1).unwrap()), "99"); assert!(num(10 * 10).is_err()); } + + #[test] + fn test_fixed_width_number_display_numeric_hexadecimal() { + fn num(n: usize) -> Result { + let mut number = Number::FixedWidth(FixedWidthNumber::new(16, 2)); + for _ in 0..n { + number.increment()?; + } + Ok(number) + } + + assert_eq!(format!("{}", num(0).unwrap()), "00"); + assert_eq!(format!("{}", num(15).unwrap()), "0f"); + assert_eq!(format!("{}", num(17).unwrap()), "11"); + assert_eq!(format!("{}", num(16 * 15 - 1).unwrap()), "ef"); + assert_eq!(format!("{}", num(16 * 15).unwrap()), "f0"); + assert_eq!(format!("{}", num(16 * 16 - 1).unwrap()), "ff"); + assert!(num(16 * 16).is_err()); + } } From 4470430c894579c452e44fb933f883e2487c84cc Mon Sep 17 00:00:00 2001 From: Jeffrey Finkelstein Date: Sun, 16 Jan 2022 10:41:52 -0500 Subject: [PATCH 13/58] split: add support for -x option (hex suffixes) Add support for the `-x` command-line option to `split`. This option causes `split` to produce filenames with hexadecimal suffixes instead of the default alphabetic suffixes. --- src/uu/split/src/split.rs | 11 +++++++++ tests/by-util/test_split.rs | 24 ++++++++++++++++++- .../split/twohundredfortyonebytes.txt | 1 + 3 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 tests/fixtures/split/twohundredfortyonebytes.txt diff --git a/src/uu/split/src/split.rs b/src/uu/split/src/split.rs index dbb537c96..70de45ce2 100644 --- a/src/uu/split/src/split.rs +++ b/src/uu/split/src/split.rs @@ -32,6 +32,7 @@ static OPT_ADDITIONAL_SUFFIX: &str = "additional-suffix"; static OPT_FILTER: &str = "filter"; static OPT_NUMBER: &str = "number"; static OPT_NUMERIC_SUFFIXES: &str = "numeric-suffixes"; +static OPT_HEX_SUFFIXES: &str = "hex-suffixes"; static OPT_SUFFIX_LENGTH: &str = "suffix-length"; static OPT_DEFAULT_SUFFIX_LENGTH: &str = "0"; static OPT_VERBOSE: &str = "verbose"; @@ -143,6 +144,14 @@ pub fn uu_app<'a>() -> App<'a> { .default_value(OPT_DEFAULT_SUFFIX_LENGTH) .help("use suffixes of length N (default 2)"), ) + .arg( + Arg::new(OPT_HEX_SUFFIXES) + .short('x') + .long(OPT_HEX_SUFFIXES) + .takes_value(true) + .default_missing_value("0") + .help("use hex suffixes starting at 0, not alphabetic"), + ) .arg( Arg::new(OPT_VERBOSE) .long(OPT_VERBOSE) @@ -255,6 +264,8 @@ impl Strategy { fn suffix_type_from(matches: &ArgMatches) -> SuffixType { if matches.occurrences_of(OPT_NUMERIC_SUFFIXES) > 0 { SuffixType::NumericDecimal + } else if matches.occurrences_of(OPT_HEX_SUFFIXES) > 0 { + SuffixType::NumericHexadecimal } else { SuffixType::Alphabetic } diff --git a/tests/by-util/test_split.rs b/tests/by-util/test_split.rs index b5d799d72..846d483b2 100644 --- a/tests/by-util/test_split.rs +++ b/tests/by-util/test_split.rs @@ -2,7 +2,7 @@ // * // * For the full copyright and license information, please view the LICENSE // * file that was distributed with this source code. -// spell-checker:ignore xzaaa sixhundredfiftyonebytes ninetyonebytes asciilowercase fghij klmno pqrst uvwxyz fivelines +// spell-checker:ignore xzaaa sixhundredfiftyonebytes ninetyonebytes asciilowercase fghij klmno pqrst uvwxyz fivelines twohundredfortyonebytes extern crate rand; extern crate regex; @@ -409,6 +409,28 @@ fn test_numeric_dynamic_suffix_length() { assert_eq!(file_read(&at, "x9000"), "a"); } +#[test] +fn test_hex_dynamic_suffix_length() { + let (at, mut ucmd) = at_and_ucmd!(); + // Split into chunks of one byte each, use hexadecimal digits + // instead of letters as file suffixes. + // + // The input file has (16^2) - 16 + 1 = 241 bytes. This is just + // enough to force `split` to dynamically increase the length of + // the filename for the very last chunk. + // + // x00, x01, x02, ..., xed, xee, xef, xf000 + // + ucmd.args(&["-x", "-b", "1", "twohundredfortyonebytes.txt"]) + .succeeds(); + for i in 0..240 { + let filename = format!("x{:02x}", i); + let contents = file_read(&at, &filename); + assert_eq!(contents, "a"); + } + assert_eq!(file_read(&at, "xf000"), "a"); +} + #[test] fn test_suffixes_exhausted() { new_ucmd!() diff --git a/tests/fixtures/split/twohundredfortyonebytes.txt b/tests/fixtures/split/twohundredfortyonebytes.txt new file mode 100644 index 000000000..10a53a61c --- /dev/null +++ b/tests/fixtures/split/twohundredfortyonebytes.txt @@ -0,0 +1 @@ +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa \ No newline at end of file From 6c3fc7b214d974397548bbbc6f7fd7dea960b09a Mon Sep 17 00:00:00 2001 From: ndd7xv Date: Tue, 15 Feb 2022 01:35:28 -0500 Subject: [PATCH 14/58] split: throw error when # chunks > # filenames from suffix length --- src/uu/split/src/filenames.rs | 2 +- src/uu/split/src/split.rs | 28 +++++++++++++++++++++++----- 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/src/uu/split/src/filenames.rs b/src/uu/split/src/filenames.rs index 0121ba87f..426c76226 100644 --- a/src/uu/split/src/filenames.rs +++ b/src/uu/split/src/filenames.rs @@ -44,7 +44,7 @@ pub enum SuffixType { impl SuffixType { /// The radix to use when representing the suffix string as digits. - fn radix(&self) -> u8 { + pub fn radix(&self) -> u8 { match self { SuffixType::Alphabetic => 26, SuffixType::NumericDecimal => 10, diff --git a/src/uu/split/src/split.rs b/src/uu/split/src/split.rs index 70de45ce2..a7a49af00 100644 --- a/src/uu/split/src/split.rs +++ b/src/uu/split/src/split.rs @@ -293,11 +293,14 @@ enum SettingsError { Strategy(StrategyError), /// Invalid suffix length parameter. - SuffixLength(String), + SuffixNotParsable(String), /// Suffix contains a directory separator, which is not allowed. SuffixContainsSeparator(String), + /// Suffix is not large enough to split into specified chunks + SuffixTooSmall(usize), + /// The `--filter` option is not supported on Windows. #[cfg(windows)] NotSupported, @@ -317,7 +320,8 @@ impl fmt::Display for SettingsError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { Self::Strategy(e) => e.fmt(f), - Self::SuffixLength(s) => write!(f, "invalid suffix length: {}", s.quote()), + Self::SuffixNotParsable(s) => write!(f, "invalid suffix length: {}", s.quote()), + Self::SuffixTooSmall(i) => write!(f, "the suffix length needs to be at least {}", i), Self::SuffixContainsSeparator(s) => write!( f, "invalid suffix {}, contains directory separator", @@ -340,15 +344,29 @@ impl Settings { if additional_suffix.contains('/') { return Err(SettingsError::SuffixContainsSeparator(additional_suffix)); } + let strategy = Strategy::from(matches).map_err(SettingsError::Strategy)?; + let suffix_type = suffix_type_from(matches); let suffix_length_str = matches.value_of(OPT_SUFFIX_LENGTH).unwrap(); + let suffix_length: usize = suffix_length_str + .parse() + .map_err(|_| SettingsError::SuffixNotParsable(suffix_length_str.to_string()))?; + if let Strategy::Number(chunks) = strategy { + if suffix_length != 0 { + let required_suffix_length = + (chunks as f64).log(suffix_type.radix() as f64).ceil() as usize; + if suffix_length < required_suffix_length { + return Err(SettingsError::SuffixTooSmall(required_suffix_length)); + } + } + } let result = Self { suffix_length: suffix_length_str .parse() - .map_err(|_| SettingsError::SuffixLength(suffix_length_str.to_string()))?, - suffix_type: suffix_type_from(matches), + .map_err(|_| SettingsError::SuffixNotParsable(suffix_length_str.to_string()))?, + suffix_type, additional_suffix, verbose: matches.occurrences_of("verbose") > 0, - strategy: Strategy::from(matches).map_err(SettingsError::Strategy)?, + strategy, input: matches.value_of(ARG_INPUT).unwrap().to_owned(), prefix: matches.value_of(ARG_PREFIX).unwrap().to_owned(), filter: matches.value_of(OPT_FILTER).map(|s| s.to_owned()), From c16c06ea0d15c46082db9205d5d461f937e778f1 Mon Sep 17 00:00:00 2001 From: xxyzz Date: Thu, 17 Feb 2022 13:43:59 +0800 Subject: [PATCH 15/58] df: add output option's valid field names --- src/uu/df/src/df.rs | 12 +++++++++--- tests/by-util/test_df.rs | 19 +++++++++++++++++++ 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/src/uu/df/src/df.rs b/src/uu/df/src/df.rs index be33238ff..cb7a84e20 100644 --- a/src/uu/df/src/df.rs +++ b/src/uu/df/src/df.rs @@ -5,6 +5,7 @@ // // For the full copyright and license information, please view the LICENSE file // that was distributed with this source code. +// spell-checker:ignore itotal iused iavail ipcent pcent mod table; use uucore::error::UResult; @@ -46,6 +47,10 @@ static OPT_SYNC: &str = "sync"; static OPT_TYPE: &str = "type"; static OPT_PRINT_TYPE: &str = "print-type"; static OPT_EXCLUDE_TYPE: &str = "exclude-type"; +static OUTPUT_FIELD_LIST: [&str; 12] = [ + "source", "fstype", "itotal", "iused", "iavail", "ipcent", "size", "used", "avail", "pcent", + "file", "target", +]; /// Store names of file systems as a selector. /// Note: `exclude` takes priority over `include`. @@ -338,7 +343,6 @@ pub fn uu_app<'a>() -> App<'a> { Arg::new(OPT_INODES) .short('i') .long("inodes") - .conflicts_with(OPT_OUTPUT) .help("list inode information instead of block usage"), ) .arg(Arg::new(OPT_KILO).short('k').help("like --block-size=1K")) @@ -359,6 +363,10 @@ pub fn uu_app<'a>() -> App<'a> { .long("output") .takes_value(true) .use_delimiter(true) + .possible_values(OUTPUT_FIELD_LIST) + .default_missing_values(&OUTPUT_FIELD_LIST) + .default_values(&["source", "size", "used", "avail", "pcent", "target"]) + .conflicts_with_all(&[OPT_INODES, OPT_PORTABILITY, OPT_PRINT_TYPE]) .help( "use the output format defined by FIELD_LIST,\ or print all fields if FIELD_LIST is omitted.", @@ -368,7 +376,6 @@ pub fn uu_app<'a>() -> App<'a> { Arg::new(OPT_PORTABILITY) .short('P') .long("portability") - .conflicts_with(OPT_OUTPUT) .help("use the POSIX output format"), ) .arg( @@ -390,7 +397,6 @@ pub fn uu_app<'a>() -> App<'a> { Arg::new(OPT_PRINT_TYPE) .short('T') .long("print-type") - .conflicts_with(OPT_OUTPUT) .help("print file system type"), ) .arg( diff --git a/tests/by-util/test_df.rs b/tests/by-util/test_df.rs index 00f1ecf3a..3af02428e 100644 --- a/tests/by-util/test_df.rs +++ b/tests/by-util/test_df.rs @@ -58,4 +58,23 @@ fn test_order_same() { assert_eq!(output1, output2); } +#[test] +fn test_output_conflict_options() { + for option in ["-i", "-T", "-P"] { + new_ucmd!().arg("--output=source").arg(option).fails(); + } +} + +#[test] +fn test_output_option() { + new_ucmd!().arg("--output").succeeds(); + new_ucmd!().arg("--output=source,target").succeeds(); + new_ucmd!().arg("--output=invalid_option").fails(); +} + +#[test] +fn test_type_option() { + new_ucmd!().args(&["-t", "ext4", "-t", "ext3"]).succeeds(); +} + // ToDO: more tests... From 494d709e0f05bdd18d5782a6d1fce565397c844f Mon Sep 17 00:00:00 2001 From: ndd7xv Date: Wed, 16 Feb 2022 23:48:12 -0500 Subject: [PATCH 16/58] split: small tweaks to wording changes `SuffixType` enums to have better names and hex suffix help to be consistent with numeric suffix help --- src/uu/split/src/filenames.rs | 20 ++++++++++---------- src/uu/split/src/split.rs | 6 +++--- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/uu/split/src/filenames.rs b/src/uu/split/src/filenames.rs index 426c76226..31742194d 100644 --- a/src/uu/split/src/filenames.rs +++ b/src/uu/split/src/filenames.rs @@ -36,10 +36,10 @@ pub enum SuffixType { Alphabetic, /// Decimal numbers. - NumericDecimal, + Decimal, /// Hexadecimal numbers. - NumericHexadecimal, + Hexadecimal, } impl SuffixType { @@ -47,8 +47,8 @@ impl SuffixType { pub fn radix(&self) -> u8 { match self { SuffixType::Alphabetic => 26, - SuffixType::NumericDecimal => 10, - SuffixType::NumericHexadecimal => 16, + SuffixType::Decimal => 10, + SuffixType::Hexadecimal => 16, } } } @@ -90,7 +90,7 @@ impl SuffixType { /// assert_eq!(it.next().unwrap(), "chunk_ac.txt"); /// ``` /// -/// For decimal numeric filenames, use `SuffixType::NumericDecimal`: +/// For decimal numeric filenames, use `SuffixType::Decimal`: /// /// ```rust,ignore /// use crate::filenames::FilenameIterator; @@ -99,7 +99,7 @@ impl SuffixType { /// let prefix = "chunk_".to_string(); /// let suffix = ".txt".to_string(); /// let width = 2; -/// let suffix_type = SuffixType::NumericDecimal; +/// let suffix_type = SuffixType::Decimal; /// let it = FilenameIterator::new(prefix, suffix, width, suffix_type); /// /// assert_eq!(it.next().unwrap(), "chunk_00.txt"); @@ -173,12 +173,12 @@ mod tests { #[test] fn test_filename_iterator_numeric_fixed_width() { - let mut it = FilenameIterator::new("chunk_", ".txt", 2, SuffixType::NumericDecimal); + let mut it = FilenameIterator::new("chunk_", ".txt", 2, SuffixType::Decimal); assert_eq!(it.next().unwrap(), "chunk_00.txt"); assert_eq!(it.next().unwrap(), "chunk_01.txt"); assert_eq!(it.next().unwrap(), "chunk_02.txt"); - let mut it = FilenameIterator::new("chunk_", ".txt", 2, SuffixType::NumericDecimal); + let mut it = FilenameIterator::new("chunk_", ".txt", 2, SuffixType::Decimal); assert_eq!(it.nth(10 * 10 - 1).unwrap(), "chunk_99.txt"); assert_eq!(it.next(), None); } @@ -198,12 +198,12 @@ mod tests { #[test] fn test_filename_iterator_numeric_dynamic_width() { - let mut it = FilenameIterator::new("chunk_", ".txt", 0, SuffixType::NumericDecimal); + let mut it = FilenameIterator::new("chunk_", ".txt", 0, SuffixType::Decimal); assert_eq!(it.next().unwrap(), "chunk_00.txt"); assert_eq!(it.next().unwrap(), "chunk_01.txt"); assert_eq!(it.next().unwrap(), "chunk_02.txt"); - let mut it = FilenameIterator::new("chunk_", ".txt", 0, SuffixType::NumericDecimal); + let mut it = FilenameIterator::new("chunk_", ".txt", 0, SuffixType::Decimal); assert_eq!(it.nth(10 * 9 - 1).unwrap(), "chunk_89.txt"); assert_eq!(it.next().unwrap(), "chunk_9000.txt"); assert_eq!(it.next().unwrap(), "chunk_9001.txt"); diff --git a/src/uu/split/src/split.rs b/src/uu/split/src/split.rs index a7a49af00..cde0f8e55 100644 --- a/src/uu/split/src/split.rs +++ b/src/uu/split/src/split.rs @@ -150,7 +150,7 @@ pub fn uu_app<'a>() -> App<'a> { .long(OPT_HEX_SUFFIXES) .takes_value(true) .default_missing_value("0") - .help("use hex suffixes starting at 0, not alphabetic"), + .help("use hex suffixes instead of alphabetic"), ) .arg( Arg::new(OPT_VERBOSE) @@ -263,9 +263,9 @@ impl Strategy { /// Parse the suffix type from the command-line arguments. fn suffix_type_from(matches: &ArgMatches) -> SuffixType { if matches.occurrences_of(OPT_NUMERIC_SUFFIXES) > 0 { - SuffixType::NumericDecimal + SuffixType::Decimal } else if matches.occurrences_of(OPT_HEX_SUFFIXES) > 0 { - SuffixType::NumericHexadecimal + SuffixType::Hexadecimal } else { SuffixType::Alphabetic } From 6718d97f97e092b6d6a0ec5edb72497ba1b85be1 Mon Sep 17 00:00:00 2001 From: Jeffrey Finkelstein Date: Wed, 9 Feb 2022 21:41:33 -0500 Subject: [PATCH 17/58] split: add support for -e argument Add the `-e` flag, which indicates whether to elide (that is, remove) empty files that would have been created by the `-n` option. The `-n` command-line argument gives a specific number of chunks into which the input files will be split. If the number of chunks is greater than the number of bytes, then empty files will be created for the excess chunks. But if `-e` is given, then empty files will not be created. For example, contrast $ printf 'a\n' > f && split -e -n 3 f && cat xaa xab xac a cat: xac: No such file or directory with $ printf 'a\n' > f && split -n 3 f && cat xaa xab xac a --- src/uu/split/src/split.rs | 36 ++++++++++++++++++++++++++++- tests/by-util/test_split.rs | 28 +++++++++++++++++++++- tests/fixtures/split/threebytes.txt | 1 + 3 files changed, 63 insertions(+), 2 deletions(-) create mode 100644 tests/fixtures/split/threebytes.txt diff --git a/src/uu/split/src/split.rs b/src/uu/split/src/split.rs index 70de45ce2..7ff08d37d 100644 --- a/src/uu/split/src/split.rs +++ b/src/uu/split/src/split.rs @@ -39,6 +39,7 @@ static OPT_VERBOSE: &str = "verbose"; //The ---io-blksize parameter is consumed and ignored. //The parameter is included to make GNU coreutils tests pass. static OPT_IO_BLKSIZE: &str = "-io-blksize"; +static OPT_ELIDE_EMPTY_FILES: &str = "elide-empty-files"; static ARG_INPUT: &str = "input"; static ARG_PREFIX: &str = "prefix"; @@ -128,6 +129,13 @@ pub fn uu_app<'a>() -> App<'a> { "write to shell COMMAND file name is $FILE (Currently not implemented for Windows)", ), ) + .arg( + Arg::new(OPT_ELIDE_EMPTY_FILES) + .long(OPT_ELIDE_EMPTY_FILES) + .short('e') + .takes_value(false) + .help("do not generate empty output files with '-n'"), + ) .arg( Arg::new(OPT_NUMERIC_SUFFIXES) .short('d') @@ -285,6 +293,16 @@ struct Settings { filter: Option, strategy: Strategy, verbose: bool, + + /// Whether to *not* produce empty files when using `-n`. + /// + /// The `-n` command-line argument gives a specific number of + /// chunks into which the input files will be split. If the number + /// of chunks is greater than the number of bytes, and this is + /// `false`, then empty files will be created for the excess + /// chunks. If this is `false`, then empty files will not be + /// created. + elide_empty_files: bool, } /// An error when parsing settings from command-line arguments. @@ -352,6 +370,7 @@ impl Settings { input: matches.value_of(ARG_INPUT).unwrap().to_owned(), prefix: matches.value_of(ARG_PREFIX).unwrap().to_owned(), filter: matches.value_of(OPT_FILTER).map(|s| s.to_owned()), + elide_empty_files: matches.is_present(OPT_ELIDE_EMPTY_FILES), }; #[cfg(windows)] if result.filter.is_some() { @@ -616,9 +635,24 @@ where { // Get the size of the input file in bytes and compute the number // of bytes per chunk. + // + // If the requested number of chunks exceeds the number of bytes + // in the file *and* the `elide_empty_files` parameter is enabled, + // then behave as if the number of chunks was set to the number of + // bytes in the file. This ensures that we don't write empty + // files. Otherwise, just write the `num_chunks - num_bytes` empty + // files. let metadata = metadata(&settings.input).unwrap(); let num_bytes = metadata.len(); - let chunk_size = (num_bytes / (num_chunks as u64)) as usize; + let will_have_empty_files = settings.elide_empty_files && num_chunks as u64 > num_bytes; + let (num_chunks, chunk_size) = if will_have_empty_files { + let num_chunks = num_bytes as usize; + let chunk_size = 1; + (num_chunks, chunk_size) + } else { + let chunk_size = ((num_bytes / (num_chunks as u64)) as usize).max(1); + (num_chunks, chunk_size) + }; // This object is responsible for creating the filename for each chunk. let mut filename_iterator = FilenameIterator::new( diff --git a/tests/by-util/test_split.rs b/tests/by-util/test_split.rs index 846d483b2..9454687ac 100644 --- a/tests/by-util/test_split.rs +++ b/tests/by-util/test_split.rs @@ -2,7 +2,7 @@ // * // * For the full copyright and license information, please view the LICENSE // * file that was distributed with this source code. -// spell-checker:ignore xzaaa sixhundredfiftyonebytes ninetyonebytes asciilowercase fghij klmno pqrst uvwxyz fivelines twohundredfortyonebytes +// spell-checker:ignore xzaaa sixhundredfiftyonebytes ninetyonebytes threebytes asciilowercase fghij klmno pqrst uvwxyz fivelines twohundredfortyonebytes extern crate rand; extern crate regex; @@ -526,3 +526,29 @@ fn test_include_newlines() { at.open("xac").read_to_string(&mut s).unwrap(); assert_eq!(s, "5\n"); } + +#[test] +fn test_allow_empty_files() { + let (at, mut ucmd) = at_and_ucmd!(); + ucmd.args(&["-n", "4", "threebytes.txt"]) + .succeeds() + .no_stdout() + .no_stderr(); + assert_eq!(at.read("xaa"), "a"); + assert_eq!(at.read("xab"), "b"); + assert_eq!(at.read("xac"), "c"); + assert_eq!(at.read("xad"), ""); +} + +#[test] +fn test_elide_empty_files() { + let (at, mut ucmd) = at_and_ucmd!(); + ucmd.args(&["-e", "-n", "4", "threebytes.txt"]) + .succeeds() + .no_stdout() + .no_stderr(); + assert_eq!(at.read("xaa"), "a"); + assert_eq!(at.read("xab"), "b"); + assert_eq!(at.read("xac"), "c"); + assert!(!at.plus("xad").exists()); +} diff --git a/tests/fixtures/split/threebytes.txt b/tests/fixtures/split/threebytes.txt new file mode 100644 index 000000000..f2ba8f84a --- /dev/null +++ b/tests/fixtures/split/threebytes.txt @@ -0,0 +1 @@ +abc \ No newline at end of file From 6900638ac6a84a9cd4313df93ef3502dcfa30dd2 Mon Sep 17 00:00:00 2001 From: Jeffrey Finkelstein Date: Wed, 16 Feb 2022 21:32:38 -0500 Subject: [PATCH 18/58] dd: don't error when outfile is /dev/null Prevent `dd` from terminating with an error when given the command-line argument `of=/dev/null`. This commit allows the call to `File::set_len()` to result in an error without causing the process to terminate prematurely. --- src/uu/dd/src/dd.rs | 11 +++++++++-- tests/by-util/test_dd.rs | 7 +++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/uu/dd/src/dd.rs b/src/uu/dd/src/dd.rs index b38671a9a..1ce64bb78 100644 --- a/src/uu/dd/src/dd.rs +++ b/src/uu/dd/src/dd.rs @@ -521,10 +521,17 @@ impl OutputTrait for Output { let mut dst = open_dst(Path::new(&fname), &cflags, &oflags) .map_err_context(|| format!("failed to open {}", fname.quote()))?; + // Seek to the index in the output file, truncating if requested. + // + // Calling `set_len()` may result in an error (for + // example, when calling it on `/dev/null`), but we don't + // want to terminate the process when that happens. + // Instead, we suppress the error by calling + // `Result::ok()`. This matches the behavior of GNU `dd` + // when given the command-line argument `of=/dev/null`. let i = seek.unwrap_or(0).try_into().unwrap(); if !cflags.notrunc { - dst.set_len(i) - .map_err_context(|| "failed to truncate output file".to_string())?; + dst.set_len(i).ok(); } dst.seek(io::SeekFrom::Start(i)) .map_err_context(|| "failed to seek in output file".to_string())?; diff --git a/tests/by-util/test_dd.rs b/tests/by-util/test_dd.rs index 7a52488eb..04f5490ec 100644 --- a/tests/by-util/test_dd.rs +++ b/tests/by-util/test_dd.rs @@ -1095,3 +1095,10 @@ fn test_truncated_record() { .stdout_is("ac") .stderr_is("0+1 records in\n0+1 records out\n2 truncated records\n"); } + +/// Test that the output file can be `/dev/null`. +#[cfg(unix)] +#[test] +fn test_outfile_dev_null() { + new_ucmd!().arg("of=/dev/null").succeeds().no_stdout(); +} From 83d2f550abcb8de9a8761e3f891e8ec15004a21f Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sat, 19 Feb 2022 21:26:34 +0100 Subject: [PATCH 19/58] try to use rust-cache on github action to build faster --- .github/workflows/CICD.yml | 15 ++++++++++++++- .github/workflows/FixPR.yml | 4 ++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/.github/workflows/CICD.yml b/.github/workflows/CICD.yml index b47540ed9..24ab82387 100644 --- a/.github/workflows/CICD.yml +++ b/.github/workflows/CICD.yml @@ -5,7 +5,7 @@ name: CICD # spell-checker:ignore (jargon) SHAs deps dequote softprops subshell toolchain # spell-checker:ignore (names) CodeCOV MacOS MinGW Peltoche rivy # spell-checker:ignore (shell/tools) choco clippy dmake dpkg esac fakeroot gmake grcov halium lcov libssl mkdir popd printf pushd rsync rustc rustfmt rustup shopt xargs -# spell-checker:ignore (misc) aarch alnum armhf bindir busytest coreutils gnueabihf issuecomment maint nullglob onexitbegin onexitend pell runtest tempfile testsuite uutils DESTDIR sizemulti +# spell-checker:ignore (misc) aarch alnum armhf bindir busytest coreutils gnueabihf issuecomment maint nullglob onexitbegin onexitend pell runtest tempfile testsuite uutils DESTDIR sizemulti Swatinem # ToDO: [2021-06; rivy] change from `cargo-tree` to `cargo tree` once MSRV is >= 1.45 @@ -37,6 +37,7 @@ jobs: - { os: windows-latest , features: feat_os_windows } steps: - uses: actions/checkout@v2 + - uses: Swatinem/rust-cache@v1 - name: Initialize workflow variables id: vars shell: bash @@ -94,6 +95,7 @@ jobs: - { os: ubuntu-latest , features: feat_os_unix } steps: - uses: actions/checkout@v2 + - uses: Swatinem/rust-cache@v1 - name: Initialize workflow variables id: vars shell: bash @@ -157,6 +159,7 @@ jobs: - { os: windows-latest , features: feat_os_windows } steps: - uses: actions/checkout@v2 + - uses: Swatinem/rust-cache@v1 - name: Initialize workflow variables id: vars shell: bash @@ -214,6 +217,7 @@ jobs: - { os: ubuntu-latest , features: feat_os_unix } steps: - uses: actions/checkout@v2 + - uses: Swatinem/rust-cache@v1 - name: Initialize workflow variables id: vars shell: bash @@ -265,6 +269,7 @@ jobs: # - { os: windows-latest , features: feat_os_windows } steps: - uses: actions/checkout@v2 + - uses: Swatinem/rust-cache@v1 - name: Initialize workflow variables id: vars shell: bash @@ -309,6 +314,7 @@ jobs: - { os: ubuntu-latest , features: feat_os_unix } steps: - uses: actions/checkout@v2 + - uses: Swatinem/rust-cache@v1 - name: Initialize workflow variables id: vars shell: bash @@ -385,6 +391,7 @@ jobs: - { os: ubuntu-latest , features: feat_os_unix } steps: - uses: actions/checkout@v2 + - uses: Swatinem/rust-cache@v1 - name: Install `rust` toolchain uses: actions-rs/toolchain@v1 with: @@ -409,6 +416,7 @@ jobs: - { os: ubuntu-latest , features: feat_os_unix } steps: - uses: actions/checkout@v2 + - uses: Swatinem/rust-cache@v1 - name: Install `rust` toolchain uses: actions-rs/toolchain@v1 with: @@ -436,6 +444,7 @@ jobs: - { os: ubuntu-latest , features: feat_os_unix } steps: - uses: actions/checkout@v2 + - uses: Swatinem/rust-cache@v1 - name: Install dependencies shell: bash run: | @@ -497,6 +506,7 @@ jobs: - { os: windows-latest , target: x86_64-pc-windows-msvc , features: feat_os_windows } steps: - uses: actions/checkout@v2 + - uses: Swatinem/rust-cache@v1 - name: Initialize workflow variables id: vars shell: bash @@ -745,6 +755,7 @@ jobs: - { os: ubuntu-latest } steps: - uses: actions/checkout@v2 + - uses: Swatinem/rust-cache@v1 - name: Install/setup prerequisites shell: bash run: | @@ -780,6 +791,7 @@ jobs: mem: 2048 steps: - uses: actions/checkout@v2 + - uses: Swatinem/rust-cache@v1 - name: Prepare, build and test ## spell-checker:ignore (ToDO) sshfs usesh vmactions uses: vmactions/freebsd-vm@v0.1.5 @@ -848,6 +860,7 @@ jobs: - { os: windows-latest , features: windows } steps: - uses: actions/checkout@v2 + - uses: Swatinem/rust-cache@v1 # - name: Reattach HEAD ## may be needed for accurate code coverage info # run: git checkout ${{ github.head_ref }} - name: Initialize workflow variables diff --git a/.github/workflows/FixPR.yml b/.github/workflows/FixPR.yml index ead0b5e81..2a5382e27 100644 --- a/.github/workflows/FixPR.yml +++ b/.github/workflows/FixPR.yml @@ -1,5 +1,7 @@ name: FixPR +# spell-checker:ignore Swatinem + # Trigger automated fixes for PRs being merged (with associated commits) # ToDO: [2021-06; rivy] change from `cargo-tree` to `cargo tree` once MSRV is >= 1.45 @@ -27,6 +29,7 @@ jobs: - { os: ubuntu-latest , features: feat_os_unix } steps: - uses: actions/checkout@v2 + - uses: Swatinem/rust-cache@v1 - name: Initialize job variables id: vars shell: bash @@ -98,6 +101,7 @@ jobs: - { os: ubuntu-latest , features: feat_os_unix } steps: - uses: actions/checkout@v2 + - uses: Swatinem/rust-cache@v1 - name: Initialize job variables id: vars shell: bash From fe8910350c0b766b0a9897da085c18bdb0f7f055 Mon Sep 17 00:00:00 2001 From: xxyzz Date: Sun, 20 Feb 2022 18:29:18 +0800 Subject: [PATCH 20/58] Run GNU root tests at the end This should fix the profiling permission denied error on non-root tests --- util/run-gnu-test.sh | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/util/run-gnu-test.sh b/util/run-gnu-test.sh index 360807013..f478e4058 100755 --- a/util/run-gnu-test.sh +++ b/util/run-gnu-test.sh @@ -31,8 +31,6 @@ export RUST_BACKTRACE=1 if test -n "$1"; then # if set, run only the test passed export RUN_TEST="TESTS=$1" -elif test -n "$CI"; then - sudo make -j "$(nproc)" check-root SUBDIRS=. RUN_EXPENSIVE_TESTS=yes RUN_VERY_EXPENSIVE_TESTS=yes VERBOSE=no gl_public_submodule_commit="" srcdir="${path_GNU}" TEST_SUITE_LOG="tests/test-suite-root.log" || : fi # * timeout used to kill occasionally errant/"stuck" processes (note: 'release' testing takes ~1 hour; 'debug' testing takes ~2.5 hours) @@ -40,3 +38,7 @@ fi # * `srcdir=..` specifies the GNU source directory for tests (fixing failing/confused 'tests/factor/tNN.sh' tests and causing no harm to other tests) #shellcheck disable=SC2086 timeout -sKILL 4h make -j "$(nproc)" check ${RUN_TEST} SUBDIRS=. RUN_EXPENSIVE_TESTS=yes RUN_VERY_EXPENSIVE_TESTS=yes VERBOSE=no gl_public_submodule_commit="" srcdir="${path_GNU}" || : # Kill after 4 hours in case something gets stuck in make + +if test -z "$1" && test -n "$CI"; then + sudo make -j "$(nproc)" check-root SUBDIRS=. RUN_EXPENSIVE_TESTS=yes RUN_VERY_EXPENSIVE_TESTS=yes VERBOSE=no gl_public_submodule_commit="" srcdir="${path_GNU}" TEST_SUITE_LOG="tests/test-suite-root.log" || : +fi From 744592e221ef5b89c08f11750874493c87d5785d Mon Sep 17 00:00:00 2001 From: xxyzz Date: Sun, 20 Feb 2022 19:15:48 +0800 Subject: [PATCH 21/58] Change the owner of gcda files to the current user --- .github/workflows/GnuTests.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/GnuTests.yml b/.github/workflows/GnuTests.yml index 91cfd0f3d..9582b2af7 100644 --- a/.github/workflows/GnuTests.yml +++ b/.github/workflows/GnuTests.yml @@ -261,6 +261,7 @@ jobs: COVERAGE_REPORT_DIR="target/debug" COVERAGE_REPORT_FILE="${COVERAGE_REPORT_DIR}/lcov.info" mkdir -p "${COVERAGE_REPORT_DIR}" + sudo chown -R "$(whoami)" "${COVERAGE_REPORT_DIR}" # display coverage files grcov . --output-type files --ignore build.rs --ignore "vendor/*" --ignore "/*" --ignore "[a-zA-Z]:/*" --excl-br-line "^\s*((debug_)?assert(_eq|_ne)?!|#\[derive\()" | sort --unique # generate coverage report From 1076fbd4926351f7c80cd7922402e3988f564572 Mon Sep 17 00:00:00 2001 From: Jeffrey Finkelstein Date: Sun, 20 Feb 2022 12:13:46 -0500 Subject: [PATCH 22/58] dd: move block-related functions into new module Create a new module `blocks.rs` to contain the block-related helper functions. This commit only moves the location of the code and related tests, it does not change the functionality of `dd`. --- src/uu/dd/src/blocks.rs | 410 ++++++++++++++++++++++++++++++++++++++++ src/uu/dd/src/dd.rs | 398 +------------------------------------- 2 files changed, 415 insertions(+), 393 deletions(-) create mode 100644 src/uu/dd/src/blocks.rs diff --git a/src/uu/dd/src/blocks.rs b/src/uu/dd/src/blocks.rs new file mode 100644 index 000000000..61a2a6675 --- /dev/null +++ b/src/uu/dd/src/blocks.rs @@ -0,0 +1,410 @@ +// * This file is part of the uutils coreutils package. +// * +// * For the full copyright and license information, please view the LICENSE +// * file that was distributed with this source code. + +// spell-checker:ignore datastructures rstat rposition cflags ctable + +use crate::conversion_tables::ConversionTable; +use crate::datastructures::InternalError; +use crate::progress::ReadStat; +use crate::Input; +use std::io::Read; + +const NEWLINE: u8 = b'\n'; +const SPACE: u8 = b' '; + +/// Splits the content of buf into cbs-length blocks +/// Appends padding as specified by conv=block and cbs=N +/// Expects ascii encoded data +fn block(buf: &[u8], cbs: usize, rstat: &mut ReadStat) -> Vec> { + let mut blocks = buf + .split(|&e| e == NEWLINE) + .map(|split| split.to_vec()) + .fold(Vec::new(), |mut blocks, mut split| { + if split.len() > cbs { + rstat.records_truncated += 1; + } + split.resize(cbs, SPACE); + blocks.push(split); + + blocks + }); + + if let Some(last) = blocks.last() { + if last.iter().all(|&e| e == SPACE) { + blocks.pop(); + } + } + + blocks +} + +/// Trims padding from each cbs-length partition of buf +/// as specified by conv=unblock and cbs=N +/// Expects ascii encoded data +fn unblock(buf: &[u8], cbs: usize) -> Vec { + buf.chunks(cbs).fold(Vec::new(), |mut acc, block| { + if let Some(last_char_idx) = block.iter().rposition(|&e| e != SPACE) { + // Include text up to last space. + acc.extend(&block[..=last_char_idx]); + } + + acc.push(NEWLINE); + acc + }) +} + +/// A helper for teasing out which options must be applied and in which order. +/// Some user options, such as the presence of conversion tables, will determine whether the input is assumed to be ascii. The parser sets the Input::non_ascii flag accordingly. +/// Examples: +/// - If conv=ebcdic or conv=ibm is specified then block, unblock or swab must be performed before the conversion happens since the source will start in ascii. +/// - If conv=ascii is specified then block, unblock or swab must be performed after the conversion since the source starts in ebcdic. +/// - If no conversion is specified then the source is assumed to be in ascii. +/// For more info see `info dd` +pub(crate) fn conv_block_unblock_helper( + mut buf: Vec, + i: &mut Input, + rstat: &mut ReadStat, +) -> Result, InternalError> { + // Local Predicate Fns ------------------------------------------------- + fn should_block_then_conv(i: &Input) -> bool { + !i.non_ascii && i.cflags.block.is_some() + } + fn should_conv_then_block(i: &Input) -> bool { + i.non_ascii && i.cflags.block.is_some() + } + fn should_unblock_then_conv(i: &Input) -> bool { + !i.non_ascii && i.cflags.unblock.is_some() + } + fn should_conv_then_unblock(i: &Input) -> bool { + i.non_ascii && i.cflags.unblock.is_some() + } + fn conv_only(i: &Input) -> bool { + i.cflags.ctable.is_some() && i.cflags.block.is_none() && i.cflags.unblock.is_none() + } + // Local Helper Fns ---------------------------------------------------- + fn apply_conversion(buf: &mut [u8], ct: &ConversionTable) { + for idx in 0..buf.len() { + buf[idx] = ct[buf[idx] as usize]; + } + } + // -------------------------------------------------------------------- + if conv_only(i) { + // no block/unblock + let ct = i.cflags.ctable.unwrap(); + apply_conversion(&mut buf, ct); + + Ok(buf) + } else if should_block_then_conv(i) { + // ascii input so perform the block first + let cbs = i.cflags.block.unwrap(); + + let mut blocks = block(&buf, cbs, rstat); + + if let Some(ct) = i.cflags.ctable { + for buf in &mut blocks { + apply_conversion(buf, ct); + } + } + + let blocks = blocks.into_iter().flatten().collect(); + + Ok(blocks) + } else if should_conv_then_block(i) { + // Non-ascii so perform the conversion first + let cbs = i.cflags.block.unwrap(); + + if let Some(ct) = i.cflags.ctable { + apply_conversion(&mut buf, ct); + } + + let blocks = block(&buf, cbs, rstat).into_iter().flatten().collect(); + + Ok(blocks) + } else if should_unblock_then_conv(i) { + // ascii input so perform the unblock first + let cbs = i.cflags.unblock.unwrap(); + + let mut buf = unblock(&buf, cbs); + + if let Some(ct) = i.cflags.ctable { + apply_conversion(&mut buf, ct); + } + + Ok(buf) + } else if should_conv_then_unblock(i) { + // Non-ascii input so perform the conversion first + let cbs = i.cflags.unblock.unwrap(); + + if let Some(ct) = i.cflags.ctable { + apply_conversion(&mut buf, ct); + } + + let buf = unblock(&buf, cbs); + + Ok(buf) + } else { + // The following error should not happen, as it results from + // insufficient command line data. This case should be caught + // by the parser before making it this far. + // Producing this error is an alternative to risking an unwrap call + // on 'cbs' if the required data is not provided. + Err(InternalError::InvalidConvBlockUnblockCase) + } +} + +#[cfg(test)] +mod tests { + + use crate::blocks::{block, unblock}; + use crate::progress::ReadStat; + + const NEWLINE: u8 = b'\n'; + const SPACE: u8 = b' '; + + #[test] + fn block_test_no_nl() { + let mut rs = ReadStat::default(); + let buf = [0u8, 1u8, 2u8, 3u8]; + let res = block(&buf, 4, &mut rs); + + assert_eq!(res, vec![vec![0u8, 1u8, 2u8, 3u8],]); + } + + #[test] + fn block_test_no_nl_short_record() { + let mut rs = ReadStat::default(); + let buf = [0u8, 1u8, 2u8, 3u8]; + let res = block(&buf, 8, &mut rs); + + assert_eq!( + res, + vec![vec![0u8, 1u8, 2u8, 3u8, SPACE, SPACE, SPACE, SPACE],] + ); + } + + #[test] + fn block_test_no_nl_trunc() { + let mut rs = ReadStat::default(); + let buf = [0u8, 1u8, 2u8, 3u8, 4u8]; + let res = block(&buf, 4, &mut rs); + + // Commented section(s) should be truncated and appear for reference only. + assert_eq!(res, vec![vec![0u8, 1u8, 2u8, 3u8 /*, 4u8*/],]); + assert_eq!(rs.records_truncated, 1); + } + + #[test] + fn block_test_nl_gt_cbs_trunc() { + let mut rs = ReadStat::default(); + let buf = [ + 0u8, 1u8, 2u8, 3u8, 4u8, NEWLINE, 0u8, 1u8, 2u8, 3u8, 4u8, NEWLINE, 5u8, 6u8, 7u8, 8u8, + ]; + let res = block(&buf, 4, &mut rs); + + assert_eq!( + res, + vec![ + // Commented section(s) should be truncated and appear for reference only. + vec![0u8, 1u8, 2u8, 3u8], + // vec![4u8, SPACE, SPACE, SPACE], + vec![0u8, 1u8, 2u8, 3u8], + // vec![4u8, SPACE, SPACE, SPACE], + vec![5u8, 6u8, 7u8, 8u8], + ] + ); + assert_eq!(rs.records_truncated, 2); + } + + #[test] + fn block_test_surrounded_nl() { + let mut rs = ReadStat::default(); + let buf = [0u8, 1u8, 2u8, 3u8, NEWLINE, 4u8, 5u8, 6u8, 7u8, 8u8]; + let res = block(&buf, 8, &mut rs); + + assert_eq!( + res, + vec![ + vec![0u8, 1u8, 2u8, 3u8, SPACE, SPACE, SPACE, SPACE], + vec![4u8, 5u8, 6u8, 7u8, 8u8, SPACE, SPACE, SPACE], + ] + ); + } + + #[test] + fn block_test_multiple_nl_same_cbs_block() { + let mut rs = ReadStat::default(); + let buf = [ + 0u8, 1u8, 2u8, 3u8, NEWLINE, 4u8, NEWLINE, 5u8, 6u8, 7u8, 8u8, 9u8, + ]; + let res = block(&buf, 8, &mut rs); + + assert_eq!( + res, + vec![ + vec![0u8, 1u8, 2u8, 3u8, SPACE, SPACE, SPACE, SPACE], + vec![4u8, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE], + vec![5u8, 6u8, 7u8, 8u8, 9u8, SPACE, SPACE, SPACE], + ] + ); + } + + #[test] + fn block_test_multiple_nl_diff_cbs_block() { + let mut rs = ReadStat::default(); + let buf = [ + 0u8, 1u8, 2u8, 3u8, NEWLINE, 4u8, 5u8, 6u8, 7u8, NEWLINE, 8u8, 9u8, + ]; + let res = block(&buf, 8, &mut rs); + + assert_eq!( + res, + vec![ + vec![0u8, 1u8, 2u8, 3u8, SPACE, SPACE, SPACE, SPACE], + vec![4u8, 5u8, 6u8, 7u8, SPACE, SPACE, SPACE, SPACE], + vec![8u8, 9u8, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE], + ] + ); + } + + #[test] + fn block_test_end_nl_diff_cbs_block() { + let mut rs = ReadStat::default(); + let buf = [0u8, 1u8, 2u8, 3u8, NEWLINE]; + let res = block(&buf, 4, &mut rs); + + assert_eq!(res, vec![vec![0u8, 1u8, 2u8, 3u8],]); + } + + #[test] + fn block_test_end_nl_same_cbs_block() { + let mut rs = ReadStat::default(); + let buf = [0u8, 1u8, 2u8, NEWLINE]; + let res = block(&buf, 4, &mut rs); + + assert_eq!(res, vec![vec![0u8, 1u8, 2u8, SPACE]]); + } + + #[test] + fn block_test_double_end_nl() { + let mut rs = ReadStat::default(); + let buf = [0u8, 1u8, 2u8, NEWLINE, NEWLINE]; + let res = block(&buf, 4, &mut rs); + + assert_eq!( + res, + vec![vec![0u8, 1u8, 2u8, SPACE], vec![SPACE, SPACE, SPACE, SPACE],] + ); + } + + #[test] + fn block_test_start_nl() { + let mut rs = ReadStat::default(); + let buf = [NEWLINE, 0u8, 1u8, 2u8, 3u8]; + let res = block(&buf, 4, &mut rs); + + assert_eq!( + res, + vec![vec![SPACE, SPACE, SPACE, SPACE], vec![0u8, 1u8, 2u8, 3u8],] + ); + } + + #[test] + fn block_test_double_surrounded_nl_no_trunc() { + let mut rs = ReadStat::default(); + let buf = [0u8, 1u8, 2u8, 3u8, NEWLINE, NEWLINE, 4u8, 5u8, 6u8, 7u8]; + let res = block(&buf, 8, &mut rs); + + assert_eq!( + res, + vec![ + vec![0u8, 1u8, 2u8, 3u8, SPACE, SPACE, SPACE, SPACE], + vec![SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE], + vec![4u8, 5u8, 6u8, 7u8, SPACE, SPACE, SPACE, SPACE], + ] + ); + } + + #[test] + fn block_test_double_surrounded_nl_double_trunc() { + let mut rs = ReadStat::default(); + let buf = [ + 0u8, 1u8, 2u8, 3u8, NEWLINE, NEWLINE, 4u8, 5u8, 6u8, 7u8, 8u8, + ]; + let res = block(&buf, 4, &mut rs); + + assert_eq!( + res, + vec![ + // Commented section(s) should be truncated and appear for reference only. + vec![0u8, 1u8, 2u8, 3u8], + vec![SPACE, SPACE, SPACE, SPACE], + vec![4u8, 5u8, 6u8, 7u8 /*, 8u8*/], + ] + ); + assert_eq!(rs.records_truncated, 1); + } + + #[test] + fn unblock_test_full_cbs() { + let buf = [0u8, 1u8, 2u8, 3u8, 4u8, 5u8, 6u8, 7u8]; + let res = unblock(&buf, 8); + + assert_eq!(res, vec![0u8, 1u8, 2u8, 3u8, 4u8, 5u8, 6u8, 7u8, NEWLINE],); + } + + #[test] + fn unblock_test_all_space() { + let buf = [SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE]; + let res = unblock(&buf, 8); + + assert_eq!(res, vec![NEWLINE],); + } + + #[test] + fn unblock_test_decoy_spaces() { + let buf = [0u8, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, 7u8]; + let res = unblock(&buf, 8); + + assert_eq!( + res, + vec![0u8, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, 7u8, NEWLINE], + ); + } + + #[test] + fn unblock_test_strip_single_cbs() { + let buf = [0u8, 1u8, 2u8, 3u8, SPACE, SPACE, SPACE, SPACE]; + let res = unblock(&buf, 8); + + assert_eq!(res, vec![0u8, 1u8, 2u8, 3u8, NEWLINE],); + } + + #[test] + fn unblock_test_strip_multi_cbs() { + let buf = vec![ + vec![0u8, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE], + vec![0u8, 1u8, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE], + vec![0u8, 1u8, 2u8, SPACE, SPACE, SPACE, SPACE, SPACE], + vec![0u8, 1u8, 2u8, 3u8, SPACE, SPACE, SPACE, SPACE], + ] + .into_iter() + .flatten() + .collect::>(); + + let res = unblock(&buf, 8); + + let exp = vec![ + vec![0u8, NEWLINE], + vec![0u8, 1u8, NEWLINE], + vec![0u8, 1u8, 2u8, NEWLINE], + vec![0u8, 1u8, 2u8, 3u8, NEWLINE], + ] + .into_iter() + .flatten() + .collect::>(); + + assert_eq!(res, exp); + } +} diff --git a/src/uu/dd/src/dd.rs b/src/uu/dd/src/dd.rs index c8004b893..52f985a2c 100644 --- a/src/uu/dd/src/dd.rs +++ b/src/uu/dd/src/dd.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 fname, tname, fpath, specfile, testfile, unspec, ifile, ofile, outfile, fullblock, urand, fileio, atoe, atoibm, behaviour, bmax, bremain, 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, wlen, wstat seekable +// spell-checker:ignore fname, tname, fpath, specfile, testfile, unspec, ifile, ofile, outfile, fullblock, urand, fileio, atoe, atoibm, behaviour, bmax, bremain, cflags, creat, ctable, ctty, datastructures, doesnt, etoa, fileout, fname, gnudd, iconvflags, nocache, noctty, noerror, nofollow, nolinks, nonblock, oconvflags, outfile, parseargs, rlen, rmax, rremain, rsofar, rstat, sigusr, wlen, wstat seekable mod datastructures; use datastructures::*; @@ -19,6 +19,9 @@ use conversion_tables::*; mod progress; use progress::{gen_prog_updater, ProgUpdate, ReadStat, StatusLevel, WriteStat}; +mod blocks; +use blocks::conv_block_unblock_helper; + use std::cmp; use std::convert::TryInto; use std::env; @@ -40,8 +43,6 @@ use uucore::InvalidEncodingHandling; const ABOUT: &str = "copy, and optionally convert, a file system resource"; const BUF_INIT_BYTE: u8 = 0xDD; -const NEWLINE: u8 = b'\n'; -const SPACE: u8 = b' '; struct Input { src: R, @@ -585,146 +586,6 @@ impl Write for Output { } } -/// Splits the content of buf into cbs-length blocks -/// Appends padding as specified by conv=block and cbs=N -/// Expects ascii encoded data -fn block(buf: &[u8], cbs: usize, rstat: &mut ReadStat) -> Vec> { - let mut blocks = buf - .split(|&e| e == NEWLINE) - .map(|split| split.to_vec()) - .fold(Vec::new(), |mut blocks, mut split| { - if split.len() > cbs { - rstat.records_truncated += 1; - } - split.resize(cbs, SPACE); - blocks.push(split); - - blocks - }); - - if let Some(last) = blocks.last() { - if last.iter().all(|&e| e == SPACE) { - blocks.pop(); - } - } - - blocks -} - -/// Trims padding from each cbs-length partition of buf -/// as specified by conv=unblock and cbs=N -/// Expects ascii encoded data -fn unblock(buf: &[u8], cbs: usize) -> Vec { - buf.chunks(cbs).fold(Vec::new(), |mut acc, block| { - if let Some(last_char_idx) = block.iter().rposition(|&e| e != SPACE) { - // Include text up to last space. - acc.extend(&block[..=last_char_idx]); - } - - acc.push(NEWLINE); - acc - }) -} - -/// A helper for teasing out which options must be applied and in which order. -/// Some user options, such as the presence of conversion tables, will determine whether the input is assumed to be ascii. The parser sets the Input::non_ascii flag accordingly. -/// Examples: -/// - If conv=ebcdic or conv=ibm is specified then block, unblock or swab must be performed before the conversion happens since the source will start in ascii. -/// - If conv=ascii is specified then block, unblock or swab must be performed after the conversion since the source starts in ebcdic. -/// - If no conversion is specified then the source is assumed to be in ascii. -/// For more info see `info dd` -fn conv_block_unblock_helper( - mut buf: Vec, - i: &mut Input, - rstat: &mut ReadStat, -) -> Result, InternalError> { - // Local Predicate Fns ------------------------------------------------- - fn should_block_then_conv(i: &Input) -> bool { - !i.non_ascii && i.cflags.block.is_some() - } - fn should_conv_then_block(i: &Input) -> bool { - i.non_ascii && i.cflags.block.is_some() - } - fn should_unblock_then_conv(i: &Input) -> bool { - !i.non_ascii && i.cflags.unblock.is_some() - } - fn should_conv_then_unblock(i: &Input) -> bool { - i.non_ascii && i.cflags.unblock.is_some() - } - fn conv_only(i: &Input) -> bool { - i.cflags.ctable.is_some() && i.cflags.block.is_none() && i.cflags.unblock.is_none() - } - // Local Helper Fns ---------------------------------------------------- - fn apply_conversion(buf: &mut [u8], ct: &ConversionTable) { - for idx in 0..buf.len() { - buf[idx] = ct[buf[idx] as usize]; - } - } - // -------------------------------------------------------------------- - if conv_only(i) { - // no block/unblock - let ct = i.cflags.ctable.unwrap(); - apply_conversion(&mut buf, ct); - - Ok(buf) - } else if should_block_then_conv(i) { - // ascii input so perform the block first - let cbs = i.cflags.block.unwrap(); - - let mut blocks = block(&buf, cbs, rstat); - - if let Some(ct) = i.cflags.ctable { - for buf in &mut blocks { - apply_conversion(buf, ct); - } - } - - let blocks = blocks.into_iter().flatten().collect(); - - Ok(blocks) - } else if should_conv_then_block(i) { - // Non-ascii so perform the conversion first - let cbs = i.cflags.block.unwrap(); - - if let Some(ct) = i.cflags.ctable { - apply_conversion(&mut buf, ct); - } - - let blocks = block(&buf, cbs, rstat).into_iter().flatten().collect(); - - Ok(blocks) - } else if should_unblock_then_conv(i) { - // ascii input so perform the unblock first - let cbs = i.cflags.unblock.unwrap(); - - let mut buf = unblock(&buf, cbs); - - if let Some(ct) = i.cflags.ctable { - apply_conversion(&mut buf, ct); - } - - Ok(buf) - } else if should_conv_then_unblock(i) { - // Non-ascii input so perform the conversion first - let cbs = i.cflags.unblock.unwrap(); - - if let Some(ct) = i.cflags.ctable { - apply_conversion(&mut buf, ct); - } - - let buf = unblock(&buf, cbs); - - Ok(buf) - } else { - // The following error should not happen, as it results from - // insufficient command line data. This case should be caught - // by the parser before making it this far. - // Producing this error is an alternative to risking an unwrap call - // on 'cbs' if the required data is not provided. - Err(InternalError::InvalidConvBlockUnblockCase) - } -} - /// Read helper performs read operations common to all dd reads, and dispatches the buffer to relevant helper functions as dictated by the operations requested by the user. fn read_helper(i: &mut Input, bsize: usize) -> std::io::Result<(ReadStat, Vec)> { // Local Predicate Fns ----------------------------------------------- @@ -1084,8 +945,7 @@ General-Flags mod tests { use crate::datastructures::{IConvFlags, IFlags, OConvFlags}; - use crate::ReadStat; - use crate::{block, calc_bsize, unblock, uu_app, Input, Output, OutputTrait}; + use crate::{calc_bsize, uu_app, Input, Output, OutputTrait}; use std::cmp; use std::fs; @@ -1104,254 +964,6 @@ mod tests { } } - const NEWLINE: u8 = b'\n'; - const SPACE: u8 = b' '; - - #[test] - fn block_test_no_nl() { - let mut rs = ReadStat::default(); - let buf = [0u8, 1u8, 2u8, 3u8]; - let res = block(&buf, 4, &mut rs); - - assert_eq!(res, vec![vec![0u8, 1u8, 2u8, 3u8],]); - } - - #[test] - fn block_test_no_nl_short_record() { - let mut rs = ReadStat::default(); - let buf = [0u8, 1u8, 2u8, 3u8]; - let res = block(&buf, 8, &mut rs); - - assert_eq!( - res, - vec![vec![0u8, 1u8, 2u8, 3u8, SPACE, SPACE, SPACE, SPACE],] - ); - } - - #[test] - fn block_test_no_nl_trunc() { - let mut rs = ReadStat::default(); - let buf = [0u8, 1u8, 2u8, 3u8, 4u8]; - let res = block(&buf, 4, &mut rs); - - // Commented section(s) should be truncated and appear for reference only. - assert_eq!(res, vec![vec![0u8, 1u8, 2u8, 3u8 /*, 4u8*/],]); - assert_eq!(rs.records_truncated, 1); - } - - #[test] - fn block_test_nl_gt_cbs_trunc() { - let mut rs = ReadStat::default(); - let buf = [ - 0u8, 1u8, 2u8, 3u8, 4u8, NEWLINE, 0u8, 1u8, 2u8, 3u8, 4u8, NEWLINE, 5u8, 6u8, 7u8, 8u8, - ]; - let res = block(&buf, 4, &mut rs); - - assert_eq!( - res, - vec![ - // Commented section(s) should be truncated and appear for reference only. - vec![0u8, 1u8, 2u8, 3u8], - // vec![4u8, SPACE, SPACE, SPACE], - vec![0u8, 1u8, 2u8, 3u8], - // vec![4u8, SPACE, SPACE, SPACE], - vec![5u8, 6u8, 7u8, 8u8], - ] - ); - assert_eq!(rs.records_truncated, 2); - } - - #[test] - fn block_test_surrounded_nl() { - let mut rs = ReadStat::default(); - let buf = [0u8, 1u8, 2u8, 3u8, NEWLINE, 4u8, 5u8, 6u8, 7u8, 8u8]; - let res = block(&buf, 8, &mut rs); - - assert_eq!( - res, - vec![ - vec![0u8, 1u8, 2u8, 3u8, SPACE, SPACE, SPACE, SPACE], - vec![4u8, 5u8, 6u8, 7u8, 8u8, SPACE, SPACE, SPACE], - ] - ); - } - - #[test] - fn block_test_multiple_nl_same_cbs_block() { - let mut rs = ReadStat::default(); - let buf = [ - 0u8, 1u8, 2u8, 3u8, NEWLINE, 4u8, NEWLINE, 5u8, 6u8, 7u8, 8u8, 9u8, - ]; - let res = block(&buf, 8, &mut rs); - - assert_eq!( - res, - vec![ - vec![0u8, 1u8, 2u8, 3u8, SPACE, SPACE, SPACE, SPACE], - vec![4u8, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE], - vec![5u8, 6u8, 7u8, 8u8, 9u8, SPACE, SPACE, SPACE], - ] - ); - } - - #[test] - fn block_test_multiple_nl_diff_cbs_block() { - let mut rs = ReadStat::default(); - let buf = [ - 0u8, 1u8, 2u8, 3u8, NEWLINE, 4u8, 5u8, 6u8, 7u8, NEWLINE, 8u8, 9u8, - ]; - let res = block(&buf, 8, &mut rs); - - assert_eq!( - res, - vec![ - vec![0u8, 1u8, 2u8, 3u8, SPACE, SPACE, SPACE, SPACE], - vec![4u8, 5u8, 6u8, 7u8, SPACE, SPACE, SPACE, SPACE], - vec![8u8, 9u8, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE], - ] - ); - } - - #[test] - fn block_test_end_nl_diff_cbs_block() { - let mut rs = ReadStat::default(); - let buf = [0u8, 1u8, 2u8, 3u8, NEWLINE]; - let res = block(&buf, 4, &mut rs); - - assert_eq!(res, vec![vec![0u8, 1u8, 2u8, 3u8],]); - } - - #[test] - fn block_test_end_nl_same_cbs_block() { - let mut rs = ReadStat::default(); - let buf = [0u8, 1u8, 2u8, NEWLINE]; - let res = block(&buf, 4, &mut rs); - - assert_eq!(res, vec![vec![0u8, 1u8, 2u8, SPACE]]); - } - - #[test] - fn block_test_double_end_nl() { - let mut rs = ReadStat::default(); - let buf = [0u8, 1u8, 2u8, NEWLINE, NEWLINE]; - let res = block(&buf, 4, &mut rs); - - assert_eq!( - res, - vec![vec![0u8, 1u8, 2u8, SPACE], vec![SPACE, SPACE, SPACE, SPACE],] - ); - } - - #[test] - fn block_test_start_nl() { - let mut rs = ReadStat::default(); - let buf = [NEWLINE, 0u8, 1u8, 2u8, 3u8]; - let res = block(&buf, 4, &mut rs); - - assert_eq!( - res, - vec![vec![SPACE, SPACE, SPACE, SPACE], vec![0u8, 1u8, 2u8, 3u8],] - ); - } - - #[test] - fn block_test_double_surrounded_nl_no_trunc() { - let mut rs = ReadStat::default(); - let buf = [0u8, 1u8, 2u8, 3u8, NEWLINE, NEWLINE, 4u8, 5u8, 6u8, 7u8]; - let res = block(&buf, 8, &mut rs); - - assert_eq!( - res, - vec![ - vec![0u8, 1u8, 2u8, 3u8, SPACE, SPACE, SPACE, SPACE], - vec![SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE], - vec![4u8, 5u8, 6u8, 7u8, SPACE, SPACE, SPACE, SPACE], - ] - ); - } - - #[test] - fn block_test_double_surrounded_nl_double_trunc() { - let mut rs = ReadStat::default(); - let buf = [ - 0u8, 1u8, 2u8, 3u8, NEWLINE, NEWLINE, 4u8, 5u8, 6u8, 7u8, 8u8, - ]; - let res = block(&buf, 4, &mut rs); - - assert_eq!( - res, - vec![ - // Commented section(s) should be truncated and appear for reference only. - vec![0u8, 1u8, 2u8, 3u8], - vec![SPACE, SPACE, SPACE, SPACE], - vec![4u8, 5u8, 6u8, 7u8 /*, 8u8*/], - ] - ); - assert_eq!(rs.records_truncated, 1); - } - - #[test] - fn unblock_test_full_cbs() { - let buf = [0u8, 1u8, 2u8, 3u8, 4u8, 5u8, 6u8, 7u8]; - let res = unblock(&buf, 8); - - assert_eq!(res, vec![0u8, 1u8, 2u8, 3u8, 4u8, 5u8, 6u8, 7u8, NEWLINE],); - } - - #[test] - fn unblock_test_all_space() { - let buf = [SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE]; - let res = unblock(&buf, 8); - - assert_eq!(res, vec![NEWLINE],); - } - - #[test] - fn unblock_test_decoy_spaces() { - let buf = [0u8, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, 7u8]; - let res = unblock(&buf, 8); - - assert_eq!( - res, - vec![0u8, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, 7u8, NEWLINE], - ); - } - - #[test] - fn unblock_test_strip_single_cbs() { - let buf = [0u8, 1u8, 2u8, 3u8, SPACE, SPACE, SPACE, SPACE]; - let res = unblock(&buf, 8); - - assert_eq!(res, vec![0u8, 1u8, 2u8, 3u8, NEWLINE],); - } - - #[test] - fn unblock_test_strip_multi_cbs() { - let buf = vec![ - vec![0u8, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE], - vec![0u8, 1u8, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE], - vec![0u8, 1u8, 2u8, SPACE, SPACE, SPACE, SPACE, SPACE], - vec![0u8, 1u8, 2u8, 3u8, SPACE, SPACE, SPACE, SPACE], - ] - .into_iter() - .flatten() - .collect::>(); - - let res = unblock(&buf, 8); - - let exp = vec![ - vec![0u8, NEWLINE], - vec![0u8, 1u8, NEWLINE], - vec![0u8, 1u8, 2u8, NEWLINE], - vec![0u8, 1u8, 2u8, 3u8, NEWLINE], - ] - .into_iter() - .flatten() - .collect::>(); - - assert_eq!(res, exp); - } - #[test] fn bsize_test_primes() { let (n, m) = (7901, 7919); From cd450cc5913934b93b51a6e24e6b53791e260522 Mon Sep 17 00:00:00 2001 From: alextibbles <45136886+alextibbles@users.noreply.github.com> Date: Mon, 21 Feb 2022 11:13:27 -0500 Subject: [PATCH 23/58] expr: update onig crate to 6.3 (#3136) * expr: update onig crate to 6.3 --- Cargo.lock | 8 ++++---- src/uu/expr/Cargo.toml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 244c56baa..e9d3add66 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1331,9 +1331,9 @@ checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5" [[package]] name = "onig" -version = "4.3.3" +version = "6.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8518fcb2b1b8c2f45f0ad499df4fda6087fc3475ca69a185c173b8315d2fb383" +checksum = "67ddfe2c93bb389eea6e6d713306880c7f6dcc99a75b659ce145d962c861b225" dependencies = [ "bitflags", "lazy_static", @@ -1343,9 +1343,9 @@ dependencies = [ [[package]] name = "onig_sys" -version = "69.1.0" +version = "69.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "388410bf5fa341f10e58e6db3975f4bea1ac30247dd79d37a9e5ced3cb4cc3b0" +checksum = "5dd3eee045c84695b53b20255bb7317063df090b68e18bfac0abb6c39cf7f33e" dependencies = [ "cc", "pkg-config", diff --git a/src/uu/expr/Cargo.toml b/src/uu/expr/Cargo.toml index 87113f84a..5d163a55f 100644 --- a/src/uu/expr/Cargo.toml +++ b/src/uu/expr/Cargo.toml @@ -19,7 +19,7 @@ clap = { version = "3.0", features = ["wrap_help", "cargo"] } libc = "0.2.42" num-bigint = "0.4.0" num-traits = "0.2.14" -onig = "~4.3.2" +onig = { version = "~6.3", default-features = false } uucore = { version=">=0.0.11", package="uucore", path="../../uucore" } [[bin]] From 53070141c1da8edfabc235e4d92846d09d6d4b2b Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Mon, 21 Feb 2022 17:14:03 +0100 Subject: [PATCH 24/58] all: add format_usage function (#3139) This should correct the usage strings in both the `--help` and user documentation. Previously, sometimes the name of the utils did not show up correctly. --- src/bin/uudoc.rs | 2 +- src/uu/base32/src/base32.rs | 9 +++------ src/uu/base32/src/base_common.rs | 7 ++++--- src/uu/base64/src/base64.rs | 7 ++----- src/uu/basename/src/basename.rs | 15 +++++---------- src/uu/basenc/src/basenc.rs | 9 +++------ src/uu/cat/src/cat.rs | 6 +++--- src/uu/chcon/src/chcon.rs | 19 +++++++----------- src/uu/chgrp/src/chgrp.rs | 21 ++++++-------------- src/uu/chmod/src/chmod.rs | 21 +++++++------------- src/uu/chown/src/chown.rs | 15 ++++++--------- src/uu/chroot/src/chroot.rs | 6 +++--- src/uu/cksum/src/cksum.rs | 6 +++--- src/uu/comm/src/comm.rs | 11 ++++------- src/uu/cp/src/cp.rs | 16 ++++++---------- src/uu/csplit/src/csplit.rs | 14 ++++---------- src/uu/cut/src/cut.rs | 8 ++++---- src/uu/date/src/date.rs | 14 ++++++-------- src/uu/df/src/df.rs | 11 ++++------- src/uu/dircolors/src/dircolors.rs | 13 ++++--------- src/uu/dirname/src/dirname.rs | 14 ++++---------- src/uu/du/src/du.rs | 17 ++++++---------- src/uu/echo/src/echo.rs | 6 +++--- src/uu/env/src/env.rs | 5 +++-- src/uu/expand/src/expand.rs | 10 ++++------ src/uu/fmt/src/fmt.rs | 11 ++++------- src/uu/fold/src/fold.rs | 6 +++--- src/uu/groups/src/groups.rs | 10 ++++------ src/uu/head/src/head.rs | 6 +++--- src/uu/hostid/src/hostid.rs | 6 +++--- src/uu/hostname/src/hostname.rs | 14 +++++++------- src/uu/id/src/id.rs | 13 ++++--------- src/uu/install/src/install.rs | 11 ++++------- src/uu/kill/src/kill.rs | 7 ++++--- src/uu/link/src/link.rs | 10 ++++------ src/uu/ln/src/ln.rs | 19 +++++++----------- src/uu/logname/src/logname.rs | 7 ++----- src/uu/ls/src/ls.rs | 11 ++++------- src/uu/mkdir/src/mkdir.rs | 15 +++++---------- src/uu/mkfifo/src/mkfifo.rs | 5 +++-- src/uu/mknod/src/mknod.rs | 6 +++--- src/uu/mktemp/src/mktemp.rs | 11 ++++------- src/uu/mv/src/mv.rs | 18 ++++++----------- src/uu/nice/src/nice.rs | 29 +++++++++++++--------------- src/uu/nl/src/nl.rs | 7 +++---- src/uu/nohup/src/nohup.rs | 16 ++++++---------- src/uu/nproc/src/nproc.rs | 10 ++++------ src/uu/numfmt/src/numfmt.rs | 11 ++++------- src/uu/od/src/od.rs | 10 ++++++---- src/uu/pathchk/src/pathchk.rs | 11 ++++------- src/uu/pinky/src/pinky.rs | 14 ++++---------- src/uu/printenv/src/printenv.rs | 12 ++++-------- src/uu/printf/src/printf.rs | 6 +++--- src/uu/ptx/src/ptx.rs | 19 ++++++++++-------- src/uu/pwd/src/pwd.rs | 11 ++++------- src/uu/readlink/src/readlink.rs | 10 ++++------ src/uu/realpath/src/realpath.rs | 11 ++++------- src/uu/relpath/src/relpath.rs | 11 ++++------- src/uu/rm/src/rm.rs | 13 ++++--------- src/uu/rmdir/src/rmdir.rs | 12 ++++-------- src/uu/runcon/src/runcon.rs | 17 ++++++---------- src/uu/seq/src/seq.rs | 17 +++++++--------- src/uu/shred/src/shred.rs | 14 ++++---------- src/uu/shuf/src/shuf.rs | 20 ++++++++++--------- src/uu/sleep/src/sleep.rs | 21 +++++++++----------- src/uu/sort/src/sort.rs | 24 +++++++---------------- src/uu/split/src/split.rs | 32 +++++++++---------------------- src/uu/stat/src/stat.rs | 14 ++++---------- src/uu/stdbuf/src/stdbuf.rs | 11 ++++------- src/uu/sum/src/sum.rs | 6 +++--- src/uu/sync/src/sync.rs | 11 ++++------- src/uu/tac/src/tac.rs | 6 +++--- src/uu/tail/src/tail.rs | 5 +++-- src/uu/tee/src/tee.rs | 11 ++++------- src/uu/test/src/test.rs | 16 +++++++++------- src/uu/timeout/src/timeout.rs | 15 ++++----------- src/uu/touch/src/touch.rs | 11 ++++------- src/uu/tr/src/tr.rs | 14 ++++---------- src/uu/truncate/src/truncate.rs | 9 +++------ src/uu/tsort/src/tsort.rs | 4 ++-- src/uu/tty/src/tty.rs | 10 +++------- src/uu/uname/src/uname.rs | 10 +++++++--- src/uu/unexpand/src/unexpand.rs | 6 +++--- src/uu/uniq/src/uniq.rs | 16 ++++------------ src/uu/uptime/src/uptime.rs | 10 ++++------ src/uu/users/src/users.rs | 13 ++++--------- src/uu/wc/src/wc.rs | 17 +++++----------- src/uu/who/src/who.rs | 17 ++++------------ src/uu/yes/src/yes.rs | 4 ++++ src/uucore/src/lib/lib.rs | 9 +++++++++ 90 files changed, 416 insertions(+), 655 deletions(-) diff --git a/src/bin/uudoc.rs b/src/bin/uudoc.rs index 33e5bf607..0ecdb96c7 100644 --- a/src/bin/uudoc.rs +++ b/src/bin/uudoc.rs @@ -94,7 +94,7 @@ fn write_usage(w: &mut impl Write, app: &mut App, name: &str) -> io::Result<()> .filter(|l| !l.is_empty()) .collect::>() .join("\n"); - usage = usage.replace(app.get_name(), name); + usage = usage.replace(uucore::execution_phrase(), name); writeln!(w, "{}", usage)?; writeln!(w, "```") } diff --git a/src/uu/base32/src/base32.rs b/src/uu/base32/src/base32.rs index 006a796f0..ff1ba502a 100644 --- a/src/uu/base32/src/base32.rs +++ b/src/uu/base32/src/base32.rs @@ -22,16 +22,13 @@ to attempt to recover from any other non-alphabet bytes in the encoded stream. "; -fn usage() -> String { - format!("{0} [OPTION]... [FILE]", uucore::execution_phrase()) -} +const USAGE: &str = "{} [OPTION]... [FILE]"; #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { let format = Format::Base32; - let usage = usage(); - let config: base_common::Config = base_common::parse_base_cmd_args(args, ABOUT, &usage)?; + let config: base_common::Config = base_common::parse_base_cmd_args(args, ABOUT, USAGE)?; // Create a reference to stdin so we can return a locked stdin from // parse_base_cmd_args @@ -48,5 +45,5 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } pub fn uu_app<'a>() -> App<'a> { - base_common::base_app(ABOUT) + base_common::base_app(ABOUT, USAGE) } diff --git a/src/uu/base32/src/base_common.rs b/src/uu/base32/src/base_common.rs index 39a46e315..1d4d8a19d 100644 --- a/src/uu/base32/src/base_common.rs +++ b/src/uu/base32/src/base_common.rs @@ -12,7 +12,7 @@ use std::io::{stdout, Read, Write}; use uucore::display::Quotable; use uucore::encoding::{wrap_print, Data, Format}; use uucore::error::{FromIo, UResult, USimpleError, UUsageError}; -use uucore::InvalidEncodingHandling; +use uucore::{format_usage, InvalidEncodingHandling}; use std::fs::File; use std::io::{BufReader, Stdin}; @@ -86,17 +86,18 @@ impl Config { } pub fn parse_base_cmd_args(args: impl uucore::Args, about: &str, usage: &str) -> UResult { - let app = base_app(about).override_usage(usage); + let app = base_app(about, usage); let arg_list = args .collect_str(InvalidEncodingHandling::ConvertLossy) .accept_any(); Config::from(&app.get_matches_from(arg_list)) } -pub fn base_app(about: &str) -> App { +pub fn base_app<'a>(about: &'a str, usage: &'a str) -> App<'a> { App::new(uucore::util_name()) .version(crate_version!()) .about(about) + .override_usage(format_usage(usage)) .setting(AppSettings::InferLongArgs) // Format arguments. .arg( diff --git a/src/uu/base64/src/base64.rs b/src/uu/base64/src/base64.rs index 20a9f55a5..1bfe29312 100644 --- a/src/uu/base64/src/base64.rs +++ b/src/uu/base64/src/base64.rs @@ -23,16 +23,13 @@ to attempt to recover from any other non-alphabet bytes in the encoded stream. "; -fn usage() -> String { - format!("{0} [OPTION]... [FILE]", uucore::execution_phrase()) -} +const USAGE: &str = "{0} [OPTION]... [FILE]"; #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { let format = Format::Base64; - let usage = usage(); - let config: base_common::Config = base_common::parse_base_cmd_args(args, ABOUT, &usage)?; + let config: base_common::Config = base_common::parse_base_cmd_args(args, ABOUT, USAGE)?; // Create a reference to stdin so we can return a locked stdin from // parse_base_cmd_args diff --git a/src/uu/basename/src/basename.rs b/src/uu/basename/src/basename.rs index b3afe7d7d..bf13fde7e 100644 --- a/src/uu/basename/src/basename.rs +++ b/src/uu/basename/src/basename.rs @@ -11,18 +11,13 @@ use clap::{crate_version, App, AppSettings, Arg}; use std::path::{is_separator, PathBuf}; use uucore::display::Quotable; use uucore::error::{UResult, UUsageError}; -use uucore::InvalidEncodingHandling; +use uucore::{format_usage, InvalidEncodingHandling}; static SUMMARY: &str = "Print NAME with any leading directory components removed If specified, also remove a trailing SUFFIX"; -fn usage() -> String { - format!( - "{0} NAME [SUFFIX] - {0} OPTION... NAME...", - uucore::execution_phrase() - ) -} +const USAGE: &str = "{} NAME [SUFFIX] + {} OPTION... NAME..."; pub mod options { pub static MULTIPLE: &str = "multiple"; @@ -36,11 +31,10 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let args = args .collect_str(InvalidEncodingHandling::ConvertLossy) .accept_any(); - let usage = usage(); // // Argument parsing // - let matches = uu_app().override_usage(&usage[..]).get_matches_from(args); + let matches = uu_app().get_matches_from(args); // too few arguments if !matches.is_present(options::NAME) { @@ -97,6 +91,7 @@ pub fn uu_app<'a>() -> App<'a> { App::new(uucore::util_name()) .version(crate_version!()) .about(SUMMARY) + .override_usage(format_usage(USAGE)) .setting(AppSettings::InferLongArgs) .arg( Arg::new(options::MULTIPLE) diff --git a/src/uu/basenc/src/basenc.rs b/src/uu/basenc/src/basenc.rs index ef133b151..9e48cf80c 100644 --- a/src/uu/basenc/src/basenc.rs +++ b/src/uu/basenc/src/basenc.rs @@ -38,12 +38,10 @@ const ENCODINGS: &[(&str, Format)] = &[ ("z85", Format::Z85), ]; -fn usage() -> String { - format!("{0} [OPTION]... [FILE]", uucore::execution_phrase()) -} +const USAGE: &str = "{} [OPTION]... [FILE]"; pub fn uu_app<'a>() -> App<'a> { - let mut app = base_common::base_app(ABOUT); + let mut app = base_common::base_app(ABOUT, USAGE); for encoding in ENCODINGS { app = app.arg(Arg::new(encoding.0).long(encoding.0)); } @@ -51,8 +49,7 @@ pub fn uu_app<'a>() -> App<'a> { } fn parse_cmd_args(args: impl uucore::Args) -> UResult<(Config, Format)> { - let usage = usage(); - let matches = uu_app().override_usage(&usage[..]).get_matches_from( + let matches = uu_app().get_matches_from( args.collect_str(InvalidEncodingHandling::ConvertLossy) .accept_any(), ); diff --git a/src/uu/cat/src/cat.rs b/src/uu/cat/src/cat.rs index 498e5e8ad..0a1ba2559 100644 --- a/src/uu/cat/src/cat.rs +++ b/src/uu/cat/src/cat.rs @@ -36,10 +36,10 @@ use std::net::Shutdown; use std::os::unix::fs::FileTypeExt; #[cfg(unix)] use unix_socket::UnixStream; -use uucore::InvalidEncodingHandling; +use uucore::{format_usage, InvalidEncodingHandling}; static NAME: &str = "cat"; -static SYNTAX: &str = "[OPTION]... [FILE]..."; +static USAGE: &str = "{} [OPTION]... [FILE]..."; static SUMMARY: &str = "Concatenate FILE(s), or standard input, to standard output With no FILE, or when FILE is -, read standard input."; @@ -243,7 +243,7 @@ pub fn uu_app<'a>() -> App<'a> { App::new(uucore::util_name()) .name(NAME) .version(crate_version!()) - .override_usage(SYNTAX) + .override_usage(format_usage(USAGE)) .about(SUMMARY) .setting(AppSettings::InferLongArgs) .arg( diff --git a/src/uu/chcon/src/chcon.rs b/src/uu/chcon/src/chcon.rs index 84de681cd..d033b62c1 100644 --- a/src/uu/chcon/src/chcon.rs +++ b/src/uu/chcon/src/chcon.rs @@ -3,6 +3,7 @@ #![allow(clippy::upper_case_acronyms)] use uucore::error::{UResult, USimpleError, UUsageError}; +use uucore::format_usage; use uucore::{display::Quotable, show_error, show_warning}; use clap::{App, AppSettings, Arg}; @@ -22,6 +23,10 @@ use errors::*; static VERSION: &str = env!("CARGO_PKG_VERSION"); static ABOUT: &str = "Change the SELinux security context of each FILE to CONTEXT. \n\ With --reference, change the security context of each FILE to that of RFILE."; +const USAGE: &str = "\ + {} [OPTION]... CONTEXT FILE... \n \ + {} [OPTION]... [-u USER] [-r ROLE] [-l RANGE] [-t TYPE] FILE... \n \ + {} [OPTION]... --reference=RFILE FILE..."; pub mod options { pub static VERBOSE: &str = "verbose"; @@ -52,20 +57,9 @@ pub mod options { } } -fn get_usage() -> String { - format!( - "{0} [OPTION]... CONTEXT FILE... \n \ - {0} [OPTION]... [-u USER] [-r ROLE] [-l RANGE] [-t TYPE] FILE... \n \ - {0} [OPTION]... --reference=RFILE FILE...", - uucore::execution_phrase() - ) -} - #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let usage = get_usage(); - - let config = uu_app().override_usage(usage.as_ref()); + let config = uu_app(); let options = match parse_command_line(config, args) { Ok(r) => r, @@ -164,6 +158,7 @@ pub fn uu_app<'a>() -> App<'a> { App::new(uucore::util_name()) .version(VERSION) .about(ABOUT) + .override_usage(format_usage(USAGE)) .setting(AppSettings::InferLongArgs) .arg( Arg::new(options::dereference::DEREFERENCE) diff --git a/src/uu/chgrp/src/chgrp.rs b/src/uu/chgrp/src/chgrp.rs index d228155b6..affaf86a3 100644 --- a/src/uu/chgrp/src/chgrp.rs +++ b/src/uu/chgrp/src/chgrp.rs @@ -10,6 +10,7 @@ use uucore::display::Quotable; pub use uucore::entries; use uucore::error::{FromIo, UResult, USimpleError}; +use uucore::format_usage; use uucore::perms::{chown_base, options, IfFrom}; use clap::{App, AppSettings, Arg, ArgMatches}; @@ -20,12 +21,9 @@ use std::os::unix::fs::MetadataExt; static ABOUT: &str = "Change the group of each FILE to GROUP."; static VERSION: &str = env!("CARGO_PKG_VERSION"); -fn get_usage() -> String { - format!( - "{0} [OPTION]... GROUP FILE...\n {0} [OPTION]... --reference=RFILE FILE...", - uucore::execution_phrase() - ) -} +const USAGE: &str = "\ + {} [OPTION]... GROUP FILE...\n \ + {} [OPTION]... --reference=RFILE FILE..."; fn parse_gid_and_uid(matches: &ArgMatches) -> UResult<(Option, Option, IfFrom)> { let dest_gid = if let Some(file) = matches.value_of(options::REFERENCE) { @@ -53,21 +51,14 @@ fn parse_gid_and_uid(matches: &ArgMatches) -> UResult<(Option, Option, #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let usage = get_usage(); - - chown_base( - uu_app().override_usage(&usage[..]), - args, - options::ARG_GROUP, - parse_gid_and_uid, - true, - ) + chown_base(uu_app(), args, options::ARG_GROUP, parse_gid_and_uid, true) } pub fn uu_app<'a>() -> App<'a> { App::new(uucore::util_name()) .version(VERSION) .about(ABOUT) + .override_usage(format_usage(USAGE)) .setting(AppSettings::InferLongArgs) .arg( Arg::new(options::verbosity::CHANGES) diff --git a/src/uu/chmod/src/chmod.rs b/src/uu/chmod/src/chmod.rs index 9b14c867f..84c5850d6 100644 --- a/src/uu/chmod/src/chmod.rs +++ b/src/uu/chmod/src/chmod.rs @@ -17,7 +17,7 @@ use uucore::fs::display_permissions_unix; use uucore::libc::mode_t; #[cfg(not(windows))] use uucore::mode; -use uucore::{show_error, InvalidEncodingHandling}; +use uucore::{format_usage, show_error, InvalidEncodingHandling}; use walkdir::WalkDir; static ABOUT: &str = "Change the mode of each FILE to MODE. @@ -35,14 +35,10 @@ mod options { pub const FILE: &str = "FILE"; } -fn usage() -> String { - format!( - "{0} [OPTION]... MODE[,MODE]... FILE... -or: {0} [OPTION]... OCTAL-MODE FILE... -or: {0} [OPTION]... --reference=RFILE FILE...", - uucore::execution_phrase() - ) -} +const USAGE: &str = "\ + {} [OPTION]... MODE[,MODE]... FILE... + {} [OPTION]... OCTAL-MODE FILE... + {} [OPTION]... --reference=RFILE FILE..."; fn get_long_usage() -> String { String::from("Each MODE is of the form '[ugoa]*([-+=]([rwxXst]*|[ugo]))+|[-+=]?[0-7]+'.") @@ -58,13 +54,9 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { // a possible MODE prefix '-' needs to be removed (e.g. "chmod -x FILE"). let mode_had_minus_prefix = mode::strip_minus_from_mode(&mut args); - let usage = usage(); let after_help = get_long_usage(); - let matches = uu_app() - .override_usage(&usage[..]) - .after_help(&after_help[..]) - .get_matches_from(args); + let matches = uu_app().after_help(&after_help[..]).get_matches_from(args); let changes = matches.is_present(options::CHANGES); let quiet = matches.is_present(options::QUIET); @@ -125,6 +117,7 @@ pub fn uu_app<'a>() -> App<'a> { App::new(uucore::util_name()) .version(crate_version!()) .about(ABOUT) + .override_usage(format_usage(USAGE)) .setting(AppSettings::InferLongArgs) .arg( Arg::new(options::CHANGES) diff --git a/src/uu/chown/src/chown.rs b/src/uu/chown/src/chown.rs index 06137e200..27a989847 100644 --- a/src/uu/chown/src/chown.rs +++ b/src/uu/chown/src/chown.rs @@ -9,6 +9,7 @@ use uucore::display::Quotable; pub use uucore::entries::{self, Group, Locate, Passwd}; +use uucore::format_usage; use uucore::perms::{chown_base, options, IfFrom}; use uucore::error::{FromIo, UResult, USimpleError}; @@ -20,12 +21,9 @@ use std::os::unix::fs::MetadataExt; static ABOUT: &str = "change file owner and group"; -fn get_usage() -> String { - format!( - "{0} [OPTION]... [OWNER][:[GROUP]] FILE...\n{0} [OPTION]... --reference=RFILE FILE...", - uucore::execution_phrase() - ) -} +const USAGE: &str = "\ + {} [OPTION]... [OWNER][:[GROUP]] FILE... + {} [OPTION]... --reference=RFILE FILE..."; fn parse_gid_uid_and_filter(matches: &ArgMatches) -> UResult<(Option, Option, IfFrom)> { let filter = if let Some(spec) = matches.value_of(options::FROM) { @@ -56,10 +54,8 @@ fn parse_gid_uid_and_filter(matches: &ArgMatches) -> UResult<(Option, Optio #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let usage = get_usage(); - chown_base( - uu_app().override_usage(&usage[..]), + uu_app(), args, options::ARG_OWNER, parse_gid_uid_and_filter, @@ -71,6 +67,7 @@ pub fn uu_app<'a>() -> App<'a> { App::new(uucore::util_name()) .version(crate_version!()) .about(ABOUT) + .override_usage(format_usage(USAGE)) .setting(AppSettings::InferLongArgs) .arg( Arg::new(options::verbosity::CHANGES) diff --git a/src/uu/chroot/src/chroot.rs b/src/uu/chroot/src/chroot.rs index f656ed77d..d18264566 100644 --- a/src/uu/chroot/src/chroot.rs +++ b/src/uu/chroot/src/chroot.rs @@ -17,10 +17,10 @@ use std::path::Path; use std::process::Command; use uucore::error::{set_exit_code, UResult}; use uucore::libc::{self, chroot, setgid, setgroups, setuid}; -use uucore::{entries, InvalidEncodingHandling}; +use uucore::{entries, format_usage, InvalidEncodingHandling}; static ABOUT: &str = "Run COMMAND with root directory set to NEWROOT."; -static SYNTAX: &str = "[OPTION]... NEWROOT [COMMAND [ARG]...]"; +static USAGE: &str = "{} [OPTION]... NEWROOT [COMMAND [ARG]...]"; mod options { pub const NEWROOT: &str = "newroot"; @@ -95,7 +95,7 @@ pub fn uu_app<'a>() -> App<'a> { App::new(uucore::util_name()) .version(crate_version!()) .about(ABOUT) - .override_usage(SYNTAX) + .override_usage(format_usage(USAGE)) .setting(AppSettings::InferLongArgs) .arg( Arg::new(options::NEWROOT) diff --git a/src/uu/cksum/src/cksum.rs b/src/uu/cksum/src/cksum.rs index ee47bfaa9..7f7ba9ed3 100644 --- a/src/uu/cksum/src/cksum.rs +++ b/src/uu/cksum/src/cksum.rs @@ -12,15 +12,15 @@ use std::io::{self, stdin, BufReader, Read}; use std::path::Path; use uucore::display::Quotable; use uucore::error::{FromIo, UResult}; -use uucore::show; use uucore::InvalidEncodingHandling; +use uucore::{format_usage, show}; // NOTE: CRC_TABLE_LEN *must* be <= 256 as we cast 0..CRC_TABLE_LEN to u8 const CRC_TABLE_LEN: usize = 256; const CRC_TABLE: [u32; CRC_TABLE_LEN] = generate_crc_table(); const NAME: &str = "cksum"; -const SYNTAX: &str = "[OPTIONS] [FILE]..."; +const USAGE: &str = "{} [OPTIONS] [FILE]..."; const SUMMARY: &str = "Print CRC and size for each file"; const fn generate_crc_table() -> [u32; CRC_TABLE_LEN] { @@ -145,7 +145,7 @@ pub fn uu_app<'a>() -> App<'a> { .name(NAME) .version(crate_version!()) .about(SUMMARY) - .override_usage(SYNTAX) + .override_usage(format_usage(USAGE)) .setting(AppSettings::InferLongArgs) .arg( Arg::new(options::FILE) diff --git a/src/uu/comm/src/comm.rs b/src/uu/comm/src/comm.rs index dd1281935..8064dd216 100644 --- a/src/uu/comm/src/comm.rs +++ b/src/uu/comm/src/comm.rs @@ -13,12 +13,13 @@ use std::io::{self, stdin, BufRead, BufReader, Stdin}; use std::path::Path; use uucore::error::FromIo; use uucore::error::UResult; -use uucore::InvalidEncodingHandling; +use uucore::{format_usage, InvalidEncodingHandling}; use clap::{crate_version, App, AppSettings, Arg, ArgMatches}; static ABOUT: &str = "compare two sorted files line by line"; static LONG_HELP: &str = ""; +const USAGE: &str = "{} [OPTION]... FILE1 FILE2"; mod options { pub const COLUMN_1: &str = "1"; @@ -30,10 +31,6 @@ mod options { pub const FILE_2: &str = "FILE2"; } -fn usage() -> String { - format!("{} [OPTION]... FILE1 FILE2", uucore::execution_phrase()) -} - fn mkdelim(col: usize, opts: &ArgMatches) -> String { let mut s = String::new(); let delim = opts.value_of(options::DELIMITER).unwrap(); @@ -132,12 +129,11 @@ fn open_file(name: &str) -> io::Result { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let usage = usage(); let args = args .collect_str(InvalidEncodingHandling::ConvertLossy) .accept_any(); - let matches = uu_app().override_usage(&usage[..]).get_matches_from(args); + let matches = uu_app().get_matches_from(args); let filename1 = matches.value_of(options::FILE_1).unwrap(); let filename2 = matches.value_of(options::FILE_2).unwrap(); let mut f1 = open_file(filename1).map_err_context(|| filename1.to_string())?; @@ -152,6 +148,7 @@ pub fn uu_app<'a>() -> App<'a> { .version(crate_version!()) .about(ABOUT) .after_help(LONG_HELP) + .override_usage(format_usage(USAGE)) .setting(AppSettings::InferLongArgs) .arg( Arg::new(options::COLUMN_1) diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index 2082fecbd..7256b6207 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -19,6 +19,7 @@ extern crate quick_error; extern crate uucore; use uucore::display::Quotable; +use uucore::format_usage; use uucore::fs::FileInformation; #[cfg(windows)] use winapi::um::fileapi::CreateFileW; @@ -230,14 +231,10 @@ static ABOUT: &str = "Copy SOURCE to DEST, or multiple SOURCE(s) to DIRECTORY."; static LONG_HELP: &str = ""; static EXIT_ERR: i32 = 1; -fn usage() -> String { - format!( - "{0} [OPTION]... [-T] SOURCE DEST - {0} [OPTION]... SOURCE... DIRECTORY - {0} [OPTION]... -t DIRECTORY SOURCE...", - uucore::execution_phrase() - ) -} +const USAGE: &str = "\ + {} [OPTION]... [-T] SOURCE DEST + {} [OPTION]... SOURCE... DIRECTORY + {} [OPTION]... -t DIRECTORY SOURCE..."; // Argument constants mod options { @@ -300,6 +297,7 @@ pub fn uu_app<'a>() -> App<'a> { App::new(uucore::util_name()) .version(crate_version!()) .about(ABOUT) + .override_usage(format_usage(USAGE)) .setting(AppSettings::InferLongArgs) .arg(Arg::new(options::TARGET_DIRECTORY) .short('t') @@ -456,14 +454,12 @@ pub fn uu_app<'a>() -> App<'a> { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let usage = usage(); let matches = uu_app() .after_help(&*format!( "{}\n{}", LONG_HELP, backup_control::BACKUP_CONTROL_LONG_HELP )) - .override_usage(&usage[..]) .get_matches_from(args); let options = Options::from_matches(&matches)?; diff --git a/src/uu/csplit/src/csplit.rs b/src/uu/csplit/src/csplit.rs index da0d24727..7793a8637 100644 --- a/src/uu/csplit/src/csplit.rs +++ b/src/uu/csplit/src/csplit.rs @@ -16,7 +16,7 @@ use clap::{crate_version, App, AppSettings, Arg, ArgMatches}; use regex::Regex; use uucore::display::Quotable; use uucore::error::{FromIo, UResult}; -use uucore::InvalidEncodingHandling; +use uucore::{format_usage, InvalidEncodingHandling}; mod csplit_error; mod patterns; @@ -27,6 +27,7 @@ use crate::split_name::SplitName; static SUMMARY: &str = "split a file into sections determined by context lines"; static LONG_HELP: &str = "Output pieces of FILE separated by PATTERN(s) to files 'xx00', 'xx01', ..., and output byte counts of each piece to standard output."; +const USAGE: &str = "{} [OPTION]... FILE PATTERN..."; mod options { pub const SUFFIX_FORMAT: &str = "suffix-format"; @@ -40,13 +41,6 @@ mod options { pub const PATTERN: &str = "pattern"; } -fn usage() -> String { - format!( - "{0} [OPTION]... FILE PATTERN...", - uucore::execution_phrase() - ) -} - /// Command line options for csplit. pub struct CsplitOptions { split_name: crate::SplitName, @@ -719,12 +713,11 @@ mod tests { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let usage = usage(); let args = args .collect_str(InvalidEncodingHandling::Ignore) .accept_any(); - let matches = uu_app().override_usage(&usage[..]).get_matches_from(args); + let matches = uu_app().get_matches_from(args); // get the file to split let file_name = matches.value_of(options::FILE).unwrap(); @@ -757,6 +750,7 @@ pub fn uu_app<'a>() -> App<'a> { App::new(uucore::util_name()) .version(crate_version!()) .about(SUMMARY) + .override_usage(format_usage(USAGE)) .setting(AppSettings::InferLongArgs) .arg( Arg::new(options::SUFFIX_FORMAT) diff --git a/src/uu/cut/src/cut.rs b/src/uu/cut/src/cut.rs index fc8494965..ce82a2737 100644 --- a/src/uu/cut/src/cut.rs +++ b/src/uu/cut/src/cut.rs @@ -20,13 +20,13 @@ use uucore::error::{FromIo, UResult, USimpleError}; use self::searcher::Searcher; use uucore::ranges::Range; -use uucore::InvalidEncodingHandling; +use uucore::{format_usage, InvalidEncodingHandling}; mod searcher; static NAME: &str = "cut"; -static SYNTAX: &str = - "[-d] [-s] [-z] [--output-delimiter] ((-f|-b|-c) {{sequence}}) {{sourcefile}}+"; +static USAGE: &str = + "{} [-d] [-s] [-z] [--output-delimiter] ((-f|-b|-c) {{sequence}}) {{sourcefile}}+"; static SUMMARY: &str = "Prints specified byte or field columns from each line of stdin or the input files"; static LONG_HELP: &str = " @@ -537,7 +537,7 @@ pub fn uu_app<'a>() -> App<'a> { App::new(uucore::util_name()) .name(NAME) .version(crate_version!()) - .override_usage(SYNTAX) + .override_usage(format_usage(USAGE)) .about(SUMMARY) .after_help(LONG_HELP) .setting(AppSettings::InferLongArgs) diff --git a/src/uu/date/src/date.rs b/src/uu/date/src/date.rs index 5a88225bb..18e06aef4 100644 --- a/src/uu/date/src/date.rs +++ b/src/uu/date/src/date.rs @@ -21,7 +21,7 @@ use uucore::display::Quotable; #[cfg(not(any(target_os = "macos", target_os = "redox")))] use uucore::error::FromIo; use uucore::error::{UResult, USimpleError}; -use uucore::show_error; +use uucore::{format_usage, show_error}; #[cfg(windows)] use winapi::{ shared::minwindef::WORD, @@ -38,8 +38,10 @@ const MINUTE: &str = "minute"; const SECOND: &str = "second"; const NS: &str = "ns"; -const NAME: &str = "date"; const ABOUT: &str = "print or set the system date and time"; +const USAGE: &str = "\ + {} [OPTION]... [+FORMAT]... + {} [OPTION]... [MMDDhhmm[[CC]YY][.ss]]"; const OPT_DATE: &str = "date"; const OPT_FORMAT: &str = "format"; @@ -142,12 +144,7 @@ impl<'a> From<&'a str> for Rfc3339Format { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let syntax = format!( - "{0} [OPTION]... [+FORMAT]... - {0} [OPTION]... [MMDDhhmm[[CC]YY][.ss]]", - NAME - ); - let matches = uu_app().override_usage(&syntax[..]).get_matches_from(args); + let matches = uu_app().get_matches_from(args); let format = if let Some(form) = matches.value_of(OPT_FORMAT) { if !form.starts_with('+') { @@ -261,6 +258,7 @@ pub fn uu_app<'a>() -> App<'a> { App::new(uucore::util_name()) .version(crate_version!()) .about(ABOUT) + .override_usage(format_usage(USAGE)) .setting(AppSettings::InferLongArgs) .arg( Arg::new(OPT_DATE) diff --git a/src/uu/df/src/df.rs b/src/uu/df/src/df.rs index 90f1b0c9a..48bd76041 100644 --- a/src/uu/df/src/df.rs +++ b/src/uu/df/src/df.rs @@ -7,10 +7,10 @@ // that was distributed with this source code. mod table; -use uucore::error::UResult; #[cfg(unix)] use uucore::fsext::statfs_fn; use uucore::fsext::{read_fs_list, FsUsage, MountInfo}; +use uucore::{error::UResult, format_usage}; use clap::{crate_version, App, AppSettings, Arg, ArgMatches}; @@ -28,6 +28,7 @@ use crate::table::{DisplayRow, Header, Row}; static ABOUT: &str = "Show information about the file system on which each FILE resides,\n\ or all file systems by default."; +const USAGE: &str = "{} [OPTION]... [FILE]..."; static OPT_ALL: &str = "all"; static OPT_BLOCKSIZE: &str = "blocksize"; @@ -94,10 +95,6 @@ struct Filesystem { usage: FsUsage, } -fn usage() -> String { - format!("{0} [OPTION]... [FILE]...", uucore::execution_phrase()) -} - impl FsSelector { /// Convert command-line arguments into a [`FsSelector`]. /// @@ -256,8 +253,7 @@ fn filter_mount_list(vmi: Vec, paths: &[String], opt: &Options) -> Ve #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let usage = usage(); - let matches = uu_app().override_usage(&usage[..]).get_matches_from(args); + let matches = uu_app().get_matches_from(args); let paths: Vec = matches .values_of(OPT_PATHS) @@ -293,6 +289,7 @@ pub fn uu_app<'a>() -> App<'a> { App::new(uucore::util_name()) .version(crate_version!()) .about(ABOUT) + .override_usage(format_usage(USAGE)) .setting(AppSettings::InferLongArgs) .arg( Arg::new(OPT_ALL) diff --git a/src/uu/dircolors/src/dircolors.rs b/src/uu/dircolors/src/dircolors.rs index 56f7e62de..dcc832ece 100644 --- a/src/uu/dircolors/src/dircolors.rs +++ b/src/uu/dircolors/src/dircolors.rs @@ -24,7 +24,7 @@ mod options { pub const FILE: &str = "FILE"; } -static SYNTAX: &str = "[OPTION]... [FILE]"; +static USAGE: &str = "{} [OPTION]... [FILE]"; static SUMMARY: &str = "Output commands to set the LS_COLORS environment variable."; static LONG_HELP: &str = " If FILE is specified, read it to determine which colors to use for which @@ -61,19 +61,13 @@ pub fn guess_syntax() -> OutputFmt { } } -fn usage() -> String { - format!("{0} {1}", uucore::execution_phrase(), SYNTAX) -} - #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { let args = args .collect_str(InvalidEncodingHandling::Ignore) .accept_any(); - let usage = usage(); - - let matches = uu_app().override_usage(&usage[..]).get_matches_from(&args); + let matches = uu_app().get_matches_from(&args); let files = matches .values_of(options::FILE) @@ -165,6 +159,7 @@ pub fn uu_app<'a>() -> App<'a> { .version(crate_version!()) .about(SUMMARY) .after_help(LONG_HELP) + .override_usage(format_usage(USAGE)) .setting(AppSettings::InferLongArgs) .arg( Arg::new(options::BOURNE_SHELL) @@ -257,7 +252,7 @@ enum ParseState { Pass, } use std::collections::HashMap; -use uucore::InvalidEncodingHandling; +use uucore::{format_usage, InvalidEncodingHandling}; fn parse(lines: T, fmt: &OutputFmt, fp: &str) -> Result where diff --git a/src/uu/dirname/src/dirname.rs b/src/uu/dirname/src/dirname.rs index e47177ea2..ad370aacf 100644 --- a/src/uu/dirname/src/dirname.rs +++ b/src/uu/dirname/src/dirname.rs @@ -9,19 +9,16 @@ use clap::{crate_version, App, AppSettings, Arg}; use std::path::Path; use uucore::display::print_verbatim; use uucore::error::{UResult, UUsageError}; -use uucore::InvalidEncodingHandling; +use uucore::{format_usage, InvalidEncodingHandling}; static ABOUT: &str = "strip last component from file name"; +const USAGE: &str = "{} [OPTION] NAME..."; mod options { pub const ZERO: &str = "zero"; pub const DIR: &str = "dir"; } -fn usage() -> String { - format!("{0} [OPTION] NAME...", uucore::execution_phrase()) -} - fn get_long_usage() -> String { String::from( "Output each NAME with its last non-slash component and trailing slashes @@ -35,13 +32,9 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { .collect_str(InvalidEncodingHandling::ConvertLossy) .accept_any(); - let usage = usage(); let after_help = get_long_usage(); - let matches = uu_app() - .override_usage(&usage[..]) - .after_help(&after_help[..]) - .get_matches_from(args); + let matches = uu_app().after_help(&after_help[..]).get_matches_from(args); let separator = if matches.is_present(options::ZERO) { "\0" @@ -87,6 +80,7 @@ pub fn uu_app<'a>() -> App<'a> { App::new(uucore::util_name()) .about(ABOUT) .version(crate_version!()) + .override_usage(format_usage(USAGE)) .setting(AppSettings::InferLongArgs) .arg( Arg::new(options::ZERO) diff --git a/src/uu/du/src/du.rs b/src/uu/du/src/du.rs index e75210ef5..34580f0ee 100644 --- a/src/uu/du/src/du.rs +++ b/src/uu/du/src/du.rs @@ -33,6 +33,7 @@ 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::format_usage; use uucore::parse_size::{parse_size, ParseSizeError}; use uucore::InvalidEncodingHandling; #[cfg(windows)] @@ -80,6 +81,9 @@ SIZE is an integer and optional unit (example: 10M is 10*1024*1024). Units are K, M, G, T, P, E, Z, Y (powers of 1024) or KB, MB,... (powers of 1000). "; +const USAGE: &str = "\ + {} [OPTION]... [FILE]... + {} [OPTION]... --files0-from=F"; // TODO: Support Z & Y (currently limited by size of u64) const UNITS: [(char, u32); 6] = [('E', 6), ('P', 5), ('T', 4), ('G', 3), ('M', 2), ('K', 1)]; @@ -391,14 +395,6 @@ fn convert_size_other(size: u64, _multiplier: u64, block_size: u64) -> String { format!("{}", ((size as f64) / (block_size as f64)).ceil()) } -fn usage() -> String { - format!( - "{0} [OPTION]... [FILE]... - {0} [OPTION]... --files0-from=F", - uucore::execution_phrase() - ) -} - #[derive(Debug)] enum DuError { InvalidMaxDepthArg(String), @@ -459,9 +455,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { .collect_str(InvalidEncodingHandling::Ignore) .accept_any(); - let usage = usage(); - - let matches = uu_app().override_usage(&usage[..]).get_matches_from(args); + let matches = uu_app().get_matches_from(args); let summarize = matches.is_present(options::SUMMARIZE); @@ -629,6 +623,7 @@ pub fn uu_app<'a>() -> App<'a> { .version(crate_version!()) .about(SUMMARY) .after_help(LONG_HELP) + .override_usage(format_usage(USAGE)) .setting(AppSettings::InferLongArgs) .arg( Arg::new(options::ALL) diff --git a/src/uu/echo/src/echo.rs b/src/uu/echo/src/echo.rs index 1cb63fe93..54a606c31 100644 --- a/src/uu/echo/src/echo.rs +++ b/src/uu/echo/src/echo.rs @@ -11,11 +11,11 @@ use std::io::{self, Write}; use std::iter::Peekable; use std::str::Chars; use uucore::error::{FromIo, UResult}; -use uucore::InvalidEncodingHandling; +use uucore::{format_usage, InvalidEncodingHandling}; const NAME: &str = "echo"; const SUMMARY: &str = "display a line of text"; -const USAGE: &str = "[OPTIONS]... [STRING]..."; +const USAGE: &str = "{} [OPTIONS]... [STRING]..."; const AFTER_HELP: &str = r#" Echo the STRING(s) to standard output. @@ -138,7 +138,7 @@ pub fn uu_app<'a>() -> App<'a> { .version(crate_version!()) .about(SUMMARY) .after_help(AFTER_HELP) - .override_usage(USAGE) + .override_usage(format_usage(USAGE)) .arg( Arg::new(options::NO_NEWLINE) .short('n') diff --git a/src/uu/env/src/env.rs b/src/uu/env/src/env.rs index c0e94a578..136100413 100644 --- a/src/uu/env/src/env.rs +++ b/src/uu/env/src/env.rs @@ -25,8 +25,9 @@ use std::iter::Iterator; use std::process::Command; use uucore::display::Quotable; use uucore::error::{UResult, USimpleError, UUsageError}; +use uucore::format_usage; -const USAGE: &str = "env [OPTION]... [-] [NAME=VALUE]... [COMMAND [ARG]...]"; +const USAGE: &str = "{} [OPTION]... [-] [NAME=VALUE]... [COMMAND [ARG]...]"; const AFTER_HELP: &str = "\ A mere - implies -i. If no COMMAND, print the resulting environment. "; @@ -125,7 +126,7 @@ pub fn uu_app<'a>() -> App<'a> { .version(crate_version!()) .author(crate_authors!()) .about(crate_description!()) - .override_usage(USAGE) + .override_usage(format_usage(USAGE)) .after_help(AFTER_HELP) .setting(AppSettings::AllowExternalSubcommands) .setting(AppSettings::InferLongArgs) diff --git a/src/uu/expand/src/expand.rs b/src/uu/expand/src/expand.rs index fd7876ad1..c2bf98093 100644 --- a/src/uu/expand/src/expand.rs +++ b/src/uu/expand/src/expand.rs @@ -19,9 +19,11 @@ use std::str::from_utf8; use unicode_width::UnicodeWidthChar; use uucore::display::Quotable; use uucore::error::{FromIo, UResult}; +use uucore::format_usage; static ABOUT: &str = "Convert tabs in each FILE to spaces, writing to standard output. With no FILE, or when FILE is -, read standard input."; +const USAGE: &str = "{} [OPTION]... [FILE]..."; pub mod options { pub static TABS: &str = "tabs"; @@ -34,10 +36,6 @@ static LONG_HELP: &str = ""; static DEFAULT_TABSTOP: usize = 8; -fn usage() -> String { - format!("{0} [OPTION]... [FILE]...", uucore::execution_phrase()) -} - /// The mode to use when replacing tabs beyond the last one specified in /// the `--tabs` argument. enum RemainingMode { @@ -173,8 +171,7 @@ impl Options { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let usage = usage(); - let matches = uu_app().override_usage(&usage[..]).get_matches_from(args); + let matches = uu_app().get_matches_from(args); expand(&Options::new(&matches)).map_err_context(|| "failed to write output".to_string()) } @@ -184,6 +181,7 @@ pub fn uu_app<'a>() -> App<'a> { .version(crate_version!()) .about(ABOUT) .after_help(LONG_HELP) + .override_usage(format_usage(USAGE)) .setting(AppSettings::InferLongArgs) .arg( Arg::new(options::INITIAL) diff --git a/src/uu/fmt/src/fmt.rs b/src/uu/fmt/src/fmt.rs index b3ca61dd5..e53617dac 100644 --- a/src/uu/fmt/src/fmt.rs +++ b/src/uu/fmt/src/fmt.rs @@ -17,6 +17,7 @@ use std::io::{stdin, stdout, Write}; use std::io::{BufReader, BufWriter, Read}; use uucore::display::Quotable; use uucore::error::{FromIo, UResult, USimpleError}; +use uucore::format_usage; use self::linebreak::break_lines; use self::parasplit::ParagraphStream; @@ -25,6 +26,7 @@ mod linebreak; mod parasplit; static ABOUT: &str = "Reformat paragraphs from input files (or stdin) to stdout."; +const USAGE: &str = "{} [OPTION]... [FILE]..."; static MAX_WIDTH: usize = 2500; static OPT_CROWN_MARGIN: &str = "crown-margin"; @@ -43,10 +45,6 @@ static OPT_TAB_WIDTH: &str = "tab-width"; static ARG_FILES: &str = "files"; -fn usage() -> String { - format!("{} [OPTION]... [FILE]...", uucore::execution_phrase()) -} - pub type FileOrStdReader = BufReader>; pub struct FmtOptions { crown: bool, @@ -69,9 +67,7 @@ pub struct FmtOptions { #[uucore::main] #[allow(clippy::cognitive_complexity)] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let usage = usage(); - - let matches = uu_app().override_usage(&usage[..]).get_matches_from(args); + let matches = uu_app().get_matches_from(args); let mut files: Vec = matches .values_of(ARG_FILES) @@ -226,6 +222,7 @@ pub fn uu_app<'a>() -> App<'a> { App::new(uucore::util_name()) .version(crate_version!()) .about(ABOUT) + .override_usage(format_usage(USAGE)) .setting(AppSettings::InferLongArgs) .arg( Arg::new(OPT_CROWN_MARGIN) diff --git a/src/uu/fold/src/fold.rs b/src/uu/fold/src/fold.rs index 667de122e..d217f0bea 100644 --- a/src/uu/fold/src/fold.rs +++ b/src/uu/fold/src/fold.rs @@ -13,12 +13,12 @@ use std::io::{stdin, BufRead, BufReader, Read}; use std::path::Path; use uucore::display::Quotable; use uucore::error::{FromIo, UResult, USimpleError}; -use uucore::InvalidEncodingHandling; +use uucore::{format_usage, InvalidEncodingHandling}; const TAB_WIDTH: usize = 8; static NAME: &str = "fold"; -static SYNTAX: &str = "[OPTION]... [FILE]..."; +static USAGE: &str = "{} [OPTION]... [FILE]..."; static SUMMARY: &str = "Writes each file (or standard input if no files are given) to standard output whilst breaking long lines"; @@ -67,7 +67,7 @@ pub fn uu_app<'a>() -> App<'a> { App::new(uucore::util_name()) .name(NAME) .version(crate_version!()) - .override_usage(SYNTAX) + .override_usage(format_usage(USAGE)) .about(SUMMARY) .setting(AppSettings::InferLongArgs) .arg( diff --git a/src/uu/groups/src/groups.rs b/src/uu/groups/src/groups.rs index 3188f13d5..d4015fb50 100644 --- a/src/uu/groups/src/groups.rs +++ b/src/uu/groups/src/groups.rs @@ -23,6 +23,7 @@ use uucore::{ display::Quotable, entries::{get_groups_gnu, gid2grp, Locate, Passwd}, error::{UError, UResult}, + format_usage, }; use clap::{crate_version, App, AppSettings, Arg}; @@ -34,9 +35,7 @@ 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 usage() -> String { - format!("{0} [OPTION]... [USERNAME]...", uucore::execution_phrase()) -} +const USAGE: &str = "{} [OPTION]... [USERNAME]..."; #[derive(Debug)] enum GroupsError { @@ -71,9 +70,7 @@ fn infallible_gid2grp(gid: &u32) -> String { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let usage = usage(); - - let matches = uu_app().override_usage(&usage[..]).get_matches_from(args); + let matches = uu_app().get_matches_from(args); let users: Vec = matches .values_of(options::USERS) @@ -109,6 +106,7 @@ pub fn uu_app<'a>() -> App<'a> { App::new(uucore::util_name()) .version(crate_version!()) .about(ABOUT) + .override_usage(format_usage(USAGE)) .setting(AppSettings::InferLongArgs) .arg( Arg::new(options::USERS) diff --git a/src/uu/head/src/head.rs b/src/uu/head/src/head.rs index 9e581a582..6691b3a6d 100644 --- a/src/uu/head/src/head.rs +++ b/src/uu/head/src/head.rs @@ -12,7 +12,7 @@ use std::io::{self, BufWriter, ErrorKind, Read, Seek, SeekFrom, Write}; use uucore::display::Quotable; use uucore::error::{FromIo, UError, UResult, USimpleError}; use uucore::lines::lines; -use uucore::show; +use uucore::{format_usage, show}; const BUF_SIZE: usize = 65536; @@ -26,7 +26,7 @@ const ABOUT: &str = "\ \n\ Mandatory arguments to long flags are mandatory for short flags too.\ "; -const USAGE: &str = "head [FLAG]... [FILE]..."; +const USAGE: &str = "{} [FLAG]... [FILE]..."; mod options { pub const BYTES_NAME: &str = "BYTES"; @@ -45,7 +45,7 @@ pub fn uu_app<'a>() -> App<'a> { App::new(uucore::util_name()) .version(crate_version!()) .about(ABOUT) - .override_usage(USAGE) + .override_usage(format_usage(USAGE)) .setting(AppSettings::InferLongArgs) .arg( Arg::new(options::BYTES_NAME) diff --git a/src/uu/hostid/src/hostid.rs b/src/uu/hostid/src/hostid.rs index 99b800f14..a82bb5e69 100644 --- a/src/uu/hostid/src/hostid.rs +++ b/src/uu/hostid/src/hostid.rs @@ -9,9 +9,9 @@ use clap::{crate_version, App, AppSettings}; use libc::c_long; -use uucore::error::UResult; +use uucore::{error::UResult, format_usage}; -static SYNTAX: &str = "[options]"; +const USAGE: &str = "{} [options]"; const SUMMARY: &str = "Print the numeric identifier (in hexadecimal) for the current host"; // currently rust libc interface doesn't include gethostid @@ -30,7 +30,7 @@ pub fn uu_app<'a>() -> App<'a> { App::new(uucore::util_name()) .version(crate_version!()) .about(SUMMARY) - .override_usage(SYNTAX) + .override_usage(format_usage(USAGE)) .setting(AppSettings::InferLongArgs) } diff --git a/src/uu/hostname/src/hostname.rs b/src/uu/hostname/src/hostname.rs index 902b9714e..b45b4b1d8 100644 --- a/src/uu/hostname/src/hostname.rs +++ b/src/uu/hostname/src/hostname.rs @@ -13,9 +13,13 @@ use std::str; use clap::{crate_version, App, AppSettings, Arg, ArgMatches}; -use uucore::error::{FromIo, UResult}; +use uucore::{ + error::{FromIo, UResult}, + format_usage, +}; static ABOUT: &str = "Display or set the system's host name."; +const USAGE: &str = "{} [OPTION]... [HOSTNAME]"; static OPT_DOMAIN: &str = "domain"; static OPT_IP_ADDRESS: &str = "ip-address"; @@ -54,14 +58,9 @@ mod wsa { } } -fn usage() -> String { - format!("{0} [OPTION]... [HOSTNAME]", uucore::execution_phrase()) -} - #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let usage = usage(); - let matches = uu_app().override_usage(&usage[..]).get_matches_from(args); + let matches = uu_app().get_matches_from(args); #[cfg(windows)] let _handle = wsa::start().map_err_context(|| "failed to start Winsock".to_owned())?; @@ -76,6 +75,7 @@ pub fn uu_app<'a>() -> App<'a> { App::new(uucore::util_name()) .version(crate_version!()) .about(ABOUT) + .override_usage(format_usage(USAGE)) .setting(AppSettings::InferLongArgs) .arg( Arg::new(OPT_DOMAIN) diff --git a/src/uu/id/src/id.rs b/src/uu/id/src/id.rs index b8132e688..b1da51173 100644 --- a/src/uu/id/src/id.rs +++ b/src/uu/id/src/id.rs @@ -45,6 +45,7 @@ use uucore::display::Quotable; use uucore::entries::{self, Group, Locate, Passwd}; use uucore::error::UResult; use uucore::error::{set_exit_code, USimpleError}; +use uucore::format_usage; pub use uucore::libc; use uucore::libc::{getlogin, uid_t}; use uucore::process::{getegid, geteuid, getgid, getuid}; @@ -57,6 +58,7 @@ macro_rules! cstr2cow { static ABOUT: &str = "Print user and group information for each specified USER, or (when USER omitted) for the current user."; +const USAGE: &str = "{} [OPTION]... [USER]..."; #[cfg(not(feature = "selinux"))] static CONTEXT_HELP_TEXT: &str = "print only the security context of the process (not enabled)"; @@ -77,10 +79,6 @@ mod options { pub const ARG_USERS: &str = "USER"; } -fn usage() -> String { - format!("{0} [OPTION]... [USER]...", uucore::execution_phrase()) -} - fn get_description() -> String { String::from( "The id utility displays the user and group names and numeric IDs, of the \ @@ -128,13 +126,9 @@ struct State { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let usage = usage(); let after_help = get_description(); - let matches = uu_app() - .override_usage(&usage[..]) - .after_help(&after_help[..]) - .get_matches_from(args); + let matches = uu_app().after_help(&after_help[..]).get_matches_from(args); let users: Vec = matches .values_of(options::ARG_USERS) @@ -351,6 +345,7 @@ pub fn uu_app<'a>() -> App<'a> { App::new(uucore::util_name()) .version(crate_version!()) .about(ABOUT) + .override_usage(format_usage(USAGE)) .setting(AppSettings::InferLongArgs) .arg( Arg::new(options::OPT_AUDIT) diff --git a/src/uu/install/src/install.rs b/src/uu/install/src/install.rs index 31e6d374c..5a106d8fc 100644 --- a/src/uu/install/src/install.rs +++ b/src/uu/install/src/install.rs @@ -19,6 +19,7 @@ use uucore::backup_control::{self, BackupMode}; use uucore::display::Quotable; use uucore::entries::{grp2gid, usr2uid}; use uucore::error::{FromIo, UError, UIoError, UResult}; +use uucore::format_usage; use uucore::mode::get_umask; use uucore::perms::{wrap_chown, Verbosity, VerbosityLevel}; @@ -144,6 +145,7 @@ impl Behavior { static ABOUT: &str = "Copy SOURCE to DEST or multiple SOURCE(s) to the existing DIRECTORY, while setting permission modes and owner/group"; +const USAGE: &str = "{} [OPTION]... [FILE]..."; static OPT_COMPARE: &str = "compare"; static OPT_DIRECTORY: &str = "directory"; @@ -163,19 +165,13 @@ static OPT_CONTEXT: &str = "context"; static ARG_FILES: &str = "files"; -fn usage() -> String { - format!("{0} [OPTION]... [FILE]...", uucore::execution_phrase()) -} - /// Main install utility function, called from main.rs. /// /// Returns a program return code. /// #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let usage = usage(); - - let matches = uu_app().override_usage(&usage[..]).get_matches_from(args); + let matches = uu_app().get_matches_from(args); let paths: Vec = matches .values_of(ARG_FILES) @@ -196,6 +192,7 @@ pub fn uu_app<'a>() -> App<'a> { App::new(uucore::util_name()) .version(crate_version!()) .about(ABOUT) + .override_usage(format_usage(USAGE)) .setting(AppSettings::InferLongArgs) .arg( backup_control::arguments::backup() diff --git a/src/uu/kill/src/kill.rs b/src/uu/kill/src/kill.rs index 413a183a8..2d0490921 100644 --- a/src/uu/kill/src/kill.rs +++ b/src/uu/kill/src/kill.rs @@ -16,9 +16,10 @@ use std::io::Error; use uucore::display::Quotable; use uucore::error::{UResult, USimpleError}; use uucore::signals::{signal_by_name_or_value, ALL_SIGNALS}; -use uucore::InvalidEncodingHandling; +use uucore::{format_usage, InvalidEncodingHandling}; static ABOUT: &str = "Send signal to processes or list information about signals."; +const USAGE: &str = "{} [OPTIONS]... PID..."; pub mod options { pub static PIDS_OR_SIGNALS: &str = "pids_of_signals"; @@ -42,8 +43,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { .accept_any(); let obs_signal = handle_obsolete(&mut args); - let usage = format!("{} [OPTIONS]... PID...", uucore::execution_phrase()); - let matches = uu_app().override_usage(&usage[..]).get_matches_from(args); + let matches = uu_app().get_matches_from(args); let mode = if matches.is_present(options::TABLE) || matches.is_present(options::TABLE_OLD) { Mode::Table @@ -82,6 +82,7 @@ pub fn uu_app<'a>() -> App<'a> { App::new(uucore::util_name()) .version(crate_version!()) .about(ABOUT) + .override_usage(format_usage(USAGE)) .setting(AppSettings::InferLongArgs) .arg( Arg::new(options::LIST) diff --git a/src/uu/link/src/link.rs b/src/uu/link/src/link.rs index d20bf3b03..9d515ee8a 100644 --- a/src/uu/link/src/link.rs +++ b/src/uu/link/src/link.rs @@ -9,21 +9,18 @@ use std::fs::hard_link; use std::path::Path; use uucore::display::Quotable; use uucore::error::{FromIo, UResult}; +use uucore::format_usage; static ABOUT: &str = "Call the link function to create a link named FILE2 to an existing FILE1."; +const USAGE: &str = "{} FILE1 FILE2"; pub mod options { pub static FILES: &str = "FILES"; } -fn usage() -> String { - format!("{0} FILE1 FILE2", uucore::execution_phrase()) -} - #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let usage = usage(); - let matches = uu_app().override_usage(&usage[..]).get_matches_from(args); + let matches = uu_app().get_matches_from(args); let files: Vec<_> = matches .values_of_os(options::FILES) @@ -40,6 +37,7 @@ pub fn uu_app<'a>() -> App<'a> { App::new(uucore::util_name()) .version(crate_version!()) .about(ABOUT) + .override_usage(format_usage(USAGE)) .setting(AppSettings::InferLongArgs) .arg( Arg::new(options::FILES) diff --git a/src/uu/ln/src/ln.rs b/src/uu/ln/src/ln.rs index 40648ab44..ec4868876 100644 --- a/src/uu/ln/src/ln.rs +++ b/src/uu/ln/src/ln.rs @@ -13,6 +13,7 @@ extern crate uucore; use clap::{crate_version, App, AppSettings, Arg}; use uucore::display::Quotable; use uucore::error::{UError, UResult}; +use uucore::format_usage; use std::borrow::Cow; use std::error::Error; @@ -90,16 +91,6 @@ impl UError for LnError { } } -fn usage() -> String { - format!( - "{0} [OPTION]... [-T] TARGET LINK_NAME (1st form) - {0} [OPTION]... TARGET (2nd form) - {0} [OPTION]... TARGET... DIRECTORY (3rd form) - {0} [OPTION]... -t DIRECTORY TARGET... (4th form)", - uucore::execution_phrase() - ) -} - fn long_usage() -> String { String::from( " In the 1st form, create a link to TARGET with the name LINK_NAME. @@ -115,6 +106,11 @@ fn long_usage() -> String { } static ABOUT: &str = "change file owner and group"; +const USAGE: &str = "\ + {} [OPTION]... [-T] TARGET LINK_NAME + {} [OPTION]... TARGET + {} [OPTION]... TARGET... DIRECTORY + {} [OPTION]... -t DIRECTORY TARGET..."; mod options { pub const FORCE: &str = "force"; @@ -131,11 +127,9 @@ static ARG_FILES: &str = "files"; #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let usage = usage(); let long_usage = long_usage(); let matches = uu_app() - .override_usage(&usage[..]) .after_help(&*format!( "{}\n{}", long_usage, @@ -183,6 +177,7 @@ pub fn uu_app<'a>() -> App<'a> { App::new(uucore::util_name()) .version(crate_version!()) .about(ABOUT) + .override_usage(format_usage(USAGE)) .setting(AppSettings::InferLongArgs) .arg(backup_control::arguments::backup()) .arg(backup_control::arguments::backup_no_args()) diff --git a/src/uu/logname/src/logname.rs b/src/uu/logname/src/logname.rs index c9c8ca447..64ad5e9bc 100644 --- a/src/uu/logname/src/logname.rs +++ b/src/uu/logname/src/logname.rs @@ -35,17 +35,13 @@ fn get_userlogin() -> Option { static SUMMARY: &str = "Print user's login name"; -fn usage() -> &'static str { - uucore::execution_phrase() -} - #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { let args = args .collect_str(InvalidEncodingHandling::Ignore) .accept_any(); - let _ = uu_app().override_usage(usage()).get_matches_from(args); + let _ = uu_app().get_matches_from(args); match get_userlogin() { Some(userlogin) => println!("{}", userlogin), @@ -58,6 +54,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app<'a>() -> App<'a> { App::new(uucore::util_name()) .version(crate_version!()) + .override_usage(uucore::execution_phrase()) .about(SUMMARY) .setting(AppSettings::InferLongArgs) } diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index b693252ff..4d0583c89 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -48,16 +48,14 @@ use uucore::{ use unicode_width::UnicodeWidthStr; #[cfg(unix)] use uucore::libc::{S_IXGRP, S_IXOTH, S_IXUSR}; -use uucore::{fs::display_permissions, version_cmp::version_cmp}; +use uucore::{format_usage, fs::display_permissions, version_cmp::version_cmp}; #[cfg(not(feature = "selinux"))] static CONTEXT_HELP_TEXT: &str = "print any security context of each file (not enabled)"; #[cfg(feature = "selinux")] static CONTEXT_HELP_TEXT: &str = "print any security context of each file"; -fn usage() -> String { - format!("{0} [OPTION]... [FILE]...", uucore::execution_phrase()) -} +const USAGE: &str = "{} [OPTION]... [FILE]..."; pub mod options { @@ -707,9 +705,7 @@ impl Config { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let usage = usage(); - - let app = uu_app().override_usage(&usage[..]); + let app = uu_app(); let matches = app.get_matches_from(args); @@ -726,6 +722,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app<'a>() -> App<'a> { App::new(uucore::util_name()) .version(crate_version!()) + .override_usage(format_usage(USAGE)) .about( "By default, ls will list the files and contents of any directories on \ the command line, expect that it will ignore files and directories \ diff --git a/src/uu/mkdir/src/mkdir.rs b/src/uu/mkdir/src/mkdir.rs index 377036174..b225bb300 100644 --- a/src/uu/mkdir/src/mkdir.rs +++ b/src/uu/mkdir/src/mkdir.rs @@ -17,11 +17,13 @@ use uucore::display::Quotable; use uucore::error::{FromIo, UResult, USimpleError}; #[cfg(not(windows))] use uucore::mode; -use uucore::InvalidEncodingHandling; +use uucore::{format_usage, InvalidEncodingHandling}; static DEFAULT_PERM: u32 = 0o755; static ABOUT: &str = "Create the given DIRECTORY(ies) if they do not exist"; +const USAGE: &str = "{} [OPTION]... [USER]"; + mod options { pub const MODE: &str = "mode"; pub const PARENTS: &str = "parents"; @@ -29,9 +31,6 @@ mod options { pub const DIRS: &str = "dirs"; } -fn usage() -> String { - format!("{0} [OPTION]... [USER]", uucore::execution_phrase()) -} fn get_long_usage() -> String { String::from("Each MODE is of the form '[ugoa]*([-+=]([rwxXst]*|[ugo]))+|[-+=]?[0-7]+'.") } @@ -87,17 +86,12 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { // Before we can parse 'args' with clap (and previously getopts), // 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 = usage(); let after_help = get_long_usage(); // Linux-specific options, not implemented // opts.optflag("Z", "context", "set SELinux security context" + // " of each created directory to CTX"), - let matches = uu_app() - .override_usage(&usage[..]) - .after_help(&after_help[..]) - .get_matches_from(args); + let matches = uu_app().after_help(&after_help[..]).get_matches_from(args); let dirs = matches.values_of_os(options::DIRS).unwrap_or_default(); let verbose = matches.is_present(options::VERBOSE); @@ -113,6 +107,7 @@ pub fn uu_app<'a>() -> App<'a> { App::new(uucore::util_name()) .version(crate_version!()) .about(ABOUT) + .override_usage(format_usage(USAGE)) .setting(AppSettings::InferLongArgs) .arg( Arg::new(options::MODE) diff --git a/src/uu/mkfifo/src/mkfifo.rs b/src/uu/mkfifo/src/mkfifo.rs index fcd26bc8f..2ac316f9d 100644 --- a/src/uu/mkfifo/src/mkfifo.rs +++ b/src/uu/mkfifo/src/mkfifo.rs @@ -12,10 +12,11 @@ use clap::{crate_version, App, AppSettings, Arg}; use libc::mkfifo; use std::ffi::CString; use uucore::error::{UResult, USimpleError}; +use uucore::format_usage; use uucore::{display::Quotable, InvalidEncodingHandling}; static NAME: &str = "mkfifo"; -static USAGE: &str = "mkfifo [OPTION]... NAME..."; +static USAGE: &str = "{} [OPTION]... NAME..."; static SUMMARY: &str = "Create a FIFO with the given name."; mod options { @@ -73,7 +74,7 @@ pub fn uu_app<'a>() -> App<'a> { App::new(uucore::util_name()) .name(NAME) .version(crate_version!()) - .override_usage(USAGE) + .override_usage(format_usage(USAGE)) .about(SUMMARY) .setting(AppSettings::InferLongArgs) .arg( diff --git a/src/uu/mknod/src/mknod.rs b/src/uu/mknod/src/mknod.rs index 0ea473a23..a27a48056 100644 --- a/src/uu/mknod/src/mknod.rs +++ b/src/uu/mknod/src/mknod.rs @@ -15,10 +15,10 @@ use libc::{S_IFBLK, S_IFCHR, S_IFIFO, S_IRGRP, S_IROTH, S_IRUSR, S_IWGRP, S_IWOT use uucore::display::Quotable; use uucore::error::{set_exit_code, UResult, USimpleError, UUsageError}; -use uucore::InvalidEncodingHandling; +use uucore::{format_usage, InvalidEncodingHandling}; static ABOUT: &str = "Create the special file NAME of the given TYPE."; -static USAGE: &str = "mknod [OPTION]... NAME TYPE [MAJOR MINOR]"; +static USAGE: &str = "{} [OPTION]... NAME TYPE [MAJOR MINOR]"; static LONG_HELP: &str = "Mandatory arguments to long options are mandatory for short options too. -m, --mode=MODE set file permission bits to MODE, not a=rw - umask --help display this help and exit @@ -146,7 +146,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app<'a>() -> App<'a> { App::new(uucore::util_name()) .version(crate_version!()) - .override_usage(USAGE) + .override_usage(format_usage(USAGE)) .after_help(LONG_HELP) .about(ABOUT) .setting(AppSettings::InferLongArgs) diff --git a/src/uu/mktemp/src/mktemp.rs b/src/uu/mktemp/src/mktemp.rs index 687915640..54d3ac651 100644 --- a/src/uu/mktemp/src/mktemp.rs +++ b/src/uu/mktemp/src/mktemp.rs @@ -11,6 +11,7 @@ use clap::{crate_version, App, AppSettings, Arg}; use uucore::display::{println_verbatim, Quotable}; use uucore::error::{FromIo, UError, UResult}; +use uucore::format_usage; use std::env; use std::error::Error; @@ -22,6 +23,7 @@ use rand::Rng; use tempfile::Builder; static ABOUT: &str = "create a temporary file or directory."; +const USAGE: &str = "{} [OPTION]... [TEMPLATE]"; static DEFAULT_TEMPLATE: &str = "tmp.XXXXXXXXXX"; @@ -34,10 +36,6 @@ static OPT_T: &str = "t"; static ARG_TEMPLATE: &str = "template"; -fn usage() -> String { - format!("{0} [OPTION]... [TEMPLATE]", uucore::execution_phrase()) -} - #[derive(Debug)] enum MkTempError { PersistError(PathBuf), @@ -76,9 +74,7 @@ impl Display for MkTempError { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let usage = usage(); - - let matches = uu_app().override_usage(&usage[..]).get_matches_from(args); + let matches = uu_app().get_matches_from(args); let template = matches.value_of(ARG_TEMPLATE).unwrap(); let tmpdir = matches.value_of(OPT_TMPDIR).unwrap_or_default(); @@ -139,6 +135,7 @@ pub fn uu_app<'a>() -> App<'a> { App::new(uucore::util_name()) .version(crate_version!()) .about(ABOUT) + .override_usage(format_usage(USAGE)) .setting(AppSettings::InferLongArgs) .arg( Arg::new(OPT_DIRECTORY) diff --git a/src/uu/mv/src/mv.rs b/src/uu/mv/src/mv.rs index ee77b63f4..c02f70dbd 100644 --- a/src/uu/mv/src/mv.rs +++ b/src/uu/mv/src/mv.rs @@ -26,6 +26,7 @@ use std::path::{Path, PathBuf}; use uucore::backup_control::{self, BackupMode}; use uucore::display::Quotable; use uucore::error::{FromIo, UError, UResult, USimpleError, UUsageError}; +use uucore::format_usage; use fs_extra::dir::{move_dir, CopyOptions as DirCopyOptions}; @@ -51,6 +52,10 @@ pub enum OverwriteMode { static ABOUT: &str = "Move SOURCE to DEST, or multiple SOURCE(s) to DIRECTORY."; static LONG_HELP: &str = ""; +const USAGE: &str = "\ + {} [OPTION]... [-T] SOURCE DEST + {} [OPTION]... SOURCE... DIRECTORY + {} [OPTION]... -t DIRECTORY SOURCE..."; static OPT_FORCE: &str = "force"; static OPT_INTERACTIVE: &str = "interactive"; @@ -63,26 +68,14 @@ static OPT_VERBOSE: &str = "verbose"; static ARG_FILES: &str = "files"; -fn usage() -> String { - format!( - "{0} [OPTION]... [-T] SOURCE DEST -{0} [OPTION]... SOURCE... DIRECTORY -{0} [OPTION]... -t DIRECTORY SOURCE...", - uucore::execution_phrase() - ) -} - #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let usage = usage(); - let matches = uu_app() .after_help(&*format!( "{}\n{}", LONG_HELP, backup_control::BACKUP_CONTROL_LONG_HELP )) - .override_usage(&usage[..]) .get_matches_from(args); let files: Vec = matches @@ -123,6 +116,7 @@ pub fn uu_app<'a>() -> App<'a> { App::new(uucore::util_name()) .version(crate_version!()) .about(ABOUT) + .override_usage(format_usage(USAGE)) .setting(AppSettings::InferLongArgs) .arg( backup_control::arguments::backup() diff --git a/src/uu/nice/src/nice.rs b/src/uu/nice/src/nice.rs index 65b610f42..94ccbe61e 100644 --- a/src/uu/nice/src/nice.rs +++ b/src/uu/nice/src/nice.rs @@ -16,31 +16,26 @@ use std::io::Error; use std::ptr; use clap::{crate_version, App, AppSettings, Arg}; -use uucore::error::{set_exit_code, UResult, USimpleError, UUsageError}; +use uucore::{ + error::{set_exit_code, UResult, USimpleError, UUsageError}, + format_usage, +}; pub mod options { pub static ADJUSTMENT: &str = "adjustment"; pub static COMMAND: &str = "COMMAND"; } -fn usage() -> String { - format!( - " - {0} [OPTIONS] [COMMAND [ARGS]] - -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).", - uucore::execution_phrase() - ) -} +const ABOUT: &str = "\ + 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)."; +const USAGE: &str = "{} [OPTIONS] [COMMAND [ARGS]]"; #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let usage = usage(); - - let matches = uu_app().override_usage(&usage[..]).get_matches_from(args); + let matches = uu_app().get_matches_from(args); let mut niceness = unsafe { nix::errno::Errno::clear(); @@ -109,6 +104,8 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app<'a>() -> App<'a> { App::new(uucore::util_name()) + .about(ABOUT) + .override_usage(format_usage(USAGE)) .setting(AppSettings::TrailingVarArg) .setting(AppSettings::InferLongArgs) .version(crate_version!()) diff --git a/src/uu/nl/src/nl.rs b/src/uu/nl/src/nl.rs index 827339720..c1661178f 100644 --- a/src/uu/nl/src/nl.rs +++ b/src/uu/nl/src/nl.rs @@ -14,13 +14,12 @@ use std::io::{stdin, BufRead, BufReader, Read}; use std::iter::repeat; use std::path::Path; use uucore::error::{FromIo, UResult, USimpleError}; -use uucore::InvalidEncodingHandling; +use uucore::{format_usage, InvalidEncodingHandling}; mod helper; static NAME: &str = "nl"; -static USAGE: &str = "nl [OPTION]... [FILE]..."; -// A regular expression matching everything. +static USAGE: &str = "{} [OPTION]... [FILE]..."; // Settings store options used by nl to produce its output. pub struct Settings { @@ -144,7 +143,7 @@ pub fn uu_app<'a>() -> App<'a> { App::new(uucore::util_name()) .name(NAME) .version(crate_version!()) - .override_usage(USAGE) + .override_usage(format_usage(USAGE)) .setting(AppSettings::InferLongArgs) .arg( Arg::new(options::FILE) diff --git a/src/uu/nohup/src/nohup.rs b/src/uu/nohup/src/nohup.rs index 0b5392ef2..c7c7350cc 100644 --- a/src/uu/nohup/src/nohup.rs +++ b/src/uu/nohup/src/nohup.rs @@ -22,7 +22,7 @@ use std::os::unix::prelude::*; use std::path::{Path, PathBuf}; use uucore::display::Quotable; use uucore::error::{set_exit_code, UError, UResult}; -use uucore::InvalidEncodingHandling; +use uucore::{format_usage, InvalidEncodingHandling}; static ABOUT: &str = "Run COMMAND ignoring hangup signals."; static LONG_HELP: &str = " @@ -31,6 +31,9 @@ If standard output is terminal, it'll be appended to nohup.out instead, or $HOME/nohup.out, if nohup.out open failed. If standard error is terminal, it'll be redirected to stdout. "; +const USAGE: &str = "\ + {} COMMAND [ARG]... + {} FLAG"; static NOHUP_OUT: &str = "nohup.out"; // exit codes that match the GNU implementation static EXIT_CANCELED: i32 = 125; @@ -83,12 +86,11 @@ impl Display for NohupError { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let usage = usage(); let args = args .collect_str(InvalidEncodingHandling::ConvertLossy) .accept_any(); - let matches = uu_app().override_usage(&usage[..]).get_matches_from(args); + let matches = uu_app().get_matches_from(args); replace_fds()?; @@ -119,6 +121,7 @@ pub fn uu_app<'a>() -> App<'a> { .version(crate_version!()) .about(ABOUT) .after_help(LONG_HELP) + .override_usage(format_usage(USAGE)) .arg( Arg::new(options::CMD) .hide(true) @@ -205,13 +208,6 @@ fn find_stdout() -> UResult { } } -fn usage() -> String { - format!( - "{0} COMMAND [ARG]...\n {0} FLAG", - uucore::execution_phrase() - ) -} - #[cfg(target_vendor = "apple")] extern "C" { fn _vprocmgr_detach_from_console(flags: u32) -> *const libc::c_int; diff --git a/src/uu/nproc/src/nproc.rs b/src/uu/nproc/src/nproc.rs index 18778c27d..de8f0c3ed 100644 --- a/src/uu/nproc/src/nproc.rs +++ b/src/uu/nproc/src/nproc.rs @@ -11,6 +11,7 @@ use clap::{crate_version, App, AppSettings, Arg}; use std::env; use uucore::display::Quotable; use uucore::error::{UResult, USimpleError}; +use uucore::format_usage; #[cfg(target_os = "linux")] pub const _SC_NPROCESSORS_CONF: libc::c_int = 83; @@ -25,15 +26,11 @@ static OPT_ALL: &str = "all"; static OPT_IGNORE: &str = "ignore"; static ABOUT: &str = "Print the number of cores available to the current process."; - -fn usage() -> String { - format!("{0} [OPTIONS]...", uucore::execution_phrase()) -} +const USAGE: &str = "{} [OPTIONS]..."; #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let usage = usage(); - let matches = uu_app().override_usage(&usage[..]).get_matches_from(args); + let matches = uu_app().get_matches_from(args); let mut ignore = match matches.value_of(OPT_IGNORE) { Some(numstr) => match numstr.parse() { @@ -75,6 +72,7 @@ pub fn uu_app<'a>() -> App<'a> { App::new(uucore::util_name()) .version(crate_version!()) .about(ABOUT) + .override_usage(format_usage(USAGE)) .setting(AppSettings::InferLongArgs) .arg( Arg::new(OPT_ALL) diff --git a/src/uu/numfmt/src/numfmt.rs b/src/uu/numfmt/src/numfmt.rs index c2b956fa3..516d7a4df 100644 --- a/src/uu/numfmt/src/numfmt.rs +++ b/src/uu/numfmt/src/numfmt.rs @@ -15,6 +15,7 @@ use clap::{crate_version, App, AppSettings, Arg, ArgMatches}; use std::io::{BufRead, Write}; use uucore::display::Quotable; use uucore::error::UResult; +use uucore::format_usage; use uucore::ranges::Range; pub mod errors; @@ -50,10 +51,7 @@ FIELDS supports cut(1) style field ranges: - all fields Multiple fields/ranges can be separated with commas "; - -fn usage() -> String { - format!("{0} [OPTION]... [NUMBER]...", uucore::execution_phrase()) -} +const USAGE: &str = "{} [OPTION]... [NUMBER]..."; fn handle_args<'a>(args: impl Iterator, options: &NumfmtOptions) -> UResult<()> { for l in args { @@ -166,9 +164,7 @@ fn parse_options(args: &ArgMatches) -> Result { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let usage = usage(); - - let matches = uu_app().override_usage(&usage[..]).get_matches_from(args); + let matches = uu_app().get_matches_from(args); let options = parse_options(&matches).map_err(NumfmtError::IllegalArgument)?; @@ -195,6 +191,7 @@ pub fn uu_app<'a>() -> App<'a> { .version(crate_version!()) .about(ABOUT) .after_help(LONG_HELP) + .override_usage(format_usage(USAGE)) .setting(AppSettings::AllowNegativeNumbers) .setting(AppSettings::InferLongArgs) .arg( diff --git a/src/uu/od/src/od.rs b/src/uu/od/src/od.rs index 3db1a00f3..3786e8e68 100644 --- a/src/uu/od/src/od.rs +++ b/src/uu/od/src/od.rs @@ -45,15 +45,17 @@ use crate::prn_char::format_ascii_dump; use clap::{crate_version, App, AppSettings, Arg, ArgMatches}; use uucore::display::Quotable; use uucore::error::{UResult, USimpleError}; +use uucore::format_usage; use uucore::parse_size::ParseSizeError; use uucore::InvalidEncodingHandling; const PEEK_BUFFER_SIZE: usize = 4; // utf-8 can be 4 bytes static ABOUT: &str = "dump files in octal and other formats"; -static USAGE: &str = r#"od [OPTION]... [--] [FILENAME]... - od [-abcdDefFhHiIlLoOsxX] [FILENAME] [[+][0x]OFFSET[.][b]] - od --traditional [OPTION]... [FILENAME] [[+][0x]OFFSET[.][b] [[+][0x]LABEL[.][b]]]"#; +static USAGE: &str = "\ + {} [OPTION]... [--] [FILENAME]... + {} [-abcdDefFhHiIlLoOsxX] [FILENAME] [[+][0x]OFFSET[.][b]] + {} --traditional [OPTION]... [FILENAME] [[+][0x]OFFSET[.][b] [[+][0x]LABEL[.][b]]]"; static LONG_HELP: &str = r#" Displays data in various human-readable formats. If multiple formats are @@ -289,7 +291,7 @@ pub fn uu_app<'a>() -> App<'a> { App::new(uucore::util_name()) .version(crate_version!()) .about(ABOUT) - .override_usage(USAGE) + .override_usage(format_usage(USAGE)) .after_help(LONG_HELP) .setting( AppSettings::TrailingVarArg | diff --git a/src/uu/pathchk/src/pathchk.rs b/src/uu/pathchk/src/pathchk.rs index 5fb75366d..2d72d4a5a 100644 --- a/src/uu/pathchk/src/pathchk.rs +++ b/src/uu/pathchk/src/pathchk.rs @@ -13,7 +13,7 @@ use std::fs; use std::io::{ErrorKind, Write}; use uucore::display::Quotable; use uucore::error::{set_exit_code, UResult, UUsageError}; -use uucore::InvalidEncodingHandling; +use uucore::{format_usage, InvalidEncodingHandling}; // operating mode enum Mode { @@ -24,6 +24,7 @@ enum Mode { } static ABOUT: &str = "Check whether file names are valid or portable"; +const USAGE: &str = "{} [OPTION]... NAME..."; mod options { pub const POSIX: &str = "posix"; @@ -36,18 +37,13 @@ mod options { const POSIX_PATH_MAX: usize = 256; const POSIX_NAME_MAX: usize = 14; -fn usage() -> String { - format!("{0} [OPTION]... NAME...", uucore::execution_phrase()) -} - #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let usage = usage(); let args = args .collect_str(InvalidEncodingHandling::ConvertLossy) .accept_any(); - let matches = uu_app().override_usage(&usage[..]).get_matches_from(args); + let matches = uu_app().get_matches_from(args); // set working mode let is_posix = matches.values_of(options::POSIX).is_some(); @@ -92,6 +88,7 @@ pub fn uu_app<'a>() -> App<'a> { App::new(uucore::util_name()) .version(crate_version!()) .about(ABOUT) + .override_usage(format_usage(USAGE)) .setting(AppSettings::InferLongArgs) .arg( Arg::new(options::POSIX) diff --git a/src/uu/pinky/src/pinky.rs b/src/uu/pinky/src/pinky.rs index 0b2cb3a87..602b010e1 100644 --- a/src/uu/pinky/src/pinky.rs +++ b/src/uu/pinky/src/pinky.rs @@ -20,11 +20,12 @@ use std::os::unix::fs::MetadataExt; use clap::{crate_version, App, AppSettings, Arg}; use std::path::PathBuf; -use uucore::InvalidEncodingHandling; +use uucore::{format_usage, InvalidEncodingHandling}; const BUFSIZE: usize = 1024; static ABOUT: &str = "pinky - lightweight finger"; +const USAGE: &str = "{} [OPTION]... [USER]..."; mod options { pub const LONG_FORMAT: &str = "long_format"; @@ -39,10 +40,6 @@ mod options { pub const USER: &str = "user"; } -fn usage() -> String { - format!("{0} [OPTION]... [USER]...", uucore::execution_phrase()) -} - fn get_long_usage() -> String { format!( "A lightweight 'finger' program; print user information.\n\ @@ -57,13 +54,9 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { .collect_str(InvalidEncodingHandling::Ignore) .accept_any(); - let usage = usage(); let after_help = get_long_usage(); - let matches = uu_app() - .override_usage(&usage[..]) - .after_help(&after_help[..]) - .get_matches_from(args); + let matches = uu_app().after_help(&after_help[..]).get_matches_from(args); let users: Vec = matches .values_of(options::USER) @@ -136,6 +129,7 @@ pub fn uu_app<'a>() -> App<'a> { App::new(uucore::util_name()) .version(crate_version!()) .about(ABOUT) + .override_usage(format_usage(USAGE)) .setting(AppSettings::InferLongArgs) .arg( Arg::new(options::LONG_FORMAT) diff --git a/src/uu/printenv/src/printenv.rs b/src/uu/printenv/src/printenv.rs index e01f66020..cb24299cc 100644 --- a/src/uu/printenv/src/printenv.rs +++ b/src/uu/printenv/src/printenv.rs @@ -9,23 +9,18 @@ use clap::{crate_version, App, AppSettings, Arg}; use std::env; -use uucore::error::UResult; +use uucore::{error::UResult, format_usage}; static ABOUT: &str = "Display the values of the specified environment VARIABLE(s), or (with no VARIABLE) display name and value pairs for them all."; +const USAGE: &str = "{} [VARIABLE]... [OPTION]..."; static OPT_NULL: &str = "null"; static ARG_VARIABLES: &str = "variables"; -fn usage() -> String { - format!("{0} [VARIABLE]... [OPTION]...", uucore::execution_phrase()) -} - #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let usage = usage(); - - let matches = uu_app().override_usage(&usage[..]).get_matches_from(args); + let matches = uu_app().get_matches_from(args); let variables: Vec = matches .values_of(ARG_VARIABLES) @@ -65,6 +60,7 @@ pub fn uu_app<'a>() -> App<'a> { App::new(uucore::util_name()) .version(crate_version!()) .about(ABOUT) + .override_usage(format_usage(USAGE)) .setting(AppSettings::InferLongArgs) .arg( Arg::new(OPT_NULL) diff --git a/src/uu/printf/src/printf.rs b/src/uu/printf/src/printf.rs index 5732d4ced..2dbc7f996 100644 --- a/src/uu/printf/src/printf.rs +++ b/src/uu/printf/src/printf.rs @@ -4,12 +4,12 @@ use clap::{crate_version, App, AppSettings, Arg}; use uucore::error::{UResult, UUsageError}; -use uucore::memo; use uucore::InvalidEncodingHandling; +use uucore::{format_usage, memo}; const VERSION: &str = "version"; const HELP: &str = "help"; -const USAGE: &str = "printf FORMATSTRING [ARGUMENT]..."; +const USAGE: &str = "{} FORMATSTRING [ARGUMENT]..."; const ABOUT: &str = "Print output based off of the format string and proceeding arguments."; const AFTER_HELP: &str = " basic anonymous string templating: @@ -294,7 +294,7 @@ pub fn uu_app<'a>() -> App<'a> { .version(crate_version!()) .about(ABOUT) .after_help(AFTER_HELP) - .override_usage(USAGE) + .override_usage(format_usage(USAGE)) .arg(Arg::new(HELP).long(HELP).help("Print help information")) .arg( Arg::new(VERSION) diff --git a/src/uu/ptx/src/ptx.rs b/src/uu/ptx/src/ptx.rs index fff70373f..20a2b13f2 100644 --- a/src/uu/ptx/src/ptx.rs +++ b/src/uu/ptx/src/ptx.rs @@ -19,15 +19,17 @@ use std::io::{stdin, stdout, BufRead, BufReader, BufWriter, Read, Write}; use std::num::ParseIntError; use uucore::display::Quotable; use uucore::error::{FromIo, UError, UResult}; -use uucore::InvalidEncodingHandling; +use uucore::{format_usage, InvalidEncodingHandling}; static NAME: &str = "ptx"; -static BRIEF: &str = "Usage: ptx [OPTION]... [INPUT]... (without -G) or: \ - ptx -G [OPTION]... [INPUT [OUTPUT]] \n Output a permuted index, \ - including context, of the words in the input files. \n\n Mandatory \ - arguments to long options are mandatory for short options too.\n - With no FILE, or when FILE is -, read standard input. \ - Default is '-F /'."; +const USAGE: &str = "\ + {} [OPTION]... [INPUT]... + {} -G [OPTION]... [INPUT [OUTPUT]]"; + +const ABOUT: &str = "\ + Output a permuted index, including context, of the words in the input files. \n\n\ + Mandatory arguments to long options are mandatory for short options too.\n\ + With no FILE, or when FILE is -, read standard input. Default is '-F /'."; #[derive(Debug)] enum OutFormat { @@ -703,8 +705,9 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app<'a>() -> App<'a> { App::new(uucore::util_name()) .name(NAME) + .about(ABOUT) .version(crate_version!()) - .override_usage(BRIEF) + .override_usage(format_usage(USAGE)) .setting(AppSettings::InferLongArgs) .arg( Arg::new(options::FILE) diff --git a/src/uu/pwd/src/pwd.rs b/src/uu/pwd/src/pwd.rs index 0fc9cbdd7..28487545c 100644 --- a/src/uu/pwd/src/pwd.rs +++ b/src/uu/pwd/src/pwd.rs @@ -9,11 +9,13 @@ use clap::{crate_version, App, AppSettings, Arg}; use std::env; use std::io; use std::path::PathBuf; +use uucore::format_usage; use uucore::display::println_verbatim; use uucore::error::{FromIo, UResult}; static ABOUT: &str = "Display the full filename of the current working directory."; +const USAGE: &str = "{} [OPTION]... FILE..."; static OPT_LOGICAL: &str = "logical"; static OPT_PHYSICAL: &str = "physical"; @@ -120,15 +122,9 @@ fn logical_path() -> io::Result { } } -fn usage() -> String { - format!("{0} [OPTION]... FILE...", uucore::execution_phrase()) -} - #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let usage = usage(); - - let matches = uu_app().override_usage(&usage[..]).get_matches_from(args); + let matches = uu_app().get_matches_from(args); let cwd = if matches.is_present(OPT_LOGICAL) { logical_path() } else { @@ -156,6 +152,7 @@ pub fn uu_app<'a>() -> App<'a> { App::new(uucore::util_name()) .version(crate_version!()) .about(ABOUT) + .override_usage(format_usage(USAGE)) .setting(AppSettings::InferLongArgs) .arg( Arg::new(OPT_LOGICAL) diff --git a/src/uu/readlink/src/readlink.rs b/src/uu/readlink/src/readlink.rs index 826a97cec..adbae7a27 100644 --- a/src/uu/readlink/src/readlink.rs +++ b/src/uu/readlink/src/readlink.rs @@ -16,9 +16,11 @@ use std::io::{stdout, Write}; use std::path::{Path, PathBuf}; use uucore::display::Quotable; use uucore::error::{FromIo, UResult, USimpleError, UUsageError}; +use uucore::format_usage; use uucore::fs::{canonicalize, MissingHandling, ResolveMode}; const ABOUT: &str = "Print value of a symbolic link or canonical file name."; +const USAGE: &str = "{} [OPTION]... [FILE]..."; const OPT_CANONICALIZE: &str = "canonicalize"; const OPT_CANONICALIZE_MISSING: &str = "canonicalize-missing"; const OPT_CANONICALIZE_EXISTING: &str = "canonicalize-existing"; @@ -30,14 +32,9 @@ const OPT_ZERO: &str = "zero"; const ARG_FILES: &str = "files"; -fn usage() -> String { - format!("{0} [OPTION]... [FILE]...", uucore::execution_phrase()) -} - #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let usage = usage(); - let matches = uu_app().override_usage(&usage[..]).get_matches_from(args); + let matches = uu_app().get_matches_from(args); let mut no_newline = matches.is_present(OPT_NO_NEWLINE); let use_zero = matches.is_present(OPT_ZERO); @@ -102,6 +99,7 @@ pub fn uu_app<'a>() -> App<'a> { App::new(uucore::util_name()) .version(crate_version!()) .about(ABOUT) + .override_help(format_usage(USAGE)) .setting(AppSettings::InferLongArgs) .arg( Arg::new(OPT_CANONICALIZE) diff --git a/src/uu/realpath/src/realpath.rs b/src/uu/realpath/src/realpath.rs index 21fb974e3..4a42930f3 100644 --- a/src/uu/realpath/src/realpath.rs +++ b/src/uu/realpath/src/realpath.rs @@ -18,10 +18,12 @@ use std::{ use uucore::{ display::{print_verbatim, Quotable}, error::{FromIo, UResult}, + format_usage, fs::{canonicalize, MissingHandling, ResolveMode}, }; static ABOUT: &str = "print the resolved path"; +const USAGE: &str = "{} [OPTION]... FILE..."; static OPT_QUIET: &str = "quiet"; static OPT_STRIP: &str = "strip"; @@ -33,15 +35,9 @@ const OPT_CANONICALIZE_EXISTING: &str = "canonicalize-existing"; static ARG_FILES: &str = "files"; -fn usage() -> String { - format!("{0} [OPTION]... FILE...", uucore::execution_phrase()) -} - #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let usage = usage(); - - let matches = uu_app().override_usage(&usage[..]).get_matches_from(args); + let matches = uu_app().get_matches_from(args); /* the list of files */ @@ -78,6 +74,7 @@ pub fn uu_app<'a>() -> App<'a> { App::new(uucore::util_name()) .version(crate_version!()) .about(ABOUT) + .override_usage(format_usage(USAGE)) .setting(AppSettings::InferLongArgs) .arg( Arg::new(OPT_QUIET) diff --git a/src/uu/relpath/src/relpath.rs b/src/uu/relpath/src/relpath.rs index 20ecfd751..a7da7a956 100644 --- a/src/uu/relpath/src/relpath.rs +++ b/src/uu/relpath/src/relpath.rs @@ -13,10 +13,11 @@ use std::path::{Path, PathBuf}; use uucore::display::println_verbatim; use uucore::error::{FromIo, UResult}; use uucore::fs::{canonicalize, MissingHandling, ResolveMode}; -use uucore::InvalidEncodingHandling; +use uucore::{format_usage, InvalidEncodingHandling}; static ABOUT: &str = "Convert TO destination to the relative path from the FROM dir. If FROM path is omitted, current working dir will be used."; +const USAGE: &str = "{} [-d DIR] TO [FROM]"; mod options { pub const DIR: &str = "DIR"; @@ -24,18 +25,13 @@ mod options { pub const FROM: &str = "FROM"; } -fn usage() -> String { - format!("{} [-d DIR] TO [FROM]", uucore::execution_phrase()) -} - #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { let args = args .collect_str(InvalidEncodingHandling::ConvertLossy) .accept_any(); - let usage = usage(); - let matches = uu_app().override_usage(&usage[..]).get_matches_from(args); + let matches = uu_app().get_matches_from(args); let to = Path::new(matches.value_of(options::TO).unwrap()).to_path_buf(); // required let from = match matches.value_of(options::FROM) { @@ -86,6 +82,7 @@ pub fn uu_app<'a>() -> App<'a> { App::new(uucore::util_name()) .version(crate_version!()) .about(ABOUT) + .override_usage(format_usage(USAGE)) .setting(AppSettings::InferLongArgs) .arg(Arg::new(options::DIR).short('d').takes_value(true).help( "If any of FROM and TO is not subpath of DIR, output absolute path instead of relative", diff --git a/src/uu/rm/src/rm.rs b/src/uu/rm/src/rm.rs index 71351f8e0..9179c6d9f 100644 --- a/src/uu/rm/src/rm.rs +++ b/src/uu/rm/src/rm.rs @@ -19,6 +19,7 @@ use std::ops::BitOr; use std::path::{Path, PathBuf}; use uucore::display::Quotable; use uucore::error::{UResult, USimpleError, UUsageError}; +use uucore::format_usage; use walkdir::{DirEntry, WalkDir}; #[derive(Eq, PartialEq, Clone, Copy)] @@ -40,6 +41,7 @@ struct Options { } static ABOUT: &str = "Remove (unlink) the FILE(s)"; +const USAGE: &str = "{} [OPTION]... FILE..."; static OPT_DIR: &str = "dir"; static OPT_INTERACTIVE: &str = "interactive"; static OPT_FORCE: &str = "force"; @@ -55,10 +57,6 @@ static PRESUME_INPUT_TTY: &str = "-presume-input-tty"; static ARG_FILES: &str = "files"; -fn usage() -> String { - format!("{0} [OPTION]... FILE...", uucore::execution_phrase()) -} - fn get_long_usage() -> String { String::from( "By default, rm does not remove directories. Use the --recursive (-r or -R) @@ -78,13 +76,9 @@ fn get_long_usage() -> String { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let usage = usage(); let long_usage = get_long_usage(); - let matches = uu_app() - .override_usage(&usage[..]) - .after_help(&long_usage[..]) - .get_matches_from(args); + let matches = uu_app().after_help(&long_usage[..]).get_matches_from(args); let files: Vec = matches .values_of(ARG_FILES) @@ -149,6 +143,7 @@ pub fn uu_app<'a>() -> App<'a> { App::new(uucore::util_name()) .version(crate_version!()) .about(ABOUT) + .override_usage(format_usage(USAGE)) .setting(AppSettings::InferLongArgs) .arg( Arg::new(OPT_FORCE) diff --git a/src/uu/rmdir/src/rmdir.rs b/src/uu/rmdir/src/rmdir.rs index d210ce105..bdb6ab60d 100644 --- a/src/uu/rmdir/src/rmdir.rs +++ b/src/uu/rmdir/src/rmdir.rs @@ -16,24 +16,19 @@ use std::io; use std::path::Path; use uucore::display::Quotable; use uucore::error::{set_exit_code, strip_errno, UResult}; -use uucore::util_name; +use uucore::{format_usage, util_name}; static ABOUT: &str = "Remove the DIRECTORY(ies), if they are empty."; +const USAGE: &str = "{} [OPTION]... DIRECTORY..."; static OPT_IGNORE_FAIL_NON_EMPTY: &str = "ignore-fail-on-non-empty"; static OPT_PARENTS: &str = "parents"; static OPT_VERBOSE: &str = "verbose"; static ARG_DIRS: &str = "dirs"; -fn usage() -> String { - format!("{0} [OPTION]... DIRECTORY...", uucore::execution_phrase()) -} - #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let usage = usage(); - - let matches = uu_app().override_usage(&usage[..]).get_matches_from(args); + let matches = uu_app().get_matches_from(args); let opts = Opts { ignore: matches.is_present(OPT_IGNORE_FAIL_NON_EMPTY), @@ -179,6 +174,7 @@ pub fn uu_app<'a>() -> App<'a> { App::new(uucore::util_name()) .version(crate_version!()) .about(ABOUT) + .override_usage(format_usage(USAGE)) .setting(AppSettings::InferLongArgs) .arg( Arg::new(OPT_IGNORE_FAIL_NON_EMPTY) diff --git a/src/uu/runcon/src/runcon.rs b/src/uu/runcon/src/runcon.rs index 9b8cf412a..55186d218 100644 --- a/src/uu/runcon/src/runcon.rs +++ b/src/uu/runcon/src/runcon.rs @@ -4,6 +4,7 @@ use uucore::error::{UResult, UUsageError}; use clap::{App, AppSettings, Arg}; use selinux::{OpaqueSecurityContext, SecurityClass, SecurityContext}; +use uucore::format_usage; use std::borrow::Cow; use std::ffi::{CStr, CString, OsStr, OsString}; @@ -18,6 +19,9 @@ use errors::{Error, Result, RunconError}; const VERSION: &str = env!("CARGO_PKG_VERSION"); const ABOUT: &str = "Run command with specified security context."; +const USAGE: &str = "\ + {} [CONTEXT COMMAND [ARG...]] + {} [-c] [-u USER] [-r ROLE] [-t TYPE] [-l RANGE] COMMAND [ARG...]"; 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\ @@ -36,19 +40,9 @@ pub mod options { pub const RANGE: &str = "range"; } -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() - ) -} - #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let usage = get_usage(); - - let config = uu_app().override_usage(usage.as_ref()); + let config = uu_app(); let options = match parse_command_line(config, args) { Ok(r) => r, @@ -114,6 +108,7 @@ pub fn uu_app<'a>() -> App<'a> { .version(VERSION) .about(ABOUT) .after_help(DESCRIPTION) + .override_usage(format_usage(USAGE)) .setting(AppSettings::InferLongArgs) .arg( Arg::new(options::COMPUTE) diff --git a/src/uu/seq/src/seq.rs b/src/uu/seq/src/seq.rs index 28524c55c..07c7f762f 100644 --- a/src/uu/seq/src/seq.rs +++ b/src/uu/seq/src/seq.rs @@ -12,6 +12,7 @@ use num_traits::Zero; use uucore::error::FromIo; use uucore::error::UResult; +use uucore::format_usage; use uucore::memo::Memo; use uucore::show; @@ -27,6 +28,10 @@ use crate::number::Number; use crate::number::PreciseNumber; static ABOUT: &str = "Display numbers from FIRST to LAST, in steps of INCREMENT."; +const USAGE: &str = "\ + {} [OPTION]... LAST + {} [OPTION]... FIRST LAST + {} [OPTION]... FIRST INCREMENT LAST"; static OPT_SEPARATOR: &str = "separator"; static OPT_TERMINATOR: &str = "terminator"; static OPT_WIDTHS: &str = "widths"; @@ -34,14 +39,6 @@ static OPT_FORMAT: &str = "format"; static ARG_NUMBERS: &str = "numbers"; -fn usage() -> String { - format!( - "{0} [OPTION]... LAST - {0} [OPTION]... FIRST LAST - {0} [OPTION]... FIRST INCREMENT LAST", - uucore::execution_phrase() - ) -} #[derive(Clone)] struct SeqOptions<'a> { separator: String, @@ -62,8 +59,7 @@ type RangeFloat = (ExtendedBigDecimal, ExtendedBigDecimal, ExtendedBigDecimal); #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let usage = usage(); - let matches = uu_app().override_usage(&usage[..]).get_matches_from(args); + let matches = uu_app().get_matches_from(args); let numbers = matches.values_of(ARG_NUMBERS).unwrap().collect::>(); @@ -152,6 +148,7 @@ pub fn uu_app<'a>() -> App<'a> { .setting(AppSettings::InferLongArgs) .version(crate_version!()) .about(ABOUT) + .override_usage(format_usage(USAGE)) .arg( Arg::new(OPT_SEPARATOR) .short('s') diff --git a/src/uu/shred/src/shred.rs b/src/uu/shred/src/shred.rs index 2ad91afd1..5c545e8e7 100644 --- a/src/uu/shred/src/shred.rs +++ b/src/uu/shred/src/shred.rs @@ -20,7 +20,7 @@ use std::io::SeekFrom; use std::path::{Path, PathBuf}; use uucore::display::Quotable; use uucore::error::{FromIo, UResult, USimpleError, UUsageError}; -use uucore::{util_name, InvalidEncodingHandling}; +use uucore::{format_usage, util_name, InvalidEncodingHandling}; #[macro_use] extern crate uucore; @@ -214,10 +214,7 @@ impl<'a> BytesGenerator<'a> { static ABOUT: &str = "Overwrite the specified FILE(s) repeatedly, in order to make it harder\n\ for even very expensive hardware probing to recover the data. "; - -fn usage() -> String { - format!("{} [OPTION]... FILE...", uucore::execution_phrase()) -} +const USAGE: &str = "{} [OPTION]... FILE..."; static AFTER_HELP: &str = "Delete FILE(s) if --remove (-u) is specified. The default is not to remove\n\ @@ -273,11 +270,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { .collect_str(InvalidEncodingHandling::Ignore) .accept_any(); - let usage = usage(); - - let app = uu_app().override_usage(&usage[..]); - - let matches = app.get_matches_from(args); + let matches = uu_app().get_matches_from(args); if !matches.is_present(options::FILE) { return Err(UUsageError::new(1, "missing file operand")); @@ -326,6 +319,7 @@ pub fn uu_app<'a>() -> App<'a> { .version(crate_version!()) .about(ABOUT) .after_help(AFTER_HELP) + .override_usage(format_usage(USAGE)) .setting(AppSettings::InferLongArgs) .arg( Arg::new(options::FORCE) diff --git a/src/uu/shuf/src/shuf.rs b/src/uu/shuf/src/shuf.rs index eb3268f0b..c331d7867 100644 --- a/src/uu/shuf/src/shuf.rs +++ b/src/uu/shuf/src/shuf.rs @@ -14,7 +14,7 @@ use std::fs::File; use std::io::{stdin, stdout, BufReader, BufWriter, Read, Write}; use uucore::display::Quotable; use uucore::error::{FromIo, UResult, USimpleError}; -use uucore::{execution_phrase, InvalidEncodingHandling}; +use uucore::{format_usage, InvalidEncodingHandling}; mod rand_read_adapter; @@ -25,10 +25,14 @@ enum Mode { } static NAME: &str = "shuf"; -static USAGE: &str = r#"shuf [OPTION]... [FILE] - or: shuf -e [OPTION]... [ARG]... - or: shuf -i LO-HI [OPTION]..."#; -static ABOUT: &str = "Shuffle the input by outputting a random permutation of input lines. Each output permutation is equally likely."; +static USAGE: &str = "\ + {} [OPTION]... [FILE] + {} -e [OPTION]... [ARG]... + {} -i LO-HI [OPTION]..."; +static ABOUT: &str = "\ + Shuffle the input by outputting a random permutation of input lines.\ + Each output permutation is equally likely.\ + With no FILE, or when FILE is -, read standard input."; struct Options { head_count: usize, @@ -55,9 +59,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { .collect_str(InvalidEncodingHandling::ConvertLossy) .accept_any(); - let matches = uu_app() - .override_usage(&USAGE.replace(NAME, execution_phrase())[..]) - .get_matches_from(args); + let matches = uu_app().get_matches_from(args); let mode = if let Some(args) = matches.values_of(options::ECHO) { Mode::Echo(args.map(String::from).collect()) @@ -122,7 +124,7 @@ pub fn uu_app<'a>() -> App<'a> { .name(NAME) .about(ABOUT) .version(crate_version!()) - .override_usage(USAGE) + .override_usage(format_usage(USAGE)) .setting(AppSettings::InferLongArgs) .arg( Arg::new(options::ECHO) diff --git a/src/uu/sleep/src/sleep.rs b/src/uu/sleep/src/sleep.rs index 8545c40c9..0c8de143e 100644 --- a/src/uu/sleep/src/sleep.rs +++ b/src/uu/sleep/src/sleep.rs @@ -8,11 +8,17 @@ use std::thread; use std::time::Duration; -use uucore::error::{UResult, USimpleError}; +use uucore::{ + error::{UResult, USimpleError}, + format_usage, +}; use clap::{crate_version, App, AppSettings, Arg}; static ABOUT: &str = "Pause for NUMBER seconds."; +const USAGE: &str = "\ + {} NUMBER[SUFFIX]... + {} OPTION"; static LONG_HELP: &str = "Pause for NUMBER seconds. SUFFIX may be 's' for seconds (the default), 'm' for minutes, 'h' for hours or 'd' for days. Unlike most implementations that require NUMBER be an integer, here NUMBER may be an arbitrary floating @@ -23,19 +29,9 @@ mod options { pub const NUMBER: &str = "NUMBER"; } -fn usage() -> String { - format!( - "{0} {1}[SUFFIX]... \n {0} OPTION", - uucore::execution_phrase(), - options::NUMBER - ) -} - #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let usage = usage(); - - let matches = uu_app().override_usage(&usage[..]).get_matches_from(args); + let matches = uu_app().get_matches_from(args); if let Some(values) = matches.values_of(options::NUMBER) { let numbers = values.collect::>(); @@ -50,6 +46,7 @@ pub fn uu_app<'a>() -> App<'a> { .version(crate_version!()) .about(ABOUT) .after_help(LONG_HELP) + .override_usage(format_usage(USAGE)) .setting(AppSettings::InferLongArgs) .arg( Arg::new(options::NUMBER) diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index 31aa2b0a2..a2c636321 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -49,11 +49,14 @@ 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; -use uucore::InvalidEncodingHandling; +use uucore::{format_usage, InvalidEncodingHandling}; use crate::tmp_dir::TmpDirWrapper; -const ABOUT: &str = "Display sorted concatenation of all FILE(s)."; +const ABOUT: &str = "\ + Display sorted concatenation of all FILE(s).\ + With no FILE, or when FILE is -, read standard input."; +const USAGE: &str = "{} [OPTION]... [FILE]..."; const LONG_HELP_KEYS: &str = "The key format is FIELD[.CHAR][OPTIONS][,FIELD[.CHAR]][OPTIONS]. @@ -1030,16 +1033,6 @@ impl FieldSelector { } } -fn usage() -> String { - format!( - "{0} [OPTION]... [FILE]... -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.", - uucore::execution_phrase() - ) -} - /// Creates an `Arg` that conflicts with all other sort modes. fn make_sort_mode_arg<'a>(mode: &'a str, short: char, help: &'a str) -> Arg<'a> { let mut arg = Arg::new(mode).short(short).long(mode).help(help); @@ -1056,13 +1049,9 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let args = args .collect_str(InvalidEncodingHandling::Ignore) .accept_any(); - let usage = usage(); let mut settings: GlobalSettings = Default::default(); - let matches = match uu_app() - .override_usage(&usage[..]) - .try_get_matches_from(args) - { + let matches = match uu_app().try_get_matches_from(args) { Ok(t) => t, Err(e) => { // not all clap "Errors" are because of a failure to parse arguments. @@ -1276,6 +1265,7 @@ pub fn uu_app<'a>() -> App<'a> { App::new(uucore::util_name()) .version(crate_version!()) .about(ABOUT) + .override_usage(format_usage(USAGE)) .setting(AppSettings::InferLongArgs) .arg( Arg::new(options::modes::SORT) diff --git a/src/uu/split/src/split.rs b/src/uu/split/src/split.rs index e789eb96a..ca59dfb6e 100644 --- a/src/uu/split/src/split.rs +++ b/src/uu/split/src/split.rs @@ -22,6 +22,7 @@ use std::num::ParseIntError; use std::path::Path; use uucore::display::Quotable; use uucore::error::{FromIo, UIoError, UResult, USimpleError, UUsageError}; +use uucore::format_usage; use uucore::parse_size::{parse_size, ParseSizeError}; use uucore::uio_error; @@ -44,32 +45,15 @@ static OPT_ELIDE_EMPTY_FILES: &str = "elide-empty-files"; static ARG_INPUT: &str = "input"; static ARG_PREFIX: &str = "prefix"; -fn usage() -> String { - format!( - "{0} [OPTION]... [INPUT [PREFIX]]", - uucore::execution_phrase() - ) -} -fn get_long_usage() -> String { - format!( - "Usage: - {0} - -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.", - usage() - ) -} +const USAGE: &str = "{} [OPTION]... [INPUT [PREFIX]]"; +const AFTER_HELP: &str = "\ + 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."; #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let usage = usage(); - let long_usage = get_long_usage(); - let matches = uu_app() - .override_usage(&usage[..]) - .after_help(&long_usage[..]) - .get_matches_from(args); + let matches = uu_app().get_matches_from(args); match Settings::from(&matches) { Ok(settings) => split(&settings), Err(e) if e.requires_usage() => Err(UUsageError::new(1, format!("{}", e))), @@ -81,6 +65,8 @@ pub fn uu_app<'a>() -> App<'a> { App::new(uucore::util_name()) .version(crate_version!()) .about("Create output files containing consecutive or interleaved sections of input") + .after_help(AFTER_HELP) + .override_usage(format_usage(USAGE)) .setting(AppSettings::InferLongArgs) // strategy (mutually exclusive) .arg( diff --git a/src/uu/stat/src/stat.rs b/src/uu/stat/src/stat.rs index 38fbc0fec..569c94d96 100644 --- a/src/uu/stat/src/stat.rs +++ b/src/uu/stat/src/stat.rs @@ -8,13 +8,13 @@ #[macro_use] extern crate uucore; use uucore::display::Quotable; -use uucore::entries; use uucore::error::{UResult, USimpleError}; use uucore::fs::display_permissions; use uucore::fsext::{ pretty_filetype, pretty_fstype, pretty_time, read_fs_list, statfs, BirthTime, FsMeta, }; use uucore::libc::mode_t; +use uucore::{entries, format_usage}; use clap::{crate_version, App, AppSettings, Arg, ArgMatches}; use std::borrow::Cow; @@ -87,6 +87,7 @@ macro_rules! print_adjusted { } static ABOUT: &str = "Display file or file system status."; +const USAGE: &str = "{} [OPTION]... FILE..."; pub mod options { pub static DEREFERENCE: &str = "dereference"; @@ -893,10 +894,6 @@ impl Stater { } } -fn usage() -> String { - format!("{0} [OPTION]... FILE...", uucore::execution_phrase()) -} - fn get_long_usage() -> String { String::from( " @@ -957,13 +954,9 @@ for details about the options it supports. #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let usage = usage(); let long_usage = get_long_usage(); - let matches = uu_app() - .override_usage(&usage[..]) - .after_help(&long_usage[..]) - .get_matches_from(args); + let matches = uu_app().after_help(&long_usage[..]).get_matches_from(args); let stater = Stater::new(&matches)?; let exit_status = stater.exec(); @@ -978,6 +971,7 @@ pub fn uu_app<'a>() -> App<'a> { App::new(uucore::util_name()) .version(crate_version!()) .about(ABOUT) + .override_usage(format_usage(USAGE)) .setting(AppSettings::InferLongArgs) .arg( Arg::new(options::DEREFERENCE) diff --git a/src/uu/stdbuf/src/stdbuf.rs b/src/uu/stdbuf/src/stdbuf.rs index c62873fb3..a568ab277 100644 --- a/src/uu/stdbuf/src/stdbuf.rs +++ b/src/uu/stdbuf/src/stdbuf.rs @@ -21,11 +21,12 @@ use tempfile::tempdir; use tempfile::TempDir; use uucore::error::{FromIo, UResult, USimpleError, UUsageError}; use uucore::parse_size::parse_size; -use uucore::InvalidEncodingHandling; +use uucore::{format_usage, InvalidEncodingHandling}; static ABOUT: &str = "Run COMMAND, with modified buffering operations for its standard streams.\n\n\ Mandatory arguments to long options are mandatory for short options too."; +const USAGE: &str = "{} OPTION... COMMAND"; static LONG_HELP: &str = "If MODE is 'L' the corresponding stream will be line buffered.\n\ This option is invalid with standard input.\n\n\ If MODE is '0' the corresponding stream will be unbuffered.\n\n\ @@ -48,10 +49,6 @@ mod options { pub const COMMAND: &str = "command"; } -fn usage() -> String { - format!("{0} OPTION... COMMAND", uucore::execution_phrase()) -} - const STDBUF_INJECT: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/libstdbuf.so")); enum BufferType { @@ -154,9 +151,8 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let args = args .collect_str(InvalidEncodingHandling::Ignore) .accept_any(); - let usage = usage(); - let matches = uu_app().override_usage(&usage[..]).get_matches_from(args); + let matches = uu_app().get_matches_from(args); let options = ProgramOptions::try_from(&matches).map_err(|e| UUsageError::new(125, e.0))?; @@ -196,6 +192,7 @@ pub fn uu_app<'a>() -> App<'a> { .version(crate_version!()) .about(ABOUT) .after_help(LONG_HELP) + .override_usage(format_usage(USAGE)) .setting(AppSettings::TrailingVarArg) .setting(AppSettings::InferLongArgs) .arg( diff --git a/src/uu/sum/src/sum.rs b/src/uu/sum/src/sum.rs index 2f7052fa9..4dacf61cb 100644 --- a/src/uu/sum/src/sum.rs +++ b/src/uu/sum/src/sum.rs @@ -16,10 +16,10 @@ use std::io::{stdin, Read}; use std::path::Path; use uucore::display::Quotable; use uucore::error::{FromIo, UResult, USimpleError}; -use uucore::InvalidEncodingHandling; +use uucore::{format_usage, InvalidEncodingHandling}; static NAME: &str = "sum"; -static USAGE: &str = "sum [OPTION]... [FILE]..."; +static USAGE: &str = "{} [OPTION]... [FILE]..."; static SUMMARY: &str = "Checksum and count the blocks in a file.\n\ With no FILE, or when FILE is -, read standard input."; @@ -144,7 +144,7 @@ pub fn uu_app<'a>() -> App<'a> { App::new(uucore::util_name()) .name(NAME) .version(crate_version!()) - .override_usage(USAGE) + .override_usage(format_usage(USAGE)) .about(SUMMARY) .setting(AppSettings::InferLongArgs) .arg( diff --git a/src/uu/sync/src/sync.rs b/src/uu/sync/src/sync.rs index 253453bfd..773e49479 100644 --- a/src/uu/sync/src/sync.rs +++ b/src/uu/sync/src/sync.rs @@ -13,8 +13,10 @@ use clap::{crate_version, App, AppSettings, Arg}; use std::path::Path; use uucore::display::Quotable; use uucore::error::{UResult, USimpleError}; +use uucore::format_usage; static ABOUT: &str = "Synchronize cached writes to persistent storage"; +const USAGE: &str = "{} [OPTION]... FILE..."; pub mod options { pub static FILE_SYSTEM: &str = "file-system"; pub static DATA: &str = "data"; @@ -157,15 +159,9 @@ mod platform { } } -fn usage() -> String { - format!("{0} [OPTION]... FILE...", uucore::execution_phrase()) -} - #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let usage = usage(); - - let matches = uu_app().override_usage(&usage[..]).get_matches_from(args); + let matches = uu_app().get_matches_from(args); let files: Vec = matches .values_of(ARG_FILES) @@ -198,6 +194,7 @@ pub fn uu_app<'a>() -> App<'a> { App::new(uucore::util_name()) .version(crate_version!()) .about(ABOUT) + .override_usage(format_usage(USAGE)) .setting(AppSettings::InferLongArgs) .arg( Arg::new(options::FILE_SYSTEM) diff --git a/src/uu/tac/src/tac.rs b/src/uu/tac/src/tac.rs index 5b48c9702..925968f74 100644 --- a/src/uu/tac/src/tac.rs +++ b/src/uu/tac/src/tac.rs @@ -19,13 +19,13 @@ use std::{ use uucore::display::Quotable; use uucore::error::UError; use uucore::error::UResult; -use uucore::show; use uucore::InvalidEncodingHandling; +use uucore::{format_usage, show}; use crate::error::TacError; static NAME: &str = "tac"; -static USAGE: &str = "[OPTION]... [FILE]..."; +static USAGE: &str = "{} [OPTION]... [FILE]..."; static SUMMARY: &str = "Write each file to standard output, last line first."; mod options { @@ -64,7 +64,7 @@ pub fn uu_app<'a>() -> App<'a> { App::new(uucore::util_name()) .name(NAME) .version(crate_version!()) - .override_usage(USAGE) + .override_usage(format_usage(USAGE)) .about(SUMMARY) .setting(AppSettings::InferLongArgs) .arg( diff --git a/src/uu/tail/src/tail.rs b/src/uu/tail/src/tail.rs index 2c9a248f0..00db47c0e 100644 --- a/src/uu/tail/src/tail.rs +++ b/src/uu/tail/src/tail.rs @@ -31,6 +31,7 @@ use std::thread::sleep; use std::time::Duration; use uucore::display::Quotable; use uucore::error::{FromIo, UResult, USimpleError}; +use uucore::format_usage; use uucore::lines::lines; use uucore::parse_size::{parse_size, ParseSizeError}; use uucore::ringbuffer::RingBuffer; @@ -47,7 +48,7 @@ const ABOUT: &str = "\ \n\ Mandatory arguments to long flags are mandatory for short flags too.\ "; -const USAGE: &str = "tail [FLAG]... [FILE]..."; +const USAGE: &str = "{} [FLAG]... [FILE]..."; pub mod options { pub mod verbosity { @@ -277,7 +278,7 @@ pub fn uu_app<'a>() -> App<'a> { App::new(uucore::util_name()) .version(crate_version!()) .about(ABOUT) - .override_usage(USAGE) + .override_usage(format_usage(USAGE)) .setting(AppSettings::InferLongArgs) .arg( Arg::new(options::BYTES) diff --git a/src/uu/tee/src/tee.rs b/src/uu/tee/src/tee.rs index 8285ac60b..a1ba6b201 100644 --- a/src/uu/tee/src/tee.rs +++ b/src/uu/tee/src/tee.rs @@ -15,11 +15,13 @@ use std::io::{copy, sink, stdin, stdout, Error, ErrorKind, Read, Result, Write}; use std::path::PathBuf; use uucore::display::Quotable; use uucore::error::UResult; +use uucore::format_usage; #[cfg(unix)] use uucore::libc; static ABOUT: &str = "Copy standard input to each FILE, and also to standard output."; +const USAGE: &str = "{} [OPTION]... [FILE]..."; mod options { pub const APPEND: &str = "append"; @@ -34,15 +36,9 @@ struct Options { files: Vec, } -fn usage() -> String { - format!("{0} [OPTION]... [FILE]...", uucore::execution_phrase()) -} - #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let usage = usage(); - - let matches = uu_app().override_usage(&usage[..]).get_matches_from(args); + let matches = uu_app().get_matches_from(args); let options = Options { append: matches.is_present(options::APPEND), @@ -63,6 +59,7 @@ pub fn uu_app<'a>() -> App<'a> { App::new(uucore::util_name()) .version(crate_version!()) .about(ABOUT) + .override_usage(format_usage(USAGE)) .after_help("If a FILE is -, it refers to a file named - .") .setting(AppSettings::InferLongArgs) .arg( diff --git a/src/uu/test/src/test.rs b/src/uu/test/src/test.rs index cc7437bff..f91aaf8ea 100644 --- a/src/uu/test/src/test.rs +++ b/src/uu/test/src/test.rs @@ -15,12 +15,14 @@ use parser::{parse, Operator, Symbol, UnaryOperator}; use std::ffi::{OsStr, OsString}; use uucore::display::Quotable; use uucore::error::{UResult, USimpleError}; +use uucore::format_usage; -const USAGE: &str = "test EXPRESSION -or: test -or: [ EXPRESSION ] -or: [ ] -or: [ OPTION"; +const USAGE: &str = "\ + {} EXPRESSION + {} + [ EXPRESSION ] + [ ] + [ OPTION"; // We use after_help so that this comes after the usage string (it would come before if we used about) const AFTER_HELP: &str = " @@ -92,7 +94,7 @@ pub fn uu_app<'a>() -> App<'a> { App::new(uucore::util_name()) .version(crate_version!()) .about(ABOUT) - .override_usage(USAGE) + .override_usage(format_usage(USAGE)) .after_help(AFTER_HELP) } @@ -109,7 +111,7 @@ pub fn uumain(mut args: impl uucore::Args) -> UResult<()> { App::new(binary_name) .version(crate_version!()) .about(ABOUT) - .override_usage(USAGE) + .override_usage(format_usage(USAGE)) .after_help(AFTER_HELP) // Disable printing of -h and -v as valid alternatives for --help and --version, // since we don't recognize -h and -v as help/version flags. diff --git a/src/uu/timeout/src/timeout.rs b/src/uu/timeout/src/timeout.rs index 2e686f811..2caed8cab 100644 --- a/src/uu/timeout/src/timeout.rs +++ b/src/uu/timeout/src/timeout.rs @@ -20,16 +20,10 @@ use uucore::display::Quotable; use uucore::error::{UResult, USimpleError}; use uucore::process::ChildExt; use uucore::signals::{signal_by_name_or_value, signal_name_by_value}; -use uucore::InvalidEncodingHandling; +use uucore::{format_usage, InvalidEncodingHandling}; static ABOUT: &str = "Start COMMAND, and kill it if still running after DURATION."; - -fn usage() -> String { - format!( - "{0} [OPTION] DURATION COMMAND...", - uucore::execution_phrase() - ) -} +const USAGE: &str = "{} [OPTION] DURATION COMMAND..."; const ERR_EXIT_STATUS: i32 = 125; @@ -106,9 +100,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { .collect_str(InvalidEncodingHandling::ConvertLossy) .accept_any(); - let usage = usage(); - - let app = uu_app().override_usage(&usage[..]); + let app = uu_app(); let matches = app.get_matches_from(args); @@ -128,6 +120,7 @@ pub fn uu_app<'a>() -> App<'a> { App::new("timeout") .version(crate_version!()) .about(ABOUT) + .override_usage(format_usage(USAGE)) .arg( Arg::new(options::FOREGROUND) .long(options::FOREGROUND) diff --git a/src/uu/touch/src/touch.rs b/src/uu/touch/src/touch.rs index f58d9e6d8..620538bbf 100644 --- a/src/uu/touch/src/touch.rs +++ b/src/uu/touch/src/touch.rs @@ -19,8 +19,10 @@ use std::fs::{self, File}; use std::path::Path; use uucore::display::Quotable; use uucore::error::{FromIo, UError, UResult, USimpleError}; +use uucore::format_usage; static ABOUT: &str = "Update the access and modification times of each FILE to the current time."; +const USAGE: &str = "{} [OPTION]... [USER]"; pub mod options { // Both SOURCES and sources are needed as we need to be able to refer to the ArgGroup. pub static SOURCES: &str = "sources"; @@ -48,15 +50,9 @@ fn local_tm_to_filetime(tm: time::Tm) -> FileTime { FileTime::from_unix_time(ts.sec as i64, ts.nsec as u32) } -fn usage() -> String { - format!("{0} [OPTION]... [USER]", uucore::execution_phrase()) -} - #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let usage = usage(); - - let matches = uu_app().override_usage(&usage[..]).get_matches_from(args); + let matches = uu_app().get_matches_from(args); let files = matches.values_of_os(ARG_FILES).ok_or_else(|| { USimpleError::new( @@ -149,6 +145,7 @@ pub fn uu_app<'a>() -> App<'a> { App::new(uucore::util_name()) .version(crate_version!()) .about(ABOUT) + .override_usage(format_usage(USAGE)) .setting(AppSettings::InferLongArgs) .arg( Arg::new(options::ACCESS) diff --git a/src/uu/tr/src/tr.rs b/src/uu/tr/src/tr.rs index f2efbb176..a92a7308a 100644 --- a/src/uu/tr/src/tr.rs +++ b/src/uu/tr/src/tr.rs @@ -15,13 +15,14 @@ use clap::{crate_version, App, AppSettings, Arg}; use nom::AsBytes; use operation::{translate_input, Sequence, SqueezeOperation, TranslateOperation}; use std::io::{stdin, stdout, BufReader, BufWriter}; -use uucore::show; +use uucore::{format_usage, show}; use crate::operation::DeleteOperation; use uucore::error::{UResult, USimpleError, UUsageError}; use uucore::{display::Quotable, InvalidEncodingHandling}; static ABOUT: &str = "translate or delete characters"; +const USAGE: &str = "{} [OPTION]... SET1 [SET2]"; mod options { pub const COMPLEMENT: &str = "complement"; @@ -31,10 +32,6 @@ mod options { pub const SETS: &str = "sets"; } -fn get_usage() -> String { - format!("{} [OPTION]... SET1 [SET2]", uucore::execution_phrase()) -} - fn get_long_usage() -> String { "Translate, squeeze, and/or delete characters from standard input, \ writing to standard output." @@ -47,13 +44,9 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { .collect_str(InvalidEncodingHandling::ConvertLossy) .accept_any(); - let usage = get_usage(); let after_help = get_long_usage(); - let matches = uu_app() - .override_usage(&usage[..]) - .after_help(&after_help[..]) - .get_matches_from(args); + let matches = uu_app().after_help(&after_help[..]).get_matches_from(args); let delete_flag = matches.is_present(options::DELETE); let complement_flag = matches.is_present(options::COMPLEMENT); @@ -148,6 +141,7 @@ pub fn uu_app<'a>() -> App<'a> { App::new(uucore::util_name()) .version(crate_version!()) .about(ABOUT) + .override_usage(format_usage(USAGE)) .setting(AppSettings::InferLongArgs) .setting(AppSettings::InferLongArgs) .arg( diff --git a/src/uu/truncate/src/truncate.rs b/src/uu/truncate/src/truncate.rs index 2e33a4bf6..416afe54a 100644 --- a/src/uu/truncate/src/truncate.rs +++ b/src/uu/truncate/src/truncate.rs @@ -15,6 +15,7 @@ use std::os::unix::fs::FileTypeExt; use std::path::Path; use uucore::display::Quotable; use uucore::error::{FromIo, UResult, USimpleError, UUsageError}; +use uucore::format_usage; use uucore::parse_size::{parse_size, ParseSizeError}; #[derive(Debug, Eq, PartialEq)] @@ -74,6 +75,7 @@ impl TruncateMode { } static ABOUT: &str = "Shrink or extend the size of each file to the specified size."; +const USAGE: &str = "{} [OPTION]... [FILE]..."; pub mod options { pub static IO_BLOCKS: &str = "io-blocks"; @@ -83,10 +85,6 @@ pub mod options { pub static ARG_FILES: &str = "files"; } -fn usage() -> String { - format!("{0} [OPTION]... [FILE]...", uucore::execution_phrase()) -} - fn get_long_usage() -> String { String::from( " @@ -111,11 +109,9 @@ fn get_long_usage() -> String { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let usage = usage(); let long_usage = get_long_usage(); let matches = uu_app() - .override_usage(&usage[..]) .after_help(&long_usage[..]) .try_get_matches_from(args) .map_err(|e| { @@ -146,6 +142,7 @@ pub fn uu_app<'a>() -> App<'a> { App::new(uucore::util_name()) .version(crate_version!()) .about(ABOUT) + .override_usage(format_usage(USAGE)) .setting(AppSettings::InferLongArgs) .arg( Arg::new(options::IO_BLOCKS) diff --git a/src/uu/tsort/src/tsort.rs b/src/uu/tsort/src/tsort.rs index 069d6dc4f..61cb95256 100644 --- a/src/uu/tsort/src/tsort.rs +++ b/src/uu/tsort/src/tsort.rs @@ -12,7 +12,7 @@ use std::io::{stdin, BufRead, BufReader, Read}; use std::path::Path; use uucore::display::Quotable; use uucore::error::{FromIo, UResult, USimpleError}; -use uucore::InvalidEncodingHandling; +use uucore::{format_usage, InvalidEncodingHandling}; static SUMMARY: &str = "Topological sort the strings in FILE. Strings are defined as any sequence of tokens separated by whitespace (tab, space, or newline). @@ -96,7 +96,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app<'a>() -> App<'a> { App::new(uucore::util_name()) .version(crate_version!()) - .override_usage(USAGE) + .override_usage(format_usage(USAGE)) .about(SUMMARY) .setting(AppSettings::InferLongArgs) .arg(Arg::new(options::FILE).default_value("-").hide(true)) diff --git a/src/uu/tty/src/tty.rs b/src/uu/tty/src/tty.rs index 69d62cf74..e3d13b67d 100644 --- a/src/uu/tty/src/tty.rs +++ b/src/uu/tty/src/tty.rs @@ -13,27 +13,22 @@ use clap::{crate_version, App, AppSettings, Arg}; use std::ffi::CStr; use std::io::Write; use uucore::error::{UResult, UUsageError}; -use uucore::InvalidEncodingHandling; +use uucore::{format_usage, InvalidEncodingHandling}; static ABOUT: &str = "Print the file name of the terminal connected to standard input."; +const USAGE: &str = "{} [OPTION]..."; mod options { pub const SILENT: &str = "silent"; } -fn usage() -> String { - format!("{0} [OPTION]...", uucore::execution_phrase()) -} - #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let usage = usage(); let args = args .collect_str(InvalidEncodingHandling::ConvertLossy) .accept_any(); let matches = uu_app() - .override_usage(&usage[..]) .try_get_matches_from(args) .map_err(|e| UUsageError::new(2, format!("{}", e)))?; @@ -75,6 +70,7 @@ pub fn uu_app<'a>() -> App<'a> { App::new(uucore::util_name()) .version(crate_version!()) .about(ABOUT) + .override_usage(format_usage(USAGE)) .setting(AppSettings::InferLongArgs) .arg( Arg::new(options::SILENT) diff --git a/src/uu/uname/src/uname.rs b/src/uu/uname/src/uname.rs index d007da1a0..c5c2b8801 100644 --- a/src/uu/uname/src/uname.rs +++ b/src/uu/uname/src/uname.rs @@ -12,9 +12,13 @@ use clap::{crate_version, App, AppSettings, Arg}; use platform_info::*; -use uucore::error::{FromIo, UResult}; +use uucore::{ + error::{FromIo, UResult}, + format_usage, +}; const ABOUT: &str = "Print certain system information. With no OPTION, same as -s."; +const USAGE: &str = "{} [OPTION]..."; pub mod options { pub static ALL: &str = "all"; @@ -49,8 +53,7 @@ const HOST_OS: &str = "Redox"; #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let usage = format!("{} [OPTION]...", uucore::execution_phrase()); - let matches = uu_app().override_usage(&usage[..]).get_matches_from(args); + let matches = uu_app().get_matches_from(args); let uname = PlatformInfo::new().map_err_context(|| "failed to create PlatformInfo".to_string())?; @@ -122,6 +125,7 @@ pub fn uu_app<'a>() -> App<'a> { App::new(uucore::util_name()) .version(crate_version!()) .about(ABOUT) + .override_usage(format_usage(USAGE)) .setting(AppSettings::InferLongArgs) .arg(Arg::new(options::ALL) .short('a') diff --git a/src/uu/unexpand/src/unexpand.rs b/src/uu/unexpand/src/unexpand.rs index dc1d0c800..55bd51ad1 100644 --- a/src/uu/unexpand/src/unexpand.rs +++ b/src/uu/unexpand/src/unexpand.rs @@ -18,10 +18,10 @@ use std::str::from_utf8; use unicode_width::UnicodeWidthChar; use uucore::display::Quotable; use uucore::error::{FromIo, UResult}; -use uucore::InvalidEncodingHandling; +use uucore::{format_usage, InvalidEncodingHandling}; static NAME: &str = "unexpand"; -static USAGE: &str = "unexpand [OPTION]... [FILE]..."; +static USAGE: &str = "{} [OPTION]... [FILE]..."; static SUMMARY: &str = "Convert blanks in each FILE to tabs, writing to standard output.\n\ With no FILE, or when FILE is -, read standard input."; @@ -106,7 +106,7 @@ pub fn uu_app<'a>() -> App<'a> { App::new(uucore::util_name()) .name(NAME) .version(crate_version!()) - .override_usage(USAGE) + .override_usage(format_usage(USAGE)) .about(SUMMARY) .setting(AppSettings::InferLongArgs) .arg(Arg::new(options::FILE).hide(true).multiple_occurrences(true)) diff --git a/src/uu/uniq/src/uniq.rs b/src/uu/uniq/src/uniq.rs index 111124c05..a22db42a9 100644 --- a/src/uu/uniq/src/uniq.rs +++ b/src/uu/uniq/src/uniq.rs @@ -13,8 +13,10 @@ use std::str::FromStr; use strum_macros::{AsRefStr, EnumString}; use uucore::display::Quotable; use uucore::error::{FromIo, UResult, USimpleError}; +use uucore::format_usage; static ABOUT: &str = "Report or omit repeated lines."; +const USAGE: &str = "{} [OPTION]... [INPUT [OUTPUT]]..."; pub mod options { pub static ALL_REPEATED: &str = "all-repeated"; pub static CHECK_CHARS: &str = "check-chars"; @@ -239,13 +241,6 @@ fn opt_parsed(opt_name: &str, matches: &ArgMatches) -> UResult String { - format!( - "{0} [OPTION]... [INPUT [OUTPUT]]...", - uucore::execution_phrase() - ) -} - fn get_long_usage() -> String { String::from( "Filter adjacent matching lines from INPUT (or standard input),\n\ @@ -257,13 +252,9 @@ fn get_long_usage() -> String { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let usage = usage(); let long_usage = get_long_usage(); - let matches = uu_app() - .override_usage(&usage[..]) - .after_help(&long_usage[..]) - .get_matches_from(args); + let matches = uu_app().after_help(&long_usage[..]).get_matches_from(args); let files: Vec = matches .values_of(ARG_FILES) @@ -303,6 +294,7 @@ pub fn uu_app<'a>() -> App<'a> { App::new(uucore::util_name()) .version(crate_version!()) .about(ABOUT) + .override_usage(format_usage(USAGE)) .setting(AppSettings::InferLongArgs) .arg( Arg::new(options::ALL_REPEATED) diff --git a/src/uu/uptime/src/uptime.rs b/src/uu/uptime/src/uptime.rs index 13ec7fd23..ac9ba6d15 100644 --- a/src/uu/uptime/src/uptime.rs +++ b/src/uu/uptime/src/uptime.rs @@ -11,6 +11,7 @@ use chrono::{Local, TimeZone, Utc}; use clap::{crate_version, App, AppSettings, Arg}; +use uucore::format_usage; // import crate time from utmpx pub use uucore::libc; use uucore::libc::time_t; @@ -20,6 +21,7 @@ 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."; +const USAGE: &str = "{} [OPTION]..."; pub mod options { pub static SINCE: &str = "since"; } @@ -32,14 +34,9 @@ extern "C" { fn GetTickCount() -> uucore::libc::uint32_t; } -fn usage() -> String { - format!("{0} [OPTION]...", uucore::execution_phrase()) -} - #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let usage = usage(); - let matches = uu_app().override_usage(&usage[..]).get_matches_from(args); + let matches = uu_app().get_matches_from(args); let (boot_time, user_count) = process_utmpx(); let uptime = get_uptime(boot_time); @@ -66,6 +63,7 @@ pub fn uu_app<'a>() -> App<'a> { App::new(uucore::util_name()) .version(crate_version!()) .about(ABOUT) + .override_usage(format_usage(USAGE)) .setting(AppSettings::InferLongArgs) .arg( Arg::new(options::SINCE) diff --git a/src/uu/users/src/users.rs b/src/uu/users/src/users.rs index d545f84f1..761080139 100644 --- a/src/uu/users/src/users.rs +++ b/src/uu/users/src/users.rs @@ -12,16 +12,14 @@ use std::path::Path; use clap::{crate_version, App, AppSettings, Arg}; use uucore::error::UResult; +use uucore::format_usage; use uucore::utmpx::{self, Utmpx}; static ABOUT: &str = "Print the user names of users currently logged in to the current host"; +const USAGE: &str = "{} [FILE]"; static ARG_FILES: &str = "files"; -fn usage() -> String { - format!("{0} [FILE]", uucore::execution_phrase()) -} - fn get_long_usage() -> String { format!( "Output who is currently logged in according to FILE. @@ -32,13 +30,9 @@ If FILE is not specified, use {}. /var/log/wtmp as FILE is common.", #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let usage = usage(); let after_help = get_long_usage(); - let matches = uu_app() - .override_usage(&usage[..]) - .after_help(&after_help[..]) - .get_matches_from(args); + let matches = uu_app().after_help(&after_help[..]).get_matches_from(args); let files: Vec<&Path> = matches .values_of_os(ARG_FILES) @@ -68,6 +62,7 @@ pub fn uu_app<'a>() -> App<'a> { App::new(uucore::util_name()) .version(crate_version!()) .about(ABOUT) + .override_usage(format_usage(USAGE)) .setting(AppSettings::InferLongArgs) .arg(Arg::new(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 97ea26b8b..a07877e0e 100644 --- a/src/uu/wc/src/wc.rs +++ b/src/uu/wc/src/wc.rs @@ -15,6 +15,7 @@ use count_fast::{count_bytes_and_lines_fast, count_bytes_fast}; use countable::WordCountable; use unicode_width::UnicodeWidthChar; use utf8::{BufReadDecoder, BufReadDecoderError}; +use uucore::format_usage; use word_count::{TitledWordCount, WordCount}; use clap::{crate_version, App, AppSettings, Arg, ArgMatches}; @@ -81,7 +82,8 @@ impl Settings { } static ABOUT: &str = "Display newline, word, and byte counts for each FILE, and a total line if -more than one FILE is specified."; +more than one FILE is specified. With no FILE, or when FILE is -, read standard input."; +const USAGE: &str = "{} [OPTION]... [FILE]..."; pub mod options { pub static BYTES: &str = "bytes"; @@ -95,14 +97,6 @@ pub mod options { static ARG_FILES: &str = "files"; static STDIN_REPR: &str = "-"; -fn usage() -> String { - format!( - "{0} [OPTION]... [FILE]... - With no FILE, or when FILE is -, read standard input.", - uucore::execution_phrase() - ) -} - enum StdinKind { /// Stdin specified on command-line with "-". Explicit, @@ -180,9 +174,7 @@ impl Display for WcError { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let usage = usage(); - - let matches = uu_app().override_usage(&usage[..]).get_matches_from(args); + let matches = uu_app().get_matches_from(args); let inputs = inputs(&matches)?; @@ -195,6 +187,7 @@ pub fn uu_app<'a>() -> App<'a> { App::new(uucore::util_name()) .version(crate_version!()) .about(ABOUT) + .override_usage(format_usage(USAGE)) .setting(AppSettings::InferLongArgs) .arg( Arg::new(options::BYTES) diff --git a/src/uu/who/src/who.rs b/src/uu/who/src/who.rs index 50dde9de0..8c0a66c36 100644 --- a/src/uu/who/src/who.rs +++ b/src/uu/who/src/who.rs @@ -17,7 +17,7 @@ use std::borrow::Cow; use std::ffi::CStr; use std::os::unix::fs::MetadataExt; use std::path::PathBuf; -use uucore::InvalidEncodingHandling; +use uucore::{format_usage, InvalidEncodingHandling}; mod options { pub const ALL: &str = "all"; @@ -38,19 +38,13 @@ mod options { } static ABOUT: &str = "Print information about users who are currently logged in."; +const USAGE: &str = "{} [OPTION]... [ FILE | ARG1 ARG2 ]"; #[cfg(any(target_os = "linux"))] 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 usage() -> String { - format!( - "{0} [OPTION]... [ FILE | ARG1 ARG2 ]", - uucore::execution_phrase() - ) -} - fn get_long_usage() -> String { format!( "If FILE is not specified, use {}. /var/log/wtmp as FILE is common.\n\ @@ -65,13 +59,9 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { .collect_str(InvalidEncodingHandling::Ignore) .accept_any(); - let usage = usage(); let after_help = get_long_usage(); - let matches = uu_app() - .override_usage(&usage[..]) - .after_help(&after_help[..]) - .get_matches_from(args); + let matches = uu_app().after_help(&after_help[..]).get_matches_from(args); let files: Vec = matches .values_of(options::FILE) @@ -165,6 +155,7 @@ pub fn uu_app<'a>() -> App<'a> { App::new(uucore::util_name()) .version(crate_version!()) .about(ABOUT) + .override_usage(format_usage(USAGE)) .setting(AppSettings::InferLongArgs) .arg( Arg::new(options::ALL) diff --git a/src/uu/yes/src/yes.rs b/src/uu/yes/src/yes.rs index 22c9a5d12..b237fb857 100644 --- a/src/uu/yes/src/yes.rs +++ b/src/uu/yes/src/yes.rs @@ -15,10 +15,13 @@ extern crate clap; use clap::{App, AppSettings, Arg}; use uucore::error::{UResult, USimpleError}; +use uucore::format_usage; #[cfg(any(target_os = "linux", target_os = "android"))] mod splice; +const USAGE: &str = "{} [STRING]..."; + // 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; @@ -48,6 +51,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { pub fn uu_app<'a>() -> App<'a> { app_from_crate!() + .override_usage(format_usage(USAGE)) .arg(Arg::new("STRING").index(1).multiple_occurrences(true)) .setting(AppSettings::InferLongArgs) } diff --git a/src/uucore/src/lib/lib.rs b/src/uucore/src/lib/lib.rs index 4dc5e6987..8f3d045eb 100644 --- a/src/uucore/src/lib/lib.rs +++ b/src/uucore/src/lib/lib.rs @@ -94,6 +94,15 @@ macro_rules! bin { }; } +/// Generate the usage string for clap. +/// +/// This function replaces all occurrences of `{}` with the execution phrase +/// and leaks the result to return a `&'static str`. It does **not** support +/// more advanced formatting features such as `{0}`. +pub fn format_usage(s: &str) -> &'static str { + &*Box::leak(s.replace("{}", crate::execution_phrase()).into_boxed_str()) +} + pub fn get_utility_is_second_arg() -> bool { crate::macros::UTILITY_IS_SECOND_ARG.load(Ordering::SeqCst) } From 0d5dd27e7193af55463cc863187549f2f843f217 Mon Sep 17 00:00:00 2001 From: Alex Tibbles Date: Mon, 21 Feb 2022 12:34:25 -0500 Subject: [PATCH 25/58] uniq: update strum to 0.23 latest --- Cargo.lock | 15 +++++++++++---- src/uu/uniq/Cargo.toml | 4 ++-- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e9d3add66..5a99700d1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1807,6 +1807,12 @@ dependencies = [ "webpki", ] +[[package]] +name = "rustversion" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2cc38e8fa666e2de3c4aba7edeb5ffc5246c1c2ed0e3d17e560aeeba736b23f" + [[package]] name = "same-file" version = "1.0.6" @@ -2000,19 +2006,20 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "strum" -version = "0.21.0" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aaf86bbcfd1fa9670b7a129f64fc0c9fcbbfe4f1bc4210e9e98fe71ffc12cde2" +checksum = "cae14b91c7d11c9a851d3fbc80a963198998c2a64eec840477fa92d8ce9b70bb" [[package]] name = "strum_macros" -version = "0.21.1" +version = "0.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d06aaeeee809dbc59eb4556183dd927df67db1540de5be8d3ec0b6636358a5ec" +checksum = "5bb0dc7ee9c15cea6199cde9a127fa16a4c5819af85395457ad72d68edc85a38" dependencies = [ "heck", "proc-macro2", "quote 1.0.14", + "rustversion", "syn", ] diff --git a/src/uu/uniq/Cargo.toml b/src/uu/uniq/Cargo.toml index 69d80a2fc..5d4a085c7 100644 --- a/src/uu/uniq/Cargo.toml +++ b/src/uu/uniq/Cargo.toml @@ -16,8 +16,8 @@ path = "src/uniq.rs" [dependencies] clap = { version = "3.0", features = ["wrap_help", "cargo"] } -strum = "0.21" -strum_macros = "0.21" +strum = "0.23.0" +strum_macros = "0.23.1" uucore = { version=">=0.0.11", package="uucore", path="../../uucore" } [[bin]] From 9f367b72e61b2267a68d041867d09205c341c208 Mon Sep 17 00:00:00 2001 From: Jeffrey Finkelstein Date: Thu, 17 Feb 2022 18:58:25 -0500 Subject: [PATCH 26/58] dd: pad partial record with spaces in some cases If `conv=block,sync` command-line arguments are given and there is at least one partial record read from the input (for example, if the length of the input is not divisible by the value of the `ibs` argument), then output an extra block of `cbs` spaces. For example, no extra spaces are printed in this example because the input is of length 10, a multiple of `ibs`: $ printf "012\nabcde\n" \ > | dd ibs=5 cbs=5 conv=block,sync status=noxfer \ > && echo $ 012 abcde$ 2+0 records in 0+1 records out But in this example, 5 extra spaces are printed because the length of the input is not a multiple of `ibs`: $ printf "012\nabcdefg\n" \ > | dd ibs=5 cbs=5 conv=block,sync status=noxfer \ > && echo $ 012 abcde $ 2+1 records in 0+1 records out 1 truncated record The number of spaces printed is the size of the conversion block, given by `cbs`. --- src/uu/dd/src/blocks.rs | 53 +++++++++++++++++++++++++--------------- tests/by-util/test_dd.rs | 25 ++++++++++++++++++- 2 files changed, 57 insertions(+), 21 deletions(-) diff --git a/src/uu/dd/src/blocks.rs b/src/uu/dd/src/blocks.rs index 61a2a6675..331bad56b 100644 --- a/src/uu/dd/src/blocks.rs +++ b/src/uu/dd/src/blocks.rs @@ -14,10 +14,17 @@ use std::io::Read; const NEWLINE: u8 = b'\n'; const SPACE: u8 = b' '; -/// Splits the content of buf into cbs-length blocks -/// Appends padding as specified by conv=block and cbs=N -/// Expects ascii encoded data -fn block(buf: &[u8], cbs: usize, rstat: &mut ReadStat) -> Vec> { +/// Split a slice into chunks, padding or truncating as necessary. +/// +/// The slice `buf` is split on newlines, then each block is resized +/// to `cbs` bytes, padding with spaces if necessary. This function +/// expects the input bytes to be ASCII-encoded. +/// +/// If `sync` is true and there has been at least one partial record +/// read from the input (as indicated in `rstat`), then leave an +/// all-spaces block at the end. Otherwise, remove the last block if +/// it is all spaces. +fn block(buf: &[u8], cbs: usize, sync: bool, rstat: &mut ReadStat) -> Vec> { let mut blocks = buf .split(|&e| e == NEWLINE) .map(|split| split.to_vec()) @@ -31,8 +38,11 @@ fn block(buf: &[u8], cbs: usize, rstat: &mut ReadStat) -> Vec> { blocks }); + // If `sync` is true and there has been at least one partial + // record read from the input, then leave the all-spaces block at + // the end. Otherwise, remove it. if let Some(last) = blocks.last() { - if last.iter().all(|&e| e == SPACE) { + if (!sync || rstat.reads_partial == 0) && last.iter().all(|&e| e == SPACE) { blocks.pop(); } } @@ -100,7 +110,7 @@ pub(crate) fn conv_block_unblock_helper( // ascii input so perform the block first let cbs = i.cflags.block.unwrap(); - let mut blocks = block(&buf, cbs, rstat); + let mut blocks = block(&buf, cbs, i.cflags.sync.is_some(), rstat); if let Some(ct) = i.cflags.ctable { for buf in &mut blocks { @@ -119,7 +129,10 @@ pub(crate) fn conv_block_unblock_helper( apply_conversion(&mut buf, ct); } - let blocks = block(&buf, cbs, rstat).into_iter().flatten().collect(); + let blocks = block(&buf, cbs, i.cflags.sync.is_some(), rstat) + .into_iter() + .flatten() + .collect(); Ok(blocks) } else if should_unblock_then_conv(i) { @@ -167,7 +180,7 @@ mod tests { fn block_test_no_nl() { let mut rs = ReadStat::default(); let buf = [0u8, 1u8, 2u8, 3u8]; - let res = block(&buf, 4, &mut rs); + let res = block(&buf, 4, false, &mut rs); assert_eq!(res, vec![vec![0u8, 1u8, 2u8, 3u8],]); } @@ -176,7 +189,7 @@ mod tests { fn block_test_no_nl_short_record() { let mut rs = ReadStat::default(); let buf = [0u8, 1u8, 2u8, 3u8]; - let res = block(&buf, 8, &mut rs); + let res = block(&buf, 8, false, &mut rs); assert_eq!( res, @@ -188,7 +201,7 @@ mod tests { fn block_test_no_nl_trunc() { let mut rs = ReadStat::default(); let buf = [0u8, 1u8, 2u8, 3u8, 4u8]; - let res = block(&buf, 4, &mut rs); + let res = block(&buf, 4, false, &mut rs); // Commented section(s) should be truncated and appear for reference only. assert_eq!(res, vec![vec![0u8, 1u8, 2u8, 3u8 /*, 4u8*/],]); @@ -201,7 +214,7 @@ mod tests { let buf = [ 0u8, 1u8, 2u8, 3u8, 4u8, NEWLINE, 0u8, 1u8, 2u8, 3u8, 4u8, NEWLINE, 5u8, 6u8, 7u8, 8u8, ]; - let res = block(&buf, 4, &mut rs); + let res = block(&buf, 4, false, &mut rs); assert_eq!( res, @@ -221,7 +234,7 @@ mod tests { fn block_test_surrounded_nl() { let mut rs = ReadStat::default(); let buf = [0u8, 1u8, 2u8, 3u8, NEWLINE, 4u8, 5u8, 6u8, 7u8, 8u8]; - let res = block(&buf, 8, &mut rs); + let res = block(&buf, 8, false, &mut rs); assert_eq!( res, @@ -238,7 +251,7 @@ mod tests { let buf = [ 0u8, 1u8, 2u8, 3u8, NEWLINE, 4u8, NEWLINE, 5u8, 6u8, 7u8, 8u8, 9u8, ]; - let res = block(&buf, 8, &mut rs); + let res = block(&buf, 8, false, &mut rs); assert_eq!( res, @@ -256,7 +269,7 @@ mod tests { let buf = [ 0u8, 1u8, 2u8, 3u8, NEWLINE, 4u8, 5u8, 6u8, 7u8, NEWLINE, 8u8, 9u8, ]; - let res = block(&buf, 8, &mut rs); + let res = block(&buf, 8, false, &mut rs); assert_eq!( res, @@ -272,7 +285,7 @@ mod tests { fn block_test_end_nl_diff_cbs_block() { let mut rs = ReadStat::default(); let buf = [0u8, 1u8, 2u8, 3u8, NEWLINE]; - let res = block(&buf, 4, &mut rs); + let res = block(&buf, 4, false, &mut rs); assert_eq!(res, vec![vec![0u8, 1u8, 2u8, 3u8],]); } @@ -281,7 +294,7 @@ mod tests { fn block_test_end_nl_same_cbs_block() { let mut rs = ReadStat::default(); let buf = [0u8, 1u8, 2u8, NEWLINE]; - let res = block(&buf, 4, &mut rs); + let res = block(&buf, 4, false, &mut rs); assert_eq!(res, vec![vec![0u8, 1u8, 2u8, SPACE]]); } @@ -290,7 +303,7 @@ mod tests { fn block_test_double_end_nl() { let mut rs = ReadStat::default(); let buf = [0u8, 1u8, 2u8, NEWLINE, NEWLINE]; - let res = block(&buf, 4, &mut rs); + let res = block(&buf, 4, false, &mut rs); assert_eq!( res, @@ -302,7 +315,7 @@ mod tests { fn block_test_start_nl() { let mut rs = ReadStat::default(); let buf = [NEWLINE, 0u8, 1u8, 2u8, 3u8]; - let res = block(&buf, 4, &mut rs); + let res = block(&buf, 4, false, &mut rs); assert_eq!( res, @@ -314,7 +327,7 @@ mod tests { fn block_test_double_surrounded_nl_no_trunc() { let mut rs = ReadStat::default(); let buf = [0u8, 1u8, 2u8, 3u8, NEWLINE, NEWLINE, 4u8, 5u8, 6u8, 7u8]; - let res = block(&buf, 8, &mut rs); + let res = block(&buf, 8, false, &mut rs); assert_eq!( res, @@ -332,7 +345,7 @@ mod tests { let buf = [ 0u8, 1u8, 2u8, 3u8, NEWLINE, NEWLINE, 4u8, 5u8, 6u8, 7u8, 8u8, ]; - let res = block(&buf, 4, &mut rs); + let res = block(&buf, 4, false, &mut rs); assert_eq!( res, diff --git a/tests/by-util/test_dd.rs b/tests/by-util/test_dd.rs index 04f5490ec..ddc0939a8 100644 --- a/tests/by-util/test_dd.rs +++ b/tests/by-util/test_dd.rs @@ -1,4 +1,4 @@ -// spell-checker:ignore fname, tname, fpath, specfile, testfile, unspec, ifile, ofile, outfile, fullblock, urand, fileio, atoe, atoibm, availible, 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 abcdefghijklm abcdefghi +// spell-checker:ignore fname, tname, fpath, specfile, testfile, unspec, ifile, ofile, outfile, fullblock, urand, fileio, atoe, atoibm, availible, 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 abcdefghijklm abcdefghi nabcde nabcdefg abcdefg use crate::common::util::*; @@ -1102,3 +1102,26 @@ fn test_truncated_record() { fn test_outfile_dev_null() { new_ucmd!().arg("of=/dev/null").succeeds().no_stdout(); } + +#[test] +fn test_block_sync() { + new_ucmd!() + .args(&["ibs=5", "cbs=5", "conv=block,sync", "status=noxfer"]) + .pipe_in("012\nabcde\n") + .succeeds() + // blocks: 1 2 + .stdout_is("012 abcde") + .stderr_is("2+0 records in\n0+1 records out\n"); + + // It seems that a partial record in is represented as an + // all-spaces block at the end of the output. The "1 truncated + // record" line is present in the status report due to the line + // "abcdefg\n" being truncated to "abcde". + new_ucmd!() + .args(&["ibs=5", "cbs=5", "conv=block,sync", "status=noxfer"]) + .pipe_in("012\nabcdefg\n") + .succeeds() + // blocks: 1 2 3 + .stdout_is("012 abcde ") + .stderr_is("2+1 records in\n0+1 records out\n1 truncated record\n"); +} From c8346fd720761b37012fe3da94e312fc5d1960ab Mon Sep 17 00:00:00 2001 From: Alex Tibbles Date: Mon, 21 Feb 2022 12:49:12 -0500 Subject: [PATCH 27/58] hashsum: update hex to 0.4.3 Update to API change. --- Cargo.lock | 4 ++-- src/uu/hashsum/Cargo.toml | 2 +- src/uu/hashsum/src/digest.rs | 4 ++-- src/uu/hashsum/src/hashsum.rs | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5a99700d1..12a2c2ffe 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -957,9 +957,9 @@ dependencies = [ [[package]] name = "hex" -version = "0.2.0" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6a22814455d41612f41161581c2883c0c6a1c41852729b17d5ed88f01e153aa" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "hex-literal" diff --git a/src/uu/hashsum/Cargo.toml b/src/uu/hashsum/Cargo.toml index d3170689a..49e99c59c 100644 --- a/src/uu/hashsum/Cargo.toml +++ b/src/uu/hashsum/Cargo.toml @@ -17,7 +17,7 @@ path = "src/hashsum.rs" [dependencies] digest = "0.10.1" clap = { version = "3.0", features = ["wrap_help", "cargo"] } -hex = "0.2.0" +hex = "0.4.3" libc = "0.2.42" memchr = "2" md5 = "0.3.5" diff --git a/src/uu/hashsum/src/digest.rs b/src/uu/hashsum/src/digest.rs index 678c44886..719eadcbf 100644 --- a/src/uu/hashsum/src/digest.rs +++ b/src/uu/hashsum/src/digest.rs @@ -14,7 +14,7 @@ extern crate sha3; use std::io::Write; -use hex::ToHex; +use hex::encode; #[cfg(windows)] use memchr::memmem; @@ -32,7 +32,7 @@ pub trait Digest { fn result_str(&mut self) -> String { let mut buf: Vec = vec![0; self.output_bytes()]; self.result(&mut buf); - buf.to_hex() + encode(buf) } } diff --git a/src/uu/hashsum/src/hashsum.rs b/src/uu/hashsum/src/hashsum.rs index fe607b554..46e8ce1b8 100644 --- a/src/uu/hashsum/src/hashsum.rs +++ b/src/uu/hashsum/src/hashsum.rs @@ -21,7 +21,7 @@ use self::digest::Digest; use self::digest::DigestWriter; use clap::{App, AppSettings, Arg, ArgMatches}; -use hex::ToHex; +use hex::encode; use md5::Context as Md5; use regex::Regex; use sha1::Sha1; @@ -652,6 +652,6 @@ fn digest_reader( let mut bytes = Vec::new(); bytes.resize((output_bits + 7) / 8, 0); digest.result(&mut bytes); - Ok(bytes.to_hex()) + Ok(encode(bytes)) } } From 41e9719d6be4da12b68f4741f3c9c6c9d2c7d7c6 Mon Sep 17 00:00:00 2001 From: Alex Tibbles Date: Mon, 21 Feb 2022 16:51:00 -0500 Subject: [PATCH 28/58] upgrade to RustCrypto Hashes MD5 "MD-5" 0.10.1 --- Cargo.lock | 21 ++++++++++++--------- src/uu/hashsum/Cargo.toml | 2 +- src/uu/hashsum/src/digest.rs | 8 ++++---- src/uu/hashsum/src/hashsum.rs | 2 +- 4 files changed, 18 insertions(+), 15 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 12a2c2ffe..869affe8f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -652,11 +652,12 @@ dependencies = [ [[package]] name = "crypto-common" -version = "0.1.1" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "683d6b536309245c849479fba3da410962a43ed8e51c26b729208ec0ac2798d0" +checksum = "57952ca27b5e3606ff4dd79b0020231aaf9d6aa76dc05fd30137538c50bd3ce8" dependencies = [ "generic-array", + "typenum", ] [[package]] @@ -719,13 +720,12 @@ checksum = "0e25ea47919b1560c4e3b7fe0aaab9becf5b84a10325ddf7db0f0ba5e1026499" [[package]] name = "digest" -version = "0.10.1" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b697d66081d42af4fba142d56918a3cb21dc8eb63372c6b85d14f44fb9c5979b" +checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" dependencies = [ "block-buffer", "crypto-common", - "generic-array", "subtle", ] @@ -1128,10 +1128,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" [[package]] -name = "md5" -version = "0.3.8" +name = "md-5" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79c56d6a0b07f9e19282511c83fc5b086364cbae4ba8c7d5f190c3d9b0425a48" +checksum = "658646b21e0b72f7866c7038ab086d3d5e1cd6271f060fd37defb241949d0582" +dependencies = [ + "digest", +] [[package]] name = "memchr" @@ -2624,7 +2627,7 @@ dependencies = [ "digest", "hex", "libc", - "md5", + "md-5", "memchr 2.4.1", "regex", "regex-syntax", diff --git a/src/uu/hashsum/Cargo.toml b/src/uu/hashsum/Cargo.toml index 49e99c59c..ae4a385a4 100644 --- a/src/uu/hashsum/Cargo.toml +++ b/src/uu/hashsum/Cargo.toml @@ -20,7 +20,7 @@ clap = { version = "3.0", features = ["wrap_help", "cargo"] } hex = "0.4.3" libc = "0.2.42" memchr = "2" -md5 = "0.3.5" +md-5 = "0.10.1" regex = "1.0.1" regex-syntax = "0.6.7" sha1 = "0.10.0" diff --git a/src/uu/hashsum/src/digest.rs b/src/uu/hashsum/src/digest.rs index 719eadcbf..f7cae2047 100644 --- a/src/uu/hashsum/src/digest.rs +++ b/src/uu/hashsum/src/digest.rs @@ -36,17 +36,17 @@ pub trait Digest { } } -impl Digest for md5::Context { +impl Digest for md5::Md5 { fn new() -> Self { - Self::new() + Self::default() } fn input(&mut self, input: &[u8]) { - self.consume(input); + digest::Digest::update(self, input); } fn result(&mut self, out: &mut [u8]) { - out.copy_from_slice(&*self.compute()); + digest::Digest::finalize_into_reset(self, out.into()); } fn reset(&mut self) { diff --git a/src/uu/hashsum/src/hashsum.rs b/src/uu/hashsum/src/hashsum.rs index 46e8ce1b8..d92336702 100644 --- a/src/uu/hashsum/src/hashsum.rs +++ b/src/uu/hashsum/src/hashsum.rs @@ -22,7 +22,7 @@ use self::digest::DigestWriter; use clap::{App, AppSettings, Arg, ArgMatches}; use hex::encode; -use md5::Context as Md5; +use md5::Md5; use regex::Regex; use sha1::Sha1; use sha2::{Sha224, Sha256, Sha384, Sha512}; From 00d3c36e8cf06eda2a3cc8f4b7fba6050c014076 Mon Sep 17 00:00:00 2001 From: Alex Tibbles Date: Mon, 21 Feb 2022 16:55:59 -0500 Subject: [PATCH 29/58] update SHA1, SHA2, SHA3 crates --- Cargo.lock | 12 ++++++------ src/uu/hashsum/Cargo.toml | 6 +++--- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 869affe8f..fd064dc76 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1889,9 +1889,9 @@ dependencies = [ [[package]] name = "sha1" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04cc229fb94bcb689ffc39bd4ded842f6ff76885efede7c6d1ffb62582878bea" +checksum = "c77f4e7f65455545c2153c1253d25056825e77ee2533f0e41deb65a93a34852f" dependencies = [ "cfg-if 1.0.0", "cpufeatures", @@ -1900,9 +1900,9 @@ dependencies = [ [[package]] name = "sha2" -version = "0.10.1" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99c3bd8169c58782adad9290a9af5939994036b76187f7b4f0e6de91dbbfc0ec" +checksum = "55deaec60f81eefe3cce0dc50bda92d6d8e88f2a27df7c5033b42afeb1ed2676" dependencies = [ "cfg-if 1.0.0", "cpufeatures", @@ -1911,9 +1911,9 @@ dependencies = [ [[package]] name = "sha3" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31f935e31cf406e8c0e96c2815a5516181b7004ae8c5f296293221e9b1e356bd" +checksum = "881bf8156c87b6301fc5ca6b27f11eeb2761224c7081e69b409d5a1951a70c86" dependencies = [ "digest", "keccak", diff --git a/src/uu/hashsum/Cargo.toml b/src/uu/hashsum/Cargo.toml index ae4a385a4..1f8f1b923 100644 --- a/src/uu/hashsum/Cargo.toml +++ b/src/uu/hashsum/Cargo.toml @@ -23,9 +23,9 @@ memchr = "2" md-5 = "0.10.1" regex = "1.0.1" regex-syntax = "0.6.7" -sha1 = "0.10.0" -sha2 = "0.10.1" -sha3 = "0.10.0" +sha1 = "0.10.1" +sha2 = "0.10.2" +sha3 = "0.10.1" blake2b_simd = "0.5.11" blake3 = "1.3.1" uucore = { version=">=0.0.11", package="uucore", path="../../uucore" } From b1c543b4d2abee8bac47e1060942286c1fd148a0 Mon Sep 17 00:00:00 2001 From: Alex Tibbles Date: Mon, 21 Feb 2022 16:58:20 -0500 Subject: [PATCH 30/58] hashsum: re-factor SHA1 implementation into common macro --- src/uu/hashsum/src/digest.rs | 23 +---------------------- 1 file changed, 1 insertion(+), 22 deletions(-) diff --git a/src/uu/hashsum/src/digest.rs b/src/uu/hashsum/src/digest.rs index f7cae2047..8cf7f55a0 100644 --- a/src/uu/hashsum/src/digest.rs +++ b/src/uu/hashsum/src/digest.rs @@ -104,28 +104,6 @@ impl Digest for blake3::Hasher { } } -impl Digest for sha1::Sha1 { - fn new() -> Self { - Self::default() - } - - fn input(&mut self, input: &[u8]) { - digest::Digest::update(self, input); - } - - fn result(&mut self, out: &mut [u8]) { - digest::Digest::finalize_into_reset(self, out.into()); - } - - fn reset(&mut self) { - *self = Self::new(); - } - - fn output_bits(&self) -> usize { - 160 - } -} - // Implements the Digest trait for sha2 / sha3 algorithms with fixed output macro_rules! impl_digest_sha { ($type: ty, $size: expr) => { @@ -180,6 +158,7 @@ macro_rules! impl_digest_shake { }; } +impl_digest_sha!(sha1::Sha1, 160); impl_digest_sha!(sha2::Sha224, 224); impl_digest_sha!(sha2::Sha256, 256); impl_digest_sha!(sha2::Sha384, 384); From afd5fea3c8d351ce9abc3eff5006125bd3205e84 Mon Sep 17 00:00:00 2001 From: Alex Tibbles Date: Mon, 21 Feb 2022 17:00:34 -0500 Subject: [PATCH 31/58] hashsum: refactor MD5 to use macro --- src/uu/hashsum/src/digest.rs | 23 +---------------------- 1 file changed, 1 insertion(+), 22 deletions(-) diff --git a/src/uu/hashsum/src/digest.rs b/src/uu/hashsum/src/digest.rs index 8cf7f55a0..0dccf03f6 100644 --- a/src/uu/hashsum/src/digest.rs +++ b/src/uu/hashsum/src/digest.rs @@ -36,28 +36,6 @@ pub trait Digest { } } -impl Digest for md5::Md5 { - fn new() -> Self { - Self::default() - } - - fn input(&mut self, input: &[u8]) { - digest::Digest::update(self, input); - } - - fn result(&mut self, out: &mut [u8]) { - digest::Digest::finalize_into_reset(self, out.into()); - } - - fn reset(&mut self) { - *self = Self::new(); - } - - fn output_bits(&self) -> usize { - 128 - } -} - impl Digest for blake2b_simd::State { fn new() -> Self { Self::new() @@ -158,6 +136,7 @@ macro_rules! impl_digest_shake { }; } +impl_digest_sha!(md5::Md5, 128); impl_digest_sha!(sha1::Sha1, 160); impl_digest_sha!(sha2::Sha224, 224); impl_digest_sha!(sha2::Sha256, 256); From 3a0f292510a5f501ac914ab298905d3efcf4a0bb Mon Sep 17 00:00:00 2001 From: Alex Tibbles Date: Mon, 21 Feb 2022 17:04:57 -0500 Subject: [PATCH 32/58] hashsum: refactor macro name --- src/uu/hashsum/src/digest.rs | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/uu/hashsum/src/digest.rs b/src/uu/hashsum/src/digest.rs index 0dccf03f6..ff71ad75d 100644 --- a/src/uu/hashsum/src/digest.rs +++ b/src/uu/hashsum/src/digest.rs @@ -83,7 +83,7 @@ impl Digest for blake3::Hasher { } // Implements the Digest trait for sha2 / sha3 algorithms with fixed output -macro_rules! impl_digest_sha { +macro_rules! impl_digest_rustcryptocommon { ($type: ty, $size: expr) => { impl Digest for $type { fn new() -> Self { @@ -136,17 +136,17 @@ macro_rules! impl_digest_shake { }; } -impl_digest_sha!(md5::Md5, 128); -impl_digest_sha!(sha1::Sha1, 160); -impl_digest_sha!(sha2::Sha224, 224); -impl_digest_sha!(sha2::Sha256, 256); -impl_digest_sha!(sha2::Sha384, 384); -impl_digest_sha!(sha2::Sha512, 512); +impl_digest_rustcryptocommon!(md5::Md5, 128); +impl_digest_rustcryptocommon!(sha1::Sha1, 160); +impl_digest_rustcryptocommon!(sha2::Sha224, 224); +impl_digest_rustcryptocommon!(sha2::Sha256, 256); +impl_digest_rustcryptocommon!(sha2::Sha384, 384); +impl_digest_rustcryptocommon!(sha2::Sha512, 512); -impl_digest_sha!(sha3::Sha3_224, 224); -impl_digest_sha!(sha3::Sha3_256, 256); -impl_digest_sha!(sha3::Sha3_384, 384); -impl_digest_sha!(sha3::Sha3_512, 512); +impl_digest_rustcryptocommon!(sha3::Sha3_224, 224); +impl_digest_rustcryptocommon!(sha3::Sha3_256, 256); +impl_digest_rustcryptocommon!(sha3::Sha3_384, 384); +impl_digest_rustcryptocommon!(sha3::Sha3_512, 512); impl_digest_shake!(sha3::Shake128); impl_digest_shake!(sha3::Shake256); From 163472ff7bd16567ebf2d891f36b75c96f3f7b9b Mon Sep 17 00:00:00 2001 From: Alex Tibbles Date: Mon, 21 Feb 2022 20:43:42 -0500 Subject: [PATCH 33/58] hashsum: pass spell checker --- src/uu/hashsum/src/digest.rs | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/uu/hashsum/src/digest.rs b/src/uu/hashsum/src/digest.rs index ff71ad75d..60275701e 100644 --- a/src/uu/hashsum/src/digest.rs +++ b/src/uu/hashsum/src/digest.rs @@ -83,7 +83,7 @@ impl Digest for blake3::Hasher { } // Implements the Digest trait for sha2 / sha3 algorithms with fixed output -macro_rules! impl_digest_rustcryptocommon { +macro_rules! impl_digest_common { ($type: ty, $size: expr) => { impl Digest for $type { fn new() -> Self { @@ -136,17 +136,17 @@ macro_rules! impl_digest_shake { }; } -impl_digest_rustcryptocommon!(md5::Md5, 128); -impl_digest_rustcryptocommon!(sha1::Sha1, 160); -impl_digest_rustcryptocommon!(sha2::Sha224, 224); -impl_digest_rustcryptocommon!(sha2::Sha256, 256); -impl_digest_rustcryptocommon!(sha2::Sha384, 384); -impl_digest_rustcryptocommon!(sha2::Sha512, 512); +impl_digest_common!(md5::Md5, 128); +impl_digest_common!(sha1::Sha1, 160); +impl_digest_common!(sha2::Sha224, 224); +impl_digest_common!(sha2::Sha256, 256); +impl_digest_common!(sha2::Sha384, 384); +impl_digest_common!(sha2::Sha512, 512); -impl_digest_rustcryptocommon!(sha3::Sha3_224, 224); -impl_digest_rustcryptocommon!(sha3::Sha3_256, 256); -impl_digest_rustcryptocommon!(sha3::Sha3_384, 384); -impl_digest_rustcryptocommon!(sha3::Sha3_512, 512); +impl_digest_common!(sha3::Sha3_224, 224); +impl_digest_common!(sha3::Sha3_256, 256); +impl_digest_common!(sha3::Sha3_384, 384); +impl_digest_common!(sha3::Sha3_512, 512); impl_digest_shake!(sha3::Shake128); impl_digest_shake!(sha3::Shake256); From b6c952c46ebd09774c3f3d55b3b3440aec531c97 Mon Sep 17 00:00:00 2001 From: Omer Tuchfeld Date: Sun, 6 Feb 2022 15:55:17 +0100 Subject: [PATCH 34/58] Fix `parse_size` to use u64 rather than usize for better 32-bit support Using usize limits 32-bit platforms to operate only on sizes of 4GiB or less. While 32-bit platforms only have 4GiB of addressable memory, not all operations require the data to be entirely in memory, so this limitation can be lifted if we use u64 instead of usize. This only fixes the core function, further commits fix the utilities making use of this function. --- src/uucore/src/lib/parser/parse_size.rs | 44 +++++++++---------------- 1 file changed, 15 insertions(+), 29 deletions(-) diff --git a/src/uucore/src/lib/parser/parse_size.rs b/src/uucore/src/lib/parser/parse_size.rs index 35a03ea32..e68c04e5c 100644 --- a/src/uucore/src/lib/parser/parse_size.rs +++ b/src/uucore/src/lib/parser/parse_size.rs @@ -33,14 +33,14 @@ use crate::display::Quotable; /// assert_eq!(Ok(9 * 1000), parse_size("9kB")); // kB is 1000 /// assert_eq!(Ok(2 * 1024), parse_size("2K")); // K is 1024 /// ``` -pub fn parse_size(size: &str) -> Result { +pub fn parse_size(size: &str) -> Result { if size.is_empty() { return Err(ParseSizeError::parse_failure(size)); } // Get the numeric part of the size argument. For example, if the // argument is "123K", then the numeric part is "123". let numeric_string: String = size.chars().take_while(|c| c.is_digit(10)).collect(); - let number: usize = if !numeric_string.is_empty() { + let number: u64 = if !numeric_string.is_empty() { match numeric_string.parse() { Ok(n) => n, Err(_) => return Err(ParseSizeError::parse_failure(size)), @@ -75,7 +75,7 @@ pub fn parse_size(size: &str) -> Result { "YB" | "yB" => (1000, 8), _ => return Err(ParseSizeError::parse_failure(size)), }; - let factor = match usize::try_from(base.pow(exponent)) { + let factor = match u64::try_from(base.pow(exponent)) { Ok(n) => n, Err(_) => return Err(ParseSizeError::size_too_big(size)), }; @@ -181,7 +181,7 @@ mod tests { #[test] fn all_suffixes() { - // Units are K,M,G,T,P,E,Z,Y (powers of 1024) or KB,MB,... (powers of 1000). + // Units are K,M,G,T,P,E,Z,Y (powers of 1024) or KB,MB,... (powers of 1000). // Binary prefixes can be used, too: KiB=K, MiB=M, and so on. let suffixes = [ ('K', 1u32), @@ -190,31 +190,30 @@ mod tests { ('T', 4u32), ('P', 5u32), ('E', 6u32), - #[cfg(target_pointer_width = "128")] - ('Z', 7u32), // ParseSizeError::SizeTooBig on x64 - #[cfg(target_pointer_width = "128")] - ('Y', 8u32), // ParseSizeError::SizeTooBig on x64 + // The following will always result ParseSizeError::SizeTooBig as they cannot fit in u64 + // ('Z', 7u32), + // ('Y', 8u32), ]; for &(c, exp) in &suffixes { let s = format!("2{}B", c); // KB - assert_eq!(Ok((2 * (1000_u128).pow(exp)) as usize), parse_size(&s)); + assert_eq!(Ok((2 * (1000_u128).pow(exp)) as u64), parse_size(&s)); let s = format!("2{}", c); // K - assert_eq!(Ok((2 * (1024_u128).pow(exp)) as usize), parse_size(&s)); + assert_eq!(Ok((2 * (1024_u128).pow(exp)) as u64), parse_size(&s)); let s = format!("2{}iB", c); // KiB - assert_eq!(Ok((2 * (1024_u128).pow(exp)) as usize), parse_size(&s)); + assert_eq!(Ok((2 * (1024_u128).pow(exp)) as u64), parse_size(&s)); let s = format!("2{}iB", c.to_lowercase()); // kiB - assert_eq!(Ok((2 * (1024_u128).pow(exp)) as usize), parse_size(&s)); + assert_eq!(Ok((2 * (1024_u128).pow(exp)) as u64), parse_size(&s)); // suffix only let s = format!("{}B", c); // KB - assert_eq!(Ok(((1000_u128).pow(exp)) as usize), parse_size(&s)); + assert_eq!(Ok(((1000_u128).pow(exp)) as u64), parse_size(&s)); let s = format!("{}", c); // K - assert_eq!(Ok(((1024_u128).pow(exp)) as usize), parse_size(&s)); + assert_eq!(Ok(((1024_u128).pow(exp)) as u64), parse_size(&s)); let s = format!("{}iB", c); // KiB - assert_eq!(Ok(((1024_u128).pow(exp)) as usize), parse_size(&s)); + assert_eq!(Ok(((1024_u128).pow(exp)) as u64), parse_size(&s)); let s = format!("{}iB", c.to_lowercase()); // kiB - assert_eq!(Ok(((1024_u128).pow(exp)) as usize), parse_size(&s)); + assert_eq!(Ok(((1024_u128).pow(exp)) as u64), parse_size(&s)); } } @@ -239,19 +238,6 @@ mod tests { ); } - #[test] - #[cfg(target_pointer_width = "32")] - fn overflow_x32() { - assert!(variant_eq( - &parse_size("1T").unwrap_err(), - &ParseSizeError::SizeTooBig(String::new()) - )); - assert!(variant_eq( - &parse_size("1000G").unwrap_err(), - &ParseSizeError::SizeTooBig(String::new()) - )); - } - #[test] fn invalid_syntax() { let test_strings = [ From 88dfb8d374e2e9f94f9cfbda9681122116ad9655 Mon Sep 17 00:00:00 2001 From: Omer Tuchfeld Date: Sun, 6 Feb 2022 21:21:07 +0100 Subject: [PATCH 35/58] Fix type-error when calling `parse_size` from dd --- src/uu/dd/src/datastructures.rs | 4 +- src/uu/dd/src/dd.rs | 61 +++++++++++++--------------- src/uu/dd/src/parseargs.rs | 70 +++++++++++++++++++++++---------- 3 files changed, 80 insertions(+), 55 deletions(-) diff --git a/src/uu/dd/src/datastructures.rs b/src/uu/dd/src/datastructures.rs index c9c89e858..067058bbe 100644 --- a/src/uu/dd/src/datastructures.rs +++ b/src/uu/dd/src/datastructures.rs @@ -83,8 +83,8 @@ pub struct OFlags { /// then becomes Bytes(N) #[derive(Debug, PartialEq)] pub enum CountType { - Reads(usize), - Bytes(usize), + Reads(u64), + Bytes(u64), } #[derive(Debug)] diff --git a/src/uu/dd/src/dd.rs b/src/uu/dd/src/dd.rs index 7cc6fb082..d8bc3acd3 100644 --- a/src/uu/dd/src/dd.rs +++ b/src/uu/dd/src/dd.rs @@ -37,9 +37,8 @@ use std::time; use clap::{crate_version, App, AppSettings, Arg, ArgMatches}; use gcd::Gcd; use uucore::display::Quotable; -use uucore::error::{FromIo, UResult, USimpleError}; -use uucore::show_error; -use uucore::InvalidEncodingHandling; +use uucore::error::{FromIo, UResult}; +use uucore::{show_error, InvalidEncodingHandling}; const ABOUT: &str = "copy, and optionally convert, a file system resource"; const BUF_INIT_BYTE: u8 = 0xDD; @@ -75,11 +74,13 @@ impl Input { }; if let Some(amt) = skip { - let num_bytes_read = i - .force_fill(amt.try_into().unwrap()) - .map_err_context(|| "failed to read input".to_string())?; - if num_bytes_read < amt { - show_error!("'standard input': cannot skip to specified offset"); + if let Err(e) = i.read_skip(amt) { + if let io::ErrorKind::UnexpectedEof = e.kind() { + show_error!("'standard input': cannot skip to specified offset"); + } else { + return io::Result::Err(e) + .map_err_context(|| "I/O error while skipping".to_string()); + } } } @@ -148,9 +149,6 @@ impl Input { }; if let Some(amt) = skip { - let amt: u64 = amt - .try_into() - .map_err(|_| USimpleError::new(1, "failed to parse seek amount"))?; src.seek(io::SeekFrom::Start(amt)) .map_err_context(|| "failed to seek in input file".to_string())?; } @@ -262,19 +260,18 @@ impl Input { }) } - /// Read the specified number of bytes from this reader. - /// - /// On success, this method returns the number of bytes read. If - /// this reader has fewer than `n` bytes available, then it reads - /// as many as possible. In that case, this method returns a - /// number less than `n`. - /// - /// # Errors - /// - /// If there is a problem reading. - fn force_fill(&mut self, n: u64) -> std::io::Result { - let mut buf = vec![]; - self.take(n).read_to_end(&mut buf) + /// Skips amount_to_read bytes from the Input by copying into a sink + fn read_skip(&mut self, amount_to_read: u64) -> std::io::Result<()> { + let copy_result = io::copy(&mut self.src.by_ref().take(amount_to_read), &mut io::sink()); + if let Ok(n) = copy_result { + if n != amount_to_read { + io::Result::Err(io::Error::new(io::ErrorKind::UnexpectedEof, "")) + } else { + Ok(()) + } + } else { + io::Result::Err(copy_result.unwrap_err()) + } } } @@ -301,8 +298,7 @@ impl OutputTrait for Output { // stdout is not seekable, so we just write null bytes. if let Some(amt) = seek { - let bytes = vec![b'\0'; amt]; - dst.write_all(&bytes) + io::copy(&mut io::repeat(0u8).take(amt as u64), &mut dst) .map_err_context(|| String::from("write error"))?; } @@ -526,7 +522,7 @@ impl OutputTrait for Output { // Instead, we suppress the error by calling // `Result::ok()`. This matches the behavior of GNU `dd` // when given the command-line argument `of=/dev/null`. - let i = seek.unwrap_or(0).try_into().unwrap(); + let i = seek.unwrap_or(0); if !cflags.notrunc { dst.set_len(i).ok(); } @@ -658,15 +654,14 @@ fn calc_loop_bsize( ) -> usize { match count { Some(CountType::Reads(rmax)) => { - let rmax: u64 = (*rmax).try_into().unwrap(); let rsofar = rstat.reads_complete + rstat.reads_partial; - let rremain: usize = (rmax - rsofar).try_into().unwrap(); - cmp::min(ideal_bsize, rremain * ibs) + let rremain = rmax - rsofar; + cmp::min(ideal_bsize as u64, rremain * ibs as u64) as usize } Some(CountType::Bytes(bmax)) => { let bmax: u128 = (*bmax).try_into().unwrap(); - let bremain: usize = (bmax - wstat.bytes_total).try_into().unwrap(); - cmp::min(ideal_bsize, bremain) + let bremain: u128 = bmax - wstat.bytes_total; + cmp::min(ideal_bsize as u128, bremain as u128) as usize } None => ideal_bsize, } @@ -677,7 +672,7 @@ fn calc_loop_bsize( fn below_count_limit(count: &Option, rstat: &ReadStat, wstat: &WriteStat) -> bool { match count { Some(CountType::Reads(n)) => { - let n = (*n).try_into().unwrap(); + let n = *n; rstat.reads_complete + rstat.reads_partial <= n } Some(CountType::Bytes(n)) => { diff --git a/src/uu/dd/src/parseargs.rs b/src/uu/dd/src/parseargs.rs index 7a0bad851..c8324c4ca 100644 --- a/src/uu/dd/src/parseargs.rs +++ b/src/uu/dd/src/parseargs.rs @@ -31,6 +31,10 @@ pub enum ParseError { BlockUnblockWithoutCBS, StatusLevelNotRecognized(String), Unimplemented(String), + BsOutOfRange, + IbsOutOfRange, + ObsOutOfRange, + CbsOutOfRange, } impl ParseError { @@ -48,6 +52,10 @@ impl ParseError { Self::BlockUnblockWithoutCBS => Self::BlockUnblockWithoutCBS, Self::StatusLevelNotRecognized(_) => Self::StatusLevelNotRecognized(s), Self::Unimplemented(_) => Self::Unimplemented(s), + Self::BsOutOfRange => Self::BsOutOfRange, + Self::IbsOutOfRange => Self::IbsOutOfRange, + Self::ObsOutOfRange => Self::ObsOutOfRange, + Self::CbsOutOfRange => Self::CbsOutOfRange, } } } @@ -92,6 +100,18 @@ impl std::fmt::Display for ParseError { Self::StatusLevelNotRecognized(arg) => { write!(f, "status=LEVEL not recognized -> {}", arg) } + ParseError::BsOutOfRange => { + write!(f, "bs=N cannot fit into memory") + } + ParseError::IbsOutOfRange => { + write!(f, "ibs=N cannot fit into memory") + } + ParseError::ObsOutOfRange => { + write!(f, "ibs=N cannot fit into memory") + } + ParseError::CbsOutOfRange => { + write!(f, "cbs=N cannot fit into memory") + } Self::Unimplemented(arg) => { write!(f, "feature not implemented on this system -> {}", arg) } @@ -334,7 +354,7 @@ fn show_zero_multiplier_warning() { } /// Parse bytes using str::parse, then map error if needed. -fn parse_bytes_only(s: &str) -> Result { +fn parse_bytes_only(s: &str) -> Result { s.parse() .map_err(|_| ParseError::MultiplierStringParseFailure(s.to_string())) } @@ -364,7 +384,7 @@ fn parse_bytes_only(s: &str) -> Result { /// assert_eq!(parse_bytes_no_x("2b").unwrap(), 2 * 512); /// assert_eq!(parse_bytes_no_x("2k").unwrap(), 2 * 1024); /// ``` -fn parse_bytes_no_x(s: &str) -> Result { +fn parse_bytes_no_x(s: &str) -> Result { let (num, multiplier) = match (s.find('c'), s.rfind('w'), s.rfind('b')) { (None, None, None) => match uucore::parse_size::parse_size(s) { Ok(n) => (n, 1), @@ -387,7 +407,7 @@ fn parse_bytes_no_x(s: &str) -> Result { /// Parse byte and multiplier like 512, 5KiB, or 1G. /// Uses uucore::parse_size, and adds the 'w' and 'c' suffixes which are mentioned /// in dd's info page. -fn parse_bytes_with_opt_multiplier(s: &str) -> Result { +fn parse_bytes_with_opt_multiplier(s: &str) -> Result { // TODO On my Linux system, there seems to be a maximum block size of 4096 bytes: // // $ printf "%0.sa" {1..10000} | dd bs=4095 count=1 status=none | wc -c @@ -420,9 +440,27 @@ fn parse_bytes_with_opt_multiplier(s: &str) -> Result { pub fn parse_ibs(matches: &Matches) -> Result { if let Some(mixed_str) = matches.value_of(options::BS) { - parse_bytes_with_opt_multiplier(mixed_str) + parse_bytes_with_opt_multiplier(mixed_str)? + .try_into() + .map_err(|_| ParseError::BsOutOfRange) } else if let Some(mixed_str) = matches.value_of(options::IBS) { - parse_bytes_with_opt_multiplier(mixed_str) + parse_bytes_with_opt_multiplier(mixed_str)? + .try_into() + .map_err(|_| ParseError::IbsOutOfRange) + } else { + Ok(512) + } +} + +pub fn parse_obs(matches: &Matches) -> Result { + if let Some(mixed_str) = matches.value_of("bs") { + parse_bytes_with_opt_multiplier(mixed_str)? + .try_into() + .map_err(|_| ParseError::BsOutOfRange) + } else if let Some(mixed_str) = matches.value_of("obs") { + parse_bytes_with_opt_multiplier(mixed_str)? + .try_into() + .map_err(|_| ParseError::ObsOutOfRange) } else { Ok(512) } @@ -430,7 +468,9 @@ pub fn parse_ibs(matches: &Matches) -> Result { fn parse_cbs(matches: &Matches) -> Result, ParseError> { if let Some(s) = matches.value_of(options::CBS) { - let bytes = parse_bytes_with_opt_multiplier(s)?; + let bytes = parse_bytes_with_opt_multiplier(s)? + .try_into() + .map_err(|_| ParseError::CbsOutOfRange)?; Ok(Some(bytes)) } else { Ok(None) @@ -447,16 +487,6 @@ pub(crate) fn parse_status_level(matches: &Matches) -> Result Result { - if let Some(mixed_str) = matches.value_of("bs") { - parse_bytes_with_opt_multiplier(mixed_str) - } else if let Some(mixed_str) = matches.value_of("obs") { - parse_bytes_with_opt_multiplier(mixed_str) - } else { - Ok(512) - } -} - fn parse_ctable(fmt: Option, case: Option) -> Option<&'static ConversionTable> { fn parse_conv_and_case_table( fmt: &ConvFlag, @@ -715,13 +745,13 @@ pub fn parse_skip_amt( ibs: &usize, iflags: &IFlags, matches: &Matches, -) -> Result, ParseError> { +) -> Result, ParseError> { if let Some(amt) = matches.value_of(options::SKIP) { let n = parse_bytes_with_opt_multiplier(amt)?; if iflags.skip_bytes { Ok(Some(n)) } else { - Ok(Some(ibs * n)) + Ok(Some(*ibs as u64 * n)) } } else { Ok(None) @@ -733,13 +763,13 @@ pub fn parse_seek_amt( obs: &usize, oflags: &OFlags, matches: &Matches, -) -> Result, ParseError> { +) -> Result, ParseError> { if let Some(amt) = matches.value_of(options::SEEK) { let n = parse_bytes_with_opt_multiplier(amt)?; if oflags.seek_bytes { Ok(Some(n)) } else { - Ok(Some(obs * n)) + Ok(Some(*obs as u64 * n)) } } else { Ok(None) From 0fe6017006e45885a4878d0de83b6de4297d7433 Mon Sep 17 00:00:00 2001 From: Omer Tuchfeld Date: Sun, 6 Feb 2022 21:21:46 +0100 Subject: [PATCH 36/58] Fix type-error when calling `parse_size` from du --- src/uu/du/src/du.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/uu/du/src/du.rs b/src/uu/du/src/du.rs index 34580f0ee..0bb1abf4a 100644 --- a/src/uu/du/src/du.rs +++ b/src/uu/du/src/du.rs @@ -12,7 +12,6 @@ use chrono::prelude::DateTime; use chrono::Local; use clap::{crate_version, App, AppSettings, Arg, ArgMatches}; use std::collections::HashSet; -use std::convert::TryFrom; use std::env; use std::fs; #[cfg(not(windows))] @@ -248,7 +247,7 @@ fn get_file_info(path: &Path) -> Option { result } -fn read_block_size(s: Option<&str>) -> usize { +fn read_block_size(s: Option<&str>) -> u64 { if let Some(s) = s { parse_size(s) .unwrap_or_else(|e| crash!(1, "{}", format_error_message(&e, s, options::BLOCK_SIZE))) @@ -483,7 +482,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { show_warning!("options --apparent-size and -b are ineffective with --inodes"); } - let block_size = u64::try_from(read_block_size(matches.value_of(options::BLOCK_SIZE))).unwrap(); + let block_size = read_block_size(matches.value_of(options::BLOCK_SIZE)); let threshold = matches.value_of(options::THRESHOLD).map(|s| { Threshold::from_str(s) @@ -807,7 +806,7 @@ impl FromStr for Threshold { fn from_str(s: &str) -> std::result::Result { let offset = if s.starts_with(&['-', '+'][..]) { 1 } else { 0 }; - let size = u64::try_from(parse_size(&s[offset..])?).unwrap(); + let size = parse_size(&s[offset..])?; if s.starts_with('-') { Ok(Self::Upper(size)) From 8d8e25880e14b81a0df7e53a491bd67b951d5b15 Mon Sep 17 00:00:00 2001 From: Omer Tuchfeld Date: Sun, 6 Feb 2022 21:21:52 +0100 Subject: [PATCH 37/58] Fix type-error when calling `parse_size` from head --- src/uu/head/src/head.rs | 52 +++++++++++++++++++++++++++------------- src/uu/head/src/parse.rs | 2 +- src/uu/head/src/take.rs | 4 ++-- 3 files changed, 39 insertions(+), 19 deletions(-) diff --git a/src/uu/head/src/head.rs b/src/uu/head/src/head.rs index 6691b3a6d..2dc0a682e 100644 --- a/src/uu/head/src/head.rs +++ b/src/uu/head/src/head.rs @@ -106,10 +106,10 @@ pub fn uu_app<'a>() -> App<'a> { #[derive(Debug, PartialEq)] enum Mode { - FirstLines(usize), - AllButLastLines(usize), - FirstBytes(usize), - AllButLastBytes(usize), + FirstLines(u64), + AllButLastLines(u64), + FirstBytes(u64), + AllButLastBytes(u64), } impl Default for Mode { @@ -199,12 +199,12 @@ impl HeadOptions { } } -fn read_n_bytes(input: R, n: usize) -> std::io::Result<()> +fn read_n_bytes(input: R, n: u64) -> std::io::Result<()> where R: Read, { // Read the first `n` bytes from the `input` reader. - let mut reader = input.take(n as u64); + let mut reader = input.take(n); // Write those bytes to `stdout`. let stdout = std::io::stdout(); @@ -215,7 +215,7 @@ where Ok(()) } -fn read_n_lines(input: &mut impl std::io::BufRead, n: usize, zero: bool) -> std::io::Result<()> { +fn read_n_lines(input: &mut impl std::io::BufRead, n: u64, zero: bool) -> std::io::Result<()> { // Read the first `n` lines from the `input` reader. let separator = if zero { b'\0' } else { b'\n' }; let mut reader = take_lines(input, n, separator); @@ -233,8 +233,9 @@ fn read_n_lines(input: &mut impl std::io::BufRead, n: usize, zero: bool) -> std: fn read_but_last_n_bytes(input: &mut impl std::io::BufRead, n: usize) -> std::io::Result<()> { if n == 0 { //prints everything - return read_n_bytes(input, std::usize::MAX); + return read_n_bytes(input, std::u64::MAX); } + let stdout = std::io::stdout(); let mut stdout = stdout.lock(); @@ -337,17 +338,18 @@ fn read_but_last_n_lines( /// assert_eq!(find_nth_line_from_end(&mut input, 4, false).unwrap(), 0); /// assert_eq!(find_nth_line_from_end(&mut input, 1000, false).unwrap(), 0); /// ``` -fn find_nth_line_from_end(input: &mut R, n: usize, zeroed: bool) -> std::io::Result +fn find_nth_line_from_end(input: &mut R, n: u64, zeroed: bool) -> std::io::Result where R: Read + Seek, { let size = input.seek(SeekFrom::End(0))?; - let size = usize::try_from(size).unwrap(); let mut buffer = [0u8; BUF_SIZE]; - let buffer = &mut buffer[..BUF_SIZE.min(size)]; - let mut i = 0usize; - let mut lines = 0usize; + let buf_size: usize = (BUF_SIZE as u64).min(size).try_into().unwrap(); + let buffer = &mut buffer[..buf_size]; + + let mut i = 0u64; + let mut lines = 0u64; loop { // the casts here are ok, `buffer.len()` should never be above a few k @@ -382,7 +384,7 @@ where fn head_backwards_file(input: &mut std::fs::File, options: &HeadOptions) -> std::io::Result<()> { match options.mode { Mode::AllButLastBytes(n) => { - let size = input.metadata()?.len().try_into().unwrap(); + let size = input.metadata()?.len(); if n >= size { return Ok(()); } else { @@ -431,12 +433,30 @@ fn uu_head(options: &HeadOptions) -> UResult<()> { } let stdin = std::io::stdin(); let mut stdin = stdin.lock(); + + // Outputting "all-but-last" requires us to use a ring buffer with size n, so n + // must be converted from u64 to usize to fit in memory. If such conversion fails, + // it means the platform doesn't have enough memory to hold the buffer, so we fail. + if let Mode::AllButLastLines(n) | Mode::AllButLastBytes(n) = options.mode { + if let Err(n) = usize::try_from(n) { + show!(USimpleError::new( + 1, + format!("{}: number of bytes is too large", n) + )); + continue; + }; + }; + match options.mode { Mode::FirstBytes(n) => read_n_bytes(&mut stdin, n), - Mode::AllButLastBytes(n) => read_but_last_n_bytes(&mut stdin, n), + // unwrap is guaranteed to succeed because we checked the value of n above + Mode::AllButLastBytes(n) => { + read_but_last_n_bytes(&mut stdin, n.try_into().unwrap()) + } Mode::FirstLines(n) => read_n_lines(&mut stdin, n, options.zeroed), + // unwrap is guaranteed to succeed because we checked the value of n above Mode::AllButLastLines(n) => { - read_but_last_n_lines(&mut stdin, n, options.zeroed) + read_but_last_n_lines(&mut stdin, n.try_into().unwrap(), options.zeroed) } } } diff --git a/src/uu/head/src/parse.rs b/src/uu/head/src/parse.rs index b44a8b69d..ee543fe06 100644 --- a/src/uu/head/src/parse.rs +++ b/src/uu/head/src/parse.rs @@ -97,7 +97,7 @@ pub fn parse_obsolete(src: &str) -> Option } /// Parses an -c or -n argument, /// the bool specifies whether to read from the end -pub fn parse_num(src: &str) -> Result<(usize, bool), ParseSizeError> { +pub fn parse_num(src: &str) -> Result<(u64, bool), ParseSizeError> { let mut size_string = src.trim(); let mut all_but_last = false; diff --git a/src/uu/head/src/take.rs b/src/uu/head/src/take.rs index a003f9328..47beba8a4 100644 --- a/src/uu/head/src/take.rs +++ b/src/uu/head/src/take.rs @@ -69,7 +69,7 @@ where /// details. pub struct TakeLines { inner: T, - limit: usize, + limit: u64, separator: u8, } @@ -103,7 +103,7 @@ impl Read for TakeLines { /// /// The `separator` defines the character to interpret as the line /// ending. For the usual notion of "line", set this to `b'\n'`. -pub fn take_lines(reader: R, limit: usize, separator: u8) -> TakeLines { +pub fn take_lines(reader: R, limit: u64, separator: u8) -> TakeLines { TakeLines { inner: reader, limit, From 5d861df9614e62a12d870aec4ee5a9540603c7ac Mon Sep 17 00:00:00 2001 From: Omer Tuchfeld Date: Sun, 6 Feb 2022 21:21:59 +0100 Subject: [PATCH 38/58] Fix type-error when calling `parse_size` from tail --- src/uu/tail/src/tail.rs | 41 +++++++++++++++++++++++++++++------------ 1 file changed, 29 insertions(+), 12 deletions(-) diff --git a/src/uu/tail/src/tail.rs b/src/uu/tail/src/tail.rs index 00db47c0e..27153117c 100644 --- a/src/uu/tail/src/tail.rs +++ b/src/uu/tail/src/tail.rs @@ -22,6 +22,7 @@ use chunks::ReverseChunks; use clap::{App, AppSettings, Arg}; use std::collections::VecDeque; +use std::convert::TryInto; use std::ffi::OsString; use std::fmt; use std::fs::{File, Metadata}; @@ -66,8 +67,8 @@ pub mod options { #[derive(Debug)] enum FilterMode { - Bytes(usize), - Lines(usize, u8), // (number of lines, delimiter) + Bytes(u64), + Lines(u64, u8), // (number of lines, delimiter) } impl Default for FilterMode { @@ -440,7 +441,7 @@ fn follow(readers: &mut [(T, &String)], settings: &Settings) -> URes /// ``` fn forwards_thru_file( reader: &mut R, - num_delimiters: usize, + num_delimiters: u64, delimiter: u8, ) -> std::io::Result where @@ -471,7 +472,7 @@ where /// Iterate over bytes in the file, in reverse, until we find the /// `num_delimiters` instance of `delimiter`. The `file` is left seek'd to the /// position just after that delimiter. -fn backwards_thru_file(file: &mut File, num_delimiters: usize, delimiter: u8) { +fn backwards_thru_file(file: &mut File, num_delimiters: u64, delimiter: u8) { // This variable counts the number of delimiters found in the file // so far (reading from the end of the file toward the beginning). let mut counter = 0; @@ -541,6 +542,18 @@ fn bounded_tail(file: &mut File, mode: &FilterMode, beginning: bool) { std::io::copy(file, &mut stdout).unwrap(); } +/// An alternative to [`Iterator::skip`] with u64 instead of usize. This is +/// necessary because the usize limit doesn't make sense when iterating over +/// something that's not in memory. For example, a very large file. This allows +/// us to skip data larger than 4 GiB even on 32-bit platforms. +fn skip_u64(iter: &mut impl Iterator, num: u64) { + for _ in 0..num { + if iter.next().is_none() { + break; + } + } +} + /// Collect the last elements of an iterator into a `VecDeque`. /// /// This function returns a [`VecDeque`] containing either the last @@ -553,10 +566,10 @@ fn bounded_tail(file: &mut File, mode: &FilterMode, beginning: bool) { /// /// If any element of `iter` is an [`Err`], then this function panics. fn unbounded_tail_collect( - iter: impl Iterator>, - count: usize, + mut iter: impl Iterator>, + count: u64, beginning: bool, -) -> VecDeque +) -> UResult> where E: fmt::Debug, { @@ -564,9 +577,13 @@ where // GNU `tail` seems to index bytes and lines starting at 1, not // at 0. It seems to treat `+0` and `+1` as the same thing. let i = count.max(1) - 1; - iter.skip(i as usize).map(|r| r.unwrap()).collect() + skip_u64(&mut iter, i); + Ok(iter.map(|r| r.unwrap()).collect()) } else { - RingBuffer::from_iter(iter.map(|r| r.unwrap()), count as usize).data + let count: usize = count + .try_into() + .map_err(|_| USimpleError::new(1, "Insufficient addressable memory"))?; + Ok(RingBuffer::from_iter(iter.map(|r| r.unwrap()), count).data) } } @@ -577,14 +594,14 @@ fn unbounded_tail(reader: &mut BufReader, settings: &Settings) -> UR match settings.mode { FilterMode::Lines(count, sep) => { let mut stdout = stdout(); - for line in unbounded_tail_collect(lines(reader, sep), count, settings.beginning) { + for line in unbounded_tail_collect(lines(reader, sep), count, settings.beginning)? { stdout .write_all(&line) .map_err_context(|| String::from("IO error"))?; } } FilterMode::Bytes(count) => { - for byte in unbounded_tail_collect(reader.bytes(), count, settings.beginning) { + for byte in unbounded_tail_collect(reader.bytes(), count, settings.beginning)? { if let Err(err) = stdout().write(&[byte]) { return Err(USimpleError::new(1, err.to_string())); } @@ -600,7 +617,7 @@ fn is_seekable(file: &mut T) -> bool { && file.seek(SeekFrom::Start(0)).is_ok() } -fn parse_num(src: &str) -> Result<(usize, bool), ParseSizeError> { +fn parse_num(src: &str) -> Result<(u64, bool), ParseSizeError> { let mut size_string = src.trim(); let mut starting_with = false; From 8535cd41e0b9e765b23215ee9db53307876ee615 Mon Sep 17 00:00:00 2001 From: Gilad Naaman Date: Sat, 5 Feb 2022 13:35:37 +0200 Subject: [PATCH 39/58] Fix type-error when calling `parse_size` from sort --- src/uu/sort/src/sort.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/uu/sort/src/sort.rs b/src/uu/sort/src/sort.rs index a2c636321..1c118b15a 100644 --- a/src/uu/sort/src/sort.rs +++ b/src/uu/sort/src/sort.rs @@ -33,6 +33,7 @@ use numeric_str_cmp::{human_numeric_str_cmp, numeric_str_cmp, NumInfo, NumInfoPa use rand::{thread_rng, Rng}; use rayon::prelude::*; use std::cmp::Ordering; +use std::convert::TryFrom; use std::env; use std::error::Error; use std::ffi::{OsStr, OsString}; @@ -354,7 +355,13 @@ impl GlobalSettings { } else if size_string.ends_with('b') { size_string.pop(); } - parse_size(&size_string) + let size = parse_size(&size_string)?; + usize::try_from(size).map_err(|_| { + ParseSizeError::SizeTooBig(format!( + "Buffer size {} does not fit in address space", + size + )) + }) } else { Err(ParseSizeError::ParseFailure("invalid suffix".to_string())) } From 159a1dc1db6b3211c6ac1b99f1d4280b4ec0efea Mon Sep 17 00:00:00 2001 From: Gilad Naaman Date: Sat, 5 Feb 2022 13:45:18 +0200 Subject: [PATCH 40/58] Fix type-error when calling `parse_size` from split --- src/uu/split/src/split.rs | 72 +++++++++++++++++++-------------------- 1 file changed, 35 insertions(+), 37 deletions(-) diff --git a/src/uu/split/src/split.rs b/src/uu/split/src/split.rs index ca59dfb6e..90a853efa 100644 --- a/src/uu/split/src/split.rs +++ b/src/uu/split/src/split.rs @@ -14,9 +14,11 @@ mod platform; use crate::filenames::FilenameIterator; use crate::filenames::SuffixType; use clap::{crate_version, App, AppSettings, Arg, ArgMatches}; +use std::convert::TryInto; use std::env; use std::fmt; use std::fs::{metadata, File}; +use std::io; use std::io::{stdin, BufReader, BufWriter, ErrorKind, Read, Write}; use std::num::ParseIntError; use std::path::Path; @@ -175,17 +177,17 @@ pub fn uu_app<'a>() -> App<'a> { /// The strategy for breaking up the input file into chunks. enum Strategy { /// Each chunk has the specified number of lines. - Lines(usize), + Lines(u64), /// Each chunk has the specified number of bytes. - Bytes(usize), + Bytes(u64), /// Each chunk has as many lines as possible without exceeding the /// specified number of bytes. - LineBytes(usize), + LineBytes(u64), /// Split the file into this many chunks. - Number(usize), + Number(u64), } /// An error when parsing a chunking strategy from command-line arguments. @@ -246,7 +248,7 @@ impl Strategy { } (0, 0, 0, 1) => { let s = matches.value_of(OPT_NUMBER).unwrap(); - let n = s.parse::().map_err(StrategyError::NumberOfChunks)?; + let n = s.parse::().map_err(StrategyError::NumberOfChunks)?; Ok(Self::Number(n)) } _ => Err(StrategyError::MultipleWays), @@ -399,17 +401,17 @@ struct ByteChunkWriter<'a> { settings: &'a Settings, /// The maximum number of bytes allowed for a single chunk of output. - chunk_size: usize, + chunk_size: u64, /// Running total of number of chunks that have been completed. - num_chunks_written: usize, + num_chunks_written: u64, /// Remaining capacity in number of bytes in the current chunk. /// /// This number starts at `chunk_size` and decreases as bytes are /// written. Once it reaches zero, a writer for a new chunk is /// initialized and this number gets reset to `chunk_size`. - num_bytes_remaining_in_current_chunk: usize, + num_bytes_remaining_in_current_chunk: u64, /// The underlying writer for the current chunk. /// @@ -423,7 +425,7 @@ struct ByteChunkWriter<'a> { } impl<'a> ByteChunkWriter<'a> { - fn new(chunk_size: usize, settings: &'a Settings) -> Option> { + fn new(chunk_size: u64, settings: &'a Settings) -> Option> { let mut filename_iterator = FilenameIterator::new( &settings.prefix, &settings.additional_suffix, @@ -453,7 +455,7 @@ impl<'a> Write for ByteChunkWriter<'a> { // different underlying writers. In that case, each iteration of // this loop writes to the underlying writer that corresponds to // the current chunk number. - let mut carryover_bytes_written = 0; + let mut carryover_bytes_written: usize = 0; loop { if buf.is_empty() { return Ok(carryover_bytes_written); @@ -464,19 +466,23 @@ impl<'a> Write for ByteChunkWriter<'a> { // write enough bytes to fill the current chunk, then increment // the chunk number and repeat. let n = buf.len(); - if n < self.num_bytes_remaining_in_current_chunk { + if (n as u64) < self.num_bytes_remaining_in_current_chunk { let num_bytes_written = self.inner.write(buf)?; - self.num_bytes_remaining_in_current_chunk -= num_bytes_written; + self.num_bytes_remaining_in_current_chunk -= num_bytes_written as u64; return Ok(carryover_bytes_written + num_bytes_written); } else { // Write enough bytes to fill the current chunk. - let i = self.num_bytes_remaining_in_current_chunk; + // + // Conversion to usize is safe because we checked that + // self.num_bytes_remaining_in_current_chunk is lower than + // n, which is already usize. + let i = self.num_bytes_remaining_in_current_chunk as usize; let num_bytes_written = self.inner.write(&buf[..i])?; // It's possible that the underlying writer did not // write all the bytes. if num_bytes_written < i { - self.num_bytes_remaining_in_current_chunk -= num_bytes_written; + self.num_bytes_remaining_in_current_chunk -= num_bytes_written as u64; return Ok(carryover_bytes_written + num_bytes_written); } else { // Move the window to look at only the remaining bytes. @@ -527,17 +533,17 @@ struct LineChunkWriter<'a> { settings: &'a Settings, /// The maximum number of lines allowed for a single chunk of output. - chunk_size: usize, + chunk_size: u64, /// Running total of number of chunks that have been completed. - num_chunks_written: usize, + num_chunks_written: u64, /// Remaining capacity in number of lines in the current chunk. /// /// This number starts at `chunk_size` and decreases as lines are /// written. Once it reaches zero, a writer for a new chunk is /// initialized and this number gets reset to `chunk_size`. - num_lines_remaining_in_current_chunk: usize, + num_lines_remaining_in_current_chunk: u64, /// The underlying writer for the current chunk. /// @@ -551,7 +557,7 @@ struct LineChunkWriter<'a> { } impl<'a> LineChunkWriter<'a> { - fn new(chunk_size: usize, settings: &'a Settings) -> Option> { + fn new(chunk_size: u64, settings: &'a Settings) -> Option> { let mut filename_iterator = FilenameIterator::new( &settings.prefix, &settings.additional_suffix, @@ -632,7 +638,7 @@ impl<'a> Write for LineChunkWriter<'a> { fn split_into_n_chunks_by_byte( settings: &Settings, reader: &mut R, - num_chunks: usize, + num_chunks: u64, ) -> UResult<()> where R: Read, @@ -648,16 +654,20 @@ where // files. let metadata = metadata(&settings.input).unwrap(); let num_bytes = metadata.len(); - let will_have_empty_files = settings.elide_empty_files && num_chunks as u64 > num_bytes; + let will_have_empty_files = settings.elide_empty_files && num_chunks > num_bytes; let (num_chunks, chunk_size) = if will_have_empty_files { - let num_chunks = num_bytes as usize; + let num_chunks = num_bytes; let chunk_size = 1; (num_chunks, chunk_size) } else { - let chunk_size = ((num_bytes / (num_chunks as u64)) as usize).max(1); + let chunk_size = (num_bytes / (num_chunks)).max(1); (num_chunks, chunk_size) }; + let num_chunks: usize = num_chunks + .try_into() + .map_err(|_| USimpleError::new(1, "Number of chunks too big"))?; + // This object is responsible for creating the filename for each chunk. let mut filename_iterator = FilenameIterator::new( &settings.prefix, @@ -682,29 +692,17 @@ where // Write `chunk_size` bytes from the reader into each writer // except the last. // - // Re-use the buffer to avoid re-allocating a `Vec` on each - // iteration. The contents will be completely overwritten each - // time we call `read_exact()`. - // // The last writer gets all remaining bytes so that if the number // of bytes in the input file was not evenly divisible by // `num_chunks`, we don't leave any bytes behind. - let mut buf = vec![0u8; chunk_size]; for writer in writers.iter_mut().take(num_chunks - 1) { - reader.read_exact(&mut buf)?; - writer.write_all(&buf)?; + io::copy(&mut reader.by_ref().take(chunk_size), writer)?; } // Write all the remaining bytes to the last chunk. - // - // To do this, we resize our buffer to have the necessary number - // of bytes. let i = num_chunks - 1; - let last_chunk_size = num_bytes as usize - (chunk_size * (num_chunks - 1)); - buf.resize(last_chunk_size, 0); - - reader.read_exact(&mut buf)?; - writers[i].write_all(&buf)?; + let last_chunk_size = num_bytes - (chunk_size * (num_chunks as u64 - 1)); + io::copy(&mut reader.by_ref().take(last_chunk_size), &mut writers[i])?; Ok(()) } From 6856c5dba528e15eb0ceaed41ff688a1b549d677 Mon Sep 17 00:00:00 2001 From: Gilad Naaman Date: Sat, 5 Feb 2022 14:10:05 +0200 Subject: [PATCH 41/58] Fix type-error when calling `parse_size` from truncate --- src/uu/truncate/src/truncate.rs | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/src/uu/truncate/src/truncate.rs b/src/uu/truncate/src/truncate.rs index 416afe54a..b88040fb8 100644 --- a/src/uu/truncate/src/truncate.rs +++ b/src/uu/truncate/src/truncate.rs @@ -7,7 +7,6 @@ // spell-checker:ignore (ToDO) RFILE refsize rfilename fsize tsize use clap::{crate_version, App, AppSettings, Arg}; -use std::convert::TryFrom; use std::fs::{metadata, OpenOptions}; use std::io::ErrorKind; #[cfg(unix)] @@ -20,13 +19,13 @@ use uucore::parse_size::{parse_size, ParseSizeError}; #[derive(Debug, Eq, PartialEq)] enum TruncateMode { - Absolute(usize), - Extend(usize), - Reduce(usize), - AtMost(usize), - AtLeast(usize), - RoundDown(usize), - RoundUp(usize), + Absolute(u64), + Extend(u64), + Reduce(u64), + AtMost(u64), + AtLeast(u64), + RoundDown(u64), + RoundUp(u64), } impl TruncateMode { @@ -55,7 +54,7 @@ impl TruncateMode { /// let fsize = 3; /// assert_eq!(mode.to_size(fsize), 0); /// ``` - fn to_size(&self, fsize: usize) -> usize { + fn to_size(&self, fsize: u64) -> u64 { match self { TruncateMode::Absolute(size) => *size, TruncateMode::Extend(size) => fsize + size, @@ -192,10 +191,10 @@ pub fn uu_app<'a>() -> App<'a> { /// /// If the file could not be opened, or there was a problem setting the /// size of the file. -fn file_truncate(filename: &str, create: bool, size: usize) -> std::io::Result<()> { +fn file_truncate(filename: &str, create: bool, size: u64) -> std::io::Result<()> { let path = Path::new(filename); let f = OpenOptions::new().write(true).create(create).open(path)?; - f.set_len(u64::try_from(size).unwrap()) + f.set_len(size) } /// Truncate files to a size relative to a given file. @@ -244,7 +243,7 @@ fn truncate_reference_and_size( ), _ => e.map_err_context(String::new), })?; - let fsize = metadata.len() as usize; + let fsize = metadata.len(); let tsize = mode.to_size(fsize); for filename in filenames { #[cfg(unix)] @@ -292,7 +291,7 @@ fn truncate_reference_file_only( ), _ => e.map_err_context(String::new), })?; - let tsize = metadata.len() as usize; + let tsize = metadata.len(); for filename in filenames { #[cfg(unix)] if std::fs::metadata(filename)?.file_type().is_fifo() { @@ -350,7 +349,7 @@ fn truncate_size_only(size_string: &str, filenames: &[String], create: bool) -> } Err(_) => 0, }; - let tsize = mode.to_size(fsize as usize); + let tsize = mode.to_size(fsize); match file_truncate(filename, create, tsize) { Ok(_) => continue, Err(e) if e.kind() == ErrorKind::NotFound && !create => continue, From 468ff8f0b9ce37694cacb926b5c3ec87721b6803 Mon Sep 17 00:00:00 2001 From: Omer Tuchfeld Date: Sun, 6 Feb 2022 21:59:59 +0100 Subject: [PATCH 42/58] Fix type-error when calling `parse_size` from od --- src/uu/od/src/inputoffset.rs | 8 ++++---- src/uu/od/src/od.rs | 18 ++++++++++-------- src/uu/od/src/parse_inputs.rs | 8 ++++---- src/uu/od/src/parse_nrofbytes.rs | 4 ++-- src/uu/od/src/partialreader.rs | 22 +++++++++++++--------- 5 files changed, 33 insertions(+), 27 deletions(-) diff --git a/src/uu/od/src/inputoffset.rs b/src/uu/od/src/inputoffset.rs index bc12098f8..25b439291 100644 --- a/src/uu/od/src/inputoffset.rs +++ b/src/uu/od/src/inputoffset.rs @@ -11,15 +11,15 @@ pub struct InputOffset { /// The radix to print the byte offset. NoPrefix will not print a byte offset. radix: Radix, /// The current position. Initialize at `new`, increase using `increase_position`. - byte_pos: usize, + byte_pos: u64, /// An optional label printed in parentheses, typically different from `byte_pos`, /// but will increase with the same value if `byte_pos` in increased. - label: Option, + label: Option, } impl InputOffset { /// creates a new `InputOffset` using the provided values. - pub fn new(radix: Radix, byte_pos: usize, label: Option) -> Self { + pub fn new(radix: Radix, byte_pos: u64, label: Option) -> Self { Self { radix, byte_pos, @@ -28,7 +28,7 @@ impl InputOffset { } /// Increase `byte_pos` and `label` if a label is used. - pub fn increase_position(&mut self, n: usize) { + pub fn increase_position(&mut self, n: u64) { self.byte_pos += n; if let Some(l) = self.label { self.label = Some(l + n); diff --git a/src/uu/od/src/od.rs b/src/uu/od/src/od.rs index 3786e8e68..3bbe3ab5d 100644 --- a/src/uu/od/src/od.rs +++ b/src/uu/od/src/od.rs @@ -29,6 +29,7 @@ mod prn_float; mod prn_int; use std::cmp; +use std::convert::TryFrom; use crate::byteorder_io::*; use crate::formatteriteminfo::*; @@ -111,9 +112,9 @@ pub(crate) mod options { struct OdOptions { byte_order: ByteOrder, - skip_bytes: usize, - read_bytes: Option, - label: Option, + skip_bytes: u64, + read_bytes: Option, + label: Option, input_strings: Vec, formats: Vec, line_bytes: usize, @@ -148,7 +149,7 @@ impl OdOptions { }, }; - let mut label: Option = None; + let mut label: Option = None; let parsed_input = parse_inputs(matches) .map_err(|e| USimpleError::new(1, format!("Invalid inputs: {}", e)))?; @@ -170,7 +171,8 @@ impl OdOptions { 16 } else { match parse_number_of_bytes(s) { - Ok(n) => n, + Ok(n) => usize::try_from(n) + .map_err(|_| USimpleError::new(1, format!("‘{}‘ is too large", s)))?, Err(e) => { return Err(USimpleError::new( 1, @@ -569,7 +571,7 @@ where ); } - input_offset.increase_position(length); + input_offset.increase_position(length as u64); } Err(e) => { show_error!("{}", e); @@ -648,8 +650,8 @@ fn print_bytes(prefix: &str, input_decoder: &MemoryDecoder, output_info: &Output /// `read_bytes` is an optional limit to the number of bytes to read fn open_input_peek_reader( input_strings: &[String], - skip_bytes: usize, - read_bytes: Option, + skip_bytes: u64, + read_bytes: Option, ) -> PeekReader> { // should return "impl PeekRead + Read + HasError" when supported in (stable) rust let inputs = input_strings diff --git a/src/uu/od/src/parse_inputs.rs b/src/uu/od/src/parse_inputs.rs index 9d64fc732..45e664ce3 100644 --- a/src/uu/od/src/parse_inputs.rs +++ b/src/uu/od/src/parse_inputs.rs @@ -32,7 +32,7 @@ impl<'a> CommandLineOpts for ArgMatches { #[derive(PartialEq, Debug)] pub enum CommandLineInputs { FileNames(Vec), - FileAndOffset((String, usize, Option)), + FileAndOffset((String, u64, Option)), } /// Interprets the command line inputs of od. @@ -141,7 +141,7 @@ pub fn parse_inputs_traditional(input_strings: &[&str]) -> Result Result { +pub fn parse_offset_operand(s: &str) -> Result { let mut start = 0; let mut len = s.len(); let mut radix = 8; @@ -164,7 +164,7 @@ pub fn parse_offset_operand(s: &str) -> Result { radix = 10; } } - match usize::from_str_radix(&s[start..len], radix) { + match u64::from_str_radix(&s[start..len], radix) { Ok(i) => Ok(i * multiply), Err(_) => Err("parse failed"), } @@ -332,7 +332,7 @@ mod tests { .unwrap_err(); } - fn parse_offset_operand_str(s: &str) -> Result { + fn parse_offset_operand_str(s: &str) -> Result { parse_offset_operand(&String::from(s)) } diff --git a/src/uu/od/src/parse_nrofbytes.rs b/src/uu/od/src/parse_nrofbytes.rs index d6329c60a..ad00452aa 100644 --- a/src/uu/od/src/parse_nrofbytes.rs +++ b/src/uu/od/src/parse_nrofbytes.rs @@ -1,6 +1,6 @@ use uucore::parse_size::{parse_size, ParseSizeError}; -pub fn parse_number_of_bytes(s: &str) -> Result { +pub fn parse_number_of_bytes(s: &str) -> Result { let mut start = 0; let mut len = s.len(); let mut radix = 16; @@ -65,7 +65,7 @@ pub fn parse_number_of_bytes(s: &str) -> Result { _ => {} } - let factor = match usize::from_str_radix(&s[start..len], radix) { + let factor = match u64::from_str_radix(&s[start..len], radix) { Ok(f) => f, Err(e) => return Err(ParseSizeError::ParseFailure(e.to_string())), }; diff --git a/src/uu/od/src/partialreader.rs b/src/uu/od/src/partialreader.rs index 68e3f30a1..8b51d8dee 100644 --- a/src/uu/od/src/partialreader.rs +++ b/src/uu/od/src/partialreader.rs @@ -15,15 +15,15 @@ const MAX_SKIP_BUFFER: usize = 16 * 1024; /// number of bytes. pub struct PartialReader { inner: R, - skip: usize, - limit: Option, + skip: u64, + limit: Option, } impl PartialReader { /// Create a new `PartialReader` wrapping `inner`, which will skip /// `skip` bytes, and limits the output to `limit` bytes. Set `limit` /// to `None` if there should be no limit. - pub fn new(inner: R, skip: usize, limit: Option) -> Self { + pub fn new(inner: R, skip: u64, limit: Option) -> Self { Self { inner, skip, limit } } } @@ -34,7 +34,7 @@ impl Read for PartialReader { let mut bytes = [0; MAX_SKIP_BUFFER]; while self.skip > 0 { - let skip_count = cmp::min(self.skip, MAX_SKIP_BUFFER); + let skip_count: usize = cmp::min(self.skip as usize, MAX_SKIP_BUFFER); match self.inner.read(&mut bytes[..skip_count])? { 0 => { @@ -44,7 +44,7 @@ impl Read for PartialReader { "tried to skip past end of input", )); } - n => self.skip -= n, + n => self.skip -= n as u64, } } } @@ -53,15 +53,15 @@ impl Read for PartialReader { None => self.inner.read(out), Some(0) => Ok(0), Some(ref mut limit) => { - let slice = if *limit > out.len() { + let slice = if *limit > (out.len() as u64) { out } else { - &mut out[0..*limit] + &mut out[0..(*limit as usize)] }; match self.inner.read(slice) { Err(e) => Err(e), Ok(r) => { - *limit -= r; + *limit -= r as u64; Ok(r) } } @@ -145,7 +145,11 @@ mod tests { fn test_read_skipping_huge_number() { let mut v = [0; 10]; // test if it does not eat all memory.... - let mut sut = PartialReader::new(Cursor::new(&b"abcdefgh"[..]), usize::max_value(), None); + let mut sut = PartialReader::new( + Cursor::new(&b"abcdefgh"[..]), + usize::max_value() as u64, + None, + ); sut.read(v.as_mut()).unwrap_err(); } From e9adf979d9bbc62bda882634531cc7350b7c117c Mon Sep 17 00:00:00 2001 From: Omer Tuchfeld Date: Sun, 6 Feb 2022 22:15:27 +0100 Subject: [PATCH 43/58] Fix type-error when calling `parse_size` from stdbuf --- src/uu/stdbuf/src/stdbuf.rs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/uu/stdbuf/src/stdbuf.rs b/src/uu/stdbuf/src/stdbuf.rs index a568ab277..d6e136dcb 100644 --- a/src/uu/stdbuf/src/stdbuf.rs +++ b/src/uu/stdbuf/src/stdbuf.rs @@ -11,7 +11,7 @@ extern crate uucore; use clap::{crate_version, App, AppSettings, Arg, ArgMatches}; -use std::convert::TryFrom; +use std::convert::{TryFrom, TryInto}; use std::fs::File; use std::io::{self, Write}; use std::os::unix::process::ExitStatusExt; @@ -117,7 +117,14 @@ fn check_option(matches: &ArgMatches, name: &str) -> Result parse_size(x).map_or_else( |e| crash!(125, "invalid mode {}", e), - |m| Ok(BufferType::Size(m)), + |m| { + Ok(BufferType::Size(m.try_into().map_err(|_| { + ProgramOptionsError(format!( + "invalid mode {}: Value too large for defined data type", + x + )) + })?)) + }, ), }, None => Ok(BufferType::Default), From fa608983542632b7b32b143f7736fad5804c6af8 Mon Sep 17 00:00:00 2001 From: Omer Tuchfeld Date: Tue, 22 Feb 2022 11:09:22 +0100 Subject: [PATCH 44/58] Adjust 32-bit tests for tail,split,truncate,head --- tests/by-util/test_head.rs | 9 +-------- tests/by-util/test_split.rs | 9 +-------- tests/by-util/test_tail.rs | 5 +---- tests/by-util/test_truncate.rs | 9 +-------- 4 files changed, 4 insertions(+), 28 deletions(-) diff --git a/tests/by-util/test_head.rs b/tests/by-util/test_head.rs index 1c4a01557..46ef59d99 100644 --- a/tests/by-util/test_head.rs +++ b/tests/by-util/test_head.rs @@ -302,14 +302,7 @@ fn test_head_invalid_num() { { let sizes = ["1000G", "10T"]; for size in &sizes { - new_ucmd!() - .args(&["-c", size]) - .fails() - .code_is(1) - .stderr_only(format!( - "head: invalid number of bytes: '{}': Value too large for defined data type", - size - )); + new_ucmd!().args(&["-c", size]).succeeds(); } } new_ucmd!() diff --git a/tests/by-util/test_split.rs b/tests/by-util/test_split.rs index 9454687ac..d888c859f 100644 --- a/tests/by-util/test_split.rs +++ b/tests/by-util/test_split.rs @@ -337,14 +337,7 @@ fn test_split_invalid_bytes_size() { { let sizes = ["1000G", "10T"]; for size in &sizes { - new_ucmd!() - .args(&["-b", size]) - .fails() - .code_is(1) - .stderr_only(format!( - "split: invalid number of bytes: '{}': Value too large for defined data type", - size - )); + new_ucmd!().args(&["-b", size]).succeeds(); } } } diff --git a/tests/by-util/test_tail.rs b/tests/by-util/test_tail.rs index ebcd29cf5..bc757c3d1 100644 --- a/tests/by-util/test_tail.rs +++ b/tests/by-util/test_tail.rs @@ -485,10 +485,7 @@ fn test_tail_invalid_num() { .args(&["-c", size]) .fails() .code_is(1) - .stderr_only(format!( - "tail: invalid number of bytes: '{}': Value too large for defined data type", - size - )); + .stderr_only("tail: Insufficient addressable memory"); } } new_ucmd!() diff --git a/tests/by-util/test_truncate.rs b/tests/by-util/test_truncate.rs index 214eb3eda..1a5716574 100644 --- a/tests/by-util/test_truncate.rs +++ b/tests/by-util/test_truncate.rs @@ -323,14 +323,7 @@ fn test_truncate_bytes_size() { { let sizes = ["1000G", "10T"]; for size in &sizes { - new_ucmd!() - .args(&["--size", size, "file"]) - .fails() - .code_is(1) - .stderr_only(format!( - "truncate: Invalid number: '{}': Value too large for defined data type", - size - )); + new_ucmd!().args(&["--size", size, "file"]).succeeds(); } } } From f3895124b9fdc05a23c6fc86f51471e28bbd3c79 Mon Sep 17 00:00:00 2001 From: Omer Tuchfeld Date: Tue, 22 Feb 2022 14:23:46 +0100 Subject: [PATCH 45/58] Remove impractical test creating a file too large --- tests/by-util/test_truncate.rs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/tests/by-util/test_truncate.rs b/tests/by-util/test_truncate.rs index 1a5716574..0d3739646 100644 --- a/tests/by-util/test_truncate.rs +++ b/tests/by-util/test_truncate.rs @@ -319,13 +319,6 @@ fn test_truncate_bytes_size() { .fails() .code_is(1) .stderr_only("truncate: Invalid number: '1Y': Value too large for defined data type"); - #[cfg(target_pointer_width = "32")] - { - let sizes = ["1000G", "10T"]; - for size in &sizes { - new_ucmd!().args(&["--size", size, "file"]).succeeds(); - } - } } /// Test that truncating a non-existent file creates that file. From 0ce22f3a08f3c026fc2c8ebef9becb9a5eb37d70 Mon Sep 17 00:00:00 2001 From: Omer Tuchfeld Date: Tue, 22 Feb 2022 20:58:41 +0100 Subject: [PATCH 46/58] Improve coverage / error messages from `parse_size` PR https://github.com/uutils/coreutils/pull/3084 (2a333ab3919484c5a8cacb294e705dbc2408ce78) had some missing coverage and was merged before I had a chance to fix it. This PR adds some coverage / improved error messages that were missing from that previous PR. --- src/uu/dd/src/parseargs.rs | 2 +- src/uu/head/src/head.rs | 4 ++-- src/uu/split/src/split.rs | 5 ++++- src/uu/stdbuf/src/stdbuf.rs | 2 +- tests/by-util/test_dd.rs | 14 ++++++++++++++ tests/by-util/test_head.rs | 10 ++++++++++ tests/by-util/test_split.rs | 25 +++++++++++++++++++++++++ tests/by-util/test_stdbuf.rs | 10 ++++++++++ 8 files changed, 67 insertions(+), 5 deletions(-) diff --git a/src/uu/dd/src/parseargs.rs b/src/uu/dd/src/parseargs.rs index c8324c4ca..081055cf9 100644 --- a/src/uu/dd/src/parseargs.rs +++ b/src/uu/dd/src/parseargs.rs @@ -107,7 +107,7 @@ impl std::fmt::Display for ParseError { write!(f, "ibs=N cannot fit into memory") } ParseError::ObsOutOfRange => { - write!(f, "ibs=N cannot fit into memory") + write!(f, "obs=N cannot fit into memory") } ParseError::CbsOutOfRange => { write!(f, "cbs=N cannot fit into memory") diff --git a/src/uu/head/src/head.rs b/src/uu/head/src/head.rs index 2dc0a682e..3bde78ec7 100644 --- a/src/uu/head/src/head.rs +++ b/src/uu/head/src/head.rs @@ -438,10 +438,10 @@ fn uu_head(options: &HeadOptions) -> UResult<()> { // must be converted from u64 to usize to fit in memory. If such conversion fails, // it means the platform doesn't have enough memory to hold the buffer, so we fail. if let Mode::AllButLastLines(n) | Mode::AllButLastBytes(n) = options.mode { - if let Err(n) = usize::try_from(n) { + if let Err(e) = usize::try_from(n) { show!(USimpleError::new( 1, - format!("{}: number of bytes is too large", n) + format!("{}: number of bytes is too large", e) )); continue; }; diff --git a/src/uu/split/src/split.rs b/src/uu/split/src/split.rs index 90a853efa..82d2f3578 100644 --- a/src/uu/split/src/split.rs +++ b/src/uu/split/src/split.rs @@ -652,7 +652,10 @@ where // bytes in the file. This ensures that we don't write empty // files. Otherwise, just write the `num_chunks - num_bytes` empty // files. - let metadata = metadata(&settings.input).unwrap(); + let metadata = metadata(&settings.input).map_err(|_| { + USimpleError::new(1, format!("{}: cannot determine file size", settings.input)) + })?; + let num_bytes = metadata.len(); let will_have_empty_files = settings.elide_empty_files && num_chunks > num_bytes; let (num_chunks, chunk_size) = if will_have_empty_files { diff --git a/src/uu/stdbuf/src/stdbuf.rs b/src/uu/stdbuf/src/stdbuf.rs index d6e136dcb..8bd28b626 100644 --- a/src/uu/stdbuf/src/stdbuf.rs +++ b/src/uu/stdbuf/src/stdbuf.rs @@ -120,7 +120,7 @@ fn check_option(matches: &ArgMatches, name: &str) -> Result = String::from("y\n").bytes().cycle().take(1024).collect(); diff --git a/tests/by-util/test_head.rs b/tests/by-util/test_head.rs index 46ef59d99..271c8f10c 100644 --- a/tests/by-util/test_head.rs +++ b/tests/by-util/test_head.rs @@ -305,6 +305,16 @@ fn test_head_invalid_num() { new_ucmd!().args(&["-c", size]).succeeds(); } } + #[cfg(target_pointer_width = "32")] + { + let sizes = ["-1000G", "-10T"]; + for size in &sizes { + new_ucmd!() + .args(&["-c", size]) + .fails() + .stderr_is("head: out of range integral type conversion attempted: number of bytes is too large"); + } + } new_ucmd!() .args(&["-c", "-³"]) .fails() diff --git a/tests/by-util/test_split.rs b/tests/by-util/test_split.rs index d888c859f..b91b945f6 100644 --- a/tests/by-util/test_split.rs +++ b/tests/by-util/test_split.rs @@ -342,6 +342,31 @@ fn test_split_invalid_bytes_size() { } } +#[test] +fn test_split_chunks_num_chunks_oversized_32() { + #[cfg(target_pointer_width = "32")] + { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + at.touch("file"); + scene + .ucmd() + .args(&["--number", "5000000000", "file"]) + .fails() + .code_is(1) + .stderr_only("split: Number of chunks too big"); + } +} + +#[test] +fn test_split_stdin_num_chunks() { + new_ucmd!() + .args(&["--number=1"]) + .fails() + .code_is(1) + .stderr_only("split: -: cannot determine file size"); +} + fn file_read(at: &AtPath, filename: &str) -> String { let mut s = String::new(); at.open(filename).read_to_string(&mut s).unwrap(); diff --git a/tests/by-util/test_stdbuf.rs b/tests/by-util/test_stdbuf.rs index e38183c25..bc723d8e7 100644 --- a/tests/by-util/test_stdbuf.rs +++ b/tests/by-util/test_stdbuf.rs @@ -75,5 +75,15 @@ fn test_stdbuf_invalid_mode_fails() { .fails() .code_is(125) .stderr_contains("stdbuf: invalid mode '1Y': Value too large for defined data type"); + #[cfg(target_pointer_width = "32")] + { + new_ucmd!() + .args(&[*option, "5GB", "head"]) + .fails() + .code_is(125) + .stderr_contains( + "stdbuf: invalid mode '5GB': Value too large for defined data type", + ); + } } } From 92d461247e077404fbe3c90b77dfd5034a5d2819 Mon Sep 17 00:00:00 2001 From: Jeffrey Finkelstein Date: Sun, 30 Jan 2022 17:15:19 -0500 Subject: [PATCH 47/58] split: extend Strategy::Number to add NumberType Make the `Strategy::Number` enumeration value more general by replacing the number parameter with a `NumberType` enum parameter. This allows a future commit to update `split` to support the various sub-strategies for the `-n`. (This commit does not add support for the other sub-strategies.) --- src/uu/split/src/split.rs | 239 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 230 insertions(+), 9 deletions(-) diff --git a/src/uu/split/src/split.rs b/src/uu/split/src/split.rs index 90a853efa..4ce46946a 100644 --- a/src/uu/split/src/split.rs +++ b/src/uu/split/src/split.rs @@ -20,7 +20,6 @@ use std::fmt; use std::fs::{metadata, File}; use std::io; use std::io::{stdin, BufReader, BufWriter, ErrorKind, Read, Write}; -use std::num::ParseIntError; use std::path::Path; use uucore::display::Quotable; use uucore::error::{FromIo, UIoError, UResult, USimpleError, UUsageError}; @@ -174,6 +173,134 @@ pub fn uu_app<'a>() -> App<'a> { ) } +/// Sub-strategy to use when splitting a file into a specific number of chunks. +#[derive(Debug, PartialEq)] +enum NumberType { + /// Split into a specific number of chunks by byte. + Bytes(u64), + + /// Split into a specific number of chunks by line (approximately). + Lines(u64), + + /// Split into a specific number of chunks by line + /// (approximately), but output only the *k*th chunk. + KthLines(u64, u64), + + /// Assign lines via round-robin to the specified number of output chunks. + RoundRobin(u64), + + /// Assign lines via round-robin to the specified number of output + /// chunks, but output only the *k*th chunk. + KthRoundRobin(u64, u64), +} + +impl NumberType { + /// The number of chunks for this number type. + fn num_chunks(&self) -> u64 { + match self { + Self::Bytes(n) => *n, + Self::Lines(n) => *n, + Self::KthLines(_, n) => *n, + Self::RoundRobin(n) => *n, + Self::KthRoundRobin(_, n) => *n, + } + } +} + +/// An error due to an invalid parameter to the `-n` command-line option. +#[derive(Debug, PartialEq)] +enum NumberTypeError { + /// The number of chunks was invalid. + /// + /// This can happen if the value of `N` in any of the following + /// command-line options is not a positive integer: + /// + /// ```ignore + /// -n N + /// -n l/N + /// -n l/K/N + /// -n r/N + /// -n r/K/N + /// ``` + NumberOfChunks(String), + + /// The chunk number was invalid. + /// + /// This can happen if the value of `K` in any of the following + /// command-line options is not a positive integer: + /// + /// ```ignore + /// -n l/K/N + /// -n r/K/N + /// ``` + ChunkNumber(String), +} + +impl NumberType { + /// Parse a `NumberType` from a string. + /// + /// The following strings are valid arguments: + /// + /// ```ignore + /// "N" + /// "l/N" + /// "l/K/N" + /// "r/N" + /// "r/K/N" + /// ``` + /// + /// The `N` represents the number of chunks and the `K` represents + /// a chunk number. + /// + /// # Errors + /// + /// If the string is not one of the valid number types, if `K` is + /// not a nonnegative integer, or if `N` is not a positive + /// integer, then this function returns [`NumberTypeError`]. + fn from(s: &str) -> Result { + let parts: Vec<&str> = s.split('/').collect(); + match &parts[..] { + [n_str] => { + let num_chunks = n_str + .parse() + .map_err(|_| NumberTypeError::NumberOfChunks(n_str.to_string()))?; + Ok(Self::Bytes(num_chunks)) + } + ["l", n_str] => { + let num_chunks = n_str + .parse() + .map_err(|_| NumberTypeError::NumberOfChunks(n_str.to_string()))?; + Ok(Self::Lines(num_chunks)) + } + ["l", k_str, n_str] => { + let num_chunks = n_str + .parse() + .map_err(|_| NumberTypeError::NumberOfChunks(n_str.to_string()))?; + let chunk_number = k_str + .parse() + .map_err(|_| NumberTypeError::ChunkNumber(k_str.to_string()))?; + Ok(Self::KthLines(chunk_number, num_chunks)) + } + ["r", n_str] => { + let num_chunks = n_str + .parse() + .map_err(|_| NumberTypeError::NumberOfChunks(n_str.to_string()))?; + Ok(Self::RoundRobin(num_chunks)) + } + ["r", k_str, n_str] => { + let num_chunks = n_str + .parse() + .map_err(|_| NumberTypeError::NumberOfChunks(n_str.to_string()))?; + let chunk_number = k_str + .parse() + .map_err(|_| NumberTypeError::ChunkNumber(k_str.to_string()))?; + Ok(Self::KthRoundRobin(chunk_number, num_chunks)) + } + _ => Err(NumberTypeError::NumberOfChunks(s.to_string())), + } + } +} + /// The strategy for breaking up the input file into chunks. enum Strategy { /// Each chunk has the specified number of lines. @@ -187,7 +314,10 @@ enum Strategy { LineBytes(u64), /// Split the file into this many chunks. - Number(u64), + /// + /// There are several sub-strategies available, as defined by + /// [`NumberType`]. + Number(NumberType), } /// An error when parsing a chunking strategy from command-line arguments. @@ -198,8 +328,8 @@ enum StrategyError { /// Invalid number of bytes. Bytes(ParseSizeError), - /// Invalid number of chunks. - NumberOfChunks(ParseIntError), + /// Invalid number type. + NumberType(NumberTypeError), /// Multiple chunking strategies were specified (but only one should be). MultipleWays, @@ -210,7 +340,12 @@ impl fmt::Display for StrategyError { match self { Self::Lines(e) => write!(f, "invalid number of lines: {}", e), Self::Bytes(e) => write!(f, "invalid number of bytes: {}", e), - Self::NumberOfChunks(e) => write!(f, "invalid number of chunks: {}", e), + Self::NumberType(NumberTypeError::NumberOfChunks(s)) => { + write!(f, "invalid number of chunks: {}", s) + } + Self::NumberType(NumberTypeError::ChunkNumber(s)) => { + write!(f, "invalid chunk number: {}", s) + } Self::MultipleWays => write!(f, "cannot split in more than one way"), } } @@ -248,8 +383,8 @@ impl Strategy { } (0, 0, 0, 1) => { let s = matches.value_of(OPT_NUMBER).unwrap(); - let n = s.parse::().map_err(StrategyError::NumberOfChunks)?; - Ok(Self::Number(n)) + let number_type = NumberType::from(s).map_err(StrategyError::NumberType)?; + Ok(Self::Number(number_type)) } _ => Err(StrategyError::MultipleWays), } @@ -356,7 +491,8 @@ impl Settings { let suffix_length: usize = suffix_length_str .parse() .map_err(|_| SettingsError::SuffixNotParsable(suffix_length_str.to_string()))?; - if let Strategy::Number(chunks) = strategy { + if let Strategy::Number(ref number_type) = strategy { + let chunks = number_type.num_chunks(); if suffix_length != 0 { let required_suffix_length = (chunks as f64).log(suffix_type.radix() as f64).ceil() as usize; @@ -723,9 +859,10 @@ fn split(settings: &Settings) -> UResult<()> { }); match settings.strategy { - Strategy::Number(num_chunks) => { + Strategy::Number(NumberType::Bytes(num_chunks)) => { split_into_n_chunks_by_byte(settings, &mut reader, num_chunks) } + Strategy::Number(_) => Err(USimpleError::new(1, "-n mode not yet fully implemented")), Strategy::Lines(chunk_size) => { let mut writer = LineChunkWriter::new(chunk_size, settings) .ok_or_else(|| USimpleError::new(1, "output file suffixes exhausted"))?; @@ -766,3 +903,87 @@ fn split(settings: &Settings) -> UResult<()> { } } } + +#[cfg(test)] +mod tests { + + use crate::NumberType; + use crate::NumberTypeError; + + #[test] + fn test_number_type_from() { + assert_eq!(NumberType::from("123").unwrap(), NumberType::Bytes(123)); + assert_eq!(NumberType::from("l/123").unwrap(), NumberType::Lines(123)); + assert_eq!( + NumberType::from("l/123/456").unwrap(), + NumberType::KthLines(123, 456) + ); + assert_eq!( + NumberType::from("r/123").unwrap(), + NumberType::RoundRobin(123) + ); + assert_eq!( + NumberType::from("r/123/456").unwrap(), + NumberType::KthRoundRobin(123, 456) + ); + } + + #[test] + fn test_number_type_from_error() { + assert_eq!( + NumberType::from("xyz").unwrap_err(), + NumberTypeError::NumberOfChunks("xyz".to_string()) + ); + assert_eq!( + NumberType::from("l/xyz").unwrap_err(), + NumberTypeError::NumberOfChunks("xyz".to_string()) + ); + assert_eq!( + NumberType::from("l/123/xyz").unwrap_err(), + NumberTypeError::NumberOfChunks("xyz".to_string()) + ); + assert_eq!( + NumberType::from("l/abc/456").unwrap_err(), + NumberTypeError::ChunkNumber("abc".to_string()) + ); + // In GNU split, the number of chunks get precedence: + // + // $ split -n l/abc/xyz + // split: invalid number of chunks: ‘xyz’ + // + assert_eq!( + NumberType::from("l/abc/xyz").unwrap_err(), + NumberTypeError::NumberOfChunks("xyz".to_string()) + ); + assert_eq!( + NumberType::from("r/xyz").unwrap_err(), + NumberTypeError::NumberOfChunks("xyz".to_string()) + ); + assert_eq!( + NumberType::from("r/123/xyz").unwrap_err(), + NumberTypeError::NumberOfChunks("xyz".to_string()) + ); + assert_eq!( + NumberType::from("r/abc/456").unwrap_err(), + NumberTypeError::ChunkNumber("abc".to_string()) + ); + // In GNU split, the number of chunks get precedence: + // + // $ split -n r/abc/xyz + // split: invalid number of chunks: ‘xyz’ + // + assert_eq!( + NumberType::from("r/abc/xyz").unwrap_err(), + NumberTypeError::NumberOfChunks("xyz".to_string()) + ); + } + + #[test] + fn test_number_type_num_chunks() { + assert_eq!(NumberType::from("123").unwrap().num_chunks(), 123); + assert_eq!(NumberType::from("l/123").unwrap().num_chunks(), 123); + assert_eq!(NumberType::from("l/123/456").unwrap().num_chunks(), 456); + assert_eq!(NumberType::from("r/123").unwrap().num_chunks(), 123); + assert_eq!(NumberType::from("r/123/456").unwrap().num_chunks(), 456); + } +} From dbbee573ab807121913eb75fc2941e4235c1461d Mon Sep 17 00:00:00 2001 From: Jeffrey Finkelstein Date: Sun, 30 Jan 2022 17:27:05 -0500 Subject: [PATCH 48/58] split: add support for "-n l/NUM" option to split Add support for `split -n l/NUM`. Previously, `split` only supported `-n NUM`, which splits a file into `NUM` chunks by byte. The `-n l/NUM` strategy splits a file into `NUM` chunks without splitting lines across chunks. --- src/uu/split/src/split.rs | 72 ++++++++++++++++++++++++++++++++++++- tests/by-util/test_split.rs | 17 +++++++++ 2 files changed, 88 insertions(+), 1 deletion(-) diff --git a/src/uu/split/src/split.rs b/src/uu/split/src/split.rs index 4ce46946a..dafe01d77 100644 --- a/src/uu/split/src/split.rs +++ b/src/uu/split/src/split.rs @@ -19,7 +19,7 @@ use std::env; use std::fmt; use std::fs::{metadata, File}; use std::io; -use std::io::{stdin, BufReader, BufWriter, ErrorKind, Read, Write}; +use std::io::{stdin, BufRead, BufReader, BufWriter, ErrorKind, Read, Write}; use std::path::Path; use uucore::display::Quotable; use uucore::error::{FromIo, UIoError, UResult, USimpleError, UUsageError}; @@ -845,6 +845,73 @@ where .map_err_context(|| "I/O error".to_string()) } +/// Split a file into a specific number of chunks by line. +/// +/// This function always creates one output file for each chunk, even +/// if there is an error reading or writing one of the chunks or if +/// the input file is truncated. However, if the `filter` option is +/// being used, then no files are created. +/// +/// # Errors +/// +/// This function returns an error if there is a problem reading from +/// `reader` or writing to one of the output files. +fn split_into_n_chunks_by_line( + settings: &Settings, + reader: &mut R, + num_chunks: u64, +) -> UResult<()> +where + R: BufRead, +{ + // Get the size of the input file in bytes and compute the number + // of bytes per chunk. + let metadata = metadata(&settings.input).unwrap(); + let num_bytes = metadata.len(); + let chunk_size = (num_bytes / (num_chunks as u64)) as usize; + + // This object is responsible for creating the filename for each chunk. + let mut filename_iterator = FilenameIterator::new( + &settings.prefix, + &settings.additional_suffix, + settings.suffix_length, + settings.suffix_type, + ); + + // Create one writer for each chunk. This will create each + // of the underlying files (if not in `--filter` mode). + let mut writers = vec![]; + for _ in 0..num_chunks { + let filename = filename_iterator + .next() + .ok_or_else(|| USimpleError::new(1, "output file suffixes exhausted"))?; + let writer = platform::instantiate_current_writer(&settings.filter, filename.as_str()); + writers.push(writer); + } + + let mut num_bytes_remaining_in_current_chunk = chunk_size; + let mut i = 0; + for line_result in reader.lines() { + let line = line_result.unwrap(); + let maybe_writer = writers.get_mut(i); + let writer = maybe_writer.unwrap(); + let bytes = line.as_bytes(); + writer.write_all(bytes)?; + writer.write_all(b"\n")?; + + // Add one byte for the newline character. + let num_bytes = bytes.len() + 1; + if num_bytes > num_bytes_remaining_in_current_chunk { + num_bytes_remaining_in_current_chunk = chunk_size; + i += 1; + } else { + num_bytes_remaining_in_current_chunk -= num_bytes; + } + } + + Ok(()) +} + fn split(settings: &Settings) -> UResult<()> { let mut reader = BufReader::new(if settings.input == "-" { Box::new(stdin()) as Box @@ -862,6 +929,9 @@ fn split(settings: &Settings) -> UResult<()> { Strategy::Number(NumberType::Bytes(num_chunks)) => { split_into_n_chunks_by_byte(settings, &mut reader, num_chunks) } + Strategy::Number(NumberType::Lines(num_chunks)) => { + split_into_n_chunks_by_line(settings, &mut reader, num_chunks) + } Strategy::Number(_) => Err(USimpleError::new(1, "-n mode not yet fully implemented")), Strategy::Lines(chunk_size) => { let mut writer = LineChunkWriter::new(chunk_size, settings) diff --git a/tests/by-util/test_split.rs b/tests/by-util/test_split.rs index d888c859f..c44791bb3 100644 --- a/tests/by-util/test_split.rs +++ b/tests/by-util/test_split.rs @@ -545,3 +545,20 @@ fn test_elide_empty_files() { assert_eq!(at.read("xac"), "c"); assert!(!at.plus("xad").exists()); } + +#[test] +fn test_lines() { + let (at, mut ucmd) = at_and_ucmd!(); + + let file_read = |f| { + let mut s = String::new(); + at.open(f).read_to_string(&mut s).unwrap(); + s + }; + + // Split into two files without splitting up lines. + ucmd.args(&["-n", "l/2", "fivelines.txt"]).succeeds(); + + assert_eq!(file_read("xaa"), "1\n2\n3\n"); + assert_eq!(file_read("xab"), "4\n5\n"); +} From fc8e9f20ea479cc3a2202f59a5f8874fcc03b4b0 Mon Sep 17 00:00:00 2001 From: Jeffrey Finkelstein Date: Tue, 22 Feb 2022 22:48:38 -0500 Subject: [PATCH 49/58] df: correctly scale bytes by block size Change `df` so that it correctly scales numbers of bytes by the default block size, 1024, when neither -h nor -H are specified on the command-line. Previously, it was not scaling the number of bytes in this case. Fixes #3058. --- src/uu/df/src/df.rs | 57 +++++++++++++++++++++++++++++++++++------- src/uu/df/src/table.rs | 50 +++++++++++++----------------------- 2 files changed, 65 insertions(+), 42 deletions(-) diff --git a/src/uu/df/src/df.rs b/src/uu/df/src/df.rs index 55e2ca458..8d5eacd79 100644 --- a/src/uu/df/src/df.rs +++ b/src/uu/df/src/df.rs @@ -61,6 +61,52 @@ struct FsSelector { exclude: HashSet, } +/// A block size to use in condensing the display of a large number of bytes. +/// +/// The [`Bytes`] variant represents a static block size. The +/// [`HumanReadableDecimal`] and [`HumanReadableBinary`] variants +/// represent dynamic block sizes: as the number of bytes increases, +/// the divisor increases as well (for example, from 1 to 1,000 to +/// 1,000,000 and so on in the case of [`HumanReadableDecimal`]). +/// +/// The default variant is `Bytes(1024)`. +enum BlockSize { + /// A fixed number of bytes. + /// + /// The number must be positive. + Bytes(u64), + + /// Use the largest divisor corresponding to a unit, like B, K, M, G, etc. + /// + /// This variant represents powers of 1,000. Contrast with + /// [`HumanReadableBinary`], which represents powers of 1,024. + HumanReadableDecimal, + + /// Use the largest divisor corresponding to a unit, like B, K, M, G, etc. + /// + /// This variant represents powers of 1,000. Contrast with + /// [`HumanReadableBinary`], which represents powers of 1,024. + HumanReadableBinary, +} + +impl Default for BlockSize { + fn default() -> Self { + Self::Bytes(1024) + } +} + +impl From<&ArgMatches> for BlockSize { + fn from(matches: &ArgMatches) -> Self { + if matches.is_present(OPT_HUMAN_READABLE) { + Self::HumanReadableBinary + } else if matches.is_present(OPT_HUMAN_READABLE_2) { + Self::HumanReadableDecimal + } else { + Self::default() + } + } +} + #[derive(Default)] struct Options { show_local_fs: bool, @@ -68,8 +114,7 @@ struct Options { show_listed_fs: bool, show_fs_type: bool, show_inode_instead: bool, - // block_size: usize, - human_readable_base: i64, + block_size: BlockSize, fs_selector: FsSelector, } @@ -82,13 +127,7 @@ impl Options { show_listed_fs: false, show_fs_type: matches.is_present(OPT_PRINT_TYPE), show_inode_instead: matches.is_present(OPT_INODES), - human_readable_base: if matches.is_present(OPT_HUMAN_READABLE) { - 1024 - } else if matches.is_present(OPT_HUMAN_READABLE_2) { - 1000 - } else { - -1 - }, + block_size: BlockSize::from(matches), fs_selector: FsSelector::from(matches), } } diff --git a/src/uu/df/src/table.rs b/src/uu/df/src/table.rs index 5876bf7d2..2f7fba456 100644 --- a/src/uu/df/src/table.rs +++ b/src/uu/df/src/table.rs @@ -11,7 +11,7 @@ //! [`DisplayRow`] implements [`std::fmt::Display`]. use number_prefix::NumberPrefix; -use crate::{Filesystem, Options}; +use crate::{BlockSize, Filesystem, Options}; use uucore::fsext::{FsUsage, MountInfo}; use std::fmt; @@ -147,23 +147,10 @@ impl<'a> DisplayRow<'a> { /// /// If the scaling factor is not 1000, 1024, or a negative number. fn scaled(&self, size: u64) -> Result { - // TODO The argument-parsing code should be responsible for - // ensuring that the `human_readable_base` number is - // positive. Then we could remove the `Err` case from this - // function. - // - // TODO We should not be using a negative number to indicate - // default behavior. The default behavior for `df` is to show - // sizes in blocks of 1K bytes each, so we should just do - // that. - // - // TODO Support arbitrary positive scaling factors (from the - // `--block-size` command-line argument). - let number_prefix = match self.options.human_readable_base { - 1000 => NumberPrefix::decimal(size as f64), - 1024 => NumberPrefix::binary(size as f64), - d if d < 0 => return Ok(size.to_string()), - _ => return Err(fmt::Error {}), + let number_prefix = match self.options.block_size { + BlockSize::HumanReadableDecimal => NumberPrefix::decimal(size as f64), + BlockSize::HumanReadableBinary => NumberPrefix::binary(size as f64), + BlockSize::Bytes(d) => return Ok((size / d).to_string()), }; match number_prefix { NumberPrefix::Standalone(bytes) => Ok(bytes.to_string()), @@ -261,7 +248,9 @@ impl fmt::Display for Header<'_> { write!(f, "{0: >12} ", "IFree")?; write!(f, "{0: >5} ", "IUse%")?; } else { - if self.options.human_readable_base == -1 { + // TODO Support arbitrary positive scaling factors (from + // the `--block-size` command-line argument). + if let BlockSize::Bytes(_) = self.options.block_size { write!(f, "{0: >12} ", "1k-blocks")?; } else { write!(f, "{0: >12} ", "Size")?; @@ -281,14 +270,11 @@ impl fmt::Display for Header<'_> { mod tests { use crate::table::{DisplayRow, Header, Row}; - use crate::Options; + use crate::{BlockSize, Options}; #[test] fn test_header_display() { - let options = Options { - human_readable_base: -1, - ..Default::default() - }; + let options = Default::default(); assert_eq!( Header::new(&options).to_string(), "Filesystem 1k-blocks Used Available Use% Mounted on " @@ -298,7 +284,6 @@ mod tests { #[test] fn test_header_display_fs_type() { let options = Options { - human_readable_base: -1, show_fs_type: true, ..Default::default() }; @@ -311,7 +296,6 @@ mod tests { #[test] fn test_header_display_inode() { let options = Options { - human_readable_base: -1, show_inode_instead: true, ..Default::default() }; @@ -324,7 +308,7 @@ mod tests { #[test] fn test_header_display_human_readable_binary() { let options = Options { - human_readable_base: 1024, + block_size: BlockSize::HumanReadableBinary, ..Default::default() }; assert_eq!( @@ -336,7 +320,7 @@ mod tests { #[test] fn test_header_display_human_readable_si() { let options = Options { - human_readable_base: 1000, + block_size: BlockSize::HumanReadableDecimal, ..Default::default() }; assert_eq!( @@ -348,7 +332,7 @@ mod tests { #[test] fn test_row_display() { let options = Options { - human_readable_base: -1, + block_size: BlockSize::Bytes(1), ..Default::default() }; let row = Row { @@ -378,7 +362,7 @@ mod tests { #[test] fn test_row_display_fs_type() { let options = Options { - human_readable_base: -1, + block_size: BlockSize::Bytes(1), show_fs_type: true, ..Default::default() }; @@ -409,7 +393,7 @@ mod tests { #[test] fn test_row_display_inodes() { let options = Options { - human_readable_base: -1, + block_size: BlockSize::Bytes(1), show_inode_instead: true, ..Default::default() }; @@ -440,7 +424,7 @@ mod tests { #[test] fn test_row_display_human_readable_si() { let options = Options { - human_readable_base: 1000, + block_size: BlockSize::HumanReadableDecimal, show_fs_type: true, ..Default::default() }; @@ -471,7 +455,7 @@ mod tests { #[test] fn test_row_display_human_readable_binary() { let options = Options { - human_readable_base: 1024, + block_size: BlockSize::HumanReadableBinary, show_fs_type: true, ..Default::default() }; From 9f71b7ac7f0a5ded9d0ac4cd9922d572593e2921 Mon Sep 17 00:00:00 2001 From: Jeffrey Finkelstein Date: Wed, 23 Feb 2022 21:08:35 -0500 Subject: [PATCH 50/58] df: correct links in BlockSize documentation --- src/uu/df/src/df.rs | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/uu/df/src/df.rs b/src/uu/df/src/df.rs index 8d5eacd79..65040627d 100644 --- a/src/uu/df/src/df.rs +++ b/src/uu/df/src/df.rs @@ -63,11 +63,12 @@ struct FsSelector { /// A block size to use in condensing the display of a large number of bytes. /// -/// The [`Bytes`] variant represents a static block size. The -/// [`HumanReadableDecimal`] and [`HumanReadableBinary`] variants -/// represent dynamic block sizes: as the number of bytes increases, -/// the divisor increases as well (for example, from 1 to 1,000 to -/// 1,000,000 and so on in the case of [`HumanReadableDecimal`]). +/// The [`BlockSize::Bytes`] variant represents a static block +/// size. The [`BlockSize::HumanReadableDecimal`] and +/// [`BlockSize::HumanReadableBinary`] variants represent dynamic +/// block sizes: as the number of bytes increases, the divisor +/// increases as well (for example, from 1 to 1,000 to 1,000,000 and +/// so on in the case of [`BlockSize::HumanReadableDecimal`]). /// /// The default variant is `Bytes(1024)`. enum BlockSize { @@ -79,13 +80,15 @@ enum BlockSize { /// Use the largest divisor corresponding to a unit, like B, K, M, G, etc. /// /// This variant represents powers of 1,000. Contrast with - /// [`HumanReadableBinary`], which represents powers of 1,024. + /// [`BlockSize::HumanReadableBinary`], which represents powers of + /// 1,024. HumanReadableDecimal, /// Use the largest divisor corresponding to a unit, like B, K, M, G, etc. /// - /// This variant represents powers of 1,000. Contrast with - /// [`HumanReadableBinary`], which represents powers of 1,024. + /// This variant represents powers of 1,024. Contrast with + /// [`BlockSize::HumanReadableDecimal`], which represents powers + /// of 1,000. HumanReadableBinary, } From 172be3a8c62e77f50d86065a26b32e2f999f63d2 Mon Sep 17 00:00:00 2001 From: ndd7xv Date: Wed, 23 Feb 2022 22:34:34 -0500 Subject: [PATCH 51/58] install: better error messages when invalid arguments executing `install` or `install file` no longer panics and insteads returns errors similar to GNU's install when it encounters similar args --- src/uu/install/src/install.rs | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/src/uu/install/src/install.rs b/src/uu/install/src/install.rs index 5a106d8fc..9b72627fa 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::display::Quotable; use uucore::entries::{grp2gid, usr2uid}; -use uucore::error::{FromIo, UError, UIoError, UResult}; +use uucore::error::{FromIo, UError, UIoError, UResult, UUsageError}; use uucore::format_usage; use uucore::mode::get_umask; use uucore::perms::{wrap_chown, Verbosity, VerbosityLevel}; @@ -442,11 +442,14 @@ fn is_new_file_path(path: &Path) -> bool { /// Returns a Result type with the Err variant containing the error message. /// fn standard(mut paths: Vec, b: &Behavior) -> UResult<()> { - let target: PathBuf = b - .target_dir - .clone() - .unwrap_or_else(|| paths.pop().unwrap()) - .into(); + let target: PathBuf = if let Some(path) = &b.target_dir { + path.into() + } else { + paths + .pop() + .ok_or_else(|| UUsageError::new(1, "missing file operand"))? + .into() + }; let sources = &paths.iter().map(PathBuf::from).collect::>(); @@ -468,7 +471,19 @@ fn standard(mut paths: Vec, b: &Behavior) -> UResult<()> { } if target.is_file() || is_new_file_path(&target) { - copy(&sources[0], &target, b) + copy( + sources.get(0).ok_or_else(|| { + UUsageError::new( + 1, + format!( + "missing destination file operand after '{}'", + target.to_str().unwrap() + ), + ) + })?, + &target, + b, + ) } else { Err(InstallError::InvalidTarget(target).into()) } From 722c5d268fdef8af83f22888ec7b27f10d43e636 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Thu, 24 Feb 2022 18:33:40 +0100 Subject: [PATCH 52/58] fix Rust 1.59 clippy lints --- src/bin/uudoc.rs | 2 +- tests/by-util/test_ls.rs | 6 +----- tests/by-util/test_relpath.rs | 2 +- 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/src/bin/uudoc.rs b/src/bin/uudoc.rs index 0ecdb96c7..a6bc16cc2 100644 --- a/src/bin/uudoc.rs +++ b/src/bin/uudoc.rs @@ -204,7 +204,7 @@ fn write_options(w: &mut impl Write, app: &App) -> io::Result<()> { writeln!( w, "
\n\n{}\n\n
", - arg.get_help().unwrap_or_default().replace("\n", "
") + arg.get_help().unwrap_or_default().replace('\n', "
") )?; } writeln!(w, "\n") diff --git a/tests/by-util/test_ls.rs b/tests/by-util/test_ls.rs index a2e9cca0c..e46b91e01 100644 --- a/tests/by-util/test_ls.rs +++ b/tests/by-util/test_ls.rs @@ -1644,11 +1644,7 @@ fn test_ls_indicator_style() { // Same test as above, but with the alternate flags. let options = vec!["--classify", "--file-type", "-p"]; for opt in options { - scene - .ucmd() - .arg(opt.to_string()) - .succeeds() - .stdout_contains(&"/"); + scene.ucmd().arg(opt).succeeds().stdout_contains(&"/"); } // Classify and File-Type all contain indicators for pipes and links. diff --git a/tests/by-util/test_relpath.rs b/tests/by-util/test_relpath.rs index 5f7d4fccf..52b829fdf 100644 --- a/tests/by-util/test_relpath.rs +++ b/tests/by-util/test_relpath.rs @@ -64,7 +64,7 @@ const TESTS: [TestCase; 10] = [ #[allow(clippy::needless_lifetimes)] fn convert_path<'a>(path: &'a str) -> Cow<'a, str> { #[cfg(windows)] - return path.replace("/", "\\").into(); + return path.replace('/', "\\").into(); #[cfg(not(windows))] return path.into(); } From b8a3795d95adbf4522a76c897ef318065290971a Mon Sep 17 00:00:00 2001 From: DevSabb <97408111+DevSabb@users.noreply.github.com> Date: Fri, 25 Feb 2022 06:11:53 -0500 Subject: [PATCH 53/58] tr: fix octal interpretation of repeat count string (#3178) * tr: fix octal interpretation of repeat count string * tr: fix formatting errors * tr: fix formatting issues 2 * tr: attempt to bypass spell check error * tr: fix spell check errors attempt 2 * tr: formatting fixes Co-authored-by: DevSabb --- src/uu/tr/src/operation.rs | 20 +++++++++++++------- tests/by-util/test_tr.rs | 22 +++++++++++++++++++++- 2 files changed, 34 insertions(+), 8 deletions(-) diff --git a/src/uu/tr/src/operation.rs b/src/uu/tr/src/operation.rs index 4d00a0af1..afb9a1881 100644 --- a/src/uu/tr/src/operation.rs +++ b/src/uu/tr/src/operation.rs @@ -277,15 +277,21 @@ impl Sequence { separated_pair(Self::parse_backslash_or_char, tag("*"), digit1), tag("]"), )(input) - .map(|(l, (c, str))| { - ( - l, - match usize::from_str_radix(str, 8) { + .map(|(l, (c, cnt_str))| { + let result = if cnt_str.starts_with('0') { + match usize::from_str_radix(cnt_str, 8) { Ok(0) => Ok(Self::CharStar(c)), Ok(count) => Ok(Self::CharRepeat(c, count)), - Err(_) => Err(BadSequence::InvalidRepeatCount(str.to_string())), - }, - ) + Err(_) => Err(BadSequence::InvalidRepeatCount(cnt_str.to_string())), + } + } else { + match cnt_str.parse::() { + Ok(0) => Ok(Self::CharStar(c)), + Ok(count) => Ok(Self::CharRepeat(c, count)), + Err(_) => Err(BadSequence::InvalidRepeatCount(cnt_str.to_string())), + } + }; + (l, result) }) } diff --git a/tests/by-util/test_tr.rs b/tests/by-util/test_tr.rs index ba41a8044..bf550a109 100644 --- a/tests/by-util/test_tr.rs +++ b/tests/by-util/test_tr.rs @@ -1,4 +1,4 @@ -// spell-checker:ignore aabbaa aabbcc aabc abbb abcc abcdefabcdef abcdefghijk abcdefghijklmn abcdefghijklmnop ABCDEFGHIJKLMNOPQRS abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ ABCDEFZZ abcxyz ABCXYZ abcxyzabcxyz ABCXYZABCXYZ acbdef alnum amzamz AMZXAMZ bbbd cclass cefgm cntrl compl dabcdef dncase Gzabcdefg PQRST upcase wxyzz xdigit xycde xyyye xyyz xyzzzzxyzzzz ZABCDEF Zamz +// spell-checker:ignore aabbaa aabbcc aabc abbb abcc abcdefabcdef abcdefghijk abcdefghijklmn abcdefghijklmnop ABCDEFGHIJKLMNOPQRS abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ ABCDEFZZ abcxyz ABCXYZ abcxyzabcxyz ABCXYZABCXYZ acbdef alnum amzamz AMZXAMZ bbbd cclass cefgm cntrl compl dabcdef dncase Gzabcdefg PQRST upcase wxyzz xdigit xycde xyyye xyyz xyzzzzxyzzzz ZABCDEF Zamz Cdefghijkl Cdefghijklmn use crate::common::util::*; #[test] @@ -869,6 +869,26 @@ fn check_against_gnu_tr_tests_o_rep_2() { .stdout_is("BCx"); } +#[test] +fn octal_repeat_count_test() { + //below will result in 8'x' and 4'y' as octal 010 = decimal 8 + new_ucmd!() + .args(&["ABCdefghijkl", "[x*010]Y"]) + .pipe_in("ABCdefghijklmn12") + .succeeds() + .stdout_is("xxxxxxxxYYYYmn12"); +} + +#[test] +fn non_octal_repeat_count_test() { + //below will result in 10'x' and 2'y' as the 10 does not have 0 prefix + new_ucmd!() + .args(&["ABCdefghijkl", "[x*10]Y"]) + .pipe_in("ABCdefghijklmn12") + .succeeds() + .stdout_is("xxxxxxxxxxYYmn12"); +} + #[test] fn check_against_gnu_tr_tests_esc() { // ['esc', qw('a\-z' A-Z), {IN=>'abc-z'}, {OUT=>'AbcBC'}], From 47be40afaeaa4176c237a1863311b54f55b74d1d Mon Sep 17 00:00:00 2001 From: ndd7xv Date: Fri, 25 Feb 2022 21:17:43 -0500 Subject: [PATCH 54/58] install: add tests for error handling --- tests/by-util/test_install.rs | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/tests/by-util/test_install.rs b/tests/by-util/test_install.rs index 23bebf224..3d78331f9 100644 --- a/tests/by-util/test_install.rs +++ b/tests/by-util/test_install.rs @@ -1100,3 +1100,27 @@ fn test_install_backup_off() { assert!(at.file_exists(file_b)); assert!(!at.file_exists(&format!("{}~", file_b))); } + +#[test] +fn test_install_missing_arguments() { + let scene = TestScenario::new(util_name!()); + + scene + .ucmd() + .fails() + .stderr_contains("install: missing file operand"); +} + +#[test] +fn test_install_missing_destination() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + let file_1 = "source_file1"; + + at.touch(file_1); + scene.ucmd().arg(file_1).fails().stderr_contains(format!( + "install: missing destination file operand after '{}'", + file_1 + )); +} From 9aca050e4af19a82a7acf5c9cf39d63280892d9c Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Mon, 31 Jan 2022 12:54:26 +0100 Subject: [PATCH 55/58] cp: override args These arguments should not have been in conflict with each other, but silently override each other. --- src/uu/cp/src/cp.rs | 36 +++++++++++++++++++++--------------- tests/by-util/test_cp.rs | 25 +++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 15 deletions(-) diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index 7256b6207..21dd74356 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -294,6 +294,13 @@ static DEFAULT_ATTRIBUTES: &[Attribute] = &[ ]; pub fn uu_app<'a>() -> App<'a> { + const MODE_ARGS: &[&str] = &[ + options::LINK, + options::REFLINK, + options::SYMBOLIC_LINK, + options::ATTRIBUTES_ONLY, + options::COPY_CONTENTS, + ]; App::new(uucore::util_name()) .version(crate_version!()) .about(ABOUT) @@ -314,17 +321,17 @@ pub fn uu_app<'a>() -> App<'a> { .arg(Arg::new(options::INTERACTIVE) .short('i') .long(options::INTERACTIVE) - .conflicts_with(options::NO_CLOBBER) + .overrides_with(options::NO_CLOBBER) .help("ask before overwriting files")) .arg(Arg::new(options::LINK) .short('l') .long(options::LINK) - .overrides_with(options::REFLINK) + .overrides_with_all(MODE_ARGS) .help("hard-link files instead of copying")) .arg(Arg::new(options::NO_CLOBBER) .short('n') .long(options::NO_CLOBBER) - .conflicts_with(options::INTERACTIVE) + .overrides_with(options::INTERACTIVE) .help("don't overwrite a file that already exists")) .arg(Arg::new(options::RECURSIVE) .short('r') @@ -344,8 +351,7 @@ pub fn uu_app<'a>() -> App<'a> { .arg(Arg::new(options::SYMBOLIC_LINK) .short('s') .long(options::SYMBOLIC_LINK) - .conflicts_with(options::LINK) - .overrides_with(options::REFLINK) + .overrides_with_all(MODE_ARGS) .help("make symbolic links instead of copying")) .arg(Arg::new(options::FORCE) .short('f') @@ -355,7 +361,7 @@ pub fn uu_app<'a>() -> App<'a> { Currently not implemented for Windows.")) .arg(Arg::new(options::REMOVE_DESTINATION) .long(options::REMOVE_DESTINATION) - .conflicts_with(options::FORCE) + .overrides_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(backup_control::arguments::backup()) @@ -370,11 +376,11 @@ pub fn uu_app<'a>() -> App<'a> { .long(options::REFLINK) .takes_value(true) .value_name("WHEN") + .overrides_with_all(MODE_ARGS) .help("control clone/CoW copies. See below")) .arg(Arg::new(options::ATTRIBUTES_ONLY) .long(options::ATTRIBUTES_ONLY) - .conflicts_with(options::COPY_CONTENTS) - .overrides_with(options::REFLINK) + .overrides_with_all(MODE_ARGS) .help("Don't copy the file data, just the attributes")) .arg(Arg::new(options::PRESERVE) .long(options::PRESERVE) @@ -384,7 +390,7 @@ pub fn uu_app<'a>() -> App<'a> { .possible_values(PRESERVABLE_ATTRIBUTES) .min_values(0) .value_name("ATTR_LIST") - .conflicts_with_all(&[options::PRESERVE_DEFAULT_ATTRIBUTES, options::NO_PRESERVE]) + .overrides_with_all(&[options::ARCHIVE, options::PRESERVE_DEFAULT_ATTRIBUTES, options::NO_PRESERVE]) // -d sets this option // --archive sets this option .help("Preserve the specified attributes (default: mode, ownership (unix only), timestamps), \ @@ -392,13 +398,13 @@ pub fn uu_app<'a>() -> App<'a> { .arg(Arg::new(options::PRESERVE_DEFAULT_ATTRIBUTES) .short('p') .long(options::PRESERVE_DEFAULT_ATTRIBUTES) - .conflicts_with_all(&[options::PRESERVE, options::NO_PRESERVE, options::ARCHIVE]) + .overrides_with_all(&[options::PRESERVE, options::NO_PRESERVE, options::ARCHIVE]) .help("same as --preserve=mode,ownership(unix only),timestamps")) .arg(Arg::new(options::NO_PRESERVE) .long(options::NO_PRESERVE) .takes_value(true) .value_name("ATTR_LIST") - .conflicts_with_all(&[options::PRESERVE_DEFAULT_ATTRIBUTES, options::PRESERVE, options::ARCHIVE]) + .overrides_with_all(&[options::PRESERVE_DEFAULT_ATTRIBUTES, options::PRESERVE, options::ARCHIVE]) .help("don't preserve the specified attributes")) .arg(Arg::new(options::PARENTS) .long(options::PARENTS) @@ -407,18 +413,18 @@ pub fn uu_app<'a>() -> App<'a> { .arg(Arg::new(options::NO_DEREFERENCE) .short('P') .long(options::NO_DEREFERENCE) - .conflicts_with(options::DEREFERENCE) + .overrides_with(options::DEREFERENCE) // -d sets this option .help("never follow symbolic links in SOURCE")) .arg(Arg::new(options::DEREFERENCE) .short('L') .long(options::DEREFERENCE) - .conflicts_with(options::NO_DEREFERENCE) + .overrides_with(options::NO_DEREFERENCE) .help("always follow symbolic links in SOURCE")) .arg(Arg::new(options::ARCHIVE) .short('a') .long(options::ARCHIVE) - .conflicts_with_all(&[options::PRESERVE_DEFAULT_ATTRIBUTES, options::PRESERVE, options::NO_PRESERVE]) + .overrides_with_all(&[options::PRESERVE_DEFAULT_ATTRIBUTES, options::PRESERVE, options::NO_PRESERVE]) .help("Same as -dR --preserve=all")) .arg(Arg::new(options::NO_DEREFERENCE_PRESERVE_LINKS) .short('d') @@ -431,7 +437,7 @@ pub fn uu_app<'a>() -> App<'a> { // TODO: implement the following args .arg(Arg::new(options::COPY_CONTENTS) .long(options::COPY_CONTENTS) - .conflicts_with(options::ATTRIBUTES_ONLY) + .overrides_with(options::ATTRIBUTES_ONLY) .help("NotImplemented: copy contents of special files when recursive")) .arg(Arg::new(options::SPARSE) .long(options::SPARSE) diff --git a/tests/by-util/test_cp.rs b/tests/by-util/test_cp.rs index cfa946d47..cb07722d7 100644 --- a/tests/by-util/test_cp.rs +++ b/tests/by-util/test_cp.rs @@ -1517,3 +1517,28 @@ fn test_cp_dir_vs_file() { .fails() .stderr_only("cp: cannot overwrite non-directory with directory"); } + +#[test] +fn test_cp_overriding_arguments() { + let s = TestScenario::new(util_name!()); + s.fixtures.touch("file1"); + for (arg1, arg2) in &[ + #[cfg(not(windows))] + ("--remove-destination", "--force"), + #[cfg(not(windows))] + ("--force", "--remove-destination"), + ("--interactive", "--no-clobber"), + ("--link", "--symbolic-link"), + ("--symbolic-link", "--link"), + ("--dereference", "--no-dereference"), + ("--no-dereference", "--dereference"), + ] { + s.ucmd() + .arg(arg1) + .arg(arg2) + .arg("file1") + .arg("file2") + .succeeds(); + s.fixtures.remove("file2"); + } +} From 7273d1f100a743da40101faf12564ce61b78ecc8 Mon Sep 17 00:00:00 2001 From: Jeffrey Finkelstein Date: Sun, 27 Feb 2022 17:33:01 -0500 Subject: [PATCH 56/58] df: use safe wrapper function for statfs() func. Replace unsafe code with a safe version of the `statfs()` function provided by `uucore`. --- src/uu/df/src/df.rs | 25 ++++--------------------- 1 file changed, 4 insertions(+), 21 deletions(-) diff --git a/src/uu/df/src/df.rs b/src/uu/df/src/df.rs index 65040627d..b7d27e245 100644 --- a/src/uu/df/src/df.rs +++ b/src/uu/df/src/df.rs @@ -9,18 +9,14 @@ mod table; #[cfg(unix)] -use uucore::fsext::statfs_fn; +use uucore::fsext::statfs; use uucore::fsext::{read_fs_list, FsUsage, MountInfo}; use uucore::{error::UResult, format_usage}; use clap::{crate_version, App, AppSettings, Arg, ArgMatches}; use std::collections::HashSet; -#[cfg(unix)] -use std::ffi::CString; use std::iter::FromIterator; -#[cfg(unix)] -use std::mem; #[cfg(windows)] use std::path::Path; @@ -183,23 +179,10 @@ impl Filesystem { } }; #[cfg(unix)] - unsafe { - let path = CString::new(_stat_path).unwrap(); - let mut statvfs = mem::zeroed(); - if statfs_fn(path.as_ptr(), &mut statvfs) < 0 { - None - } else { - Some(Self { - mount_info, - usage: FsUsage::new(statvfs), - }) - } - } + let usage = FsUsage::new(statfs(_stat_path).ok()?); #[cfg(windows)] - Some(Self { - mount_info, - usage: FsUsage::new(Path::new(&_stat_path)), - }) + let usage = FsUsage::new(Path::new(&_stat_path)); + Some(Self { mount_info, usage }) } } From 5cce2b0d9aa7aee017f157dc2006a5a3a5205dae Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Mon, 28 Feb 2022 15:16:47 +0100 Subject: [PATCH 57/58] cp: fix typo in help text --- src/uu/cp/src/cp.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index 21dd74356..9aafe762b 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -363,7 +363,7 @@ pub fn uu_app<'a>() -> App<'a> { .long(options::REMOVE_DESTINATION) .overrides_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.")) + (contrast with --force). On Windows, currently only works for writeable files.")) .arg(backup_control::arguments::backup()) .arg(backup_control::arguments::backup_no_args()) .arg(backup_control::arguments::suffix()) From eace4bc9073048d9f6e3daaf75020358bf3badd6 Mon Sep 17 00:00:00 2001 From: Eli Youngs <56945701+water-ghosts@users.noreply.github.com> Date: Thu, 3 Mar 2022 13:58:27 -0800 Subject: [PATCH 58/58] cp: Support copying FIFOs with -r (#3032) --- src/uu/cp/src/cp.rs | 44 +++++++++++++++++++++++++++++++++------- tests/by-util/test_cp.rs | 14 +++++++++++++ 2 files changed, 51 insertions(+), 7 deletions(-) diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index 9aafe762b..3673be931 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -30,6 +30,8 @@ use std::borrow::Cow; use clap::{crate_version, App, AppSettings, Arg, ArgMatches}; use filetime::FileTime; +#[cfg(unix)] +use libc::mkfifo; use quick_error::ResultExt; use std::collections::HashSet; use std::env; @@ -43,6 +45,10 @@ use std::fs::OpenOptions; use std::io; use std::io::{stdin, stdout, Write}; use std::mem; +#[cfg(unix)] +use std::os::unix::ffi::OsStrExt; +#[cfg(unix)] +use std::os::unix::fs::{FileTypeExt, PermissionsExt}; #[cfg(target_os = "linux")] use std::os::unix::io::AsRawFd; #[cfg(windows)] @@ -55,9 +61,6 @@ use uucore::error::{set_exit_code, ExitCode, UError, UResult}; use uucore::fs::{canonicalize, MissingHandling, ResolveMode}; use walkdir::WalkDir; -#[cfg(unix)] -use std::os::unix::fs::PermissionsExt; - #[cfg(target_os = "linux")] ioctl!(write ficlone with 0x94, 9; std::os::raw::c_int); @@ -150,7 +153,7 @@ pub type Target = PathBuf; pub type TargetSlice = Path; /// Specifies whether when overwrite files -#[derive(Clone, Eq, PartialEq)] +#[derive(Clone, Copy, Eq, PartialEq)] pub enum ClobberMode { Force, RemoveDestination, @@ -158,7 +161,7 @@ pub enum ClobberMode { } /// Specifies whether when overwrite files -#[derive(Clone, Eq, PartialEq)] +#[derive(Clone, Copy, Eq, PartialEq)] pub enum OverwriteMode { /// [Default] Always overwrite existing files Clobber(ClobberMode), @@ -1391,12 +1394,23 @@ fn copy_helper( let parent = dest.parent().unwrap_or(dest); fs::create_dir_all(parent)?; } - let is_symlink = fs::symlink_metadata(&source)?.file_type().is_symlink(); + + let file_type = fs::symlink_metadata(&source)?.file_type(); + let is_symlink = file_type.is_symlink(); + + #[cfg(unix)] + let is_fifo = file_type.is_fifo(); + #[cfg(not(unix))] + let is_fifo = false; + if source.as_os_str() == "/dev/null" { /* workaround a limitation of fs::copy * https://github.com/rust-lang/rust/issues/79390 */ File::create(dest).context(dest.display().to_string())?; + } else if is_fifo && options.recursive { + #[cfg(unix)] + copy_fifo(dest, options.overwrite)?; } else if is_symlink { copy_link(source, dest, symlinked_files)?; } else if options.reflink_mode != ReflinkMode::Never { @@ -1416,6 +1430,23 @@ fn copy_helper( Ok(()) } +// "Copies" a FIFO by creating a new one. This workaround is because Rust's +// built-in fs::copy does not handle FIFOs (see rust-lang/rust/issues/79390). +#[cfg(unix)] +fn copy_fifo(dest: &Path, overwrite: OverwriteMode) -> CopyResult<()> { + if dest.exists() { + overwrite.verify(dest)?; + fs::remove_file(&dest)?; + } + + let name = CString::new(dest.as_os_str().as_bytes()).unwrap(); + let err = unsafe { mkfifo(name.as_ptr(), 0o666) }; + if err == -1 { + return Err(format!("cannot create fifo {}: File exists", dest.quote()).into()); + } + Ok(()) +} + fn copy_link( source: &Path, dest: &Path, @@ -1499,7 +1530,6 @@ fn copy_on_write_macos( // Extract paths in a form suitable to be passed to a syscall. // The unwrap() is safe because they come from the command-line and so contain non nul // character. - use std::os::unix::ffi::OsStrExt; let src = CString::new(source.as_os_str().as_bytes()).unwrap(); let dst = CString::new(dest.as_os_str().as_bytes()).unwrap(); diff --git a/tests/by-util/test_cp.rs b/tests/by-util/test_cp.rs index cb07722d7..c9199151a 100644 --- a/tests/by-util/test_cp.rs +++ b/tests/by-util/test_cp.rs @@ -1463,6 +1463,20 @@ fn test_cp_archive_on_nonexistent_file() { ); } +#[test] +#[cfg(unix)] +fn test_cp_fifo() { + let (at, mut ucmd) = at_and_ucmd!(); + at.mkfifo("fifo"); + ucmd.arg("-r") + .arg("fifo") + .arg("fifo2") + .succeeds() + .no_stderr() + .no_stdout(); + assert!(at.is_fifo("fifo2")); +} + #[test] fn test_dir_recursive_copy() { let scene = TestScenario::new(util_name!());